https://github.com/DaanDeMeyer updated 
https://github.com/llvm/llvm-project/pull/137840

>From 4edcb6e0a0d4272a72a272ae97b32439f597ebf3 Mon Sep 17 00:00:00 2001
From: Daan De Meyer <daan.j.deme...@gmail.com>
Date: Tue, 29 Apr 2025 18:26:36 +0200
Subject: [PATCH] clang-format: Add IncludeSortKey option

Sorting by stem gives nicer results when various header file names
are substrings of other header file names, for example, a CLI application
with a main header named analyze.h and a analyze-xxx.h header for each
subcommand currently will always put analyze.h last after all the
analyze-xxx.h headers, but putting analyze.h first instead of last is arguable
nicer to read.

TLDR; Instead of

"""
/#include "analyze-blame.h"
/#include "analyze.h"
"""

You'd get

"""
/#include "analyze.h"
/#include "analyze-blame.h"
"""

Let's allow sorting by stem instead of full path by introducing a new
option IncludeSortKey with two values "Path" and "Stem".
---
 clang/docs/ClangFormatStyleOptions.rst        | 13 +++++++++
 clang/include/clang/Format/Format.h           |  1 +
 .../clang/Tooling/Inclusions/IncludeStyle.h   | 17 ++++++++++++
 clang/lib/Format/Format.cpp                   | 27 +++++++++++++------
 clang/lib/Tooling/Inclusions/IncludeStyle.cpp |  6 +++++
 clang/unittests/Format/SortIncludesTest.cpp   | 16 +++++++++++
 6 files changed, 72 insertions(+), 8 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst 
b/clang/docs/ClangFormatStyleOptions.rst
index 3f8a5f49313b2..248d489b1a878 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -4226,6 +4226,19 @@ the configuration (without a prefix: ``Auto``).
   ``ClassImpl.hpp`` would not have the main include file put on top
   before any other include.
 
+.. _IncludeSortKey:
+
+**IncludeSortKey** (``IncludeSortKeyDiscriminator``) 
:versionbadge:`clang-format 20` :ref:`¶ <IncludeSortKey>`
+  When sorting includes in each block, sort by the specified sort key
+
+  Possible values:
+
+  * ``ISK_Path`` (in configuration: ``Path``)
+    Includes are sorted by their full path (the default).
+
+  * ``ISK_Stem`` (in configuration: ``AngleBracket``)
+    Includes are sorted by their full path without the file extension.
+
 .. _IndentAccessModifiers:
 
 **IndentAccessModifiers** (``Boolean``) :versionbadge:`clang-format 13` 
:ref:`¶ <IndentAccessModifiers>`
diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index f6ceef08b46da..23ce34b560198 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5368,6 +5368,7 @@ struct FormatStyle {
            IncludeStyle.IncludeIsMainSourceRegex ==
                R.IncludeStyle.IncludeIsMainSourceRegex &&
            IncludeStyle.MainIncludeChar == R.IncludeStyle.MainIncludeChar &&
+           IncludeStyle.IncludeSortKey == R.IncludeStyle.IncludeSortKey &&
            IndentAccessModifiers == R.IndentAccessModifiers &&
            IndentCaseBlocks == R.IndentCaseBlocks &&
            IndentCaseLabels == R.IndentCaseLabels &&
diff --git a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h 
b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h
index fba90d8c51a66..8df8f54e7ee0e 100644
--- a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h
+++ b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h
@@ -166,6 +166,15 @@ struct IncludeStyle {
   /// directives that use the specified character are considered.
   /// \version 19
   MainIncludeCharDiscriminator MainIncludeChar;
+
+  enum IncludeSortKeyDiscriminator : int8_t {
+    /// Includes are sorted alphabetically by their full path.
+    ISK_Path,
+    /// Includes are sorted alphabetically by their full path without file 
extension.
+    ISK_Stem,
+  };
+
+  IncludeSortKeyDiscriminator IncludeSortKey;
 };
 
 } // namespace tooling
@@ -197,6 +206,14 @@ struct ScalarEnumerationTraits<
       clang::tooling::IncludeStyle::MainIncludeCharDiscriminator &Value);
 };
 
+template <>
+struct ScalarEnumerationTraits<
+    clang::tooling::IncludeStyle::IncludeSortKeyDiscriminator> {
+  static void enumeration(
+      IO &IO,
+      clang::tooling::IncludeStyle::IncludeSortKeyDiscriminator &Value);
+};
+
 } // namespace yaml
 } // namespace llvm
 
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 5a1c3f556b331..12abc753d9058 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1062,6 +1062,7 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("IncludeIsMainRegex", 
Style.IncludeStyle.IncludeIsMainRegex);
     IO.mapOptional("IncludeIsMainSourceRegex",
                    Style.IncludeStyle.IncludeIsMainSourceRegex);
+    IO.mapOptional("IncludeSortKey", Style.IncludeStyle.IncludeSortKey);
     IO.mapOptional("IndentAccessModifiers", Style.IndentAccessModifiers);
     IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks);
     IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels);
@@ -3219,17 +3220,27 @@ static void sortCppIncludes(const FormatStyle &Style,
 
   if (Style.SortIncludes == FormatStyle::SI_CaseInsensitive) {
     stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
-      const auto LHSFilenameLower = Includes[LHSI].Filename.lower();
-      const auto RHSFilenameLower = Includes[RHSI].Filename.lower();
-      return std::tie(Includes[LHSI].Priority, LHSFilenameLower,
-                      Includes[LHSI].Filename) <
-             std::tie(Includes[RHSI].Priority, RHSFilenameLower,
-                      Includes[RHSI].Filename);
+      SmallString<128> LHSStem = Includes[LHSI].Filename;
+      SmallString<128> RHSStem = Includes[RHSI].Filename;
+      if (Style.IncludeStyle.IncludeSortKey == 
tooling::IncludeStyle::ISK_Stem) {
+        llvm::sys::path::replace_extension(LHSStem, "");
+        llvm::sys::path::replace_extension(RHSStem, "");
+      }
+      const auto LHSStemLower = LHSStem.str().lower();
+      const auto RHSStemLower = RHSStem.str().lower();
+      return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem) <
+             std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem);
     });
   } else {
     stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
-      return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) <
-             std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename);
+      SmallString<128> LHSStem = Includes[LHSI].Filename;
+      SmallString<128> RHSStem = Includes[RHSI].Filename;
+      if (Style.IncludeStyle.IncludeSortKey == 
tooling::IncludeStyle::ISK_Stem) {
+        llvm::sys::path::replace_extension(LHSStem, "");
+        llvm::sys::path::replace_extension(RHSStem, "");
+      }
+      return std::tie(Includes[LHSI].Priority, LHSStem) <
+             std::tie(Includes[RHSI].Priority, RHSStem);
     });
   }
 
diff --git a/clang/lib/Tooling/Inclusions/IncludeStyle.cpp 
b/clang/lib/Tooling/Inclusions/IncludeStyle.cpp
index 05dfb50589de0..98852ed3aa8fa 100644
--- a/clang/lib/Tooling/Inclusions/IncludeStyle.cpp
+++ b/clang/lib/Tooling/Inclusions/IncludeStyle.cpp
@@ -35,5 +35,11 @@ void 
ScalarEnumerationTraits<IncludeStyle::MainIncludeCharDiscriminator>::
   IO.enumCase(Value, "Any", IncludeStyle::MICD_Any);
 }
 
+void ScalarEnumerationTraits<IncludeStyle::IncludeSortKeyDiscriminator>::
+    enumeration(IO &IO, IncludeStyle::IncludeSortKeyDiscriminator &Value) {
+  IO.enumCase(Value, "Path", IncludeStyle::ISK_Path);
+  IO.enumCase(Value, "Stem", IncludeStyle::ISK_Stem);
+}
+
 } // namespace yaml
 } // namespace llvm
diff --git a/clang/unittests/Format/SortIncludesTest.cpp 
b/clang/unittests/Format/SortIncludesTest.cpp
index f20862f5f7461..a7f4256b30e1b 100644
--- a/clang/unittests/Format/SortIncludesTest.cpp
+++ b/clang/unittests/Format/SortIncludesTest.cpp
@@ -1485,6 +1485,22 @@ TEST_F(SortIncludesTest, BlockCommentedOutIncludes) {
   verifyFormat(Code, sort(Code, "input.cpp", 0));
 }
 
+TEST_F(SortIncludesTest, IncludeSortKey) {
+  verifyFormat("#include <a-util.h>\n"
+               "#include <a.h>",
+               sort("#include <a-util.h>\n"
+                    "#include <a.h>",
+                    "input.h", 0));
+
+  Style.IncludeSortKey = tooling::IncludeStyle::ISK_Stem;
+
+  verifyFormat("#include <a.h>\n"
+               "#include <a-util.h>",
+               sort("#include <a-util.h>\n"
+                    "#include <a.h>",
+                    "input.h", 1));
+}
+
 } // end namespace
 } // end namespace format
 } // end namespace clang

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to