Skip to content

Commit a8105b3

Browse files
kentsommermkurdej
authored andcommitted
[clang-format] Add case aware include sorting.
Adds an option to [clang-format] which sorts headers in an alphabetical manner using case only for tie-breakers. The options is off by default in favor of the current ASCIIbetical sorting style. Reviewed By: MyDeveloperDay, curdeius, HazardyKnusperkeks Differential Revision: https://reviews.llvm.org/D95017
1 parent 467a045 commit a8105b3

File tree

8 files changed

+197
-26
lines changed

8 files changed

+197
-26
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3004,14 +3004,43 @@ the configuration (without a prefix: ``Auto``).
30043004
/* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of
30053005
* information */
30063006
3007-
**SortIncludes** (``bool``)
3008-
If ``true``, clang-format will sort ``#includes``.
3007+
**SortIncludes** (``SortIncludesOptions``)
3008+
Controls if and how clang-format will sort ``#includes``.
30093009

3010-
.. code-block:: c++
3010+
Possible Values:
30113011

3012-
false: true:
3013-
#include "b.h" vs. #include "a.h"
3014-
#include "a.h" #include "b.h"
3012+
* ``SI_Never`` (in configuration ``Never``)
3013+
Includes are never sorted.
3014+
3015+
.. code-block:: c++
3016+
3017+
#include "B/A.h"
3018+
#include "A/B.h"
3019+
#include "a/b.h"
3020+
#include "A/b.h"
3021+
#include "B/a.h"
3022+
3023+
* ``SI_CaseInsensitive`` (in configuration ``CaseInsensitive``)
3024+
Includes are sorted in an ASCIIbetical or case insensitive fashion.
3025+
3026+
.. code-block:: c++
3027+
3028+
#include "A/B.h"
3029+
#include "A/b.h"
3030+
#include "B/A.h"
3031+
#include "B/a.h"
3032+
#include "a/b.h"
3033+
3034+
* ``SI_CaseSensitive`` (in configuration ``CaseSensitive``)
3035+
Includes are sorted in an alphabetical or case sensitive fashion.
3036+
3037+
.. code-block:: c++
3038+
3039+
#include "A/B.h"
3040+
#include "A/b.h"
3041+
#include "a/b.h"
3042+
#include "B/A.h"
3043+
#include "B/a.h"
30153044

30163045
**SortJavaStaticImport** (``SortJavaStaticImportOptions``)
30173046
When sorting Java imports, by default static imports are placed before

clang/docs/ReleaseNotes.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,35 @@ clang-format
159159
- Option ``SpacesInLineCommentPrefix`` has been added to control the
160160
number of spaces in a line comments prefix.
161161

162+
- Option ``SortIncludes`` has been updated from a ``bool`` to an
163+
``enum`` with backwards compatibility. In addition to the previous
164+
``true``/``false`` states (now ``CaseInsensitive``/``Never``), a third
165+
state has been added (``CaseSensitive``) which causes an alphabetical sort
166+
with case used as a tie-breaker.
167+
168+
.. code-block:: c++
169+
170+
// Never (previously false)
171+
#include "B/A.h"
172+
#include "A/B.h"
173+
#include "a/b.h"
174+
#include "A/b.h"
175+
#include "B/a.h"
176+
177+
// CaseInsensitive (previously true)
178+
#include "A/B.h"
179+
#include "A/b.h"
180+
#include "B/A.h"
181+
#include "B/a.h"
182+
#include "a/b.h"
183+
184+
// CaseSensitive
185+
#include "A/B.h"
186+
#include "A/b.h"
187+
#include "a/b.h"
188+
#include "B/A.h"
189+
#include "B/a.h"
190+
162191
libclang
163192
--------
164193

clang/include/clang/Format/Format.h

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,13 +2613,44 @@ struct FormatStyle {
26132613
bool ReflowComments;
26142614
// clang-format on
26152615

2616-
/// If ``true``, clang-format will sort ``#includes``.
2617-
/// \code
2618-
/// false: true:
2619-
/// #include "b.h" vs. #include "a.h"
2620-
/// #include "a.h" #include "b.h"
2621-
/// \endcode
2622-
bool SortIncludes;
2616+
/// Include sorting options.
2617+
enum SortIncludesOptions : unsigned char {
2618+
/// Includes are never sorted.
2619+
/// \code
2620+
/// #include "B/A.h"
2621+
/// #include "A/B.h"
2622+
/// #include "a/b.h"
2623+
/// #include "A/b.h"
2624+
/// #include "B/a.h"
2625+
/// \endcode
2626+
SI_Never,
2627+
/// Includes are sorted in an ASCIIbetical or case insensitive fashion.
2628+
/// \code
2629+
/// #include "A/B.h"
2630+
/// #include "A/b.h"
2631+
/// #include "B/A.h"
2632+
/// #include "B/a.h"
2633+
/// #include "a/b.h"
2634+
/// \endcode
2635+
SI_CaseInsensitive,
2636+
/// Includes are sorted in an alphabetical or case sensitive fashion.
2637+
/// \code
2638+
/// #include "A/B.h"
2639+
/// #include "A/b.h"
2640+
/// #include "a/b.h"
2641+
/// #include "B/A.h"
2642+
/// #include "B/a.h"
2643+
/// \endcode
2644+
SI_CaseSensitive,
2645+
};
2646+
2647+
/// Controls if and how clang-format will sort ``#includes``.
2648+
/// If ``Never``, includes are never sorted.
2649+
/// If ``CaseInsensitive``, includes are sorted in an ASCIIbetical or case
2650+
/// insensitive fashion.
2651+
/// If ``CaseSensitive``, includes are sorted in an alphabetical or case
2652+
/// sensitive fashion.
2653+
SortIncludesOptions SortIncludes;
26232654

26242655
/// Position for Java Static imports.
26252656
enum SortJavaStaticImportOptions : unsigned char {
@@ -3161,6 +3192,7 @@ struct FormatStyle {
31613192
R.PenaltyBreakTemplateDeclaration &&
31623193
PointerAlignment == R.PointerAlignment &&
31633194
RawStringFormats == R.RawStringFormats &&
3195+
SortIncludes == R.SortIncludes &&
31643196
SortJavaStaticImport == R.SortJavaStaticImport &&
31653197
SpaceAfterCStyleCast == R.SpaceAfterCStyleCast &&
31663198
SpaceAfterLogicalNot == R.SpaceAfterLogicalNot &&

clang/lib/Format/Format.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,18 @@ struct ScalarEnumerationTraits<FormatStyle::BitFieldColonSpacingStyle> {
415415
}
416416
};
417417

418+
template <> struct ScalarEnumerationTraits<FormatStyle::SortIncludesOptions> {
419+
static void enumeration(IO &IO, FormatStyle::SortIncludesOptions &Value) {
420+
IO.enumCase(Value, "Never", FormatStyle::SI_Never);
421+
IO.enumCase(Value, "CaseInsensitive", FormatStyle::SI_CaseInsensitive);
422+
IO.enumCase(Value, "CaseSensitive", FormatStyle::SI_CaseSensitive);
423+
424+
// For backward compatibility.
425+
IO.enumCase(Value, "false", FormatStyle::SI_Never);
426+
IO.enumCase(Value, "true", FormatStyle::SI_CaseInsensitive);
427+
}
428+
};
429+
418430
template <>
419431
struct ScalarEnumerationTraits<FormatStyle::SortJavaStaticImportOptions> {
420432
static void enumeration(IO &IO,
@@ -1030,7 +1042,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
10301042
LLVMStyle.PenaltyIndentedWhitespace = 0;
10311043

10321044
LLVMStyle.DisableFormat = false;
1033-
LLVMStyle.SortIncludes = true;
1045+
LLVMStyle.SortIncludes = FormatStyle::SI_CaseInsensitive;
10341046
LLVMStyle.SortJavaStaticImport = FormatStyle::SJSIO_Before;
10351047
LLVMStyle.SortUsingDeclarations = true;
10361048
LLVMStyle.StatementAttributeLikeMacros.push_back("Q_EMIT");
@@ -1233,7 +1245,7 @@ FormatStyle getChromiumStyle(FormatStyle::LanguageKind Language) {
12331245
"java",
12341246
"javax",
12351247
};
1236-
ChromiumStyle.SortIncludes = true;
1248+
ChromiumStyle.SortIncludes = FormatStyle::SI_CaseInsensitive;
12371249
} else if (Language == FormatStyle::LK_JavaScript) {
12381250
ChromiumStyle.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
12391251
ChromiumStyle.AllowShortLoopsOnASingleLine = false;
@@ -1347,7 +1359,7 @@ FormatStyle getMicrosoftStyle(FormatStyle::LanguageKind Language) {
13471359
FormatStyle getNoStyle() {
13481360
FormatStyle NoStyle = getLLVMStyle();
13491361
NoStyle.DisableFormat = true;
1350-
NoStyle.SortIncludes = false;
1362+
NoStyle.SortIncludes = FormatStyle::SI_Never;
13511363
NoStyle.SortUsingDeclarations = false;
13521364
return NoStyle;
13531365
}
@@ -2226,10 +2238,23 @@ static void sortCppIncludes(const FormatStyle &Style,
22262238
for (unsigned i = 0, e = Includes.size(); i != e; ++i) {
22272239
Indices.push_back(i);
22282240
}
2229-
llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
2230-
return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) <
2231-
std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename);
2232-
});
2241+
2242+
if (Style.SortIncludes == FormatStyle::SI_CaseSensitive) {
2243+
llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
2244+
const auto LHSFilenameLower = Includes[LHSI].Filename.lower();
2245+
const auto RHSFilenameLower = Includes[RHSI].Filename.lower();
2246+
return std::tie(Includes[LHSI].Priority, LHSFilenameLower,
2247+
Includes[LHSI].Filename) <
2248+
std::tie(Includes[RHSI].Priority, RHSFilenameLower,
2249+
Includes[RHSI].Filename);
2250+
});
2251+
} else {
2252+
llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
2253+
return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) <
2254+
std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename);
2255+
});
2256+
}
2257+
22332258
// The index of the include on which the cursor will be put after
22342259
// sorting/deduplicating.
22352260
unsigned CursorIndex;

clang/tools/clang-format/ClangFormat.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,12 @@ static bool format(StringRef FileName) {
402402
return true;
403403
}
404404

405-
if (SortIncludes.getNumOccurrences() != 0)
406-
FormatStyle->SortIncludes = SortIncludes;
405+
if (SortIncludes.getNumOccurrences() != 0) {
406+
if (SortIncludes)
407+
FormatStyle->SortIncludes = FormatStyle::SI_CaseInsensitive;
408+
else
409+
FormatStyle->SortIncludes = FormatStyle::SI_Never;
410+
}
407411
unsigned CursorPosition = Cursor;
408412
Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
409413
AssumedFileName, &CursorPosition);

clang/unittests/Format/FormatTest.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15463,7 +15463,6 @@ TEST_F(FormatTest, ParsesConfigurationBools) {
1546315463
CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList);
1546415464
CHECK_PARSE_BOOL(Cpp11BracedListStyle);
1546515465
CHECK_PARSE_BOOL(ReflowComments);
15466-
CHECK_PARSE_BOOL(SortIncludes);
1546715466
CHECK_PARSE_BOOL(SortUsingDeclarations);
1546815467
CHECK_PARSE_BOOL(SpacesInParentheses);
1546915468
CHECK_PARSE_BOOL(SpacesInSquareBrackets);
@@ -15959,6 +15958,16 @@ TEST_F(FormatTest, ParsesConfiguration) {
1595915958
CHECK_PARSE("IncludeIsMainSourceRegex: 'abc$'",
1596015959
IncludeStyle.IncludeIsMainSourceRegex, "abc$");
1596115960

15961+
Style.SortIncludes = FormatStyle::SI_Never;
15962+
CHECK_PARSE("SortIncludes: true", SortIncludes,
15963+
FormatStyle::SI_CaseInsensitive);
15964+
CHECK_PARSE("SortIncludes: false", SortIncludes, FormatStyle::SI_Never);
15965+
CHECK_PARSE("SortIncludes: CaseInsensitive", SortIncludes,
15966+
FormatStyle::SI_CaseInsensitive);
15967+
CHECK_PARSE("SortIncludes: CaseSensitive", SortIncludes,
15968+
FormatStyle::SI_CaseSensitive);
15969+
CHECK_PARSE("SortIncludes: Never", SortIncludes, FormatStyle::SI_Never);
15970+
1596215971
Style.RawStringFormats.clear();
1596315972
std::vector<FormatStyle::RawStringFormat> ExpectedRawStringFormats = {
1596415973
{
@@ -17970,7 +17979,7 @@ TEST_F(ReplacementTest, SortIncludesAfterReplacement) {
1797017979
"#include \"b.h\"\n")});
1797117980

1797217981
format::FormatStyle Style = format::getLLVMStyle();
17973-
Style.SortIncludes = true;
17982+
Style.SortIncludes = FormatStyle::SI_CaseInsensitive;
1797417983
auto FormattedReplaces = formatReplacements(Code, Replaces, Style);
1797517984
EXPECT_TRUE(static_cast<bool>(FormattedReplaces))
1797617985
<< llvm::toString(FormattedReplaces.takeError()) << "\n";

clang/unittests/Format/SortImportsTestJava.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SortImportsTestJava : public ::testing::Test {
3232
SortImportsTestJava() {
3333
FmtStyle = getGoogleStyle(FormatStyle::LK_Java);
3434
FmtStyle.JavaImportGroups = {"com.test", "org", "com"};
35-
FmtStyle.SortIncludes = true;
35+
FmtStyle.SortIncludes = FormatStyle::SI_CaseInsensitive;
3636
}
3737
};
3838

clang/unittests/Format/SortIncludesTest.cpp

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ TEST_F(SortIncludesTest, SupportClangFormatOffCStyle) {
269269
}
270270

271271
TEST_F(SortIncludesTest, IncludeSortingCanBeDisabled) {
272-
FmtStyle.SortIncludes = false;
272+
FmtStyle.SortIncludes = FormatStyle::SI_Never;
273273
EXPECT_EQ("#include \"a.h\"\n"
274274
"#include \"c.h\"\n"
275275
"#include \"b.h\"\n",
@@ -598,6 +598,49 @@ TEST_F(SortIncludesTest, MainHeaderIsSeparatedWhenRegroupping) {
598598
"a.cc"));
599599
}
600600

601+
TEST_F(SortIncludesTest, SupportOptionalCaseSensitiveSorting) {
602+
EXPECT_FALSE(FmtStyle.SortIncludes == FormatStyle::SI_CaseSensitive);
603+
604+
FmtStyle.SortIncludes = FormatStyle::SI_CaseSensitive;
605+
606+
EXPECT_EQ("#include \"A/B.h\"\n"
607+
"#include \"A/b.h\"\n"
608+
"#include \"a/b.h\"\n"
609+
"#include \"B/A.h\"\n"
610+
"#include \"B/a.h\"\n",
611+
sort("#include \"B/a.h\"\n"
612+
"#include \"B/A.h\"\n"
613+
"#include \"A/B.h\"\n"
614+
"#include \"a/b.h\"\n"
615+
"#include \"A/b.h\"\n",
616+
"a.h"));
617+
618+
Style.IncludeBlocks = clang::tooling::IncludeStyle::IBS_Regroup;
619+
Style.IncludeCategories = {
620+
{"^\"", 1, 0, false}, {"^<.*\\.h>$", 2, 0, false}, {"^<", 3, 0, false}};
621+
622+
StringRef UnsortedCode = "#include \"qt.h\"\n"
623+
"#include <algorithm>\n"
624+
"#include <qtwhatever.h>\n"
625+
"#include <Qtwhatever.h>\n"
626+
"#include <Algorithm>\n"
627+
"#include \"vlib.h\"\n"
628+
"#include \"Vlib.h\"\n"
629+
"#include \"AST.h\"\n";
630+
631+
EXPECT_EQ("#include \"AST.h\"\n"
632+
"#include \"qt.h\"\n"
633+
"#include \"Vlib.h\"\n"
634+
"#include \"vlib.h\"\n"
635+
"\n"
636+
"#include <Qtwhatever.h>\n"
637+
"#include <qtwhatever.h>\n"
638+
"\n"
639+
"#include <Algorithm>\n"
640+
"#include <algorithm>\n",
641+
sort(UnsortedCode));
642+
}
643+
601644
TEST_F(SortIncludesTest, SupportCaseInsensitiveMatching) {
602645
// Setup an regex for main includes so we can cover those as well.
603646
Style.IncludeIsMainRegex = "([-_](test|unittest))?$";

0 commit comments

Comments
 (0)