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
