Skip to content

Commit cd23949

Browse files
authored
[clang-format] Support globstar in .clang-format-ignore (#121404)
Closes #110160. Closes #114969.
1 parent c7ebe4f commit cd23949

File tree

4 files changed

+63
-12
lines changed

4 files changed

+63
-12
lines changed

clang/docs/ClangFormat.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ names. It has the following format:
150150
* Patterns follow the rules specified in `POSIX 2.13.1, 2.13.2, and Rule 1 of
151151
2.13.3 <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/
152152
V3_chap02.html#tag_18_13>`_.
153+
* Bash globstar (``**``) is supported.
153154
* A pattern is negated if it starts with a bang (``!``).
154155

155156
To match all files in a directory, use e.g. ``foo/bar/*``. To match all files in

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ clang-format
11251125
- Adds ``RemoveEmptyLinesInUnwrappedLines`` option.
11261126
- Adds ``KeepFormFeed`` option and set it to ``true`` for ``GNU`` style.
11271127
- Adds ``AllowShortNamespacesOnASingleLine`` option.
1128+
- Adds support for bash globstar in ``.clang-format-ignore``.
11281129

11291130
libclang
11301131
--------

clang/lib/Format/MatchFilePath.cpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ bool matchFilePath(StringRef Pattern, StringRef FilePath) {
2525
assert(!Pattern.empty());
2626
assert(!FilePath.empty());
2727

28+
const auto FilePathBack = FilePath.back();
29+
2830
// No match if `Pattern` ends with a non-meta character not equal to the last
2931
// character of `FilePath`.
30-
if (const auto C = Pattern.back(); !strchr("?*]", C) && C != FilePath.back())
32+
if (const auto C = Pattern.back(); !strchr("?*]", C) && C != FilePathBack)
3133
return false;
3234

3335
constexpr auto Separator = '/';
@@ -49,25 +51,37 @@ bool matchFilePath(StringRef Pattern, StringRef FilePath) {
4951
return false;
5052
break;
5153
case '*': {
52-
while (++I < EOP && Pattern[I] == '*') { // Skip consecutive stars.
54+
bool Globstar = I == 0 || Pattern[I - 1] == Separator;
55+
int StarCount = 1;
56+
for (; ++I < EOP && Pattern[I] == '*'; ++StarCount) {
57+
// Skip consecutive stars.
5358
}
59+
if (StarCount != 2)
60+
Globstar = false;
5461
const auto K = FilePath.find(Separator, J); // Index of next `Separator`.
5562
const bool NoMoreSeparatorsInFilePath = K == StringRef::npos;
5663
if (I == EOP) // `Pattern` ends with a star.
57-
return NoMoreSeparatorsInFilePath;
58-
// `Pattern` ends with a lone backslash.
59-
if (Pattern[I] == '\\' && ++I == EOP)
60-
return false;
64+
return Globstar || NoMoreSeparatorsInFilePath;
65+
if (Pattern[I] != Separator) {
66+
Globstar = false;
67+
// `Pattern` ends with a lone backslash.
68+
if (Pattern[I] == '\\' && ++I == EOP)
69+
return false;
70+
}
6171
// The star is followed by a (possibly escaped) `Separator`.
6272
if (Pattern[I] == Separator) {
63-
if (NoMoreSeparatorsInFilePath)
64-
return false;
65-
J = K; // Skip to next `Separator` in `FilePath`.
66-
break;
73+
if (!Globstar) {
74+
if (NoMoreSeparatorsInFilePath)
75+
return false;
76+
J = K; // Skip to next `Separator` in `FilePath`.
77+
break;
78+
}
79+
if (++I == EOP)
80+
return FilePathBack == Separator;
6781
}
6882
// Recurse.
69-
for (auto Pat = Pattern.substr(I); J < End && FilePath[J] != Separator;
70-
++J) {
83+
for (auto Pat = Pattern.substr(I);
84+
J < End && (Globstar || FilePath[J] != Separator); ++J) {
7185
if (matchFilePath(Pat, FilePath.substr(J)))
7286
return true;
7387
}

clang/unittests/Format/MatchFilePathTest.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,41 @@ TEST_F(MatchFilePathTest, Path) {
164164
EXPECT_FALSE(match("foo\\", R"(foo*\)"));
165165
}
166166

167+
TEST_F(MatchFilePathTest, Globstar) {
168+
EXPECT_TRUE(match("/", "**"));
169+
EXPECT_TRUE(match("foo", "**"));
170+
EXPECT_TRUE(match("/foo", "**"));
171+
EXPECT_TRUE(match("foo/", "**"));
172+
EXPECT_TRUE(match("foo/bar", "**"));
173+
174+
EXPECT_TRUE(match("/", "**/"));
175+
EXPECT_TRUE(match("foo/", "**/"));
176+
EXPECT_TRUE(match("/foo/", "**/"));
177+
EXPECT_TRUE(match("foo/bar/", "**/"));
178+
179+
EXPECT_TRUE(match("/", "/**"));
180+
EXPECT_TRUE(match("/foo", "/**"));
181+
EXPECT_TRUE(match("/foo/", "/**"));
182+
EXPECT_TRUE(match("/foo/bar", "/**"));
183+
184+
EXPECT_TRUE(match("foo", "**/foo"));
185+
EXPECT_TRUE(match("/foo", "**/foo"));
186+
EXPECT_TRUE(match("foo/bar", "**/bar"));
187+
EXPECT_TRUE(match("/foo/bar", "**/foo/bar"));
188+
EXPECT_TRUE(match("foo/bar/baz", "**/bar/baz"));
189+
190+
EXPECT_TRUE(match("abc/foo", "abc/**"));
191+
EXPECT_TRUE(match("abc/foo/", "abc/**"));
192+
EXPECT_TRUE(match("abc/foo/bar", "abc/**"));
193+
194+
EXPECT_TRUE(match("a/b", "a/**/b"));
195+
EXPECT_TRUE(match("a/x/b", "a/**/b"));
196+
EXPECT_TRUE(match("a/x/y/b", "a/**/b"));
197+
198+
EXPECT_FALSE(match("a/x/b", "a**/b"));
199+
EXPECT_FALSE(match("a/x/b", "a/**b"));
200+
}
201+
167202
} // namespace
168203
} // namespace format
169204
} // namespace clang

0 commit comments

Comments
 (0)