https://github.com/j-jorge created https://github.com/llvm/llvm-project/pull/78752
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. >From db59f23f7dff603b00c40761c8fa935876b1654b Mon Sep 17 00:00:00 2001 From: Julien Jorge <julien.jo...@stuff-o-matic.com> Date: Fri, 19 Jan 2024 18:44:58 +0100 Subject: [PATCH] [clang-format] Add MainIncludeChar option. Resolves #27008, #39735, #53013, #63619. --- clang/docs/ClangFormatStyleOptions.rst | 18 ++++++++++++ .../clang/Tooling/Inclusions/IncludeStyle.h | 22 ++++++++++++++ clang/lib/Format/Format.cpp | 2 ++ .../lib/Tooling/Inclusions/HeaderIncludes.cpp | 14 +++++++-- clang/lib/Tooling/Inclusions/IncludeStyle.cpp | 7 +++++ .../main-include-char-bracket-group.cpp | 27 +++++++++++++++++ .../Format/main-include-char-quote-group.cpp | 29 +++++++++++++++++++ clang/test/Format/main-include-char.cpp | 15 ++++++++++ 8 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 clang/test/Format/main-include-char-bracket-group.cpp create mode 100644 clang/test/Format/main-include-char-quote-group.cpp create mode 100644 clang/test/Format/main-include-char.cpp diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 8bc13e45bf2f5f..af72b725640e9b 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3392,6 +3392,24 @@ the configuration (without a prefix: ``Auto``). Priority: 1 SortPriority: 0 +.. _MainIncludeChar: + +**MainIncludeChar** (``MainIncludeCharDiscriminator``) :versionbadge:`clang-format 18` :ref:`¶ <MainIncludeChar>` + When guessing whether a #include is the "main" include (to assign + category 0, see above), only the include directives that use the + specified character are considered. + + Possible values: + + * ``MICD_Quote`` (the default, in configuration: ``Quote``) + The main include uses quotes, e.g. ``#include "foo.hpp"``. + + * ``MICD_Bracket`` (in configuration: ``Bracket``) + The main include uses brackets, e.g. ``#include <foo.hpp>``. + + * ``MICD_Any`` (in configuration: ``Any``) + The main include uses either quotes or brackets. + .. _IncludeIsMainRegex: **IncludeIsMainRegex** (``String``) :versionbadge:`clang-format 3.9` :ref:`¶ <IncludeIsMainRegex>` diff --git a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h index d6b2b0192477dc..5ec72dac227a09 100644 --- a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h +++ b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h @@ -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 { + /// Main include uses quotes: ``#include "foo.hpp"`` + MICD_Quote, + /// Main include uses brackets: ``#include <foo.hpp>`` + MICD_Bracket, + /// Main include uses either quotes or 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 @@ -174,6 +189,13 @@ 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 diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 7c2f4dcf3d2308..edb4acf9fe74df 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1032,6 +1032,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); @@ -1514,6 +1515,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; diff --git a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp index d275222ac6b587..94f26e3ec80500 100644 --- a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -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_Bracket: + if (!IncludeName.starts_with("<")) + return false; + break; + case IncludeStyle::MICD_Any: + break; + } IncludeName = IncludeName.drop_front(1).drop_back(1); // remove the surrounding "" or <> diff --git a/clang/lib/Tooling/Inclusions/IncludeStyle.cpp b/clang/lib/Tooling/Inclusions/IncludeStyle.cpp index da5bb00d1013a6..3e756f8ff0427d 100644 --- a/clang/lib/Tooling/Inclusions/IncludeStyle.cpp +++ b/clang/lib/Tooling/Inclusions/IncludeStyle.cpp @@ -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, "Bracket", IncludeStyle::MICD_Bracket); + IO.enumCase(Value, "Any", IncludeStyle::MICD_Any); +} + } // namespace yaml } // namespace llvm diff --git a/clang/test/Format/main-include-char-bracket-group.cpp b/clang/test/Format/main-include-char-bracket-group.cpp new file mode 100644 index 00000000000000..3309b923ebed4d --- /dev/null +++ b/clang/test/Format/main-include-char-bracket-group.cpp @@ -0,0 +1,27 @@ +// Test the combination of regrouped include directives, via regexes and +// priorities, with a main header included with brackets. + +// RUN: clang-format %s -style="{MainIncludeChar: Bracket, IncludeBlocks: Regroup, IncludeCategories: [{Regex: 'lib-a', Priority: 1}, {Regex: 'lib-b', Priority: 2}, {Regex: 'lib-c', Priority: 3}]}" | FileCheck %s + +#include <lib-c/header-1.hpp> +#include <lib-c/header-2.hpp> +#include <lib-c/header-3.hpp> +#include <lib-b/header-1.hpp> +#include <lib-b/main-include-char-bracket-group.hpp> +#include <lib-b/header-3.hpp> +#include <lib-a/header-1.hpp> +#include <lib-a/main-include-char-bracket-group.hpp> +#include <lib-a/header-3.hpp> + +// CHECK: <lib-b/main-include-char-bracket-group.hpp> +// CHECK-EMPTY: +// CHECK-NEXT: <lib-a/header-1.hpp> +// CHECK-NEXT: <lib-a/header-3.hpp> +// CHECK-NEXT: <lib-a/main-include-char-bracket-group.hpp> +// CHECK-EMPTY: +// CHECK-NEXT: <lib-b/header-1.hpp> +// CHECK-NEXT: <lib-b/header-3.hpp> +// CHECK-EMPTY: +// CHECK-NEXT: <lib-c/header-1.hpp> +// CHECK-NEXT: <lib-c/header-2.hpp> +// CHECK-NEXT: <lib-c/header-3.hpp> diff --git a/clang/test/Format/main-include-char-quote-group.cpp b/clang/test/Format/main-include-char-quote-group.cpp new file mode 100644 index 00000000000000..b8419761544133 --- /dev/null +++ b/clang/test/Format/main-include-char-quote-group.cpp @@ -0,0 +1,29 @@ +// Test the combination of regrouped include directives, via regexes and +// priorities, with a main header included with quotes. Quotes for the main +// header being the default behavior, the first test does not specify it. + +// RUN: clang-format %s -style="{IncludeBlocks: Regroup, IncludeCategories: [{Regex: 'lib-a', Priority: 1}, {Regex: 'lib-b', Priority: 2}, {Regex: 'lib-c', Priority: 3}]}" | tee delme | FileCheck %s +// RUN: clang-format %s -style="{MainIncludeChar: Quote, IncludeBlocks: Regroup, IncludeCategories: [{Regex: 'lib-a', Priority: 1}, {Regex: 'lib-b', Priority: 2}, {Regex: 'lib-c', Priority: 3}]}" | FileCheck %s + +#include <lib-c/header-1.hpp> +#include <lib-c/header-2.hpp> +#include <lib-c/header-3.hpp> +#include <lib-b/header-1.hpp> +#include "lib-b/main-include-char-quote-group.hpp" +#include <lib-b/header-3.hpp> +#include <lib-a/header-1.hpp> +#include <lib-a/main-include-char-quote-group.hpp> +#include <lib-a/header-3.hpp> + +// CHECK: "lib-b/main-include-char-quote-group.hpp" +// CHECK-EMPTY: +// CHECK-NEXT: <lib-a/header-1.hpp> +// CHECK-NEXT: <lib-a/header-3.hpp> +// CHECK-NEXT: <lib-a/main-include-char-quote-group.hpp> +// CHECK-EMPTY: +// CHECK-NEXT: <lib-b/header-1.hpp> +// CHECK-NEXT: <lib-b/header-3.hpp> +// CHECK-EMPTY: +// CHECK-NEXT: <lib-c/header-1.hpp> +// CHECK-NEXT: <lib-c/header-2.hpp> +// CHECK-NEXT: <lib-c/header-3.hpp> diff --git a/clang/test/Format/main-include-char.cpp b/clang/test/Format/main-include-char.cpp new file mode 100644 index 00000000000000..1835fc069f3597 --- /dev/null +++ b/clang/test/Format/main-include-char.cpp @@ -0,0 +1,15 @@ +// RUN: clang-format %s -style="{}" | FileCheck %s -check-prefix=QUOTE +// RUN: clang-format %s -style="{MainIncludeChar: Quote}" | FileCheck %s -check-prefix=QUOTE +// RUN: clang-format %s -style="{MainIncludeChar: Bracket}" | FileCheck %s -check-prefix=BRACKET + +#include <a> +#include "quote/main-include-char.hpp" +#include <bracket/main-include-char.hpp> + +// QUOTE: "quote/main-include-char.hpp" +// QUOTE-NEXT: <a> +// QUOTE-NEXT: <bracket/main-include-char.hpp> + +// BRACKET: <bracket/main-include-char.hpp> +// BRACKET-NEXT: "quote/main-include-char.hpp" +// BRACKET-NEXT: <a> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits