[clang] [clang-format] Add MainIncludeChar option. (PR #78752)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 19 10:06:52 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: None (j-jorge)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/78752.diff


8 Files Affected:

- (modified) clang/docs/ClangFormatStyleOptions.rst (+18) 
- (modified) clang/include/clang/Tooling/Inclusions/IncludeStyle.h (+22) 
- (modified) clang/lib/Format/Format.cpp (+2) 
- (modified) clang/lib/Tooling/Inclusions/HeaderIncludes.cpp (+12-2) 
- (modified) clang/lib/Tooling/Inclusions/IncludeStyle.cpp (+7) 
- (added) clang/test/Format/main-include-char-bracket-group.cpp (+27) 
- (added) clang/test/Format/main-include-char-quote-group.cpp (+29) 
- (added) clang/test/Format/main-include-char.cpp (+15) 


``````````diff
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>

``````````

</details>


https://github.com/llvm/llvm-project/pull/78752


More information about the cfe-commits mailing list