Skip to content

Commit 755cd1a

Browse files
authored
Merge PR #155: Extract shared AST utilities into astutil package
2 parents 2306b95 + c11d0a0 commit 755cd1a

15 files changed

Lines changed: 469 additions & 592 deletions

File tree

internal/rules/astutil/astutil.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package astutil
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/jeduden/mdsmith/internal/lint"
7+
"github.com/yuin/goldmark/ast"
8+
)
9+
10+
// HeadingLine returns the 1-based source line of a heading node.
11+
// Setext headings expose their line via Lines(); ATX headings are found
12+
// by walking inline descendants until the first text segment. Returns 1
13+
// as a safe fallback.
14+
func HeadingLine(heading *ast.Heading, f *lint.File) int {
15+
lines := heading.Lines()
16+
if lines.Len() > 0 {
17+
return f.LineOfOffset(lines.At(0).Start)
18+
}
19+
20+
line := 1
21+
_ = ast.Walk(heading, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
22+
if !entering || n == heading {
23+
return ast.WalkContinue, nil
24+
}
25+
t, ok := n.(*ast.Text)
26+
if !ok {
27+
return ast.WalkContinue, nil
28+
}
29+
line = f.LineOfOffset(t.Segment.Start)
30+
return ast.WalkStop, nil
31+
})
32+
33+
return line
34+
}
35+
36+
// ParagraphLine returns the 1-based source line of a paragraph node.
37+
func ParagraphLine(para *ast.Paragraph, f *lint.File) int {
38+
lines := para.Lines()
39+
if lines.Len() > 0 {
40+
return f.LineOfOffset(lines.At(0).Start)
41+
}
42+
return 1
43+
}
44+
45+
// IsTable reports whether a paragraph node is actually a GFM table
46+
// (goldmark parses tables as paragraphs when the table extension is
47+
// absent). It checks whether the first line starts with "|".
48+
func IsTable(para *ast.Paragraph, f *lint.File) bool {
49+
lines := para.Lines()
50+
if lines.Len() == 0 {
51+
return false
52+
}
53+
seg := lines.At(0)
54+
return bytes.HasPrefix(bytes.TrimSpace(f.Source[seg.Start:seg.Stop]), []byte("|"))
55+
}
56+
57+
// HeadingText returns the plain-text content of a heading by
58+
// recursively extracting all text segments from its children.
59+
func HeadingText(heading *ast.Heading, source []byte) string {
60+
var buf bytes.Buffer
61+
for c := heading.FirstChild(); c != nil; c = c.NextSibling() {
62+
ExtractText(c, source, &buf)
63+
}
64+
return buf.String()
65+
}
66+
67+
// ExtractText recursively writes the text content of n and its
68+
// descendants into buf.
69+
func ExtractText(n ast.Node, source []byte, buf *bytes.Buffer) {
70+
if t, ok := n.(*ast.Text); ok {
71+
buf.Write(t.Segment.Value(source))
72+
return
73+
}
74+
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
75+
ExtractText(c, source, buf)
76+
}
77+
}

0 commit comments

Comments
 (0)