https://github.com/owenca updated 
https://github.com/llvm/llvm-project/pull/182791

>From 33b738c61c448c8701f76958a3fa9698deaea1df Mon Sep 17 00:00:00 2001
From: Owen Pan <[email protected]>
Date: Sun, 22 Feb 2026 18:07:52 -0800
Subject: [PATCH] [clang-format] Allow InheritParentConfig to accept a
 directory

Add support for `BasedOnStyle: InheritParentConfig=<directory-path>` in
config files to redirect inheritance to the `.clang-format` or
`_clang-format` file in the `<directory_path>` directory.

Closes #107808
---
 clang/docs/ClangFormatStyleOptions.rst     | 10 ++--
 clang/include/clang/Format/Format.h        |  2 +-
 clang/lib/Format/Format.cpp                | 58 +++++++++++++++++-----
 clang/unittests/Format/ConfigParseTest.cpp | 52 +++++++++++++++++++
 4 files changed, 104 insertions(+), 18 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst 
b/clang/docs/ClangFormatStyleOptions.rst
index 4f81a084dd65b..13f5b92a9ea0b 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -182,11 +182,13 @@ the configuration (without a prefix: ``Auto``).
     Not a real style, but allows to use the ``.clang-format`` file from the
     parent directory (or its parent if there is none). If there is no parent
     file found it falls back to the ``fallback`` style, and applies the changes
-    to that.
-
-    With this option you can overwrite some parts of your main style for your
-    subdirectories. This is also possible through the command line, e.g.:
+    to that. With this option you can overwrite some parts of your main style
+    for your subdirectories. This is also possible through the command line,
+    e.g.:
     ``--style={BasedOnStyle: InheritParentConfig, ColumnLimit: 20}``
+  * ``InheritParentConfig=<directory-path>``
+    Same as the above except that the inheritance is redirected to
+    ``<directory-path``. This is only supported in configuration files.
 
 .. START_FORMAT_STYLE_OPTIONS
 
diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index c7e57d47f9ed1..1b3656a14a84f 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -56,7 +56,7 @@ struct FormatStyle {
   // If the BasedOn: was InheritParentConfig and this style needs the file from
   // the parent directories. It is not part of the actual style for formatting.
   // Thus the // instead of ///.
-  bool InheritsParentConfig;
+  std::string InheritConfig;
 
   /// The extra indent or outdent of access modifiers, e.g. ``public:``.
   /// \version 3.3
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index f0e9aff2fd21a..7bcf367f82904 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -24,6 +24,7 @@
 #include "UsingDeclarationsSorter.h"
 #include "clang/Tooling/Inclusions/HeaderIncludes.h"
 #include "llvm/ADT/Sequence.h"
+#include "llvm/ADT/StringSet.h"
 #include <limits>
 
 #define DEBUG_TYPE "format-formatter"
@@ -1762,7 +1763,6 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind 
Language) {
   LLVMStyle.IndentRequiresClause = true;
   LLVMStyle.IndentWidth = 2;
   LLVMStyle.IndentWrappedFunctionNames = false;
-  LLVMStyle.InheritsParentConfig = false;
   LLVMStyle.InsertBraces = false;
   LLVMStyle.InsertNewlineAtEOF = false;
   LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None;
@@ -2207,6 +2207,8 @@ FormatStyle getNoStyle() {
 
 bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language,
                         FormatStyle *Style) {
+  constexpr StringRef Prefix("inheritparentconfig=");
+
   if (Name.equals_insensitive("llvm"))
     *Style = getLLVMStyle(Language);
   else if (Name.equals_insensitive("chromium"))
@@ -2225,8 +2227,10 @@ bool getPredefinedStyle(StringRef Name, 
FormatStyle::LanguageKind Language,
     *Style = getClangFormatStyle();
   else if (Name.equals_insensitive("none"))
     *Style = getNoStyle();
-  else if (Name.equals_insensitive("inheritparentconfig"))
-    Style->InheritsParentConfig = true;
+  else if (Name.equals_insensitive(Prefix.drop_back()))
+    Style->InheritConfig = "..";
+  else if (Name.size() > Prefix.size() && Name.starts_with_insensitive(Prefix))
+    Style->InheritConfig = Name.substr(Prefix.size());
   else
     return false;
 
@@ -4424,7 +4428,7 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
       return make_string_error("Error parsing -style: " + ec.message());
     }
 
-    if (!Style.InheritsParentConfig)
+    if (Style.InheritConfig.empty())
       return Style;
 
     ChildFormatTextToApply.emplace_back(
@@ -4438,7 +4442,7 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
   const bool IsDotHFile = FileName.ends_with(".h");
 
   // User provided clang-format file using -style=file:path/to/format/file.
-  if (!Style.InheritsParentConfig &&
+  if (Style.InheritConfig.empty() &&
       StyleName.starts_with_insensitive("file:")) {
     auto ConfigFile = StyleName.substr(5);
     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
@@ -4452,7 +4456,7 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
     LLVM_DEBUG(llvm::dbgs()
                << "Using configuration file " << ConfigFile << "\n");
 
-    if (!Style.InheritsParentConfig)
+    if (Style.InheritConfig.empty())
       return Style;
 
     // Search for parent configs starting from the parent directory of
@@ -4464,10 +4468,10 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
   // If the style inherits the parent configuration it is a command line
   // configuration, which wants to inherit, so we have to skip the check of the
   // StyleName.
-  if (!Style.InheritsParentConfig && !StyleName.equals_insensitive("file")) {
+  if (Style.InheritConfig.empty() && !StyleName.equals_insensitive("file")) {
     if (!getPredefinedStyle(StyleName, Style.Language, &Style))
       return make_string_error("Invalid value for -style");
-    if (!Style.InheritsParentConfig)
+    if (Style.InheritConfig.empty())
       return Style;
   }
 
@@ -4476,7 +4480,7 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
     return make_string_error(EC.message());
 
   // Reset possible inheritance
-  Style.InheritsParentConfig = false;
+  Style.InheritConfig.clear();
 
   auto dropDiagnosticHandler = [](const llvm::SMDiagnostic &, void *) {};
 
@@ -4496,9 +4500,12 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
   FilesToLookFor.push_back(".clang-format");
   FilesToLookFor.push_back("_clang-format");
 
-  SmallString<128> UnsuitableConfigFiles;
+  llvm::StringSet<> Directories; // Inherited directories.
+  bool Redirected = false;
+  llvm::SmallString<128> Dir, UnsuitableConfigFiles;
   for (StringRef Directory = Path; !Directory.empty();
-       Directory = llvm::sys::path::parent_path(Directory)) {
+       Directory = Redirected ? Dir.str()
+                              : llvm::sys::path::parent_path(Directory)) {
     auto Status = FS->status(Directory);
     if (!Status ||
         Status->getType() != llvm::sys::fs::file_type::directory_file) {
@@ -4534,7 +4541,7 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
       LLVM_DEBUG(llvm::dbgs()
                  << "Using configuration file " << ConfigFile << "\n");
 
-      if (!Style.InheritsParentConfig) {
+      if (Style.InheritConfig.empty()) {
         if (!ChildFormatTextToApply.empty()) {
           LLVM_DEBUG(llvm::dbgs() << "Applying child configurations\n");
           applyChildFormatTexts(&Style);
@@ -4542,10 +4549,35 @@ Expected<FormatStyle> getStyle(StringRef StyleName, 
StringRef FileName,
         return Style;
       }
 
+      if (!Directories.insert(Directory).second) {
+        return make_string_error(
+            "Loop detected when inheriting configuration file in " + 
Directory);
+      }
+
       LLVM_DEBUG(llvm::dbgs() << "Inherits parent configuration\n");
 
+      if (Style.InheritConfig == "..") {
+        Redirected = false;
+      } else {
+        Redirected = true;
+        switch (Style.InheritConfig[0]) {
+        case '/':
+          Dir = Style.InheritConfig;
+          break;
+        case '~':
+          llvm::sys::fs::expand_tilde(Style.InheritConfig, Dir);
+          break;
+        default: {
+          Dir = Directory;
+          Dir.append("/");
+          Dir.append(Style.InheritConfig);
+        }
+        }
+        llvm::sys::fs::make_absolute(Dir);
+      }
+
       // Reset inheritance of style
-      Style.InheritsParentConfig = false;
+      Style.InheritConfig.clear();
 
       ChildFormatTextToApply.emplace_back(std::move(*Text));
 
diff --git a/clang/unittests/Format/ConfigParseTest.cpp 
b/clang/unittests/Format/ConfigParseTest.cpp
index fec1c48c448d2..e702c25e5bc72 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -1696,6 +1696,58 @@ TEST(ConfigParseTest, GetStyleOutput) {
   ASSERT_TRUE(Output.empty());
 }
 
+TEST(ConfigParseTest, RedirectInheritance) {
+  llvm::vfs::InMemoryFileSystem FS;
+  constexpr StringRef Config("BasedOnStyle: InheritParentConfig\n"
+                             "ColumnLimit: 20");
+  constexpr StringRef Code("int *f(int i, int j);");
+
+  ASSERT_TRUE(
+      FS.addFile("/a/.clang-format", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: GNU")));
+  ASSERT_TRUE(FS.addFile("/a/config/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(Config)));
+
+  ASSERT_TRUE(FS.addFile("/a/proj/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(
+                             "BasedOnStyle: InheritParentConfig=../config")));
+  ASSERT_TRUE(
+      FS.addFile("/a/proj/test.cc", 0, 
llvm::MemoryBuffer::getMemBuffer(Code)));
+  auto Style = getStyle("file", "/a/proj/test.cc", "none", "", &FS);
+  ASSERT_TRUE(static_cast<bool>(Style));
+  ASSERT_EQ(*Style, [] {
+    auto Style = getGNUStyle();
+    Style.ColumnLimit = 20;
+    return Style;
+  }());
+
+  ASSERT_TRUE(FS.addFile(
+      "/a/proj/src/.clang-format", 0,
+      llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: InheritParentConfig\n"
+                                       "PointerAlignment: Left")));
+  ASSERT_TRUE(FS.addFile("/a/proj/src/test.cc", 0,
+                         llvm::MemoryBuffer::getMemBuffer(Code)));
+  Style = getStyle("file", "/a/proj/src/test.cc", "none", "", &FS);
+  ASSERT_TRUE(static_cast<bool>(Style));
+  ASSERT_EQ(*Style, [] {
+    auto Style = getGNUStyle();
+    Style.ColumnLimit = 20;
+    Style.PointerAlignment = FormatStyle::PAS_Left;
+    return Style;
+  }());
+
+  ASSERT_TRUE(FS.addFile("/a/loop/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(
+                             "BasedOnStyle: InheritParentConfig=config")));
+  ASSERT_TRUE(
+      FS.addFile("/a/loop/test.cc", 0, 
llvm::MemoryBuffer::getMemBuffer(Code)));
+  ASSERT_TRUE(FS.addFile("/a/loop/config/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(Config)));
+  Style = getStyle("file", "/a/loop/test.cc", "none", "", &FS);
+  ASSERT_FALSE(static_cast<bool>(Style));
+  llvm::consumeError(Style.takeError());
+}
+
 } // namespace
 } // namespace format
 } // namespace clang

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to