From bb8b547023b0867f31d7452faceef5769a45b94b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 23:40:56 +0200 Subject: explain: shows a duplicate's skipped delete, leaves the cache alone, walks only for duplicate tests --- internal/engine/match.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'internal/engine/match.go') diff --git a/internal/engine/match.go b/internal/engine/match.go index 162730b..0e8b3d5 100644 --- a/internal/engine/match.go +++ b/internal/engine/match.go @@ -227,6 +227,7 @@ type Explanation struct { Excludes []ExcludeTrace Excluded string // the first exclude that matches, which sets the file aside; "" when none does Rules []RuleTrace + NoDelete string // why a delete from the matching rules would be skipped (spec ยง5.5); "" when it would not } // Explain reports, for one file, whether krino's ordinary scan would ever @@ -266,8 +267,18 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error) excl := e.excludeDirs(d) skip := explainSkip(d, sf, excl, now) - run := newMatchRun(e, d, ctx, now, e.filesForExplain(d, sf, excl, now)) - e.openCache(run) // read only: Explain never writes the cache + // The directory is walked only for a duplicate test, the one thing that + // needs its other files (triage 21). + files := []scan.File{sf} + if len(d.DupScopes) > 0 { + files = e.filesForExplain(d, sf, excl, now) + } + run := newMatchRun(e, d, ctx, now, files) + if len(d.ContentKeywords) > 0 { + // Loaded only: openCache would remove the cache of a directory with + // no content tests, and Explain holds no lock (triage 28e). + e.openCache(run) + } f := newFacts(run, sf) var excludes []ExcludeTrace @@ -282,6 +293,7 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error) } var rules []RuleTrace + var matched []RuleMatch stoppedBy := "" for _, r := range d.Rules { if stoppedBy != "" { @@ -292,14 +304,19 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error) match := trace.Value if match { f.matched = true + matched = append(matched, RuleMatch{Rule: r}) } rules = append(rules, RuleTrace{Rule: r, Match: match, Trace: trace}) if match && r.Conf.Stop { stoppedBy = r.Name } } + noDel := "" + if len(d.DupScopes) > 0 && deletes(matched) { + noDel = noDelete(f, d.DupScopes) + } - return &Explanation{Dir: d, File: sf, Skip: skip, Excludes: excludes, Excluded: excluded, Rules: rules}, nil + return &Explanation{Dir: d, File: sf, Skip: skip, Excludes: excludes, Excluded: excluded, Rules: rules, NoDelete: noDel}, nil } // cacheFingerprint identifies what a cached keyword answer of d depends on -- cgit v1.3