Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions internal/archdocs/graph2md/graph2md.go
Original file line number Diff line number Diff line change
Expand Up @@ -1578,8 +1578,12 @@ func (c *renderContext) writeGraphData(sb *strings.Builder) {
lineCount := 0
startLine := getNum(n.Properties, "startLine")
endLine := getNum(n.Properties, "endLine")
if startLine > 0 && endLine > 0 {
lineCount = endLine - startLine + 1
if endLine > 0 {
effectiveStart := startLine
if effectiveStart <= 0 {
effectiveStart = 1
}
lineCount = endLine - effectiveStart + 1
}
lang := getStr(n.Properties, "language")
callCount := len(c.calls[nodeID])
Expand Down
113 changes: 113 additions & 0 deletions internal/archdocs/graph2md/graph2md_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ import (
"testing"
)

// parseGraphData extracts the "graph_data" JSON from a rendered markdown file's
// frontmatter and returns the parsed graphData struct.
func parseGraphData(t *testing.T, content string) struct {
Nodes []struct {
ID string `json:"id"`
LC int `json:"lc"`
} `json:"nodes"`
} {
t.Helper()
const key = `graph_data: "`
idx := strings.Index(content, key)
if idx < 0 {
t.Fatal("graph_data key not found in output")
}
start := idx + len(key)
// graph_data value is a quoted Go string — find the closing unescaped "
end := strings.Index(content[start:], "\"\n")
if end < 0 {
t.Fatal("graph_data closing quote not found")
}
// Unquote the embedded JSON
raw := strings.ReplaceAll(content[start:start+end], `\"`, `"`)
var gd struct {
Nodes []struct {
ID string `json:"id"`
LC int `json:"lc"`
} `json:"nodes"`
}
if err := json.Unmarshal([]byte(raw), &gd); err != nil {
t.Fatalf("unmarshal graph_data: %v\nraw: %s", err, raw)
}
return gd
}

// buildGraphJSON serialises nodes and relationships into a Graph JSON file
// that loadGraph can parse.
func buildGraphJSON(t *testing.T, nodes []Node, rels []Relationship) string {
Expand Down Expand Up @@ -140,3 +174,82 @@ func TestLineCountMissingStartLine(t *testing.T) {
t.Errorf("expected line_count: 50 in output, got:\n%s", content)
}
}

// TestGraphDataLineCountMissingStartLine verifies that the graph_data JSON
// embedded in the markdown frontmatter uses the same effectiveStart=1 logic
// as the text line_count field. Before the fix, a node with endLine=50 but
// no startLine would have lc=0 (condition startLine>0 was false), while the
// frontmatter line_count correctly showed 50.
//
// A DEFINES_FUNCTION relationship to a file is included so that the function
// node has at least one neighbor; writeGraphData skips output when len(nodes)<2.
func TestGraphDataLineCountMissingStartLine(t *testing.T) {
nodes := []Node{
{
ID: "file:src/foo.go",
Labels: []string{"File"},
Properties: map[string]interface{}{
"path": "src/foo.go",
"lineCount": float64(100),
},
},
{
ID: "fn:src/foo.go:bar",
Labels: []string{"Function"},
Properties: map[string]interface{}{
"name": "bar",
"filePath": "src/foo.go",
"endLine": float64(50), // startLine intentionally absent
},
},
}
rels := []Relationship{
{
ID: "r1",
Type: "DEFINES_FUNCTION",
StartNode: "file:src/foo.go",
EndNode: "fn:src/foo.go:bar",
},
}

graphFile := buildGraphJSON(t, nodes, rels)
outDir := t.TempDir()

if err := Run(graphFile, outDir, "testrepo", "", 0); err != nil {
t.Fatalf("Run: %v", err)
}

// Find the function's markdown file
entries, _ := os.ReadDir(outDir)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle ReadDir errors explicitly.

At Line 223, the error is ignored. If directory read fails, the test may fail later with a less useful message (“function markdown file not found”) instead of the real I/O error.

Suggested fix
-	entries, _ := os.ReadDir(outDir)
+	entries, err := os.ReadDir(outDir)
+	if err != nil {
+		t.Fatalf("ReadDir: %v", err)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
entries, _ := os.ReadDir(outDir)
entries, err := os.ReadDir(outDir)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/archdocs/graph2md/graph2md_test.go` at line 223, The test ignores
the error returned by os.ReadDir(outDir) (entries, _ := os.ReadDir(outDir));
update the graph2md_test.go test to capture the error (entries, err :=
os.ReadDir(outDir)), check err != nil and fail early with a clear message (e.g.,
t.Fatalf("ReadDir(%s) failed: %v", outDir, err)) so subsequent assertions (like
checking for function markdown files) don't mask the real I/O failure.

var fnFile string
for _, e := range entries {
if strings.HasPrefix(e.Name(), "fn-") {
fnFile = filepath.Join(outDir, e.Name())
break
}
}
if fnFile == "" {
t.Fatal("function markdown file not found")
}

content, err := os.ReadFile(fnFile)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}

gd := parseGraphData(t, string(content))
// Find the function node in graph_data
var fnLC int = -1
for _, n := range gd.Nodes {
if n.ID == "fn:src/foo.go:bar" {
fnLC = n.LC
break
}
}
if fnLC == -1 {
t.Fatalf("function node not found in graph_data nodes: %v", gd.Nodes)
}
if fnLC != 50 {
t.Errorf("graph_data lc = %d, want 50 (endLine=50, effectiveStart=1)", fnLC)
}
}
Loading