diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -154,6 +154,15 @@ * ``GNU`` A style complying with the `GNU coding standards `_ + * ``InheritParentConfig`` + 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.: + ``--style={BasedOnStyle: InheritParentConfig, ColumnLimit: 20}`` .. START_FORMAT_STYLE_OPTIONS diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -189,6 +189,9 @@ #include "B/A.h" #include "B/a.h" +- ``BasedOnStyle: InheritParentConfig`` allows to use the ``.clang-format`` of + the parent directories to overwrite only parts of it. + libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -52,6 +52,11 @@ /// The ``FormatStyle`` is used to configure the formatting to follow /// specific guidelines. 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; + /// The extra indent or outdent of access modifiers, e.g. ``public:``. int AccessModifierOffset; diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -908,6 +908,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { FormatStyle LLVMStyle; + LLVMStyle.InheritsParentConfig = false; LLVMStyle.Language = Language; LLVMStyle.AccessModifierOffset = -2; LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right; @@ -1382,6 +1383,8 @@ *Style = getMicrosoftStyle(Language); } else if (Name.equals_lower("none")) { *Style = getNoStyle(); + } else if (Name.equals_lower("inheritparentconfig")) { + Style->InheritsParentConfig = true; } else { return false; } @@ -2947,21 +2950,36 @@ if (!getPredefinedStyle(FallbackStyleName, Style.Language, &FallbackStyle)) return make_string_error("Invalid fallback style \"" + FallbackStyleName); + llvm::SmallVector, 1> + ChildFormatTextToApply; + if (StyleName.startswith("{")) { // Parse YAML/JSON style from the command line. - if (std::error_code ec = parseConfiguration( - llvm::MemoryBufferRef(StyleName, ""), &Style, - AllowUnknownOptions)) + StringRef Source = ""; + if (std::error_code ec = + parseConfiguration(llvm::MemoryBufferRef(StyleName, Source), &Style, + AllowUnknownOptions)) return make_string_error("Error parsing -style: " + ec.message()); - return Style; + if (Style.InheritsParentConfig) + ChildFormatTextToApply.emplace_back( + llvm::MemoryBuffer::getMemBuffer(StyleName, Source, false)); + else + return Style; } - if (!StyleName.equals_lower("file")) { + // 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_lower("file")) { if (!getPredefinedStyle(StyleName, Style.Language, &Style)) return make_string_error("Invalid value for -style"); - return Style; + if (!Style.InheritsParentConfig) + return Style; } + // Reset possible inheritance + Style.InheritsParentConfig = false; + // Look for .clang-format/_clang-format file in the file's parent directories. SmallString<128> UnsuitableConfigFiles; SmallString<128> Path(FileName); @@ -3008,7 +3026,35 @@ } LLVM_DEBUG(llvm::dbgs() << "Using configuration file " << ConfigFile << "\n"); - return Style; + + if (!Style.InheritsParentConfig) { + if (ChildFormatTextToApply.empty()) + return Style; + + LLVM_DEBUG(llvm::dbgs() << "Applying child configurations\n"); + + for (const auto& MemBuf : llvm::reverse(ChildFormatTextToApply)){ + auto Ec = parseConfiguration(*MemBuf, &Style, AllowUnknownOptions); + // It was already correctly parsed. + assert(!Ec); + static_cast(Ec); + } + + return Style; + } + + LLVM_DEBUG(llvm::dbgs() << "Inherits parent configuration\n"); + + // Reset inheritance of style + Style.InheritsParentConfig = false; + + ChildFormatTextToApply.emplace_back(std::move(*Text)); + + // Breaking out of the inner loop, since we don't want to parse + // .clang-format AND _clang-format, if both exist. Then we continue the + // inner loop (parent directories) in search for the parent + // configuration. + break; } } } @@ -3016,6 +3062,20 @@ return make_string_error("Configuration file(s) do(es) not support " + getLanguageName(Style.Language) + ": " + UnsuitableConfigFiles); + + if (!ChildFormatTextToApply.empty()) { + assert(ChildFormatTextToApply.size() == 1); + + LLVM_DEBUG(llvm::dbgs() + << "Applying child configuration on fallback style\n"); + + auto Ec = parseConfiguration(*ChildFormatTextToApply.front(), + &FallbackStyle, AllowUnknownOptions); + // It was already correctly parsed. + assert(!Ec); + static_cast(Ec); + } + return FallbackStyle; } diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -17923,6 +17923,111 @@ auto StyleTd = getStyle("file", "x.td", "llvm", "", &FS); ASSERT_TRUE((bool)StyleTd); ASSERT_EQ(*StyleTd, getLLVMStyle(FormatStyle::LK_TableGen)); + + // Test 9.1: overwriting a file style, when parent no file exists with no + // fallback style + ASSERT_TRUE(FS.addFile( + "/e/sub/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: InheritParentConfig\n" + "ColumnLimit: 20"))); + ASSERT_TRUE(FS.addFile("/e/sub/code.cpp", 0, + llvm::MemoryBuffer::getMemBuffer("int i;"))); + auto Style9 = getStyle("file", "/e/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [] { + auto Style = getNoStyle(); + Style.ColumnLimit = 20; + return Style; + }()); + + // Test 9.2: with LLVM fallback style + Style9 = getStyle("file", "/e/sub/code.cpp", "LLVM", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [] { + auto Style = getLLVMStyle(); + Style.ColumnLimit = 20; + return Style; + }()); + + // Test 9.3: with a parent file + ASSERT_TRUE( + FS.addFile("/e/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google\n" + "UseTab: Always"))); + Style9 = getStyle("file", "/e/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [] { + auto Style = getGoogleStyle(); + Style.ColumnLimit = 20; + Style.UseTab = FormatStyle::UT_Always; + return Style; + }()); + + // Test 9.4: propagate more than one level + ASSERT_TRUE(FS.addFile("/e/sub/sub/code.cpp", 0, + llvm::MemoryBuffer::getMemBuffer("int i;"))); + ASSERT_TRUE(FS.addFile("/e/sub/sub/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer( + "BasedOnStyle: InheritParentConfig\n" + "WhitespaceSensitiveMacros: ['FOO', 'BAR']"))); + std::vector NonDefaultWhiteSpaceMacros{"FOO", "BAR"}; + + const auto SubSubStyle = [&NonDefaultWhiteSpaceMacros] { + auto Style = getGoogleStyle(); + Style.ColumnLimit = 20; + Style.UseTab = FormatStyle::UT_Always; + Style.WhitespaceSensitiveMacros = NonDefaultWhiteSpaceMacros; + return Style; + }(); + + ASSERT_NE(Style9->WhitespaceSensitiveMacros, NonDefaultWhiteSpaceMacros); + Style9 = getStyle("file", "/e/sub/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, SubSubStyle); + + // Test 9.5: use InheritParentConfig as style name + Style9 = + getStyle("inheritparentconfig", "/e/sub/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, SubSubStyle); + + // Test 9.6: use command line style with inheritance + Style9 = getStyle("{BasedOnStyle: InheritParentConfig}", "/e/sub/code.cpp", + "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, SubSubStyle); + + // Test 9.7: use command line style with inheritance and own config + Style9 = getStyle("{BasedOnStyle: InheritParentConfig, " + "WhitespaceSensitiveMacros: ['FOO', 'BAR']}", + "/e/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, SubSubStyle); + + // Test 9.8: use inheritance from a file without BasedOnStyle + ASSERT_TRUE(FS.addFile("/e/withoutbase/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("ColumnLimit: 123"))); + ASSERT_TRUE( + FS.addFile("/e/withoutbase/sub/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer( + "BasedOnStyle: InheritParentConfig\nIndentWidth: 7"))); + // Make sure we do not use the fallback style + Style9 = getStyle("file", "/e/withoutbase/code.cpp", "google", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [] { + auto Style = getLLVMStyle(); + Style.ColumnLimit = 123; + return Style; + }()); + + Style9 = getStyle("file", "/e/withoutbase/sub/code.cpp", "google", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [] { + auto Style = getLLVMStyle(); + Style.ColumnLimit = 123; + Style.IndentWidth = 7; + return Style; + }()); } TEST_F(ReplacementTest, FormatCodeAfterReplacements) {