summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-15 00:27:43 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-15 00:27:43 +0200
commit59015f1f91d640502fdfc34019cf0154b5206bb8 (patch)
tree30e3ea85407b5ece06c92f3e971cf0cdfac8656b
parentfc6eda20555ea33f1a8260610056b7079c8e8fd9 (diff)
downloadkrino-59015f1f91d640502fdfc34019cf0154b5206bb8.tar.gz
krino-59015f1f91d640502fdfc34019cf0154b5206bb8.zip
explain walks the directory for a duplicate test in an exclude too
-rw-r--r--internal/engine/exclude_test.go29
-rw-r--r--internal/engine/match.go18
2 files changed, 44 insertions, 3 deletions
diff --git a/internal/engine/exclude_test.go b/internal/engine/exclude_test.go
index 5a758bb..9aafd7f 100644
--- a/internal/engine/exclude_test.go
+++ b/internal/engine/exclude_test.go
@@ -483,3 +483,32 @@ func TestPartlyReadableDocumentIsNotCached(t *testing.T) {
}
}
}
+
+// TestExplainAgreesWithMatchOnADuplicateExclude: a (duplicate) test inside
+// an exclude needs the directory's other files, as one inside a rule does,
+// so explain sets aside exactly the files Match does (plan 11 review M3).
+func TestExplainAgreesWithMatchOnADuplicateExclude(t *testing.T) {
+ h, dl := excludeTree(t, map[string]string{"a.txt": "same", "b.txt": "same", "c.txt": "other"})
+ main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": `
+(path "~/dl")
+(exclude (not (duplicate)))
+(rule "all" (move "Out"))
+`})
+ e, errs := Load(main)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+ r, err := e.Match(context.Background(), e.Dirs[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, fm := range append(append([]FileMatch{}, r.Matched...), r.Unmatched...) {
+ x, err := e.Explain(context.Background(), filepath.Join(dl, fm.File.Rel))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if x.Excluded != fm.Excluded {
+ t.Errorf("%s: explain sets it aside as %q, Match as %q", fm.File.Rel, x.Excluded, fm.Excluded)
+ }
+ }
+}
diff --git a/internal/engine/match.go b/internal/engine/match.go
index 6b98d73..c252596 100644
--- a/internal/engine/match.go
+++ b/internal/engine/match.go
@@ -280,10 +280,11 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error)
excl := e.excludeDirs(d)
skip := explainSkip(d, sf, excl, now)
- // The directory is walked only for a duplicate test, the one thing that
- // needs its other files (triage 21).
+ // The directory is walked only for a duplicate test - in a rule or in an
+ // exclude - the one thing that needs its other files (triage 21, plan 11
+ // review M3).
files := []scan.File{sf}
- if len(d.DupScopes) > 0 {
+ if len(d.DupScopes) > 0 || excludesUseDuplicate(d) {
files = e.filesForExplain(d, sf, excl, now)
}
run := newMatchRun(e, d, ctx, now, files)
@@ -332,6 +333,17 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error)
return &Explanation{Dir: d, File: sf, Skip: skip, Excludes: excludes, Excluded: excluded, Rules: rules, NoDelete: noDel}, nil
}
+// excludesUseDuplicate reports whether any of d's excludes has a
+// (duplicate) test.
+func excludesUseDuplicate(d *Dir) bool {
+ for _, x := range d.Excludes {
+ if len(x.Cond.DupDirs) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
// cacheFingerprint identifies what a cached keyword answer of d depends on
// besides the file: the extractor (its version and tools), normalisation
// and its Unicode tables, the Go release, and d's max-read, which caps what