Skip to content

Commit 25f753c

Browse files
[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
1 parent 08c09bb commit 25f753c

File tree

5 files changed

+189
-7
lines changed

5 files changed

+189
-7
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ the configuration (without a prefix: ``Auto``).
154154
* ``GNU``
155155
A style complying with the `GNU coding standards
156156
<https://www.gnu.org/prep/standards/standards.html>`_
157+
* ``InheritParentConfig``
158+
Not a real style, but allows to use the ``.clang-format`` file from the
159+
parent directory (or its parent if there is none). If there is no parent
160+
file found it falls back to the ``fallback`` style, and applies the changes
161+
to that.
162+
163+
With this option you can overwrite some parts of your main style for your
164+
subdirectories. This is also possible through the command line, e.g.:
165+
``--style={BasedOnStyle: InheritParentConfig, ColumnLimit: 20}``
157166

158167
.. START_FORMAT_STYLE_OPTIONS
159168

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ clang-format
189189
#include "B/A.h"
190190
#include "B/a.h"
191191

192+
- ``BasedOnStyle: InheritParentConfig`` allows to use the ``.clang-format`` of
193+
the parent directories to overwrite only parts of it.
194+
192195
libclang
193196
--------
194197

clang/include/clang/Format/Format.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ std::error_code make_error_code(ParseError e);
5252
/// The ``FormatStyle`` is used to configure the formatting to follow
5353
/// specific guidelines.
5454
struct FormatStyle {
55+
// If the BasedOn: was InheritParentConfig and this style needs the file from
56+
// the parent directories. It is not part of the actual style for formatting.
57+
// Thus the // instead of ///.
58+
bool InheritsParentConfig;
59+
5560
/// The extra indent or outdent of access modifiers, e.g. ``public:``.
5661
int AccessModifierOffset;
5762

clang/lib/Format/Format.cpp

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ static FormatStyle expandPresets(const FormatStyle &Style) {
908908

909909
FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
910910
FormatStyle LLVMStyle;
911+
LLVMStyle.InheritsParentConfig = false;
911912
LLVMStyle.Language = Language;
912913
LLVMStyle.AccessModifierOffset = -2;
913914
LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right;
@@ -1382,6 +1383,8 @@ bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language,
13821383
*Style = getMicrosoftStyle(Language);
13831384
} else if (Name.equals_lower("none")) {
13841385
*Style = getNoStyle();
1386+
} else if (Name.equals_lower("inheritparentconfig")) {
1387+
Style->InheritsParentConfig = true;
13851388
} else {
13861389
return false;
13871390
}
@@ -2947,21 +2950,36 @@ llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
29472950
if (!getPredefinedStyle(FallbackStyleName, Style.Language, &FallbackStyle))
29482951
return make_string_error("Invalid fallback style \"" + FallbackStyleName);
29492952

2953+
llvm::SmallVector<std::unique_ptr<llvm::MemoryBuffer>, 1>
2954+
ChildFormatTextToApply;
2955+
29502956
if (StyleName.startswith("{")) {
29512957
// Parse YAML/JSON style from the command line.
2952-
if (std::error_code ec = parseConfiguration(
2953-
llvm::MemoryBufferRef(StyleName, "<command-line>"), &Style,
2954-
AllowUnknownOptions))
2958+
StringRef Source = "<command-line>";
2959+
if (std::error_code ec =
2960+
parseConfiguration(llvm::MemoryBufferRef(StyleName, Source), &Style,
2961+
AllowUnknownOptions))
29552962
return make_string_error("Error parsing -style: " + ec.message());
2956-
return Style;
2963+
if (Style.InheritsParentConfig)
2964+
ChildFormatTextToApply.emplace_back(
2965+
llvm::MemoryBuffer::getMemBuffer(StyleName, Source, false));
2966+
else
2967+
return Style;
29572968
}
29582969

2959-
if (!StyleName.equals_lower("file")) {
2970+
// If the style inherits the parent configuration it is a command line
2971+
// configuration, which wants to inherit, so we have to skip the check of the
2972+
// StyleName.
2973+
if (!Style.InheritsParentConfig && !StyleName.equals_lower("file")) {
29602974
if (!getPredefinedStyle(StyleName, Style.Language, &Style))
29612975
return make_string_error("Invalid value for -style");
2962-
return Style;
2976+
if (!Style.InheritsParentConfig)
2977+
return Style;
29632978
}
29642979

2980+
// Reset possible inheritance
2981+
Style.InheritsParentConfig = false;
2982+
29652983
// Look for .clang-format/_clang-format file in the file's parent directories.
29662984
SmallString<128> UnsuitableConfigFiles;
29672985
SmallString<128> Path(FileName);
@@ -3008,14 +3026,56 @@ llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
30083026
}
30093027
LLVM_DEBUG(llvm::dbgs()
30103028
<< "Using configuration file " << ConfigFile << "\n");
3011-
return Style;
3029+
3030+
if (!Style.InheritsParentConfig) {
3031+
if (ChildFormatTextToApply.empty())
3032+
return Style;
3033+
3034+
LLVM_DEBUG(llvm::dbgs() << "Applying child configurations\n");
3035+
3036+
for (const auto& MemBuf : llvm::reverse(ChildFormatTextToApply)){
3037+
auto Ec = parseConfiguration(*MemBuf, &Style, AllowUnknownOptions);
3038+
// It was already correctly parsed.
3039+
assert(!Ec);
3040+
static_cast<void>(Ec);
3041+
}
3042+
3043+
return Style;
3044+
}
3045+
3046+
LLVM_DEBUG(llvm::dbgs() << "Inherits parent configuration\n");
3047+
3048+
// Reset inheritance of style
3049+
Style.InheritsParentConfig = false;
3050+
3051+
ChildFormatTextToApply.emplace_back(std::move(*Text));
3052+
3053+
// Breaking out of the inner loop, since we don't want to parse
3054+
// .clang-format AND _clang-format, if both exist. Then we continue the
3055+
// inner loop (parent directories) in search for the parent
3056+
// configuration.
3057+
break;
30123058
}
30133059
}
30143060
}
30153061
if (!UnsuitableConfigFiles.empty())
30163062
return make_string_error("Configuration file(s) do(es) not support " +
30173063
getLanguageName(Style.Language) + ": " +
30183064
UnsuitableConfigFiles);
3065+
3066+
if (!ChildFormatTextToApply.empty()) {
3067+
assert(ChildFormatTextToApply.size() == 1);
3068+
3069+
LLVM_DEBUG(llvm::dbgs()
3070+
<< "Applying child configuration on fallback style\n");
3071+
3072+
auto Ec = parseConfiguration(*ChildFormatTextToApply.front(),
3073+
&FallbackStyle, AllowUnknownOptions);
3074+
// It was already correctly parsed.
3075+
assert(!Ec);
3076+
static_cast<void>(Ec);
3077+
}
3078+
30193079
return FallbackStyle;
30203080
}
30213081

clang/unittests/Format/FormatTest.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17923,6 +17923,111 @@ TEST(FormatStyle, GetStyleOfFile) {
1792317923
auto StyleTd = getStyle("file", "x.td", "llvm", "", &FS);
1792417924
ASSERT_TRUE((bool)StyleTd);
1792517925
ASSERT_EQ(*StyleTd, getLLVMStyle(FormatStyle::LK_TableGen));
17926+
17927+
// Test 9.1: overwriting a file style, when parent no file exists with no
17928+
// fallback style
17929+
ASSERT_TRUE(FS.addFile(
17930+
"/e/sub/.clang-format", 0,
17931+
llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: InheritParentConfig\n"
17932+
"ColumnLimit: 20")));
17933+
ASSERT_TRUE(FS.addFile("/e/sub/code.cpp", 0,
17934+
llvm::MemoryBuffer::getMemBuffer("int i;")));
17935+
auto Style9 = getStyle("file", "/e/sub/code.cpp", "none", "", &FS);
17936+
ASSERT_TRUE(static_cast<bool>(Style9));
17937+
ASSERT_EQ(*Style9, [] {
17938+
auto Style = getNoStyle();
17939+
Style.ColumnLimit = 20;
17940+
return Style;
17941+
}());
17942+
17943+
// Test 9.2: with LLVM fallback style
17944+
Style9 = getStyle("file", "/e/sub/code.cpp", "LLVM", "", &FS);
17945+
ASSERT_TRUE(static_cast<bool>(Style9));
17946+
ASSERT_EQ(*Style9, [] {
17947+
auto Style = getLLVMStyle();
17948+
Style.ColumnLimit = 20;
17949+
return Style;
17950+
}());
17951+
17952+
// Test 9.3: with a parent file
17953+
ASSERT_TRUE(
17954+
FS.addFile("/e/.clang-format", 0,
17955+
llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google\n"
17956+
"UseTab: Always")));
17957+
Style9 = getStyle("file", "/e/sub/code.cpp", "none", "", &FS);
17958+
ASSERT_TRUE(static_cast<bool>(Style9));
17959+
ASSERT_EQ(*Style9, [] {
17960+
auto Style = getGoogleStyle();
17961+
Style.ColumnLimit = 20;
17962+
Style.UseTab = FormatStyle::UT_Always;
17963+
return Style;
17964+
}());
17965+
17966+
// Test 9.4: propagate more than one level
17967+
ASSERT_TRUE(FS.addFile("/e/sub/sub/code.cpp", 0,
17968+
llvm::MemoryBuffer::getMemBuffer("int i;")));
17969+
ASSERT_TRUE(FS.addFile("/e/sub/sub/.clang-format", 0,
17970+
llvm::MemoryBuffer::getMemBuffer(
17971+
"BasedOnStyle: InheritParentConfig\n"
17972+
"WhitespaceSensitiveMacros: ['FOO', 'BAR']")));
17973+
std::vector<std::string> NonDefaultWhiteSpaceMacros{"FOO", "BAR"};
17974+
17975+
const auto SubSubStyle = [&NonDefaultWhiteSpaceMacros] {
17976+
auto Style = getGoogleStyle();
17977+
Style.ColumnLimit = 20;
17978+
Style.UseTab = FormatStyle::UT_Always;
17979+
Style.WhitespaceSensitiveMacros = NonDefaultWhiteSpaceMacros;
17980+
return Style;
17981+
}();
17982+
17983+
ASSERT_NE(Style9->WhitespaceSensitiveMacros, NonDefaultWhiteSpaceMacros);
17984+
Style9 = getStyle("file", "/e/sub/sub/code.cpp", "none", "", &FS);
17985+
ASSERT_TRUE(static_cast<bool>(Style9));
17986+
ASSERT_EQ(*Style9, SubSubStyle);
17987+
17988+
// Test 9.5: use InheritParentConfig as style name
17989+
Style9 =
17990+
getStyle("inheritparentconfig", "/e/sub/sub/code.cpp", "none", "", &FS);
17991+
ASSERT_TRUE(static_cast<bool>(Style9));
17992+
ASSERT_EQ(*Style9, SubSubStyle);
17993+
17994+
// Test 9.6: use command line style with inheritance
17995+
Style9 = getStyle("{BasedOnStyle: InheritParentConfig}", "/e/sub/code.cpp",
17996+
"none", "", &FS);
17997+
ASSERT_TRUE(static_cast<bool>(Style9));
17998+
ASSERT_EQ(*Style9, SubSubStyle);
17999+
18000+
// Test 9.7: use command line style with inheritance and own config
18001+
Style9 = getStyle("{BasedOnStyle: InheritParentConfig, "
18002+
"WhitespaceSensitiveMacros: ['FOO', 'BAR']}",
18003+
"/e/sub/code.cpp", "none", "", &FS);
18004+
ASSERT_TRUE(static_cast<bool>(Style9));
18005+
ASSERT_EQ(*Style9, SubSubStyle);
18006+
18007+
// Test 9.8: use inheritance from a file without BasedOnStyle
18008+
ASSERT_TRUE(FS.addFile("/e/withoutbase/.clang-format", 0,
18009+
llvm::MemoryBuffer::getMemBuffer("ColumnLimit: 123")));
18010+
ASSERT_TRUE(
18011+
FS.addFile("/e/withoutbase/sub/.clang-format", 0,
18012+
llvm::MemoryBuffer::getMemBuffer(
18013+
"BasedOnStyle: InheritParentConfig\nIndentWidth: 7")));
18014+
// Make sure we do not use the fallback style
18015+
Style9 = getStyle("file", "/e/withoutbase/code.cpp", "google", "", &FS);
18016+
ASSERT_TRUE(static_cast<bool>(Style9));
18017+
ASSERT_EQ(*Style9, [] {
18018+
auto Style = getLLVMStyle();
18019+
Style.ColumnLimit = 123;
18020+
return Style;
18021+
}());
18022+
18023+
Style9 = getStyle("file", "/e/withoutbase/sub/code.cpp", "google", "", &FS);
18024+
ASSERT_TRUE(static_cast<bool>(Style9));
18025+
ASSERT_EQ(*Style9, [] {
18026+
auto Style = getLLVMStyle();
18027+
Style.ColumnLimit = 123;
18028+
Style.IndentWidth = 7;
18029+
return Style;
18030+
}());
1792618031
}
1792718032

1792818033
TEST_F(ReplacementTest, FormatCodeAfterReplacements) {

0 commit comments

Comments
 (0)