Skip to content

Commit 8fa3e47

Browse files
greynewellclaude
andauthored
fix(graph2md): writeGraphData uses zero lineCount when startLine absent (#103)
* fix(graph2md): writeGraphData uses zero lineCount when startLine absent writeGraphData computed lineCount only when startLine > 0 && endLine > 0. For nodes without a startLine (API returns 0), the condition was false and lineCount remained 0, so the graph visualisation data showed lc=0 even though the same node's frontmatter correctly computed line_count=endLine (using effectiveStart=1). Fix: mirror the effectiveStart=1 defaulting used by all frontmatter writers — if endLine > 0 but startLine <= 0, treat startLine as 1. Adds TestGraphDataLineCountMissingStartLine to catch the regression. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lint): remove extra spaces in parseGraphData struct literal goimports rejects manually aligned spaces; use single space. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8947caa commit 8fa3e47

File tree

2 files changed

+119
-2
lines changed

2 files changed

+119
-2
lines changed

internal/archdocs/graph2md/graph2md.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,8 +1578,12 @@ func (c *renderContext) writeGraphData(sb *strings.Builder) {
15781578
lineCount := 0
15791579
startLine := getNum(n.Properties, "startLine")
15801580
endLine := getNum(n.Properties, "endLine")
1581-
if startLine > 0 && endLine > 0 {
1582-
lineCount = endLine - startLine + 1
1581+
if endLine > 0 {
1582+
effectiveStart := startLine
1583+
if effectiveStart <= 0 {
1584+
effectiveStart = 1
1585+
}
1586+
lineCount = endLine - effectiveStart + 1
15831587
}
15841588
lang := getStr(n.Properties, "language")
15851589
callCount := len(c.calls[nodeID])

internal/archdocs/graph2md/graph2md_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,40 @@ import (
88
"testing"
99
)
1010

11+
// parseGraphData extracts the "graph_data" JSON from a rendered markdown file's
12+
// frontmatter and returns the parsed graphData struct.
13+
func parseGraphData(t *testing.T, content string) struct {
14+
Nodes []struct {
15+
ID string `json:"id"`
16+
LC int `json:"lc"`
17+
} `json:"nodes"`
18+
} {
19+
t.Helper()
20+
const key = `graph_data: "`
21+
idx := strings.Index(content, key)
22+
if idx < 0 {
23+
t.Fatal("graph_data key not found in output")
24+
}
25+
start := idx + len(key)
26+
// graph_data value is a quoted Go string — find the closing unescaped "
27+
end := strings.Index(content[start:], "\"\n")
28+
if end < 0 {
29+
t.Fatal("graph_data closing quote not found")
30+
}
31+
// Unquote the embedded JSON
32+
raw := strings.ReplaceAll(content[start:start+end], `\"`, `"`)
33+
var gd struct {
34+
Nodes []struct {
35+
ID string `json:"id"`
36+
LC int `json:"lc"`
37+
} `json:"nodes"`
38+
}
39+
if err := json.Unmarshal([]byte(raw), &gd); err != nil {
40+
t.Fatalf("unmarshal graph_data: %v\nraw: %s", err, raw)
41+
}
42+
return gd
43+
}
44+
1145
// buildGraphJSON serialises nodes and relationships into a Graph JSON file
1246
// that loadGraph can parse.
1347
func buildGraphJSON(t *testing.T, nodes []Node, rels []Relationship) string {
@@ -140,3 +174,82 @@ func TestLineCountMissingStartLine(t *testing.T) {
140174
t.Errorf("expected line_count: 50 in output, got:\n%s", content)
141175
}
142176
}
177+
178+
// TestGraphDataLineCountMissingStartLine verifies that the graph_data JSON
179+
// embedded in the markdown frontmatter uses the same effectiveStart=1 logic
180+
// as the text line_count field. Before the fix, a node with endLine=50 but
181+
// no startLine would have lc=0 (condition startLine>0 was false), while the
182+
// frontmatter line_count correctly showed 50.
183+
//
184+
// A DEFINES_FUNCTION relationship to a file is included so that the function
185+
// node has at least one neighbor; writeGraphData skips output when len(nodes)<2.
186+
func TestGraphDataLineCountMissingStartLine(t *testing.T) {
187+
nodes := []Node{
188+
{
189+
ID: "file:src/foo.go",
190+
Labels: []string{"File"},
191+
Properties: map[string]interface{}{
192+
"path": "src/foo.go",
193+
"lineCount": float64(100),
194+
},
195+
},
196+
{
197+
ID: "fn:src/foo.go:bar",
198+
Labels: []string{"Function"},
199+
Properties: map[string]interface{}{
200+
"name": "bar",
201+
"filePath": "src/foo.go",
202+
"endLine": float64(50), // startLine intentionally absent
203+
},
204+
},
205+
}
206+
rels := []Relationship{
207+
{
208+
ID: "r1",
209+
Type: "DEFINES_FUNCTION",
210+
StartNode: "file:src/foo.go",
211+
EndNode: "fn:src/foo.go:bar",
212+
},
213+
}
214+
215+
graphFile := buildGraphJSON(t, nodes, rels)
216+
outDir := t.TempDir()
217+
218+
if err := Run(graphFile, outDir, "testrepo", "", 0); err != nil {
219+
t.Fatalf("Run: %v", err)
220+
}
221+
222+
// Find the function's markdown file
223+
entries, _ := os.ReadDir(outDir)
224+
var fnFile string
225+
for _, e := range entries {
226+
if strings.HasPrefix(e.Name(), "fn-") {
227+
fnFile = filepath.Join(outDir, e.Name())
228+
break
229+
}
230+
}
231+
if fnFile == "" {
232+
t.Fatal("function markdown file not found")
233+
}
234+
235+
content, err := os.ReadFile(fnFile)
236+
if err != nil {
237+
t.Fatalf("ReadFile: %v", err)
238+
}
239+
240+
gd := parseGraphData(t, string(content))
241+
// Find the function node in graph_data
242+
var fnLC int = -1
243+
for _, n := range gd.Nodes {
244+
if n.ID == "fn:src/foo.go:bar" {
245+
fnLC = n.LC
246+
break
247+
}
248+
}
249+
if fnLC == -1 {
250+
t.Fatalf("function node not found in graph_data nodes: %v", gd.Nodes)
251+
}
252+
if fnLC != 50 {
253+
t.Errorf("graph_data lc = %d, want 50 (endLine=50, effectiveStart=1)", fnLC)
254+
}
255+
}

0 commit comments

Comments
 (0)