https://github.com/tshakalekholoane updated https://github.com/llvm/llvm-project/pull/210788
>From f59af122059e8625f6ea47e9f59707ac3a389606 Mon Sep 17 00:00:00 2001 From: Tshaka Lekholoane <[email protected]> Date: Mon, 20 Jul 2026 16:08:43 +0200 Subject: [PATCH] [clang-format] Add Natural option for SortIncludes SortIncludes currently orders includes lexicographically with the option to ignore case or extension. This adds another option, Natural, that compares embedded runs of digits as numbers rather than sequences of characters, matching the "natural sort" behaviour found in most file managers and tools like sort when called with the -V option. --- clang/docs/ClangFormatStyleOptions.rst | 8 ++++ clang/include/clang/Format/Format.h | 11 ++++- clang/lib/Format/Format.cpp | 48 +++++++++++++++++++-- clang/unittests/Format/ConfigParseTest.cpp | 28 ++++++------ clang/unittests/Format/SortIncludesTest.cpp | 34 +++++++++++++++ 5 files changed, 112 insertions(+), 17 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 7b1b7a7384b07..7e12ec12babac 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -6821,6 +6821,14 @@ the configuration (without a prefix: ``Auto``). # include "A.inc" # include "A.h" # include "A-util.h" # include "A.inc" + * ``bool Natural`` Whether or not includes are sorted by natural ordering i.e., whether embedded + runs of digits are compared as numbers rather than sequences of characters. + + .. code-block:: c++ + + true: false: + #include "A2.h" vs. #include "A10.h" + #include "A10.h" #include "A2.h" .. _SortJavaStaticImport: diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 27b2d8f4a405b..0c8acd6c4dbbb 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -5074,9 +5074,18 @@ struct FormatStyle { /// # include "A-util.h" # include "A.inc" /// \endcode bool IgnoreExtension; + /// Whether or not includes are sorted by natural ordering i.e., whether + /// embedded runs of digits are compared as numbers rather than sequences of + /// characters. + /// \code + /// true: false: + /// #include "A2.h" vs. #include "A10.h" + /// #include "A10.h" #include "A2.h" + /// \endcode + bool Natural; bool operator==(const SortIncludesOptions &R) const { return Enabled == R.Enabled && IgnoreCase == R.IgnoreCase && - IgnoreExtension == R.IgnoreExtension; + IgnoreExtension == R.IgnoreExtension && Natural == R.Natural; } bool operator!=(const SortIncludesOptions &R) const { return !(*this == R); diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 037111d8e9e5d..409cd4526e0a6 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -24,6 +24,7 @@ #include "UsingDeclarationsSorter.h" #include "clang/Tooling/Inclusions/HeaderIncludes.h" #include "llvm/ADT/Sequence.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" #include <limits> @@ -844,24 +845,33 @@ template <> struct MappingTraits<FormatStyle::SortIncludesOptions> { IO.enumCase(Value, "CaseInsensitive", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/true, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); IO.enumCase(Value, "CaseSensitive", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); + IO.enumCase(Value, "Natural", + FormatStyle::SortIncludesOptions{/*Enabled=*/true, + /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, + /*Natural=*/true}); // For backward compatibility. IO.enumCase(Value, "false", FormatStyle::SortIncludesOptions{}); IO.enumCase(Value, "true", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); } static void mapping(IO &IO, FormatStyle::SortIncludesOptions &Value) { IO.mapOptional("Enabled", Value.Enabled); IO.mapOptional("IgnoreCase", Value.IgnoreCase); IO.mapOptional("IgnoreExtension", Value.IgnoreExtension); + IO.mapOptional("Natural", Value.Natural); } }; @@ -1994,7 +2004,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.ShortNamespaceLines = 1; LLVMStyle.SkipMacroDefinitionBody = false; LLVMStyle.SortIncludes = {/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}; + /*IgnoreExtension=*/false, /*Natural=*/false}; LLVMStyle.SortJavaStaticImport = FormatStyle::SJSIO_Before; LLVMStyle.SortUsingDeclarations = FormatStyle::SUD_LexicographicNumeric; LLVMStyle.SpaceAfterCStyleCast = false; @@ -3639,6 +3649,7 @@ static void sortCppIncludes(const FormatStyle &Style, llvm::sys::path::replace_extension(LHSStem, ""); llvm::sys::path::replace_extension(RHSStem, ""); } + std::string LHSStemLower, RHSStemLower; std::string LHSFilenameLower, RHSFilenameLower; if (Style.SortIncludes.IgnoreCase) { @@ -3647,6 +3658,35 @@ static void sortCppIncludes(const FormatStyle &Style, LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } + + if (Style.SortIncludes.Natural) { + auto LHSPriority = Includes[LHSI].Priority; + auto RHSPriority = Includes[RHSI].Priority; + if (LHSPriority != RHSPriority) + return LHSPriority < RHSPriority; + + StringRef LHSStemCase, RHSStemCase; + if (Style.SortIncludes.IgnoreCase) { + LHSStemCase = StringRef(LHSStemLower); + RHSStemCase = StringRef(RHSStemLower); + } else { + LHSStemCase = StringRef(LHSStem); + RHSStemCase = StringRef(RHSStem); + } + if (auto Cmp = LHSStemCase.compare_numeric(RHSStemCase)) + return Cmp < 0; + + StringRef LHSFilenameCase, RHSFilenameCase; + if (Style.SortIncludes.IgnoreCase) { + LHSFilenameCase = StringRef(LHSFilenameLower); + RHSFilenameCase = StringRef(RHSFilenameLower); + } else { + LHSFilenameCase = Includes[LHSI].Filename; + RHSFilenameCase = Includes[RHSI].Filename; + } + return LHSFilenameCase.compare_numeric(RHSFilenameCase) < 0; + } + return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem, LHSFilenameLower, Includes[LHSI].Filename) < std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem, diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 205cc9bcb6d31..17a27032207c2 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -1167,20 +1167,24 @@ TEST(ConfigParseTest, ParsesConfiguration) { IncludeStyle.IncludeIsMainSourceRegex, "abc$"); Style.SortIncludes = {}; - CHECK_PARSE( - "SortIncludes: true", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/false, /*IgnoreExtension=*/false})); + CHECK_PARSE("SortIncludes: true", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural*/ false})); CHECK_PARSE("SortIncludes: false", SortIncludes, FormatStyle::SortIncludesOptions{}); - CHECK_PARSE( - "SortIncludes: CaseInsensitive", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/true, /*IgnoreExtension=*/false})); - CHECK_PARSE( - "SortIncludes: CaseSensitive", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/false, /*IgnoreExtension=*/false})); + CHECK_PARSE("SortIncludes: CaseInsensitive", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/true, + /*IgnoreExtension=*/false, /*Natural*/ false})); + CHECK_PARSE("SortIncludes: CaseSensitive", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural*/ false})); + CHECK_PARSE("SortIncludes: Natural", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural*/ true})); CHECK_PARSE("SortIncludes: Never", SortIncludes, FormatStyle::SortIncludesOptions{}); diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp index 48ecd5d32d034..a6e9e18496f8d 100644 --- a/clang/unittests/Format/SortIncludesTest.cpp +++ b/clang/unittests/Format/SortIncludesTest.cpp @@ -668,6 +668,40 @@ TEST_F(SortIncludesTest, SupportOptionalCaseSensitiveSorting) { sort(UnsortedCode)); } +TEST_F(SortIncludesTest, SupportNaturalSorting) { + FmtStyle.SortIncludes.Natural = true; + verifyFormat("#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha20.h\"")); +} + +TEST_F(SortIncludesTest, SupportNaturalSortingWithIgnoreCase) { + FmtStyle.SortIncludes.Natural = true; + FmtStyle.SortIncludes.IgnoreCase = true; + + verifyFormat("#include \"crypto/chacha8.h\"\n" + "#include \"Crypto/ChaCha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"Crypto/ChaCha12.h\"\n" + "#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha20.h\"")); +} + +TEST_F(SortIncludesTest, SupportNaturalSortingWithIgnoreExtension) { + FmtStyle.SortIncludes.Natural = true; + FmtStyle.SortIncludes.IgnoreExtension = true; + + verifyFormat("#include \"crypto/chacha8.c\"\n" + "#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"\n" + "#include \"crypto/chacha8.c\"")); +} + TEST_F(SortIncludesTest, SupportCaseInsensitiveMatching) { // Setup an regex for main includes so we can cover those as well. Style.IncludeIsMainRegex = "([-_](test|unittest))?$"; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
