[clang] [clang-format] Add .clang-format-ignore for ignoring files (PR #76327)
Owen Pan via cfe-commits
cfe-commits at lists.llvm.org
Thu Dec 28 21:50:56 PST 2023
https://github.com/owenca updated https://github.com/llvm/llvm-project/pull/76327
>From 4afd12db61528b40d842a7fbee9af37c2235822c Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Sun, 24 Dec 2023 01:18:55 -0800
Subject: [PATCH 1/7] [clang-format] Add .clang-format.ignore for ignoring
files
Closes #52975.
---
clang/docs/ClangFormat.rst | 18 ++++++
clang/test/Format/clang-format-ignore.cpp | 24 ++++++++
clang/tools/clang-format/ClangFormat.cpp | 71 ++++++++++++++++++++++-
3 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Format/clang-format-ignore.cpp
diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst
index f52f35550d03eb..a0b28f2273991f 100644
--- a/clang/docs/ClangFormat.rst
+++ b/clang/docs/ClangFormat.rst
@@ -131,6 +131,24 @@ An easy way to create the ``.clang-format`` file is:
Available style options are described in :doc:`ClangFormatStyleOptions`.
+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:
+- A blank line is skipped.
+- Leading and trailing spaces of a line are trimmed.
+- A line starting with a hash (``#``) is a comment.
+- 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).
+- Patterns follow the rules specified in POSIX 2.13.1, 2.13.2, and Rule 1 of
+ 2.13.3.
+- A pattern is negated if it starts with a bang (``!``).
+To match all files in a directory, use e.g. ``foo/bar/*``. To match all files in
+the directory of the ``.clang-format-ignore`` file, use ``*``.
+Multiple ``.clang-format-ignore`` files are supported similar to the
+``.clang-format`` files, with a lower directory level file voiding the higher
+level ones.
Vim Integration
===============
diff --git a/clang/test/Format/clang-format-ignore.cpp b/clang/test/Format/clang-format-ignore.cpp
new file mode 100644
index 00000000000000..a2210266034d4c
--- /dev/null
+++ b/clang/test/Format/clang-format-ignore.cpp
@@ -0,0 +1,24 @@
+// RUN: mkdir -p %t.dir/level1/level2
+
+// RUN: cd %t.dir
+// RUN: printf "*\nlevel*/*.c*\n*/*2/foo.*\n" > .clang-format-ignore
+// RUN: touch foo.cc
+// RUN: clang-format -verbose .clang-format-ignore foo.cc 2> %t.stderr
+// RUN: not grep "Formatting" %t.stderr
+
+// RUN: cd level1
+// RUN: touch bar.cc baz.c
+// RUN: clang-format -verbose bar.cc baz.c 2> %t.stderr
+// RUN: not grep "Formatting" %t.stderr
+
+// RUN: cd level2
+// RUN: touch foo.c foo.js
+// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
+// RUN: not grep "Formatting" %t.stderr
+// RUN: printf "*.js\n" > .clang-format-ignore
+// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
+// RUN: grep -E "Formatting (.*)foo.c(.*)" %t.stderr
+// RUN: not grep -E "Formatting (.*)foo.js(.*)" %t.stderr
+
+// RUN: cd ../../..
+// RUN: rm -rf %t.dir
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index d2e3d8d43aef21..be78f8cbebf5e1 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -12,6 +12,7 @@
///
//===----------------------------------------------------------------------===//
+#include "../../lib/Format/MatchFilePath.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
@@ -570,6 +571,71 @@ static int dumpConfig(bool IsSTDIN) {
return 0;
}
+// Check whether `FilePath` is ignored according to the nearest
+// .clang-format-ignore file based on the rules below:
+// - A blank line is skipped.
+// - Leading and trailing spaces of a line are trimmed.
+// - A line starting with a hash (`#`) is a comment.
+// - 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).
+// - A pattern is negated if it starts with a bang (`!`).
+static bool isIgnored(const StringRef FilePath) {
+ if (!llvm::sys::fs::is_regular_file(FilePath))
+ return false;
+
+ using namespace llvm::sys::path;
+ SmallString<128> Path, AbsPath{convert_to_slash(FilePath)};
+
+ llvm::vfs::getRealFileSystem()->makeAbsolute(AbsPath);
+ remove_dots(AbsPath, /*remove_dot_dot=*/true);
+
+ StringRef IgnoreDir{AbsPath};
+ do {
+ IgnoreDir = parent_path(IgnoreDir);
+ if (IgnoreDir.empty())
+ return false;
+
+ Path = IgnoreDir;
+ append(Path, ".clang-format-ignore");
+ } while (!llvm::sys::fs::is_regular_file(Path));
+
+ std::ifstream IgnoreFile{Path.c_str()};
+ if (!IgnoreFile.good())
+ return false;
+
+ bool HasMatch = false;
+ for (std::string Pattern; std::getline(IgnoreFile, Pattern);) {
+ Pattern = StringRef(Pattern).trim();
+ if (Pattern.empty() || Pattern[0] == '#')
+ continue;
+
+ const bool IsNegated = Pattern[0] == '!';
+ if (IsNegated)
+ Pattern.erase(0, 1);
+
+ if (Pattern.empty())
+ continue;
+
+ Pattern = StringRef(Pattern).ltrim();
+ if (Pattern[0] != '/') {
+ Path = IgnoreDir;
+ append(Path, Pattern);
+ remove_dots(Path, /*remove_dot_dot=*/true);
+ Pattern = Path.str();
+ }
+
+ if (clang::format::matchFilePath(Pattern, AbsPath.str()) == !IsNegated) {
+ HasMatch = true;
+ break;
+ }
+ }
+
+ IgnoreFile.close();
+ return HasMatch;
+}
+
int main(int argc, const char **argv) {
llvm::InitLLVM X(argc, argv);
@@ -618,11 +684,14 @@ int main(int argc, const char **argv) {
unsigned FileNo = 1;
bool Error = false;
for (const auto &FileName : FileNames) {
+ const bool IsSTDIN = FileName == "-";
+ if (!IsSTDIN && isIgnored(FileName))
+ continue;
if (Verbose) {
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
<< FileName << "\n";
}
- Error |= clang::format::format(FileName, FileName == "-");
+ Error |= clang::format::format(FileName, IsSTDIN);
}
return Error ? 1 : 0;
}
>From 0abdba2a457be700921e903f66b5a3d7ca54c55f Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Sun, 24 Dec 2023 01:41:22 -0800
Subject: [PATCH 2/7] Update clang/docs/ClangFormat.rst
---
clang/docs/ClangFormat.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst
index a0b28f2273991f..822839c6842521 100644
--- a/clang/docs/ClangFormat.rst
+++ b/clang/docs/ClangFormat.rst
@@ -140,9 +140,9 @@ 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 follow the rules specified in POSIX 2.13.1, 2.13.2, and Rule 1 of
- 2.13.3.
+2.13.3.
- A pattern is negated if it starts with a bang (``!``).
To match all files in a directory, use e.g. ``foo/bar/*``. To match all files in
the directory of the ``.clang-format-ignore`` file, use ``*``.
>From fbf142f563a72ecc9b561296d14c96f2558f80ea Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Tue, 26 Dec 2023 00:48:01 -0800
Subject: [PATCH 3/7] Fix bugs for Windows.
---
clang/docs/ClangFormat.rst | 1 +
clang/test/Format/clang-format-ignore.cpp | 21 ++++++++++++++-------
clang/tools/clang-format/ClangFormat.cpp | 15 +++++++++------
3 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst
index 822839c6842521..67fdffbd116d8a 100644
--- a/clang/docs/ClangFormat.rst
+++ b/clang/docs/ClangFormat.rst
@@ -144,6 +144,7 @@ names. It has the following format:
- Patterns follow the rules specified in POSIX 2.13.1, 2.13.2, and Rule 1 of
2.13.3.
- A pattern is negated if it starts with a bang (``!``).
+
To match all files in a directory, use e.g. ``foo/bar/*``. To match all files in
the directory of the ``.clang-format-ignore`` file, use ``*``.
Multiple ``.clang-format-ignore`` files are supported similar to the
diff --git a/clang/test/Format/clang-format-ignore.cpp b/clang/test/Format/clang-format-ignore.cpp
index a2210266034d4c..3209588de80b02 100644
--- a/clang/test/Format/clang-format-ignore.cpp
+++ b/clang/test/Format/clang-format-ignore.cpp
@@ -1,24 +1,31 @@
+// RUN: rm -rf %t.dir
// RUN: mkdir -p %t.dir/level1/level2
// RUN: cd %t.dir
-// RUN: printf "*\nlevel*/*.c*\n*/*2/foo.*\n" > .clang-format-ignore
+// RUN: printf "%%s\n" "\*" "level*/*.c*" "*/*2/foo.*" > .clang-format-ignore
// RUN: touch foo.cc
// RUN: clang-format -verbose .clang-format-ignore foo.cc 2> %t.stderr
-// RUN: not grep "Formatting" %t.stderr
+// RUN: not grep Formatting %t.stderr
// RUN: cd level1
// RUN: touch bar.cc baz.c
// RUN: clang-format -verbose bar.cc baz.c 2> %t.stderr
-// RUN: not grep "Formatting" %t.stderr
+// RUN: not grep Formatting %t.stderr
// RUN: cd level2
// RUN: touch foo.c foo.js
// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
-// RUN: not grep "Formatting" %t.stderr
-// RUN: printf "*.js\n" > .clang-format-ignore
+// RUN: not grep Formatting %t.stderr
+
+// RUN: touch .clang-format-ignore
+// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
+// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
+// RUN: grep "Formatting \[2/2] foo.js" %t.stderr
+
+// RUN: printf "%%s\n" "*.js" > .clang-format-ignore
// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
-// RUN: grep -E "Formatting (.*)foo.c(.*)" %t.stderr
-// RUN: not grep -E "Formatting (.*)foo.js(.*)" %t.stderr
+// 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
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index be78f8cbebf5e1..7d39aab9ff37dc 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -581,14 +581,15 @@ static int dumpConfig(bool IsSTDIN) {
// - A pattern is relative to the directory of the .clang-format-ignore file (or
// the root directory if the pattern starts with a slash).
// - A pattern is negated if it starts with a bang (`!`).
-static bool isIgnored(const StringRef FilePath) {
- if (!llvm::sys::fs::is_regular_file(FilePath))
+static bool isIgnored(StringRef FilePath) {
+ using namespace llvm::sys::fs;
+ if (!is_regular_file(FilePath))
return false;
using namespace llvm::sys::path;
- SmallString<128> Path, AbsPath{convert_to_slash(FilePath)};
+ SmallString<128> Path, AbsPath{FilePath};
- llvm::vfs::getRealFileSystem()->makeAbsolute(AbsPath);
+ make_absolute(AbsPath);
remove_dots(AbsPath, /*remove_dot_dot=*/true);
StringRef IgnoreDir{AbsPath};
@@ -599,12 +600,14 @@ static bool isIgnored(const StringRef FilePath) {
Path = IgnoreDir;
append(Path, ".clang-format-ignore");
- } while (!llvm::sys::fs::is_regular_file(Path));
+ } while (!is_regular_file(Path));
std::ifstream IgnoreFile{Path.c_str()};
if (!IgnoreFile.good())
return false;
+ AbsPath = convert_to_slash(AbsPath);
+
bool HasMatch = false;
for (std::string Pattern; std::getline(IgnoreFile, Pattern);) {
Pattern = StringRef(Pattern).trim();
@@ -623,7 +626,7 @@ static bool isIgnored(const StringRef FilePath) {
Path = IgnoreDir;
append(Path, Pattern);
remove_dots(Path, /*remove_dot_dot=*/true);
- Pattern = Path.str();
+ Pattern = convert_to_slash(Path);
}
if (clang::format::matchFilePath(Pattern, AbsPath.str()) == !IsNegated) {
>From 1a6c747eff6d4583a72611639a191be91930e3fc Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Tue, 26 Dec 2023 01:34:00 -0800
Subject: [PATCH 4/7] Update clang/test/Format/clang-format-ignore.cpp
---
clang/test/Format/clang-format-ignore.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/test/Format/clang-format-ignore.cpp b/clang/test/Format/clang-format-ignore.cpp
index 3209588de80b02..38dd6212da3b5c 100644
--- a/clang/test/Format/clang-format-ignore.cpp
+++ b/clang/test/Format/clang-format-ignore.cpp
@@ -2,7 +2,9 @@
// RUN: mkdir -p %t.dir/level1/level2
// RUN: cd %t.dir
-// RUN: printf "%%s\n" "\*" "level*/*.c*" "*/*2/foo.*" > .clang-format-ignore
+// RUN: echo "*" > .clang-format-ignore
+// RUN: echo "level*/*.c*" >> .clang-format-ignore
+// RUN: echo "*/*2/foo.*" >> .clang-format-ignore
// RUN: touch foo.cc
// RUN: clang-format -verbose .clang-format-ignore foo.cc 2> %t.stderr
// RUN: not grep Formatting %t.stderr
>From 840f2ddc2767e4dd68de69c03f87d2eaea271438 Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Tue, 26 Dec 2023 01:34:41 -0800
Subject: [PATCH 5/7] Make clang/test/Format/clang-format-ignore.cpp work on
Windows
---
clang/test/Format/clang-format-ignore.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Format/clang-format-ignore.cpp b/clang/test/Format/clang-format-ignore.cpp
index 38dd6212da3b5c..0d6396a64a668d 100644
--- a/clang/test/Format/clang-format-ignore.cpp
+++ b/clang/test/Format/clang-format-ignore.cpp
@@ -24,7 +24,7 @@
// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
// RUN: grep "Formatting \[2/2] foo.js" %t.stderr
-// RUN: printf "%%s\n" "*.js" > .clang-format-ignore
+// RUN: echo "*.js" > .clang-format-ignore
// RUN: clang-format -verbose foo.c foo.js 2> %t.stderr
// RUN: grep "Formatting \[1/2] foo.c" %t.stderr
// RUN: not grep "Formatting \[2/2] foo.js" %t.stderr
>From 36be87713572fd962bf60bb9252c4dd613665155 Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Tue, 26 Dec 2023 07:18:50 -0800
Subject: [PATCH 6/7] Update clang/tools/clang-format/ClangFormat.cpp
---
clang/tools/clang-format/ClangFormat.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index 7d39aab9ff37dc..e0ebbcb87d0239 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -623,10 +623,10 @@ static bool isIgnored(StringRef FilePath) {
Pattern = StringRef(Pattern).ltrim();
if (Pattern[0] != '/') {
- Path = IgnoreDir;
- append(Path, Pattern);
- remove_dots(Path, /*remove_dot_dot=*/true);
- Pattern = convert_to_slash(Path);
+ Path = convert_to_slash(IgnoreDir);
+ append(Path, Style::posix, Pattern);
+ remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
+ Pattern = Path.str();
}
if (clang::format::matchFilePath(Pattern, AbsPath.str()) == !IsNegated) {
>From 5e42f86b8ba7840ae7045bb7bcde41b673730537 Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Thu, 28 Dec 2023 21:49:46 -0800
Subject: [PATCH 7/7] Addresses review comments.
---
clang/tools/clang-format/ClangFormat.cpp | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index e0ebbcb87d0239..925a6996a53ac9 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -608,20 +608,19 @@ static bool isIgnored(StringRef FilePath) {
AbsPath = convert_to_slash(AbsPath);
- bool HasMatch = false;
- for (std::string Pattern; std::getline(IgnoreFile, Pattern);) {
- Pattern = StringRef(Pattern).trim();
+ for (std::string Line; std::getline(IgnoreFile, Line);) {
+ auto Pattern = StringRef(Line).trim();
if (Pattern.empty() || Pattern[0] == '#')
continue;
const bool IsNegated = Pattern[0] == '!';
if (IsNegated)
- Pattern.erase(0, 1);
+ Pattern = Pattern.drop_front();
if (Pattern.empty())
continue;
- Pattern = StringRef(Pattern).ltrim();
+ Pattern = Pattern.ltrim();
if (Pattern[0] != '/') {
Path = convert_to_slash(IgnoreDir);
append(Path, Style::posix, Pattern);
@@ -629,14 +628,11 @@ static bool isIgnored(StringRef FilePath) {
Pattern = Path.str();
}
- if (clang::format::matchFilePath(Pattern, AbsPath.str()) == !IsNegated) {
- HasMatch = true;
- break;
- }
+ if (clang::format::matchFilePath(Pattern, AbsPath.str()) == !IsNegated)
+ return true;
}
- IgnoreFile.close();
- return HasMatch;
+ return false;
}
int main(int argc, const char **argv) {
More information about the cfe-commits
mailing list