[clang] 6bfbb2e - [clang-format] Add FilesBeforeFolders option to SortIncludes (#208954)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 4 13:53:11 PDT 2026
Author: Savvas Shiakas
Date: 2026-09-04T20:53:06Z
New Revision: 6bfbb2ef1bf54cf1b0491226094a2a7bd5c6d6c5
URL: https://github.com/llvm/llvm-project/commit/6bfbb2ef1bf54cf1b0491226094a2a7bd5c6d6c5
DIFF: https://github.com/llvm/llvm-project/commit/6bfbb2ef1bf54cf1b0491226094a2a7bd5c6d6c5.diff
LOG: [clang-format] Add FilesBeforeFolders option to SortIncludes (#208954)
Add a new `SortIncludes.FilesBeforeFolders` boolean option that, when
enabled, sorts includes so that files in a directory appear before
subdirectories at each level, recursively. Within a level, files and
subdirectories are each sorted alphabetically.
For example:
```
true: false (default):
#include "x.h" vs. #include "bar/alpha/e.h"
#include "y.h" #include "bar/alpha/f.h"
#include "z.h" #include "bar/beta/d.h"
#include "bar/g.h" #include "bar/g.h"
#include "bar/h.h" #include "bar/h.h"
#include "bar/i.h" #include "bar/i.h"
#include "bar/alpha/e.h" #include "foo/a.h"
#include "bar/alpha/f.h" #include "x.h"
#include "bar/beta/d.h" #include "y.h"
#include "foo/a.h" #include "z.h"
```
Assisted-by: Github Copilot CLI
Added:
Modified:
clang/docs/ClangFormatStyleOptions.md
clang/include/clang/Format/Format.h
clang/lib/Format/Format.cpp
clang/unittests/Format/ConfigParseTest.cpp
clang/unittests/Format/SortIncludesTest.cpp
Removed:
################################################################################
diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index f575f61745cd6..81984ff185e53 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -7006,6 +7006,29 @@ the configuration (without a prefix: `Auto`).
#include "A10.h" #include "A2.h"
```
+ - `bool FilesBeforeFolders` When `true`, sort includes so that files in a directory appear
+ before subdirectories at each level, recursively. Within a level,
+ files and folders are each sorted alphabetically.
+ When `false` (default), sorts includes purely alphabetically.
+
+ This option is a secondary sort key within each `Priority` group
+ defined by `IncludeCategories`. Includes in
diff erent `Priority`
+ groups are still separated by that primary ordering.
+
+ ```c++
+ true: false (default):
+ #include "x.h" vs. #include "bar/alpha/e.h"
+ #include "y.h" #include "bar/alpha/f.h"
+ #include "z.h" #include "bar/beta/d.h"
+ #include "bar/g.h" #include "bar/g.h"
+ #include "bar/h.h" #include "bar/h.h"
+ #include "bar/i.h" #include "bar/i.h"
+ #include "bar/alpha/e.h" #include "foo/a.h"
+ #include "bar/alpha/f.h" #include "x.h"
+ #include "bar/beta/d.h" #include "y.h"
+ #include "foo/a.h" #include "z.h"
+ ```
+
(sortjavastaticimport)=
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 6deb5bcbea80c..baf56a9937957 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5122,9 +5122,32 @@ struct FormatStyle {
/// #include "A10.h" #include "A2.h"
/// \endcode
bool Natural;
+ /// When `true`, sort includes so that files in a directory appear
+ /// before subdirectories at each level, recursively. Within a level,
+ /// files and folders are each sorted alphabetically.
+ /// When `false` (default), sorts includes purely alphabetically.
+ ///
+ /// This option is a secondary sort key within each `Priority` group
+ /// defined by `IncludeCategories`. Includes in
diff erent `Priority`
+ /// groups are still separated by that primary ordering.
+ /// \code
+ /// true: false (default):
+ /// #include "x.h" vs. #include "bar/alpha/e.h"
+ /// #include "y.h" #include "bar/alpha/f.h"
+ /// #include "z.h" #include "bar/beta/d.h"
+ /// #include "bar/g.h" #include "bar/g.h"
+ /// #include "bar/h.h" #include "bar/h.h"
+ /// #include "bar/i.h" #include "bar/i.h"
+ /// #include "bar/alpha/e.h" #include "foo/a.h"
+ /// #include "bar/alpha/f.h" #include "x.h"
+ /// #include "bar/beta/d.h" #include "y.h"
+ /// #include "foo/a.h" #include "z.h"
+ /// \endcode
+ bool FilesBeforeFolders;
bool operator==(const SortIncludesOptions &R) const {
return Enabled == R.Enabled && IgnoreCase == R.IgnoreCase &&
- IgnoreExtension == R.IgnoreExtension && Natural == R.Natural;
+ IgnoreExtension == R.IgnoreExtension && Natural == R.Natural &&
+ FilesBeforeFolders == R.FilesBeforeFolders;
}
bool operator!=(const SortIncludesOptions &R) const {
return !(*this == R);
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index eec0a9dbced81..4c78c1dbe9f80 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -850,17 +850,20 @@ template <> struct MappingTraits<FormatStyle::SortIncludesOptions> {
FormatStyle::SortIncludesOptions{/*Enabled=*/true,
/*IgnoreCase=*/true,
/*IgnoreExtension=*/false,
- /*Natural=*/false});
+ /*Natural=*/false,
+ /*FilesBeforeFolders=*/false});
IO.enumCase(Value, "CaseSensitive",
FormatStyle::SortIncludesOptions{/*Enabled=*/true,
/*IgnoreCase=*/false,
/*IgnoreExtension=*/false,
- /*Natural=*/false});
+ /*Natural=*/false,
+ /*FilesBeforeFolders=*/false});
IO.enumCase(Value, "Natural",
FormatStyle::SortIncludesOptions{/*Enabled=*/true,
/*IgnoreCase=*/false,
/*IgnoreExtension=*/false,
- /*Natural=*/true});
+ /*Natural=*/true,
+ /*FilesBeforeFolders=*/false});
// For backward compatibility.
IO.enumCase(Value, "false", FormatStyle::SortIncludesOptions{});
@@ -868,14 +871,15 @@ template <> struct MappingTraits<FormatStyle::SortIncludesOptions> {
FormatStyle::SortIncludesOptions{/*Enabled=*/true,
/*IgnoreCase=*/false,
/*IgnoreExtension=*/false,
- /*Natural=*/false});
+ /*Natural=*/false,
+ /*FilesBeforeFolders=*/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);
+ IO.mapOptional("FilesBeforeFolders", Value.FilesBeforeFolders);
}
};
@@ -2029,7 +2033,8 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.ShortNamespaceLines = 1;
LLVMStyle.SkipMacroDefinitionBody = false;
LLVMStyle.SortIncludes = {/*Enabled=*/true, /*IgnoreCase=*/false,
- /*IgnoreExtension=*/false, /*Natural=*/false};
+ /*IgnoreExtension=*/false, /*Natural=*/false,
+ /*FilesBeforeFolders=*/false};
LLVMStyle.SortJavaStaticImport = FormatStyle::SJSIO_Before;
LLVMStyle.SortUsingDeclarations = FormatStyle::SUD_LexicographicNumeric;
LLVMStyle.SpaceAfterCStyleCast = false;
@@ -3697,6 +3702,37 @@ static void sortCppIncludes(const FormatStyle &Style,
? &StringRef::compare_numeric
: &StringRef::compare;
+ while (Style.SortIncludes.FilesBeforeFolders) {
+ auto [LHead, LTail] = LHSStem.split('/');
+ auto [RHead, RTail] = RHSStem.split('/');
+
+ bool LIsFile = LTail.empty();
+ bool RIsFile = RTail.empty();
+
+ // A file at this level sorts before a subdirectory at this level.
+ if (LIsFile != RIsFile)
+ return LIsFile;
+
+ // Both are leaf filenames — they are equal at this level, so we
+ // can break out of the loop and compare the full filenames later.
+ if (LIsFile)
+ break;
+
+ // Both are directory components at this level — compare them.
+ if (Style.SortIncludes.IgnoreCase) {
+ int Cmp = std::invoke(Compare, StringRef(LHead.lower()),
+ StringRef(RHead.lower()));
+ if (Cmp != 0)
+ return Cmp < 0;
+ }
+ if (int Cmp = std::invoke(Compare, LHead, RHead); Cmp != 0)
+ return Cmp < 0;
+
+ // Directory components are equal; descend into the next level.
+ LHSStem = LTail;
+ RHSStem = RTail;
+ }
+
if (Style.SortIncludes.IgnoreCase) {
int Cmp = std::invoke(Compare, StringRef(LHSStemLower), RHSStemLower);
if (Cmp != 0)
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 6d12dfd715ae3..86511edb9d40d 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -274,6 +274,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_NESTED_BOOL(SpacesInParensOptions, InEmptyParentheses);
CHECK_PARSE_NESTED_BOOL(SpacesInParensOptions, Other);
CHECK_PARSE_NESTED_BOOL(SortIncludes, Enabled);
+ CHECK_PARSE_NESTED_BOOL(SortIncludes, FilesBeforeFolders);
CHECK_PARSE_NESTED_BOOL(SortIncludes, IgnoreCase);
CHECK_PARSE_NESTED_BOOL(SortIncludes, IgnoreExtension);
}
@@ -1172,21 +1173,25 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("SortIncludes: true", SortIncludes,
FormatStyle::SortIncludesOptions(
{/*Enabled=*/true, /*IgnoreCase=*/false,
- /*IgnoreExtension=*/false, /*Natural=*/false}));
+ /*IgnoreExtension=*/false, /*Natural=*/false,
+ /*FilesBeforeFolders=*/false}));
CHECK_PARSE("SortIncludes: false", SortIncludes,
FormatStyle::SortIncludesOptions{});
CHECK_PARSE("SortIncludes: CaseInsensitive", SortIncludes,
FormatStyle::SortIncludesOptions(
{/*Enabled=*/true, /*IgnoreCase=*/true,
- /*IgnoreExtension=*/false, /*Natural=*/false}));
+ /*IgnoreExtension=*/false, /*Natural=*/false,
+ /*FilesBeforeFolders=*/false}));
CHECK_PARSE("SortIncludes: CaseSensitive", SortIncludes,
FormatStyle::SortIncludesOptions(
{/*Enabled=*/true, /*IgnoreCase=*/false,
- /*IgnoreExtension=*/false, /*Natural=*/false}));
+ /*IgnoreExtension=*/false, /*Natural=*/false,
+ /*FilesBeforeFolders=*/false}));
CHECK_PARSE("SortIncludes: Natural", SortIncludes,
FormatStyle::SortIncludesOptions(
{/*Enabled=*/true, /*IgnoreCase=*/false,
- /*IgnoreExtension=*/false, /*Natural=*/true}));
+ /*IgnoreExtension=*/false, /*Natural=*/true,
+ /*FilesBeforeFolders=*/false}));
CHECK_PARSE("SortIncludes: Never", SortIncludes,
FormatStyle::SortIncludesOptions{});
diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp
index a6e9e18496f8d..ac2b227a9e255 100644
--- a/clang/unittests/Format/SortIncludesTest.cpp
+++ b/clang/unittests/Format/SortIncludesTest.cpp
@@ -1537,6 +1537,124 @@ TEST_F(SortIncludesTest, IgnoreExtension) {
"input.h"));
}
+TEST_F(SortIncludesTest, FilesBeforeFolders) {
+ FmtStyle.SortIncludes.FilesBeforeFolders = false;
+ verifyFormat("#include \"bar/alpha/e.h\"\n"
+ "#include \"bar/alpha/f.h\"\n"
+ "#include \"bar/beta/d.h\"\n"
+ "#include \"bar/g.h\"\n"
+ "#include \"bar/h.h\"\n"
+ "#include \"bar/i.h\"\n"
+ "#include \"foo/a.h\"\n"
+ "#include \"x.h\"\n"
+ "#include \"y.h\"\n"
+ "#include \"z.h\"",
+ sort("#include \"z.h\"\n"
+ "#include \"bar/alpha/f.h\"\n"
+ "#include \"foo/a.h\"\n"
+ "#include \"bar/g.h\"\n"
+ "#include \"x.h\"\n"
+ "#include \"bar/beta/d.h\"\n"
+ "#include \"bar/i.h\"\n"
+ "#include \"y.h\"\n"
+ "#include \"bar/h.h\"\n"
+ "#include \"bar/alpha/e.h\"",
+ "input.h"));
+
+ FmtStyle.SortIncludes.FilesBeforeFolders = true;
+ verifyFormat("#include \"x.h\"\n"
+ "#include \"y.h\"\n"
+ "#include \"z.h\"\n"
+ "#include \"bar/g.h\"\n"
+ "#include \"bar/h.h\"\n"
+ "#include \"bar/i.h\"\n"
+ "#include \"bar/alpha/e.h\"\n"
+ "#include \"bar/alpha/f.h\"\n"
+ "#include \"bar/beta/d.h\"\n"
+ "#include \"foo/a.h\"",
+ sort("#include \"z.h\"\n"
+ "#include \"bar/alpha/f.h\"\n"
+ "#include \"foo/a.h\"\n"
+ "#include \"bar/g.h\"\n"
+ "#include \"x.h\"\n"
+ "#include \"bar/beta/d.h\"\n"
+ "#include \"bar/i.h\"\n"
+ "#include \"y.h\"\n"
+ "#include \"bar/h.h\"\n"
+ "#include \"bar/alpha/e.h\"",
+ "input.h"));
+
+ verifyFormat("#include \"dir/a.h\"\n"
+ "#include \"dir/b.h\"\n"
+ "#include \"dir/sub/a.h\"\n"
+ "#include \"dir/sub/b.h\"",
+ sort("#include \"dir/sub/b.h\"\n"
+ "#include \"dir/a.h\"\n"
+ "#include \"dir/sub/a.h\"\n"
+ "#include \"dir/b.h\"",
+ "input.h"));
+
+ FmtStyle.SortIncludes.IgnoreCase = true;
+ verifyFormat("#include \"A.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"Bar/a.h\"\n"
+ "#include \"foo/B.h\"",
+ sort("#include \"foo/B.h\"\n"
+ "#include \"Bar/a.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"A.h\"",
+ "input.h"));
+ verifyFormat("#include \"Bar/a.h\"\n"
+ "#include \"bar/a.h\"",
+ sort("#include \"bar/a.h\"\n"
+ "#include \"Bar/a.h\"",
+ "input.h"));
+ FmtStyle.SortIncludes.IgnoreCase = false;
+
+ FmtStyle.SortIncludes.IgnoreExtension = true;
+ verifyFormat("#include \"a.h\"\n"
+ "#include \"a.inc\"\n"
+ "#include \"a-util.h\"\n"
+ "#include \"bar/a.h\"\n"
+ "#include \"bar/b.h\"",
+ sort("#include \"bar/b.h\"\n"
+ "#include \"a-util.h\"\n"
+ "#include \"bar/a.h\"\n"
+ "#include \"a.inc\"\n"
+ "#include \"a.h\"",
+ "input.h"));
+ FmtStyle.SortIncludes.IgnoreExtension = false;
+
+ FmtStyle.IncludeStyle.IncludeCategories.clear();
+ FmtStyle.SortIncludes.FilesBeforeFolders = false;
+ verifyFormat("#include \"beta/x.hpp\"\n"
+ "#include <alpha.hpp>",
+ sort("#include <alpha.hpp>\n"
+ "#include \"beta/x.hpp\"",
+ "input.h"));
+ FmtStyle.SortIncludes.FilesBeforeFolders = true;
+ verifyFormat("#include <alpha.hpp>\n"
+ "#include \"beta/x.hpp\"",
+ sort("#include \"beta/x.hpp\"\n"
+ "#include <alpha.hpp>",
+ "input.h"));
+}
+
+TEST_F(SortIncludesTest, FilesBeforeFoldersWithPriority) {
+ Style.IncludeBlocks = tooling::IncludeStyle::IBS_Regroup;
+ Style.IncludeCategories = {{"^<", 1, 0, false}, {"^\"", 2, 0, false}};
+ FmtStyle.SortIncludes.FilesBeforeFolders = true;
+ verifyFormat("#include <stdio.h>\n"
+ "#include <sys/stat.h>\n"
+ "\n"
+ "#include \"utils.h\"\n"
+ "#include \"foo/bar.h\"",
+ sort("#include <sys/stat.h>\n"
+ "#include \"foo/bar.h\"\n"
+ "#include <stdio.h>\n"
+ "#include \"utils.h\""));
+}
+
} // end namespace
} // end namespace format
} // end namespace clang
More information about the cfe-commits
mailing list