Skip to content

Commit 42ec976

Browse files
authored
[clang-format] Optimize processing .clang-format-ignore files (#76733)
Reuse the patterns governing the previous input file being formatted if the current input file is from the same directory.
1 parent 6ae7f66 commit 42ec976

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

clang/docs/ClangFormat.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ An easy way to create the ``.clang-format`` file is:
131131
132132
Available style options are described in :doc:`ClangFormatStyleOptions`.
133133

134+
.clang-format-ignore
135+
====================
136+
134137
You can create ``.clang-format-ignore`` files to make ``clang-format`` ignore
135138
certain files. A ``.clang-format-ignore`` file consists of patterns of file path
136139
names. It has the following format:
@@ -141,7 +144,8 @@ names. It has the following format:
141144
* A non-comment line is a single pattern.
142145
* The slash (``/``) is used as the directory separator.
143146
* A pattern is relative to the directory of the ``.clang-format-ignore`` file
144-
(or the root directory if the pattern starts with a slash).
147+
(or the root directory if the pattern starts with a slash). Patterns
148+
containing drive names (e.g. ``C:``) are not supported.
145149
* Patterns follow the rules specified in `POSIX 2.13.1, 2.13.2, and Rule 1 of
146150
2.13.3 <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/
147151
V3_chap02.html#tag_18_13>`_.

clang/test/Format/clang-format-ignore.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,26 @@
2121

2222
// RUN: touch .clang-format-ignore
2323
// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
24-
// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
25-
// RUN: grep "Formatting \[2/2] foo.js" %t.stderr
24+
// RUN: grep -Fx "Formatting [1/2] foo.c" %t.stderr
25+
// RUN: grep -Fx "Formatting [2/2] foo.js" %t.stderr
2626

2727
// RUN: echo "*.js" > .clang-format-ignore
2828
// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
29-
// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
30-
// RUN: not grep "Formatting \[2/2] foo.js" %t.stderr
29+
// RUN: grep -Fx "Formatting [1/2] foo.c" %t.stderr
30+
// RUN: not grep -F foo.js %t.stderr
3131

32-
// RUN: cd ../../..
33-
// RUN: rm -rf %t.dir
32+
// RUN: cd ../..
33+
// RUN: clang-format -verbose *.cc level1/*.c* level1/level2/foo.* 2> %t.stderr
34+
// RUN: grep -x "Formatting \[1/5] .*foo\.c" %t.stderr
35+
// RUN: not grep -F foo.js %t.stderr
36+
37+
// RUN: rm .clang-format-ignore
38+
// RUN: clang-format -verbose *.cc level1/*.c* level1/level2/foo.* 2> %t.stderr
39+
// RUN: grep -x "Formatting \[1/5] .*foo\.cc" %t.stderr
40+
// RUN: grep -x "Formatting \[2/5] .*bar\.cc" %t.stderr
41+
// RUN: grep -x "Formatting \[3/5] .*baz\.c" %t.stderr
42+
// RUN: grep -x "Formatting \[4/5] .*foo\.c" %t.stderr
43+
// RUN: not grep -F foo.js %t.stderr
44+
45+
// RUN: cd ..
46+
// RUN: rm -r %t.dir

clang/tools/clang-format/ClangFormat.cpp

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,11 @@ static int dumpConfig(bool IsSTDIN) {
571571
return 0;
572572
}
573573

574+
using String = SmallString<128>;
575+
static String IgnoreDir; // Directory of .clang-format-ignore file.
576+
static StringRef PrevDir; // Directory of previous `FilePath`.
577+
static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
578+
574579
// Check whether `FilePath` is ignored according to the nearest
575580
// .clang-format-ignore file based on the rules below:
576581
// - A blank line is skipped.
@@ -586,45 +591,65 @@ static bool isIgnored(StringRef FilePath) {
586591
if (!is_regular_file(FilePath))
587592
return false;
588593

589-
using namespace llvm::sys::path;
590-
SmallString<128> Path, AbsPath{FilePath};
594+
String Path;
595+
String AbsPath{FilePath};
591596

597+
using namespace llvm::sys::path;
592598
make_absolute(AbsPath);
593599
remove_dots(AbsPath, /*remove_dot_dot=*/true);
594600

595-
StringRef IgnoreDir{AbsPath};
596-
do {
597-
IgnoreDir = parent_path(IgnoreDir);
598-
if (IgnoreDir.empty())
601+
if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
602+
PrevDir = Dir;
603+
604+
for (;;) {
605+
Path = Dir;
606+
append(Path, ".clang-format-ignore");
607+
if (is_regular_file(Path))
608+
break;
609+
Dir = parent_path(Dir);
610+
if (Dir.empty())
611+
return false;
612+
}
613+
614+
IgnoreDir = convert_to_slash(Dir);
615+
616+
std::ifstream IgnoreFile{Path.c_str()};
617+
if (!IgnoreFile.good())
599618
return false;
600619

601-
Path = IgnoreDir;
602-
append(Path, ".clang-format-ignore");
603-
} while (!is_regular_file(Path));
620+
Patterns.clear();
604621

605-
std::ifstream IgnoreFile{Path.c_str()};
606-
if (!IgnoreFile.good())
607-
return false;
622+
for (std::string Line; std::getline(IgnoreFile, Line);) {
623+
if (const auto Pattern{StringRef{Line}.trim()};
624+
// Skip empty and comment lines.
625+
!Pattern.empty() && Pattern[0] != '#') {
626+
Patterns.push_back(Pattern);
627+
}
628+
}
629+
}
608630

609-
const auto Pathname = convert_to_slash(AbsPath);
610-
for (std::string Line; std::getline(IgnoreFile, Line);) {
611-
auto Pattern = StringRef(Line).trim();
612-
if (Pattern.empty() || Pattern[0] == '#')
613-
continue;
631+
if (IgnoreDir.empty())
632+
return false;
614633

615-
const bool IsNegated = Pattern[0] == '!';
634+
const auto Pathname{convert_to_slash(AbsPath)};
635+
for (const auto &Pat : Patterns) {
636+
const bool IsNegated = Pat[0] == '!';
637+
StringRef Pattern{Pat};
616638
if (IsNegated)
617639
Pattern = Pattern.drop_front();
618640

619641
if (Pattern.empty())
620642
continue;
621643

622644
Pattern = Pattern.ltrim();
645+
646+
// `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
647+
// This doesn't support patterns containing drive names (e.g. `C:`).
623648
if (Pattern[0] != '/') {
624-
Path = convert_to_slash(IgnoreDir);
649+
Path = IgnoreDir;
625650
append(Path, Style::posix, Pattern);
626651
remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
627-
Pattern = Path.str();
652+
Pattern = Path;
628653
}
629654

630655
if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)

0 commit comments

Comments
 (0)