[clang] 2a2348e - [clang-format] Honor later negated .clang-format-ignore patterns (#195432)
via cfe-commits
cfe-commits at lists.llvm.org
Sun May 3 06:28:23 PDT 2026
Author: Dudenwatschn
Date: 2026-05-03T13:28:18Z
New Revision: 2a2348eb52241db20406a7e2ce8aa12f15be26ae
URL: https://github.com/llvm/llvm-project/commit/2a2348eb52241db20406a7e2ce8aa12f15be26ae
DIFF: https://github.com/llvm/llvm-project/commit/2a2348eb52241db20406a7e2ce8aa12f15be26ae.diff
LOG: [clang-format] Honor later negated .clang-format-ignore patterns (#195432)
This addresses (#178344).
In this issue the negation (`!`) for paths inside .clang-format-ignore
doesn't behave as intended, as clang-format stops processing patterns on
the first match, rather than processing further rules/patterns.
Rather than
```
foo/*
!foo/*.h
```
un-ignoring `.h`-files in `foo/` - the header-files remain ignored,
since the ignore-condition exits early and considers the files ignored
when checking `foo/*`.
I've tried to make negation work with the old behaviour (like mentioned
in #178344), but could find no sensible way to use it.
>From the draft/proposal of .clang-format-ignore (#52975) it looks like
the intent of the negation pattern was to behave similar to .gitignore -
where pattern matching continues after the first match is found.
With this change **patterns will continue to be evaluated after the
first match**, closer resembling the behaviour of .gitignore.
I have also added some simple unit-tests for the negation-pattern, as
there weren't any beforehand.
Co-authored-by: Alexander Imp <alexander.imp at gmx.at>
Added:
Modified:
clang/test/Format/clang-format-ignore.cpp
clang/test/Format/list-ignored.cpp
clang/tools/clang-format/ClangFormat.cpp
Removed:
################################################################################
diff --git a/clang/test/Format/clang-format-ignore.cpp b/clang/test/Format/clang-format-ignore.cpp
index 67d68aebde5d0..6689137b48074 100644
--- a/clang/test/Format/clang-format-ignore.cpp
+++ b/clang/test/Format/clang-format-ignore.cpp
@@ -56,5 +56,43 @@
// RUN: | FileCheck %s -check-prefix=CHECK7 -match-full-lines
// CHECK7: int i;
+// RUN: echo "foo.h" > .clang-format-ignore
+// RUN: echo "!foo.c" >> .clang-format-ignore
+// RUN: echo "int i ;" > foo.c
+// RUN: echo "int i ;" > foo.h
+// RUN: clang-format -verbose foo.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK8 -match-full-lines
+// CHECK8: Formatting [1/1] foo.c
+// RUN: clang-format -verbose foo.h 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK9 -allow-empty
+// CHECK9-NOT: int
+
+// RUN: mkdir -p %t.dir/ignore.dir/format.dir
+// RUN: echo "ignore.dir/*" > .clang-format-ignore
+// RUN: echo "!ignore.dir/format.dir/*" >> .clang-format-ignore
+// RUN: echo "int i ;" > ignore.dir/foo.c
+// RUN: echo "int i ;" > ignore.dir/format.dir/bar.c
+// RUN: clang-format -verbose ignore.dir/**/*.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK10 -match-full-lines
+// CHECK10: {{Formatting \[1/1] .*ignore\.dir[/\\]format\.dir[/\\]bar\.c}}
+
+// RUN: echo "foo.*" > .clang-format-ignore
+// RUN: clang-format -verbose foo.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK11 -allow-empty
+// CHECK11-NOT: int
+// RUN: echo "!foo.c" >> .clang-format-ignore
+// RUN: clang-format -verbose foo.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK12 -match-full-lines
+// CHECK12: Formatting [1/1] foo.c
+
+// RUN: echo "!foo.*" > .clang-format-ignore
+// RUN: clang-format -verbose foo.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK13 -match-full-lines
+// CHECK13: Formatting [1/1] foo.c
+// RUN: echo "foo.c" >> .clang-format-ignore
+// RUN: clang-format -verbose foo.c 2>&1 \
+// RUN: | FileCheck %s -check-prefix=CHECK14 -allow-empty
+// CHECK14-NOT: int
+
// RUN: cd ..
// RUN: rm -r %t.dir
diff --git a/clang/test/Format/list-ignored.cpp b/clang/test/Format/list-ignored.cpp
index 6e65a68a6f996..d5cc449c837f6 100644
--- a/clang/test/Format/list-ignored.cpp
+++ b/clang/test/Format/list-ignored.cpp
@@ -56,5 +56,16 @@
// CHECK7-NOT: foo.c
// CHECK7: foo.js
+// RUN: echo "!foo.c" > .clang-format-ignore
+// RUN: clang-format -list-ignored foo.c foo.h 2>&1 \
+// RUN: | FileCheck %s -allow-empty -check-prefix=CHECK8
+// CHECK8-NOT: foo.c
+
+// RUN: echo "*" > .clang-format-ignore
+// RUN: echo "!foo.*" >> .clang-format-ignore
+// RUN: clang-format -list-ignored foo.c 2>&1 \
+// RUN: | FileCheck %s -allow-empty -check-prefix=CHECK9
+// CHECK9-NOT: foo.c
+
// RUN: cd ..
// RUN: rm -r %t.dir
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index 37d0eb83414f4..1a38ef195cbac 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -636,6 +636,7 @@ static bool isIgnored(StringRef FilePath) {
if (IgnoreDir.empty())
return false;
+ bool IsIgnored = false;
const auto Pathname{convert_to_slash(AbsPath)};
for (const auto &Pat : Patterns) {
const bool IsNegated = Pat[0] == '!';
@@ -657,11 +658,11 @@ static bool isIgnored(StringRef FilePath) {
Pattern = Path;
}
- if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
- return true;
+ if (clang::format::matchFilePath(Pattern, Pathname))
+ IsIgnored = !IsNegated;
}
- return false;
+ return IsIgnored;
}
int main(int argc, const char **argv) {
More information about the cfe-commits
mailing list