Author: owenca
Date: 2026-03-01T20:12:17-08:00
New Revision: ab1d59e72524c0fbb77b614c46d67265f1f6a47d

URL: 
https://github.com/llvm/llvm-project/commit/ab1d59e72524c0fbb77b614c46d67265f1f6a47d
DIFF: 
https://github.com/llvm/llvm-project/commit/ab1d59e72524c0fbb77b614c46d67265f1f6a47d.diff

LOG: [clang-format] Allow InheritParentConfig to accept a directory (#182791)

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

Added: 
    

Modified: 
    clang/docs/ClangFormatStyleOptions.rst
    clang/include/clang/Format/Format.h
    clang/lib/Format/Format.cpp
    clang/unittests/Format/ConfigParseTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ClangFormatStyleOptions.rst 
b/clang/docs/ClangFormatStyleOptions.rst
index ed4e2aaa26052..b72481c7fd27b 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 2028c963ac306..a18987113938a 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -57,7 +57,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 2f67ec86b101a..c77960274f9c2 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"
@@ -1823,7 +1824,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;
@@ -2274,6 +2274,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"))
@@ -2292,8 +2294,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;
 
@@ -4491,7 +4495,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(
@@ -4505,7 +4509,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 =
@@ -4519,7 +4523,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
@@ -4531,19 +4535,29 @@ 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;
   }
 
-  SmallString<128> Path(FileName);
+  using namespace llvm::sys::path;
+  using String = SmallString<128>;
+
+  String Path(FileName);
   if (std::error_code EC = FS->makeAbsolute(Path))
     return make_string_error(EC.message());
 
+  auto Normalize = [](String &Path) {
+    Path = convert_to_slash(Path);
+    remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
+  };
+
+  Normalize(Path);
+
   // Reset possible inheritance
-  Style.InheritsParentConfig = false;
+  Style.InheritConfig.clear();
 
   auto dropDiagnosticHandler = [](const llvm::SMDiagnostic &, void *) {};
 
@@ -4563,19 +4577,24 @@ 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;
+  String Dir, UnsuitableConfigFiles;
   for (StringRef Directory = Path; !Directory.empty();
-       Directory = llvm::sys::path::parent_path(Directory)) {
+       Directory = Redirected ? Dir.str() : parent_path(Directory)) {
     auto Status = FS->status(Directory);
     if (!Status ||
         Status->getType() != llvm::sys::fs::file_type::directory_file) {
-      continue;
+      if (!Redirected)
+        continue;
+      return make_string_error("Failed to inherit configuration directory " +
+                               Directory);
     }
 
     for (const auto &F : FilesToLookFor) {
-      SmallString<128> ConfigFile(Directory);
+      String ConfigFile(Directory);
 
-      llvm::sys::path::append(ConfigFile, F);
+      append(ConfigFile, F);
       LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
 
       Status = FS->status(ConfigFile);
@@ -4601,7 +4620,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);
@@ -4609,10 +4628,30 @@ 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;
+        String ExpandedDir;
+        llvm::sys::fs::expand_tilde(Style.InheritConfig, ExpandedDir);
+        Normalize(ExpandedDir);
+        if (is_absolute(ExpandedDir, Style::posix)) {
+          Dir = ExpandedDir;
+        } else {
+          Dir = Directory.str();
+          append(Dir, Style::posix, ExpandedDir);
+        }
+      }
+
       // 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 f270602f32604..31326c27b3c59 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -1750,6 +1750,63 @@ 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/proj/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(
+                             "BasedOnStyle: InheritParentConfig=/a/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_FALSE(static_cast<bool>(Style));
+  ASSERT_EQ(toString(Style.takeError()),
+            "Failed to inherit configuration directory /a/config");
+
+  ASSERT_TRUE(FS.addFile("/a/config/.clang-format", 0,
+                         llvm::MemoryBuffer::getMemBuffer(Config)));
+  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));
+  ASSERT_EQ(toString(Style.takeError()),
+            "Loop detected when inheriting configuration file in /a/loop");
+}
+
 } // 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