Skip to content

Commit d261c93

Browse files
docs(service): annotate walker and index helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 3f9da92 commit d261c93

4 files changed

Lines changed: 92 additions & 3 deletions

File tree

internal/parse/treesitter/walker.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func NewWalker(spec *LangSpec, opts ...WalkerOption) *Walker {
106106
}
107107

108108
// Spec returns the language specification backing this walker.
109+
// @intent expose the configured language rules and query paths for this walker instance
109110
func (w *Walker) Spec() *LangSpec {
110111
if w == nil {
111112
return nil
@@ -271,6 +272,11 @@ func (w *Walker) executeQueries(root *sitter.Node, content []byte, filePath stri
271272

272273
// import dedup: "importPath:line" → true
273274
importSeen := make(map[string]bool)
275+
callRewriter := semanticsOrDefault(w.spec).CallRewriter(SemanticContext{
276+
Root: root,
277+
Content: content,
278+
FilePath: filePath,
279+
})
274280

275281
for {
276282
m, ok := qc.NextMatch()
@@ -407,6 +413,14 @@ func (w *Walker) executeQueries(root *sitter.Node, content []byte, filePath stri
407413
callee := w.extractCallName(callNode, content)
408414
if callee != "" {
409415
line := int(callNode.StartPoint().Row) + 1
416+
callee = callRewriter.RewriteCall(CallRewriteContext{
417+
Root: root,
418+
Node: callNode,
419+
Content: content,
420+
FilePath: filePath,
421+
Callee: callee,
422+
Line: line,
423+
})
410424
edges = append(edges, model.Edge{
411425
Kind: model.EdgeKindCalls,
412426
FilePath: filePath,

internal/parse/treesitter/walker_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,42 @@ var (
463463
}
464464
}
465465

466+
func TestParseGo_InterfaceAssertionsFromMainCommand(t *testing.T) {
467+
src, err := os.ReadFile("../../../cmd/ccg/main.go")
468+
if err != nil {
469+
t.Fatalf("read cmd main: %v", err)
470+
}
471+
ctx := WithImportPackages(context.Background(), map[string]string{
472+
"github.com/tae2089/code-context-graph/internal/analysis/flows": "flows",
473+
"github.com/tae2089/code-context-graph/internal/analysis/impact": "impact",
474+
"github.com/tae2089/code-context-graph/internal/analysis/query": "query",
475+
"github.com/tae2089/code-context-graph/internal/mcp": "mcp",
476+
})
477+
w := NewWalker(GoSpec)
478+
_, edges, err := w.ParseWithContext(ctx, "cmd/ccg/main.go", src)
479+
if err != nil {
480+
t.Fatalf("parse cmd main: %v", err)
481+
}
482+
implEdges := filterEdgesByKind(edges, model.EdgeKindImplements)
483+
wantFingerprints := []string{
484+
"implements:cmd/ccg/main.go:impact.Analyzer:mcp.ImpactAnalyzer",
485+
"implements:cmd/ccg/main.go:flows.Tracer:mcp.FlowTracer",
486+
"implements:cmd/ccg/main.go:query.Service:mcp.QueryService",
487+
}
488+
for _, want := range wantFingerprints {
489+
found := false
490+
for _, e := range implEdges {
491+
if e.Fingerprint == want {
492+
found = true
493+
break
494+
}
495+
}
496+
if !found {
497+
t.Errorf("expected fingerprint %q in cmd main assertion edges, got %#v", want, implEdges)
498+
}
499+
}
500+
}
501+
466502
func TestParseGo_ImportAliasVersionedPath(t *testing.T) {
467503
// When the import path ends with a version segment like ".v3", the canonical
468504
// package name is the segment before the version (e.g. "yaml" for "gopkg.in/yaml.v3").

internal/service/indexer.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @index Graph building service that orchestrates parsing, persistence, and search indexing.
12
package service
23

34
import (
@@ -265,10 +266,8 @@ func (e *UnreadableFilesError) Error() string {
265266
}
266267

267268
// Build walks source files, stores parsed graph data, and rebuilds search docs.
268-
// @intent 지원 언어 소스를 그래프와 검색 문서로 일괄 동기화한다.
269+
// @intent perform a full graph build from the specified directory.
269270
// @sideEffect 파일 시스템을 읽고 그래프 저장소·DB·검색 인덱스를 갱신한다.
270-
// @requires s.Store, s.Walkers가 초기화되어 있어야 한다.
271-
// @mutates 그래프 노드/엣지/어노테이션, search_documents
272271
func (s *GraphService) Build(ctx context.Context, opts BuildOptions) (BuildStats, error) {
273272
var stats BuildStats
274273

@@ -628,6 +627,8 @@ func partitionBuildEdges(edgeBatches []parsedBuildEdgeBatch) ([]model.Edge, []pa
628627
return implementsEdges, otherBatches
629628
}
630629

630+
// splitImplementsEdges separates implements edges from other relationship types.
631+
// @intent ensure interface fulfillment edges are handled before call dispatch resolution.
631632
func splitImplementsEdges(edges []model.Edge) ([]model.Edge, []model.Edge) {
632633
var implementsEdges []model.Edge
633634
var otherEdges []model.Edge
@@ -641,6 +642,8 @@ func splitImplementsEdges(edges []model.Edge) ([]model.Edge, []model.Edge) {
641642
return implementsEdges, otherEdges
642643
}
643644

645+
// importEdgesByFile groups import-from edges by their source file path.
646+
// @intent optimize edge resolution by pre-loading import context for each file.
644647
func importEdgesByFile(edgeBatches []parsedBuildEdgeBatch) map[string][]model.Edge {
645648
byFile := make(map[string][]model.Edge, len(edgeBatches))
646649
for _, batch := range edgeBatches {
@@ -654,6 +657,8 @@ func importEdgesByFile(edgeBatches []parsedBuildEdgeBatch) map[string][]model.Ed
654657
return byFile
655658
}
656659

660+
// chunkWithImportWarmup combines a chunk of call edges with their file's import edges.
661+
// @intent ensure the edge resolver has enough context to resolve call targets through imports.
657662
func chunkWithImportWarmup(chunk []model.Edge, imports []model.Edge) []model.Edge {
658663
if len(chunk) == 0 {
659664
return nil
@@ -1313,6 +1318,8 @@ func parseForBuild(ctx context.Context, parser Parser, relPath string, content [
13131318
return nodes, edges, nil, "", err
13141319
}
13151320

1321+
// collectLanguagePackages discovers language-specific package information within the build directory.
1322+
// @intent identify package boundaries and file memberships to populate the graph's package structure.
13161323
func (s *GraphService) collectLanguagePackages(ctx context.Context, absDir string, opts BuildOptions) map[string]languagePackageInfo {
13171324
merged := make(map[string]languagePackageInfo)
13181325
ambiguous := make(map[string]struct{})
@@ -1340,10 +1347,14 @@ func (s *GraphService) collectLanguagePackages(ctx context.Context, absDir strin
13401347
return merged
13411348
}
13421349

1350+
// packageDiscoverySpecs collects language specifications for all active parsers and walkers.
1351+
// @intent provide a unique set of discovery rules for all supported source languages.
13431352
func (s *GraphService) packageDiscoverySpecs() []*treesitter.LangSpec {
13441353
if len(s.Walkers) == 0 && len(s.Parsers) == 0 {
13451354
return nil
13461355
}
1356+
// parserWithSpec exposes language metadata from parsers that own a LangSpec.
1357+
// @intent let package discovery collect language specs without depending on a concrete parser type.
13471358
type parserWithSpec interface {
13481359
Spec() *treesitter.LangSpec
13491360
}
@@ -1382,10 +1393,14 @@ func (s *GraphService) packageDiscoverySpecs() []*treesitter.LangSpec {
13821393
return specs
13831394
}
13841395

1396+
// withImportPackageContext attaches discovered package names to the context for use during edge resolution.
1397+
// @intent ensure cross-package imports can be resolved using their semantic names.
13851398
func (s *GraphService) withImportPackageContext(ctx context.Context, packages map[string]languagePackageInfo) context.Context {
13861399
return treesitter.WithImportPackages(ctx, importPackageNames(packages))
13871400
}
13881401

1402+
// mergeLanguagePackages aggregates discovered packages into a central map while filtering ambiguous paths.
1403+
// @intent consolidate package discovery results while discarding conflicting definitions.
13891404
func mergeLanguagePackages(dst map[string]languagePackageInfo, ambiguous map[string]struct{}, src map[string]languagePackageInfo) {
13901405
for importPath, pkg := range src {
13911406
if importPath == "" {
@@ -1410,6 +1425,8 @@ func mergeLanguagePackages(dst map[string]languagePackageInfo, ambiguous map[str
14101425
}
14111426
}
14121427

1428+
// importPackageNames extracts a mapping of import paths to package names.
1429+
// @intent allow the edge resolver to look up package names by their source import paths.
14131430
func importPackageNames(packages map[string]languagePackageInfo) map[string]string {
14141431
if len(packages) == 0 {
14151432
return nil
@@ -1423,6 +1440,8 @@ func importPackageNames(packages map[string]languagePackageInfo) map[string]stri
14231440
return names
14241441
}
14251442

1443+
// packageNodes converts discovered package info into graph nodes.
1444+
// @intent project package metadata into the graph schema for persistence.
14261445
func packageNodes(packages map[string]languagePackageInfo) []model.Node {
14271446
if len(packages) == 0 {
14281447
return nil
@@ -1444,6 +1463,8 @@ func packageNodes(packages map[string]languagePackageInfo) []model.Node {
14441463
return nodes
14451464
}
14461465

1466+
// packageContainsEdgeCount returns the total number of file-to-package containment relationships.
1467+
// @intent estimate the edge overhead for package structural nodes.
14471468
func packageContainsEdgeCount(packages map[string]languagePackageInfo) int {
14481469
count := 0
14491470
for _, pkg := range packages {
@@ -1452,6 +1473,8 @@ func packageContainsEdgeCount(packages map[string]languagePackageInfo) int {
14521473
return count
14531474
}
14541475

1476+
// upsertPackageNodes persists discovered package nodes into the graph store.
1477+
// @intent ensure package nodes exist before their member files are linked.
14551478
func upsertPackageNodes(ctx context.Context, txStore store.GraphStore, packages map[string]languagePackageInfo) error {
14561479
nodes := packageNodes(packages)
14571480
if len(nodes) == 0 {
@@ -1460,6 +1483,8 @@ func upsertPackageNodes(ctx context.Context, txStore store.GraphStore, packages
14601483
return txStore.UpsertNodes(ctx, nodes)
14611484
}
14621485

1486+
// upsertPackageContainsEdges links package nodes to their member file nodes.
1487+
// @intent populate the graph's structural hierarchy by connecting packages to their source files.
14631488
func upsertPackageContainsEdges(ctx context.Context, txStore store.GraphStore, packages map[string]languagePackageInfo) error {
14641489
if len(packages) == 0 {
14651490
return nil
@@ -1498,6 +1523,8 @@ func upsertPackageContainsEdges(ctx context.Context, txStore store.GraphStore, p
14981523
return txStore.UpsertEdges(ctx, edges)
14991524
}
15001525

1526+
// sortedPackageImportPaths returns a deterministic list of all discovered import paths.
1527+
// @intent keep package-related database operations stable across build runs.
15011528
func sortedPackageImportPaths(packages map[string]languagePackageInfo) []string {
15021529
paths := make([]string, 0, len(packages))
15031530
for importPath := range packages {
@@ -1507,6 +1534,8 @@ func sortedPackageImportPaths(packages map[string]languagePackageInfo) []string
15071534
return paths
15081535
}
15091536

1537+
// packageFilePaths extracts all unique file paths belonging to the discovered packages.
1538+
// @intent collect all files that need to be linked to their containing package nodes.
15101539
func packageFilePaths(packages map[string]languagePackageInfo) []string {
15111540
seen := make(map[string]struct{})
15121541
var paths []string
@@ -1523,6 +1552,8 @@ func packageFilePaths(packages map[string]languagePackageInfo) []string {
15231552
return paths
15241553
}
15251554

1555+
// singleNodeOfKind returns the first node in a list that matches the specified kind, or nil if none or multiple exist.
1556+
// @intent ensure unambiguous node selection during structural edge linking.
15261557
func singleNodeOfKind(nodes []model.Node, kind model.NodeKind) *model.Node {
15271558
var found *model.Node
15281559
for i := range nodes {
@@ -1537,11 +1568,15 @@ func singleNodeOfKind(nodes []model.Node, kind model.NodeKind) *model.Node {
15371568
return found
15381569
}
15391570

1571+
// packageContainsFingerprint generates a stable identifier for a package-to-file relationship.
1572+
// @intent ensure package structural edges can be upserted without duplication.
15401573
func packageContainsFingerprint(importPath, filePath string) string {
15411574
sum := sha256.Sum256([]byte(importPath + "\x00" + filePath))
15421575
return fmt.Sprintf("contains:package:%x", sum)
15431576
}
15441577

1578+
// appendUniqueString appends a string to a slice only if it is not already present.
1579+
// @intent maintain a unique set of strings while preserving insertion order for small sets.
15451580
func appendUniqueString(values []string, value string) []string {
15461581
for _, existing := range values {
15471582
if existing == value {
@@ -1551,6 +1586,8 @@ func appendUniqueString(values []string, value string) []string {
15511586
return append(values, value)
15521587
}
15531588

1589+
// appendUniqueStrings appends multiple strings to a slice, ensuring each is unique.
1590+
// @intent aggregate strings from multiple sources while filtering duplicates.
15541591
func appendUniqueStrings(values []string, add ...string) []string {
15551592
for _, value := range add {
15561593
values = appendUniqueString(values, value)

internal/store/gormstore/gormstore.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ func (s *Store) GetFileNodesByPathSuffix(ctx context.Context, suffix string) ([]
221221
return out, nil
222222
}
223223

224+
// commonPathSuffixDepth calculates the depth of common directory suffix between two paths.
225+
// @intent identify the best matching directory for an import path based on trailing segments.
224226
func commonPathSuffixDepth(a, b string) int {
225227
a = strings.Trim(a, "/")
226228
b = strings.Trim(b, "/")

0 commit comments

Comments
 (0)