Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang-format] Optimize processing .clang-format-ignore files #76733

Merged
merged 2 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion clang/docs/ClangFormat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ An easy way to create the ``.clang-format`` file is:

Available style options are described in :doc:`ClangFormatStyleOptions`.

.clang-format-ignore
====================

You can create ``.clang-format-ignore`` files to make ``clang-format`` ignore
certain files. A ``.clang-format-ignore`` file consists of patterns of file path
names. It has the following format:
Expand All @@ -141,7 +144,8 @@ names. It has the following format:
* A non-comment line is a single pattern.
* The slash (``/``) is used as the directory separator.
* A pattern is relative to the directory of the ``.clang-format-ignore`` file
(or the root directory if the pattern starts with a slash).
(or the root directory if the pattern starts with a slash). Patterns
containing drive names (e.g. ``C:``) are not supported.
* Patterns follow the rules specified in `POSIX 2.13.1, 2.13.2, and Rule 1 of
2.13.3 <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/
V3_chap02.html#tag_18_13>`_.
Expand Down
17 changes: 15 additions & 2 deletions clang/test/Format/clang-format-ignore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,18 @@
// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
// RUN: not grep "Formatting \[2/2] foo.js" %t.stderr

// RUN: cd ../../..
// RUN: rm -rf %t.dir
// RUN: cd ../..
// RUN: clang-format -verbose *.cc level1/*.c* level1/level2/foo.* 2> %t.stderr
// RUN: grep "Formatting \[1/5] level1/level2/foo.c" %t.stderr
// RUN: not grep "Formatting \[2/5] level1/level2/foo.js" %t.stderr

// RUN: rm .clang-format-ignore
// RUN: clang-format -verbose *.cc level1/*.c* level1/level2/foo.* 2> %t.stderr
// RUN: grep "Formatting \[1/5] foo.cc" %t.stderr
// RUN: grep "Formatting \[2/5] level1/bar.cc" %t.stderr
// RUN: grep "Formatting \[3/5] level1/baz.c" %t.stderr
// RUN: grep "Formatting \[4/5] level1/level2/foo.c" %t.stderr
// RUN: not grep "Formatting \[5/5] level1/level2/foo.js" %t.stderr

// RUN: cd ..
// RUN: rm -r %t.dir
65 changes: 45 additions & 20 deletions clang/tools/clang-format/ClangFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,11 @@ static int dumpConfig(bool IsSTDIN) {
return 0;
}

using String = SmallString<128>;
static String IgnoreDir; // Directory of .clang-format-ignore file.
static StringRef PrevDir; // Directory of previous `FilePath`.
static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.

// Check whether `FilePath` is ignored according to the nearest
// .clang-format-ignore file based on the rules below:
// - A blank line is skipped.
Expand All @@ -586,45 +591,65 @@ static bool isIgnored(StringRef FilePath) {
if (!is_regular_file(FilePath))
return false;

using namespace llvm::sys::path;
SmallString<128> Path, AbsPath{FilePath};
String Path;
String AbsPath{FilePath};

using namespace llvm::sys::path;
make_absolute(AbsPath);
remove_dots(AbsPath, /*remove_dot_dot=*/true);

StringRef IgnoreDir{AbsPath};
do {
IgnoreDir = parent_path(IgnoreDir);
if (IgnoreDir.empty())
if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
PrevDir = Dir;

for (;;) {
Path = Dir;
append(Path, ".clang-format-ignore");
if (is_regular_file(Path))
break;
Dir = parent_path(Dir);
if (Dir.empty())
return false;
}

IgnoreDir = convert_to_slash(Dir);

std::ifstream IgnoreFile{Path.c_str()};
if (!IgnoreFile.good())
return false;

Path = IgnoreDir;
append(Path, ".clang-format-ignore");
} while (!is_regular_file(Path));
Patterns.clear();

std::ifstream IgnoreFile{Path.c_str()};
if (!IgnoreFile.good())
return false;
for (std::string Line; std::getline(IgnoreFile, Line);) {
if (const auto Pattern{StringRef{Line}.trim()};
// Skip empty and comment lines.
!Pattern.empty() && Pattern[0] != '#') {
Patterns.push_back(Pattern);
}
}
}

const auto Pathname = convert_to_slash(AbsPath);
for (std::string Line; std::getline(IgnoreFile, Line);) {
auto Pattern = StringRef(Line).trim();
if (Pattern.empty() || Pattern[0] == '#')
continue;
if (IgnoreDir.empty())
return false;

const bool IsNegated = Pattern[0] == '!';
const auto Pathname{convert_to_slash(AbsPath)};
for (const auto &Pat : Patterns) {
const bool IsNegated = Pat[0] == '!';
StringRef Pattern{Pat};
if (IsNegated)
Pattern = Pattern.drop_front();

if (Pattern.empty())
continue;

Pattern = Pattern.ltrim();

// `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
// This doesn't support patterns containing drive names (e.g. `C:`).
if (Pattern[0] != '/') {
Path = convert_to_slash(IgnoreDir);
Path = IgnoreDir;
append(Path, Style::posix, Pattern);
remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
Pattern = Path.str();
Pattern = Path;
}

if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
Expand Down
Loading