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] Add .clang-format-ignore for ignoring files #76327

Merged
merged 9 commits into from
Dec 30, 2023

Conversation

owenca
Copy link
Contributor

@owenca owenca commented Dec 24, 2023

Closes #52975.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-format labels Dec 24, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Dec 24, 2023

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-format

Author: Owen Pan (owenca)

Changes

Closes #52975.


Full diff: https://github.com/llvm/llvm-project/pull/76327.diff

3 Files Affected:

  • (modified) clang/docs/ClangFormat.rst (+18)
  • (added) clang/test/Format/clang-format-ignore.cpp (+24)
  • (modified) clang/tools/clang-format/ClangFormat.cpp (+70-1)
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;
 }

@owenca owenca removed the clang Clang issues not falling into any other category label Dec 24, 2023
clang/docs/ClangFormat.rst Outdated Show resolved Hide resolved
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Dec 24, 2023
clang/test/Format/clang-format-ignore.cpp Outdated Show resolved Hide resolved
clang/test/Format/clang-format-ignore.cpp Outdated Show resolved Hide resolved
clang/tools/clang-format/ClangFormat.cpp Outdated Show resolved Hide resolved
clang/tools/clang-format/ClangFormat.cpp Outdated Show resolved Hide resolved
clang/tools/clang-format/ClangFormat.cpp Outdated Show resolved Hide resolved
@HazardyKnusperkeks
Copy link
Contributor

In the description (and the commit messages?) you wrote clang-format.ignore instead of clang-format-ignore.

@owenca owenca changed the title [clang-format] Add .clang-format.ignore for ignoring files [clang-format] Add .clang-format-ignore for ignoring files Dec 29, 2023
@owenca owenca removed the clang Clang issues not falling into any other category label Dec 29, 2023
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Dec 29, 2023
@owenca owenca removed the clang Clang issues not falling into any other category label Dec 29, 2023
clang/tools/clang-format/ClangFormat.cpp Outdated Show resolved Hide resolved
clang/tools/clang-format/ClangFormat.cpp Outdated Show resolved Hide resolved
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Dec 30, 2023
@owenca owenca removed the clang Clang issues not falling into any other category label Dec 30, 2023
@owenca owenca merged commit 0930812 into llvm:main Dec 30, 2023
4 of 5 checks passed
@owenca owenca deleted the 52975 branch December 30, 2023 03:40
jpienaar added a commit that referenced this pull request Jan 1, 2024
Post #76327 the build rule required the header in lib as source too.
Tried to just do minimal change specific to build.
@steakhal
Copy link
Contributor

steakhal commented Jan 4, 2024

FYI commit 0930812 added a test that does not pass on Windows (MSVC).

An example failure:
https://lab.llvm.org/buildbot/#/builders/123/builds/23811/steps/8/logs/stdio

Testing: 
FAIL: Clang :: Format/clang-format-ignore.cpp (1 of 19133)
******************** TEST 'Clang :: Format/clang-format-ignore.cpp' FAILED ********************
Exit Code: 1
Command Output (stdout):
--
# RUN: at line 1
rm -rf C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir
# executed command: rm -rf 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir'
# RUN: at line 2
mkdir -p C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir/level1/level2
# executed command: mkdir -p 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir/level1/level2'
# RUN: at line 4
cd C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir
# executed command: cd 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.dir'
# RUN: at line 5
echo "*" > .clang-format-ignore
# executed command: echo '*'
# RUN: at line 6
echo "level*/*.c*" >> .clang-format-ignore
# executed command: echo 'level*/*.c*'
# RUN: at line 7
echo "*/*2/foo.*" >> .clang-format-ignore
# executed command: echo '*/*2/foo.*'
# RUN: at line 8
touch foo.cc
# executed command: touch foo.cc
# RUN: at line 9
c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe -verbose .clang-format-ignore foo.cc 2> C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: 'c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe' -verbose .clang-format-ignore foo.cc
# RUN: at line 10
not grep Formatting C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: not grep Formatting 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr'
# RUN: at line 12
cd level1
# executed command: cd level1
# RUN: at line 13
touch bar.cc baz.c
# executed command: touch bar.cc baz.c
# RUN: at line 14
c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe -verbose bar.cc baz.c 2> C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: 'c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe' -verbose bar.cc baz.c
# RUN: at line 15
not grep Formatting C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: not grep Formatting 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr'
# RUN: at line 17
cd level2
# executed command: cd level2
# RUN: at line 18
touch foo.c foo.js
# executed command: touch foo.c foo.js
# RUN: at line 19
c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe -verbose foo.c foo.js 2> C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: 'c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe' -verbose foo.c foo.js
# RUN: at line 20
not grep Formatting C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: not grep Formatting 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr'
# RUN: at line 22
touch .clang-format-ignore
# executed command: touch .clang-format-ignore
# RUN: at line 23
c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe -verbose foo.c foo.js 2> C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: 'c:\b\slave\clang-x64-windows-msvc\build\stage1\bin\clang-format.exe' -verbose foo.c foo.js
# RUN: at line 24
grep -Fx "Formatting [1/2] foo.c" C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr
# executed command: grep -Fx 'Formatting [1/2] foo.c' 'C:\b\slave\clang-x64-windows-msvc\build\stage1\tools\clang\test\Format\Output\clang-format-ignore.cpp.tmp.stderr'
# note: command had no output on stdout or stderr
# error: command failed with exit status: 1
--
********************
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90..
********************
Failed Tests (1):
  Clang :: Format/clang-format-ignore.cpp

@owenca
Copy link
Contributor Author

owenca commented Jan 4, 2024

Actually, it's commit 42ec976. Please see here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang-format] Support .clang-format-ignore file
4 participants