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,13 @@ * ``GNU`` A style complying with the `GNU coding standards `_ + * ``File`` + 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 fount it falls back to the ``LLVM`` style. + + With this option you can overwrite some parts of your main style for your + subdirectories. .. 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 @@ -303,6 +303,8 @@ - Option ``SpacesInLineCommentPrefix`` has been added to control the number of spaces in a line comments prefix. +- ``BasedOnStyle: File`` 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 File 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 NeedsParentFile; + /// 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 @@ -867,6 +867,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { FormatStyle LLVMStyle; + LLVMStyle.NeedsParentFile = false; LLVMStyle.Language = Language; LLVMStyle.AccessModifierOffset = -2; LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right; @@ -1341,6 +1342,8 @@ *Style = getMicrosoftStyle(Language); } else if (Name.equals_lower("none")) { *Style = getNoStyle(); + } else if (Name.equals_lower("file")) { + Style->NeedsParentFile = true; } else { return false; } @@ -2946,6 +2949,36 @@ } LLVM_DEBUG(llvm::dbgs() << "Using configuration file " << ConfigFile << "\n"); + + if (Style.NeedsParentFile) { + LLVM_DEBUG(llvm::dbgs() << "Needs a parent configuration\n"); + + auto ParentDirectory = llvm::sys::path::parent_path(Directory); + + if (!ParentDirectory.empty()) { + SmallString<128> FileForLanguage(ParentDirectory); + llvm::sys::path::append(FileForLanguage, + llvm::sys::path::filename(FileName)); + if (auto OuterStyle = getStyle(DefaultFormatStyle, FileForLanguage, + FallbackStyleName, Code, FS, + AllowUnknownOptions)) { + parseConfiguration(*Text.get(), &*OuterStyle, + AllowUnknownOptions); + return *OuterStyle; + } else { + LLVM_DEBUG( + llvm::dbgs() + << "Error while parsing parent: " << OuterStyle.takeError() + << "\nUsing FallbackStyle:" << FallbackStyleName << "\n"); + } + } else { + LLVM_DEBUG(llvm::dbgs() << "No parent left, using FallbackStyle:" + << FallbackStyleName << "\n"); + } + + return FallbackStyle; + } + return Style; } } 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 @@ -16667,6 +16667,63 @@ 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: File\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: File\n" + "WhitespaceSensitiveMacros: ['FOO', 'BAR']"))); + std::vector NonDefaultWhiteSpaceMacros{"FOO", "BAR"}; + ASSERT_NE(Style9->WhitespaceSensitiveMacros, NonDefaultWhiteSpaceMacros); + Style9 = getStyle("file", "/e/sub/sub/code.cpp", "none", "", &FS); + ASSERT_TRUE(static_cast(Style9)); + ASSERT_EQ(*Style9, [&NonDefaultWhiteSpaceMacros] { + auto Style = getGoogleStyle(); + Style.ColumnLimit = 20; + Style.UseTab = FormatStyle::UT_Always; + Style.WhitespaceSensitiveMacros = NonDefaultWhiteSpaceMacros; + return Style; + }()); } TEST_F(ReplacementTest, FormatCodeAfterReplacements) {