Skip to content

Commit 4edcb6e

Browse files
committed
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".
1 parent bb21a68 commit 4edcb6e

File tree

6 files changed

+72
-8
lines changed

6 files changed

+72
-8
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4226,6 +4226,19 @@ the configuration (without a prefix: ``Auto``).
42264226
``ClassImpl.hpp`` would not have the main include file put on top
42274227
before any other include.
42284228

4229+
.. _IncludeSortKey:
4230+
4231+
**IncludeSortKey** (``IncludeSortKeyDiscriminator``) :versionbadge:`clang-format 20` :ref:`<IncludeSortKey>`
4232+
When sorting includes in each block, sort by the specified sort key
4233+
4234+
Possible values:
4235+
4236+
* ``ISK_Path`` (in configuration: ``Path``)
4237+
Includes are sorted by their full path (the default).
4238+
4239+
* ``ISK_Stem`` (in configuration: ``AngleBracket``)
4240+
Includes are sorted by their full path without the file extension.
4241+
42294242
.. _IndentAccessModifiers:
42304243

42314244
**IndentAccessModifiers** (``Boolean``) :versionbadge:`clang-format 13` :ref:`<IndentAccessModifiers>`

clang/include/clang/Format/Format.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5368,6 +5368,7 @@ struct FormatStyle {
53685368
IncludeStyle.IncludeIsMainSourceRegex ==
53695369
R.IncludeStyle.IncludeIsMainSourceRegex &&
53705370
IncludeStyle.MainIncludeChar == R.IncludeStyle.MainIncludeChar &&
5371+
IncludeStyle.IncludeSortKey == R.IncludeStyle.IncludeSortKey &&
53715372
IndentAccessModifiers == R.IndentAccessModifiers &&
53725373
IndentCaseBlocks == R.IndentCaseBlocks &&
53735374
IndentCaseLabels == R.IndentCaseLabels &&

clang/include/clang/Tooling/Inclusions/IncludeStyle.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ struct IncludeStyle {
166166
/// directives that use the specified character are considered.
167167
/// \version 19
168168
MainIncludeCharDiscriminator MainIncludeChar;
169+
170+
enum IncludeSortKeyDiscriminator : int8_t {
171+
/// Includes are sorted alphabetically by their full path.
172+
ISK_Path,
173+
/// Includes are sorted alphabetically by their full path without file extension.
174+
ISK_Stem,
175+
};
176+
177+
IncludeSortKeyDiscriminator IncludeSortKey;
169178
};
170179

171180
} // namespace tooling
@@ -197,6 +206,14 @@ struct ScalarEnumerationTraits<
197206
clang::tooling::IncludeStyle::MainIncludeCharDiscriminator &Value);
198207
};
199208

209+
template <>
210+
struct ScalarEnumerationTraits<
211+
clang::tooling::IncludeStyle::IncludeSortKeyDiscriminator> {
212+
static void enumeration(
213+
IO &IO,
214+
clang::tooling::IncludeStyle::IncludeSortKeyDiscriminator &Value);
215+
};
216+
200217
} // namespace yaml
201218
} // namespace llvm
202219

clang/lib/Format/Format.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,7 @@ template <> struct MappingTraits<FormatStyle> {
10621062
IO.mapOptional("IncludeIsMainRegex", Style.IncludeStyle.IncludeIsMainRegex);
10631063
IO.mapOptional("IncludeIsMainSourceRegex",
10641064
Style.IncludeStyle.IncludeIsMainSourceRegex);
1065+
IO.mapOptional("IncludeSortKey", Style.IncludeStyle.IncludeSortKey);
10651066
IO.mapOptional("IndentAccessModifiers", Style.IndentAccessModifiers);
10661067
IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks);
10671068
IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels);
@@ -3219,17 +3220,27 @@ static void sortCppIncludes(const FormatStyle &Style,
32193220

32203221
if (Style.SortIncludes == FormatStyle::SI_CaseInsensitive) {
32213222
stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
3222-
const auto LHSFilenameLower = Includes[LHSI].Filename.lower();
3223-
const auto RHSFilenameLower = Includes[RHSI].Filename.lower();
3224-
return std::tie(Includes[LHSI].Priority, LHSFilenameLower,
3225-
Includes[LHSI].Filename) <
3226-
std::tie(Includes[RHSI].Priority, RHSFilenameLower,
3227-
Includes[RHSI].Filename);
3223+
SmallString<128> LHSStem = Includes[LHSI].Filename;
3224+
SmallString<128> RHSStem = Includes[RHSI].Filename;
3225+
if (Style.IncludeStyle.IncludeSortKey == tooling::IncludeStyle::ISK_Stem) {
3226+
llvm::sys::path::replace_extension(LHSStem, "");
3227+
llvm::sys::path::replace_extension(RHSStem, "");
3228+
}
3229+
const auto LHSStemLower = LHSStem.str().lower();
3230+
const auto RHSStemLower = RHSStem.str().lower();
3231+
return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem) <
3232+
std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem);
32283233
});
32293234
} else {
32303235
stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
3231-
return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) <
3232-
std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename);
3236+
SmallString<128> LHSStem = Includes[LHSI].Filename;
3237+
SmallString<128> RHSStem = Includes[RHSI].Filename;
3238+
if (Style.IncludeStyle.IncludeSortKey == tooling::IncludeStyle::ISK_Stem) {
3239+
llvm::sys::path::replace_extension(LHSStem, "");
3240+
llvm::sys::path::replace_extension(RHSStem, "");
3241+
}
3242+
return std::tie(Includes[LHSI].Priority, LHSStem) <
3243+
std::tie(Includes[RHSI].Priority, RHSStem);
32333244
});
32343245
}
32353246

clang/lib/Tooling/Inclusions/IncludeStyle.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,11 @@ void ScalarEnumerationTraits<IncludeStyle::MainIncludeCharDiscriminator>::
3535
IO.enumCase(Value, "Any", IncludeStyle::MICD_Any);
3636
}
3737

38+
void ScalarEnumerationTraits<IncludeStyle::IncludeSortKeyDiscriminator>::
39+
enumeration(IO &IO, IncludeStyle::IncludeSortKeyDiscriminator &Value) {
40+
IO.enumCase(Value, "Path", IncludeStyle::ISK_Path);
41+
IO.enumCase(Value, "Stem", IncludeStyle::ISK_Stem);
42+
}
43+
3844
} // namespace yaml
3945
} // namespace llvm

clang/unittests/Format/SortIncludesTest.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,22 @@ TEST_F(SortIncludesTest, BlockCommentedOutIncludes) {
14851485
verifyFormat(Code, sort(Code, "input.cpp", 0));
14861486
}
14871487

1488+
TEST_F(SortIncludesTest, IncludeSortKey) {
1489+
verifyFormat("#include <a-util.h>\n"
1490+
"#include <a.h>",
1491+
sort("#include <a-util.h>\n"
1492+
"#include <a.h>",
1493+
"input.h", 0));
1494+
1495+
Style.IncludeSortKey = tooling::IncludeStyle::ISK_Stem;
1496+
1497+
verifyFormat("#include <a.h>\n"
1498+
"#include <a-util.h>",
1499+
sort("#include <a-util.h>\n"
1500+
"#include <a.h>",
1501+
"input.h", 1));
1502+
}
1503+
14881504
} // end namespace
14891505
} // end namespace format
14901506
} // end namespace clang

0 commit comments

Comments
 (0)