https://github.com/tshakalekholoane updated 
https://github.com/llvm/llvm-project/pull/210788

>From 8c7fc16f5b0eaa8c349573f11863da7679d68e7a 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                 | 46 +++++++++++++++++++--
 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..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..9da5f70353e34 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,34 @@ 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;
+
+        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

Reply via email to