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
