[clang] clang-format: Add IncludeSortKey option (PR #137840)
Daan De Meyer via cfe-commits
cfe-commits at lists.llvm.org
Thu May 1 04:58:51 PDT 2025
https://github.com/DaanDeMeyer updated https://github.com/llvm/llvm-project/pull/137840
>From 73a18a033e0d978d1662a391fedf731dbad7de27 Mon Sep 17 00:00:00 2001
From: Daan De Meyer <daan.j.demeyer at 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 | 18 ++++++++++++
clang/lib/Format/Format.cpp | 29 ++++++++++++++-----
clang/lib/Tooling/Inclusions/IncludeStyle.cpp | 6 ++++
clang/unittests/Format/SortIncludesTest.cpp | 16 ++++++++++
6 files changed, 75 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..6699a667c6daa 100644
--- a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h
+++ b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h
@@ -166,6 +166,16 @@ 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 +207,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..c2aa44e4d1f25 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,29 @@ 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
More information about the cfe-commits
mailing list