From 25f753c51e7b17bfca08155c1d777c5667110970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= Date: Fri, 29 Jan 2021 09:29:00 +0100 Subject: [PATCH] [clang-format] Add possibility to be based on parent directory This allows the define BasedOnStyle: InheritParentConfig and then clang-format looks into the parent directories for their .clang-format and takes that as a basis. Differential Revision: https://reviews.llvm.org/D93844 --- clang/docs/ClangFormatStyleOptions.rst | 9 +++ clang/docs/ReleaseNotes.rst | 3 + clang/include/clang/Format/Format.h | 5 ++ clang/lib/Format/Format.cpp | 74 +++++++++++++++-- clang/unittests/Format/FormatTest.cpp | 105 +++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 7 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index d55c0d59b36a..7ae8ea913099 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -154,6 +154,15 @@ the configuration (without a prefix: ``Auto``). * ``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 index ff73ac92b758..6b28ead401cb 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -189,6 +189,9 @@ clang-format #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 index 7cedbfb80610..1a669ebf07f5 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -52,6 +52,11 @@ std::error_code make_error_code(ParseError e); /// 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 index fbef7c5148a2..a8808b389f84 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -908,6 +908,7 @@ static FormatStyle expandPresets(const FormatStyle &Style) { 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 @@ bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language, *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 @@ llvm::Expected getStyle(StringRef StyleName, StringRef FileName, 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::Expected getStyle(StringRef StyleName, StringRef FileName, } 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 @@ llvm::Expected getStyle(StringRef StyleName, StringRef FileName, 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 index da267b159f88..ccda0a87bef3 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -17923,6 +17923,111 @@ TEST(FormatStyle, GetStyleOfFile) { 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) {