[clang] [clang-format] Add .clang-format.ignore for ignoring files (PR #76327)
Owen Pan via cfe-commits
cfe-commits at lists.llvm.org
Sun Dec 24 01:32:50 PST 2023
https://github.com/owenca created https://github.com/llvm/llvm-project/pull/76327
Closes #52975.
>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] [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;
}
More information about the cfe-commits
mailing list