[clang] 82c7b9a - [clang-format] Add Natural option for SortIncludes (#210788)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 07:09:56 PDT 2026
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))?$";
More information about the cfe-commits
mailing list