Skip to content

Commit

Permalink
[clang-format] Add MainIncludeChar option. (#78752)
Browse files Browse the repository at this point in the history
Resolves #27008, #39735, #53013, #63619.

Hello, this PR adds the MainIncludeChar option to clang-format, allowing
to select which include syntax must be considered when searching for the
main header: quotes (`#include "foo.hpp"`, the default), brackets
(`#include <foo.hpp>`), or both.

The lack of support for brackets has been reported many times, see the
linked issues, so I am pretty sure there is a need for it :)

A short note about why I did not implement a regex approach as discussed
in #53013: while a regex would have allowed many extra ways to describe
the main header, the bug descriptions listed above suggest a very simple
need: support brackets for the main header. This PR answers this needs
in a quite simple way, with a very simple style option. IMHO the feature
space covered by the regex (again, for which there is no demand :)) can
be implemented latter, in addition to the proposed option.

The PR also includes tests for the option with and without grouped
includes.
  • Loading branch information
j-jorge committed Feb 6, 2024
1 parent 933247d commit 984dd15
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 2 deletions.
19 changes: 19 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4142,6 +4142,25 @@ the configuration (without a prefix: ``Auto``).
A(z); -> z;
A(a, b); // will not be expanded.

.. _MainIncludeChar:

**MainIncludeChar** (``MainIncludeCharDiscriminator``) :versionbadge:`clang-format 18` :ref:`<MainIncludeChar>`
When guessing whether a #include is the "main" include, only the include
directives that use the specified character are considered.

Possible values:

* ``MICD_Quote`` (in configuration: ``Quote``)
Main include uses quotes: ``#include "foo.hpp"`` (the default).

* ``MICD_AngleBracket`` (in configuration: ``AngleBracket``)
Main include uses angle brackets: ``#include <foo.hpp>``.

* ``MICD_Any`` (in configuration: ``Any``)
Main include uses either quotes or angle brackets.



.. _MaxEmptyLinesToKeep:

**MaxEmptyLinesToKeep** (``Unsigned``) :versionbadge:`clang-format 3.7` :ref:`<MaxEmptyLinesToKeep>`
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -4846,6 +4846,7 @@ struct FormatStyle {
R.IncludeStyle.IncludeIsMainRegex &&
IncludeStyle.IncludeIsMainSourceRegex ==
R.IncludeStyle.IncludeIsMainSourceRegex &&
IncludeStyle.MainIncludeChar == R.IncludeStyle.MainIncludeChar &&
IndentAccessModifiers == R.IndentAccessModifiers &&
IndentCaseBlocks == R.IndentCaseBlocks &&
IndentCaseLabels == R.IndentCaseLabels &&
Expand Down
23 changes: 23 additions & 0 deletions clang/include/clang/Tooling/Inclusions/IncludeStyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ struct IncludeStyle {
/// before any other include.
/// \version 10
std::string IncludeIsMainSourceRegex;

/// Character to consider in the include directives for the main header.
enum MainIncludeCharDiscriminator : int8_t {
/// Main include uses quotes: ``#include "foo.hpp"`` (the default).
MICD_Quote,
/// Main include uses angle brackets: ``#include <foo.hpp>``.
MICD_AngleBracket,
/// Main include uses either quotes or angle brackets.
MICD_Any
};

/// When guessing whether a #include is the "main" include, only the include
/// directives that use the specified character are considered.
/// \version 18
MainIncludeCharDiscriminator MainIncludeChar;
};

} // namespace tooling
Expand All @@ -174,6 +189,14 @@ struct ScalarEnumerationTraits<
enumeration(IO &IO, clang::tooling::IncludeStyle::IncludeBlocksStyle &Value);
};

template <>
struct ScalarEnumerationTraits<
clang::tooling::IncludeStyle::MainIncludeCharDiscriminator> {
static void enumeration(
IO &IO,
clang::tooling::IncludeStyle::MainIncludeCharDiscriminator &Value);
};

} // namespace yaml
} // namespace llvm

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);
IO.mapOptional("MacroBlockEnd", Style.MacroBlockEnd);
IO.mapOptional("Macros", Style.Macros);
IO.mapOptional("MainIncludeChar", Style.IncludeStyle.MainIncludeChar);
IO.mapOptional("MaxEmptyLinesToKeep", Style.MaxEmptyLinesToKeep);
IO.mapOptional("NamespaceIndentation", Style.NamespaceIndentation);
IO.mapOptional("NamespaceMacros", Style.NamespaceMacros);
Expand Down Expand Up @@ -1496,6 +1497,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
{".*", 1, 0, false}};
LLVMStyle.IncludeStyle.IncludeIsMainRegex = "(Test)?$";
LLVMStyle.IncludeStyle.IncludeBlocks = tooling::IncludeStyle::IBS_Preserve;
LLVMStyle.IncludeStyle.MainIncludeChar = tooling::IncludeStyle::MICD_Quote;
LLVMStyle.IndentAccessModifiers = false;
LLVMStyle.IndentCaseLabels = false;
LLVMStyle.IndentCaseBlocks = false;
Expand Down
14 changes: 12 additions & 2 deletions clang/lib/Tooling/Inclusions/HeaderIncludes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,18 @@ int IncludeCategoryManager::getSortIncludePriority(StringRef IncludeName,
return Ret;
}
bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const {
if (!IncludeName.starts_with("\""))
return false;
switch (Style.MainIncludeChar) {
case IncludeStyle::MICD_Quote:
if (!IncludeName.starts_with("\""))
return false;
break;
case IncludeStyle::MICD_AngleBracket:
if (!IncludeName.starts_with("<"))
return false;
break;
case IncludeStyle::MICD_Any:
break;
}

IncludeName =
IncludeName.drop_front(1).drop_back(1); // remove the surrounding "" or <>
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Tooling/Inclusions/IncludeStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,12 @@ void ScalarEnumerationTraits<IncludeStyle::IncludeBlocksStyle>::enumeration(
IO.enumCase(Value, "Regroup", IncludeStyle::IBS_Regroup);
}

void ScalarEnumerationTraits<IncludeStyle::MainIncludeCharDiscriminator>::
enumeration(IO &IO, IncludeStyle::MainIncludeCharDiscriminator &Value) {
IO.enumCase(Value, "Quote", IncludeStyle::MICD_Quote);
IO.enumCase(Value, "AngleBracket", IncludeStyle::MICD_AngleBracket);
IO.enumCase(Value, "Any", IncludeStyle::MICD_Any);
}

} // namespace yaml
} // namespace llvm
106 changes: 106 additions & 0 deletions clang/unittests/Format/SortIncludesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,112 @@ TEST_F(SortIncludesTest,
EXPECT_EQ(Code, sort(Code, "input.h", 0));
}

TEST_F(SortIncludesTest, MainIncludeChar) {
std::string Code = "#include <a>\n"
"#include \"quote/input.h\"\n"
"#include <angle-bracket/input.h>\n";

// Default behavior
EXPECT_EQ("#include \"quote/input.h\"\n"
"#include <a>\n"
"#include <angle-bracket/input.h>\n",
sort(Code, "input.cc", 1));

Style.MainIncludeChar = tooling::IncludeStyle::MICD_Quote;
EXPECT_EQ("#include \"quote/input.h\"\n"
"#include <a>\n"
"#include <angle-bracket/input.h>\n",
sort(Code, "input.cc", 1));

Style.MainIncludeChar = tooling::IncludeStyle::MICD_AngleBracket;
EXPECT_EQ("#include <angle-bracket/input.h>\n"
"#include \"quote/input.h\"\n"
"#include <a>\n",
sort(Code, "input.cc", 1));
}

TEST_F(SortIncludesTest, MainIncludeCharAnyPickQuote) {
Style.MainIncludeChar = tooling::IncludeStyle::MICD_Any;
EXPECT_EQ("#include \"input.h\"\n"
"#include <a>\n"
"#include <b>\n",
sort("#include <a>\n"
"#include \"input.h\"\n"
"#include <b>\n",
"input.cc", 1));
}

TEST_F(SortIncludesTest, MainIncludeCharAnyPickAngleBracket) {
Style.MainIncludeChar = tooling::IncludeStyle::MICD_Any;
EXPECT_EQ("#include <input.h>\n"
"#include <a>\n"
"#include <b>\n",
sort("#include <a>\n"
"#include <input.h>\n"
"#include <b>\n",
"input.cc", 1));
}

TEST_F(SortIncludesTest, MainIncludeCharQuoteAndRegroup) {
Style.IncludeCategories = {
{"lib-a", 1, 0, false}, {"lib-b", 2, 0, false}, {"lib-c", 3, 0, false}};
Style.IncludeBlocks = tooling::IncludeStyle::IBS_Regroup;
Style.MainIncludeChar = tooling::IncludeStyle::MICD_Quote;

EXPECT_EQ("#include \"lib-b/input.h\"\n"
"\n"
"#include <lib-a/h-1.h>\n"
"#include <lib-a/h-3.h>\n"
"#include <lib-a/input.h>\n"
"\n"
"#include <lib-b/h-1.h>\n"
"#include <lib-b/h-3.h>\n"
"\n"
"#include <lib-c/h-1.h>\n"
"#include <lib-c/h-2.h>\n"
"#include <lib-c/h-3.h>\n",
sort("#include <lib-c/h-1.h>\n"
"#include <lib-c/h-2.h>\n"
"#include <lib-c/h-3.h>\n"
"#include <lib-b/h-1.h>\n"
"#include \"lib-b/input.h\"\n"
"#include <lib-b/h-3.h>\n"
"#include <lib-a/h-1.h>\n"
"#include <lib-a/input.h>\n"
"#include <lib-a/h-3.h>\n",
"input.cc"));
}

TEST_F(SortIncludesTest, MainIncludeCharAngleBracketAndRegroup) {
Style.IncludeCategories = {
{"lib-a", 1, 0, false}, {"lib-b", 2, 0, false}, {"lib-c", 3, 0, false}};
Style.IncludeBlocks = tooling::IncludeStyle::IBS_Regroup;
Style.MainIncludeChar = tooling::IncludeStyle::MICD_AngleBracket;

EXPECT_EQ("#include <lib-a/input.h>\n"
"\n"
"#include <lib-a/h-1.h>\n"
"#include <lib-a/h-3.h>\n"
"\n"
"#include \"lib-b/input.h\"\n"
"#include <lib-b/h-1.h>\n"
"#include <lib-b/h-3.h>\n"
"\n"
"#include <lib-c/h-1.h>\n"
"#include <lib-c/h-2.h>\n"
"#include <lib-c/h-3.h>\n",
sort("#include <lib-c/h-1.h>\n"
"#include <lib-c/h-2.h>\n"
"#include <lib-c/h-3.h>\n"
"#include <lib-b/h-1.h>\n"
"#include \"lib-b/input.h\"\n"
"#include <lib-b/h-3.h>\n"
"#include <lib-a/h-1.h>\n"
"#include <lib-a/input.h>\n"
"#include <lib-a/h-3.h>\n",
"input.cc"));
}

TEST_F(SortIncludesTest, DoNotRegroupGroupsInGoogleObjCStyle) {
FmtStyle = getGoogleStyle(FormatStyle::LK_ObjC);

Expand Down

0 comments on commit 984dd15

Please sign in to comment.