[clang] [clang-format] Add Natural option for SortIncludes (PR #210788)
Tshaka Lekholoane via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 23 10:47:23 PDT 2026
https://github.com/tshakalekholoane updated https://github.com/llvm/llvm-project/pull/210788
>From 09fc8e10504160942c80cbf2fb6fea7d8d1e6929 Mon Sep 17 00:00:00 2001
From: Tshaka Lekholoane <mail+git at tshaka.dev>
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..8d07a17a3a7e9 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;
@@ -3647,6 +3656,24 @@ static void sortCppIncludes(const FormatStyle &Style,
LHSFilenameLower = Includes[LHSI].Filename.lower();
RHSFilenameLower = Includes[RHSI].Filename.lower();
}
+ if (Style.SortIncludes.Natural) {
+ if (Includes[LHSI].Priority != Includes[RHSI].Priority)
+ return Includes[LHSI].Priority < Includes[RHSI].Priority;
+ if (Style.SortIncludes.IgnoreCase) {
+ if (int Cmp = StringRef(LHSStemLower).compare_numeric(RHSStemLower))
+ return Cmp < 0;
+ }
+ if (int Cmp = StringRef(LHSStem).compare_numeric(RHSStem))
+ return Cmp < 0;
+ if (Style.SortIncludes.IgnoreCase) {
+ if (int 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..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