[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