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 1/2] [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 >From 46bef7193e6992476ad7c902709ea102e31173e1 Mon Sep 17 00:00:00 2001 From: Owen Pan <[email protected]> Date: Tue, 24 Feb 2026 04:41:50 -0800 Subject: [PATCH 2/2] Fix Windows bugs and clean up --- clang/docs/ClangFormatStyleOptions.rst | 2 +- clang/lib/Format/Format.cpp | 47 +++++++++++++--------- clang/unittests/Format/ConfigParseTest.cpp | 19 +++++---- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 13f5b92a9ea0b..9f976488bc08f 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -188,7 +188,7 @@ the configuration (without a prefix: ``Auto``). ``--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. + ``<directory-path>``. This is only supported in configuration files. .. START_FORMAT_STYLE_OPTIONS diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 7bcf367f82904..42c71f84315ac 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -4475,10 +4475,20 @@ Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName, 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.InheritConfig.clear(); @@ -4502,20 +4512,22 @@ Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName, llvm::StringSet<> Directories; // Inherited directories. bool Redirected = false; - llvm::SmallString<128> Dir, UnsuitableConfigFiles; + String Dir, UnsuitableConfigFiles; for (StringRef Directory = Path; !Directory.empty(); - Directory = Redirected ? Dir.str() - : 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); @@ -4560,20 +4572,15 @@ Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName, 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); - } + 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); } - llvm::sys::fs::make_absolute(Dir); } // Reset inheritance of style diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index e702c25e5bc72..a886eb3d6c9b0 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -1705,15 +1705,19 @@ TEST(ConfigParseTest, RedirectInheritance) { 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"))); + "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(); @@ -1738,14 +1742,15 @@ TEST(ConfigParseTest, RedirectInheritance) { ASSERT_TRUE(FS.addFile("/a/loop/.clang-format", 0, llvm::MemoryBuffer::getMemBuffer( - "BasedOnStyle: InheritParentConfig=config"))); + "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, + 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()); + ASSERT_EQ(toString(Style.takeError()), + "Loop detected when inheriting configuration file in /a/loop"); } } // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
