https://github.com/tshakalekholoane updated https://github.com/llvm/llvm-project/pull/210788
>From 699fb65dcd82640a8e762ae62ed7b55770578b2b 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 | 10 ++++++ clang/include/clang/Format/Format.h | 11 ++++++- clang/lib/Format/Format.cpp | 35 ++++++++++++++++++--- clang/unittests/Format/ConfigParseTest.cpp | 28 ++++++++++------- clang/unittests/Format/SortIncludesTest.cpp | 34 ++++++++++++++++++++ 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 7b1b7a7384b07..7c5f42b974fb7 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -6821,6 +6821,16 @@ 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..17efcb81a98a7 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -844,24 +844,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 +2003,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 +3648,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 +3657,23 @@ static void sortCppIncludes(const FormatStyle &Style, LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } + if (Style.SortIncludes.Natural) { + int Cmp; + if (Style.SortIncludes.IgnoreCase) { + if ((Cmp = StringRef(LHSStemLower).compare_numeric(RHSStemLower))) + return Cmp < 0; + } + if ((Cmp = StringRef(LHSStem).compare_numeric(RHSStem))) + return Cmp < 0; + if (Style.SortIncludes.IgnoreCase) { + if ((Cmp = StringRef(LHSFilenameLower) + .compare_numeric(RHSFilenameLower))) { + return Cmp < 0; + } + } + return StringRef(Includes[LHSI].Filename) + .compare_numeric(Includes[RHSI].Filename) < 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
