Author: Tshaka Lekholoane Date: 2026-07-29T16:09:52+02:00 New Revision: 82c7b9ac09e55cfc9887cb4e0de6221209c690b5
URL: https://github.com/llvm/llvm-project/commit/82c7b9ac09e55cfc9887cb4e0de6221209c690b5 DIFF: https://github.com/llvm/llvm-project/commit/82c7b9ac09e55cfc9887cb4e0de6221209c690b5.diff LOG: [clang-format] Add Natural option for SortIncludes (#210788) `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. **Disclaimer** AI assistance was used in initial exploration and review but the code is "hand generated". Added: Modified: clang/docs/ClangFormatStyleOptions.rst clang/include/clang/Format/Format.h clang/lib/Format/Format.cpp clang/unittests/Format/ConfigParseTest.cpp clang/unittests/Format/SortIncludesTest.cpp Removed: ################################################################################ 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..7d55c738a9ca3 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -25,6 +25,7 @@ #include "clang/Tooling/Inclusions/HeaderIncludes.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringSet.h" +#include <functional> #include <limits> #define DEBUG_TYPE "format-formatter" @@ -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; @@ -3632,25 +3642,52 @@ static void sortCppIncludes(const FormatStyle &Style, if (Style.SortIncludes.Enabled) { stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { - SmallString<128> LHSStem, RHSStem; + if (Includes[LHSI].Priority != Includes[RHSI].Priority) + return Includes[LHSI].Priority < Includes[RHSI].Priority; + + auto LHSStem = Includes[LHSI].Filename; + auto RHSStem = Includes[RHSI].Filename; + + SmallString<128> LHSStemStorage, RHSStemStorage; if (Style.SortIncludes.IgnoreExtension) { - LHSStem = Includes[LHSI].Filename; - RHSStem = Includes[RHSI].Filename; - llvm::sys::path::replace_extension(LHSStem, ""); - llvm::sys::path::replace_extension(RHSStem, ""); + LHSStemStorage = Includes[LHSI].Filename; + RHSStemStorage = Includes[RHSI].Filename; + llvm::sys::path::replace_extension(LHSStemStorage, ""); + llvm::sys::path::replace_extension(RHSStemStorage, ""); + LHSStem = LHSStemStorage; + RHSStem = RHSStemStorage; } + std::string LHSStemLower, RHSStemLower; std::string LHSFilenameLower, RHSFilenameLower; if (Style.SortIncludes.IgnoreCase) { - LHSStemLower = LHSStem.str().lower(); - RHSStemLower = RHSStem.str().lower(); + LHSStemLower = LHSStem.lower(); + RHSStemLower = RHSStem.lower(); LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } - return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem, - LHSFilenameLower, Includes[LHSI].Filename) < - std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem, - RHSFilenameLower, Includes[RHSI].Filename); + + const auto Compare = Style.SortIncludes.Natural + ? &StringRef::compare_numeric + : &StringRef::compare; + + if (Style.SortIncludes.IgnoreCase) { + int Cmp = std::invoke(Compare, StringRef(LHSStemLower), RHSStemLower); + if (Cmp != 0) + return Cmp < 0; + } + + if (int Cmp = std::invoke(Compare, LHSStem, RHSStem); Cmp != 0) + return Cmp < 0; + + if (Style.SortIncludes.IgnoreCase) { + int Cmp = + std::invoke(Compare, StringRef(LHSFilenameLower), RHSFilenameLower); + if (Cmp != 0) + return Cmp < 0; + } + return std::invoke(Compare, Includes[LHSI].Filename, + Includes[RHSI].Filename) < 0; }); } diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 205cc9bcb6d31..51e59345325a9 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
