Skip to content

Commit c11d0a0

Browse files
committed
fix: address Copilot review and coverage gaps
- Remove dangling isTable comments from paragraphreadability and paragraphstructure (leftover from the private helper deletion) - Add TestHeadingLine_WalkDescendsIntoNonTextChild to cover the ast.Walk body in HeadingLine (reached only via synthetic headings with no Lines() set; real goldmark ATX headings always set Lines()) - Add TestHeadingLastLine_NoLines_FallsBackToAstutil to cover the astutil.HeadingLine fallback in headingLastLine - Add TestHeadingText_LinkText, TestExtractText_LinkNode, and TestHeadingText_AndExtractText_NoChildren as requested by Copilot; these also document behavior for links and empty nodes - astutil package now has 100% statement coverage https://claude.ai/code/session_01YbFAKES95Szi7i251Yv1XE
1 parent 5df1f3f commit c11d0a0

4 files changed

Lines changed: 88 additions & 2 deletions

File tree

internal/rules/astutil/astutil_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
1010
"github.com/yuin/goldmark/ast"
11+
"github.com/yuin/goldmark/text"
1112
)
1213

1314
// --- HeadingLine ---
@@ -270,3 +271,78 @@ func TestExtractText_DirectTextNode(t *testing.T) {
270271
return ast.WalkContinue, nil
271272
})
272273
}
274+
275+
// TestHeadingLine_WalkDescendsIntoNonTextChild exercises the ast.Walk path in
276+
// HeadingLine for headings where Lines() is empty (e.g. synthetic nodes).
277+
// The walk must descend through a non-text child (Emphasis) to reach the Text.
278+
func TestHeadingLine_WalkDescendsIntoNonTextChild(t *testing.T) {
279+
src := []byte("Text\n\n## end\n")
280+
// "end" starts at byte offset 9 (line 3).
281+
f, err := lint.NewFile("test.md", src)
282+
require.NoError(t, err)
283+
284+
heading := ast.NewHeading(2) // no Lines() set
285+
emph := ast.NewEmphasis(1)
286+
txt := ast.NewText()
287+
txt.Segment = text.NewSegment(9, 12)
288+
emph.AppendChild(emph, txt)
289+
heading.AppendChild(heading, emph)
290+
291+
assert.Equal(t, 3, HeadingLine(heading, f))
292+
}
293+
294+
// --- HeadingText and ExtractText additional cases ---
295+
296+
func TestHeadingText_LinkText(t *testing.T) {
297+
src := []byte("# [mdsmith](https://example.com)\n")
298+
f, err := lint.NewFile("test.md", src)
299+
require.NoError(t, err)
300+
301+
found := false
302+
_ = ast.Walk(f.AST, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
303+
if !entering {
304+
return ast.WalkContinue, nil
305+
}
306+
if h, ok := n.(*ast.Heading); ok {
307+
found = true
308+
assert.Equal(t, "mdsmith", HeadingText(h, f.Source))
309+
return ast.WalkStop, nil
310+
}
311+
return ast.WalkContinue, nil
312+
})
313+
require.True(t, found)
314+
}
315+
316+
func TestExtractText_LinkNode(t *testing.T) {
317+
src := []byte("# [mdsmith](https://example.com)\n")
318+
f, err := lint.NewFile("test.md", src)
319+
require.NoError(t, err)
320+
321+
found := false
322+
_ = ast.Walk(f.AST, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
323+
if !entering {
324+
return ast.WalkContinue, nil
325+
}
326+
if h, ok := n.(*ast.Heading); ok {
327+
link, ok2 := h.FirstChild().(*ast.Link)
328+
require.True(t, ok2)
329+
var buf bytes.Buffer
330+
ExtractText(link, f.Source, &buf)
331+
assert.Equal(t, "mdsmith", buf.String())
332+
found = true
333+
return ast.WalkStop, nil
334+
}
335+
return ast.WalkContinue, nil
336+
})
337+
require.True(t, found)
338+
}
339+
340+
func TestHeadingText_AndExtractText_NoChildren(t *testing.T) {
341+
h := ast.NewHeading(1)
342+
assert.Equal(t, "", HeadingText(h, nil))
343+
344+
var buf bytes.Buffer
345+
emptyLink := ast.NewLink()
346+
ExtractText(emptyLink, nil, &buf)
347+
assert.Equal(t, "", buf.String())
348+
}

internal/rules/blanklinearoundheadings/rule_coverage_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,18 @@ func TestFix_Heading_InsertsBlankLines(t *testing.T) {
165165
assert.Contains(t, string(result), "\n\nmore text")
166166
}
167167

168+
// --- headingLastLine fallback ---
169+
170+
func TestHeadingLastLine_NoLines_FallsBackToAstutil(t *testing.T) {
171+
// Synthetic heading with no Lines() set exercises the fallback branch
172+
// in headingLastLine that delegates to astutil.HeadingLine.
173+
heading := ast.NewHeading(1)
174+
f, err := lint.NewFile("test.md", []byte("# X\n"))
175+
require.NoError(t, err)
176+
last := headingLastLine(heading, f)
177+
assert.Equal(t, 1, last)
178+
}
179+
168180
// --- Category ---
169181

170182
func TestCategory(t *testing.T) {

internal/rules/paragraphreadability/rule.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,4 @@ func (r *Rule) DefaultSettings() map[string]any {
142142
}
143143
}
144144

145-
// isTable returns true if the paragraph's first line starts with a pipe,
146145
var _ rule.Configurable = (*Rule)(nil)

internal/rules/paragraphstructure/rule.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,4 @@ func (r *Rule) DefaultSettings() map[string]any {
135135
}
136136
}
137137

138-
// isTable returns true if the paragraph's first line starts with a pipe,
139138
var _ rule.Configurable = (*Rule)(nil)

0 commit comments

Comments
 (0)