Skip to content

Commit 725c8af

Browse files
committed
fix(noreferencestyle): return nil when all footnote matches are filtered out
The round-2 fix only guarded the zero-regex-match case. When FindAllSubmatchIndex finds matches but every one is filtered out by isFootnoteDefinitionAt/codeLines/codeSpans (e.g. a footnote-shaped token entirely inside a code span or code block), the pre-sized out stayed a non-nil empty slice instead of nil. Move the nil check to after the filter loop (len(out) == 0) instead of before it (len(matches) == 0), covering both cases with one guard. Caught independently by two round-3 review angles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTLNzEVChZtqSc7m7ph5Ah
1 parent ed2c65a commit 725c8af

2 files changed

Lines changed: 33 additions & 9 deletions

File tree

internal/rules/noreferencestyle/alloc_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ func TestScanFootnoteDefinitions_PresizedAllocs(t *testing.T) {
101101
// docs/development/high-performance-go.md's "return nil, not []T{}"
102102
// convention for the zero-match case: make([]footnoteOccurrence, 0,
103103
// len(matches)) with len(matches) == 0 still returns a non-nil empty
104-
// slice in Go, so the pre-sizing fix must special-case the no-match
105-
// return to stay nil, matching the convention this same PR applies to
106-
// headingincrement and tableformat.
104+
// slice in Go, so the pre-sizing fix checks len(out) == 0 after the
105+
// filter loop and returns nil, matching the convention this same PR
106+
// applies to headingincrement and tableformat.
107107
func TestScanFootnoteReferences_NoMatches_ReturnsNil(t *testing.T) {
108108
f, err := lint.NewFile("clean.md", []byte("No footnotes here.\n"))
109109
require.NoError(t, err)
@@ -117,3 +117,27 @@ func TestScanFootnoteDefinitions_NoMatches_ReturnsNil(t *testing.T) {
117117
out := scanFootnoteDefinitions(f, map[int]struct{}{})
118118
assert.Nil(t, out, "scanFootnoteDefinitions must return nil when there are no matches")
119119
}
120+
121+
// TestScanFootnoteReferences_AllFilteredOut_ReturnsNil and
122+
// TestScanFootnoteDefinitions_AllFilteredOut_ReturnsNil cover the case
123+
// the zero-match guard alone misses: matches is non-empty, but every
124+
// entry is filtered out by isFootnoteDefinitionAt/codeLines/codeSpans
125+
// (here, a footnote-shaped token inside a code span). The result must
126+
// still be nil, not the pre-sized empty backing array. Caught by code
127+
// review round 3.
128+
func TestScanFootnoteReferences_AllFilteredOut_ReturnsNil(t *testing.T) {
129+
f, err := lint.NewFile("codespan.md", []byte("Use the `[^1]` token.\n"))
130+
require.NoError(t, err)
131+
codeSpans := f.CodeSpanLiteralRanges()
132+
out := scanFootnoteReferences(f, map[int]struct{}{}, codeSpans)
133+
assert.Nil(t, out, "scanFootnoteReferences must return nil when every match is filtered out")
134+
}
135+
136+
func TestScanFootnoteDefinitions_AllFilteredOut_ReturnsNil(t *testing.T) {
137+
src := "Example:\n\n```text\n[^1]: not a real definition\n```\n"
138+
f, err := lint.NewFile("codeblock.md", []byte(src))
139+
require.NoError(t, err)
140+
codeLines := lint.CollectCodeBlockLines(f)
141+
out := scanFootnoteDefinitions(f, codeLines)
142+
assert.Nil(t, out, "scanFootnoteDefinitions must return nil when every match is filtered out")
143+
}

internal/rules/noreferencestyle/rule.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,6 @@ func scanFootnoteReferences(
373373
) []footnoteOccurrence {
374374
source := f.Source
375375
matches := footnoteRefRE.FindAllSubmatchIndex(source, -1)
376-
if len(matches) == 0 {
377-
return nil
378-
}
379376
out := make([]footnoteOccurrence, 0, len(matches))
380377
for _, m := range matches {
381378
start := m[0]
@@ -398,6 +395,9 @@ func scanFootnoteReferences(
398395
end: m[1],
399396
})
400397
}
398+
if len(out) == 0 {
399+
return nil
400+
}
401401
return out
402402
}
403403

@@ -406,9 +406,6 @@ func scanFootnoteDefinitions(
406406
) []footnoteOccurrence {
407407
source := f.Source
408408
matches := footnoteDefRE.FindAllSubmatchIndex(source, -1)
409-
if len(matches) == 0 {
410-
return nil
411-
}
412409
out := make([]footnoteOccurrence, 0, len(matches))
413410
for _, m := range matches {
414411
start := m[0]
@@ -424,6 +421,9 @@ func scanFootnoteDefinitions(
424421
end: m[1],
425422
})
426423
}
424+
if len(out) == 0 {
425+
return nil
426+
}
427427
return out
428428
}
429429

0 commit comments

Comments
 (0)