Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
78cf062
feat: Add core data structures for call graph (PR #1)
shivasurya Oct 26, 2025
0359585
feat: Implement module registry - Pass 1 of 3-pass algorithm (PR #2)
shivasurya Oct 26, 2025
13d57d7
feat: Implement import extraction with tree-sitter - Pass 2 Part A
shivasurya Oct 26, 2025
a0e18dd
feat: Implement relative import resolution - Pass 2 Part B
shivasurya Oct 26, 2025
f0f5139
feat: Implement call site extraction from AST - Pass 2 Part C
shivasurya Oct 26, 2025
572ee59
feat: Implement call graph builder - Pass 3
shivasurya Oct 26, 2025
6476c73
feat: Create CFG data structures for control flow analysis
shivasurya Oct 27, 2025
5265c76
feat: Add pattern registry with hardcoded code injection example
shivasurya Oct 27, 2025
c20b3fe
feat: Integrate call graph into initialization pipeline
shivasurya Oct 27, 2025
d7b1666
add callgraph integration
shivasurya Oct 27, 2025
9d811b4
chore: comment the debugging code
shivasurya Oct 27, 2025
be15011
feat: Add comprehensive benchmark suite for performance testing
shivasurya Oct 27, 2025
b15ebdf
feat: Add ImportMap caching with sync.RWMutex for performance
shivasurya Oct 27, 2025
60ce087
fix: Correct matchesFunctionName test expectations
shivasurya Oct 27, 2025
549e961
fix: Update main_test.go to include analyze command in expected output
shivasurya Oct 27, 2025
53f8003
feature: add diagnostic report command for callgraph resolution
shivasurya Oct 28, 2025
22769cf
feature: added resolution for framework and its corresponding support
shivasurya Oct 28, 2025
e924cf5
chore: fixed lint issues
shivasurya Oct 29, 2025
0575ae0
added orm related resolutions with framework support
shivasurya Oct 29, 2025
461cd69
Merge main into shiva/callgraph-infra-13
shivasurya Oct 29, 2025
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
7 changes: 4 additions & 3 deletions sourcecode-parser/graph/callgraph/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,18 +339,19 @@ func BenchmarkResolveCallTarget(b *testing.B) {
importMap.AddImport("helper", "myapp.helpers")

currentModule := "myapp.main"
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}

b.ResetTimer()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
// Test simple attribute access (most common case)
_, _ = resolveCallTarget("utils.process_data", importMap, registry, currentModule)
_, _ = resolveCallTarget("utils.process_data", importMap, registry, currentModule, codeGraph)

// Test aliased import
_, _ = resolveCallTarget("helper.format", importMap, registry, currentModule)
_, _ = resolveCallTarget("helper.format", importMap, registry, currentModule, codeGraph)

// Test fully qualified name
_, _ = resolveCallTarget("myapp.utils.validate", importMap, registry, currentModule)
_, _ = resolveCallTarget("myapp.utils.validate", importMap, registry, currentModule, codeGraph)
}
}
14 changes: 12 additions & 2 deletions sourcecode-parser/graph/callgraph/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func BuildCallGraph(codeGraph *graph.CodeGraph, registry *ModuleRegistry, projec
}

// Resolve the call target to a fully qualified name
targetFQN, resolved := resolveCallTarget(callSite.Target, importMap, registry, modulePath)
targetFQN, resolved := resolveCallTarget(callSite.Target, importMap, registry, modulePath, codeGraph)

// Update call site with resolution information
callSite.TargetFQN = targetFQN
Expand Down Expand Up @@ -423,7 +423,7 @@ func categorizeResolutionFailure(target, targetFQN string) string {
return "unknown"
}

func resolveCallTarget(target string, importMap *ImportMap, registry *ModuleRegistry, currentModule string) (string, bool) {
func resolveCallTarget(target string, importMap *ImportMap, registry *ModuleRegistry, currentModule string, codeGraph *graph.CodeGraph) (string, bool) {
// Handle self.method() calls - resolve to current module
if strings.HasPrefix(target, "self.") {
methodName := strings.TrimPrefix(target, "self.")
Expand Down Expand Up @@ -479,6 +479,10 @@ func resolveCallTarget(target string, importMap *ImportMap, registry *ModuleRegi
if isKnown, _ := IsKnownFramework(fullFQN); isKnown {
return fullFQN, true
}
// Check if it's an ORM pattern (before validateFQN, since ORM methods don't exist in source)
if ormFQN, resolved := ResolveORMCall(target, currentModule, registry, codeGraph); resolved {
return ormFQN, true
}
if validateFQN(fullFQN, registry) {
return fullFQN, true
}
Expand All @@ -492,6 +496,12 @@ func resolveCallTarget(target string, importMap *ImportMap, registry *ModuleRegi
return fullFQN, true
}

// Before giving up, check if it's an ORM pattern (Django, SQLAlchemy, etc.)
// ORM methods are dynamically generated at runtime and won't be in source
if ormFQN, resolved := ResolveORMCall(target, currentModule, registry, codeGraph); resolved {
return ormFQN, true
}

// Can't resolve - return as-is
return target, false
}
Expand Down
33 changes: 23 additions & 10 deletions sourcecode-parser/graph/callgraph/builder_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"testing"

"github.com/shivasurya/code-pathfinder/sourcecode-parser/graph"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -63,28 +64,31 @@ def test_stdlib():
modulePath, ok := registry.FileToModule[testFile]
assert.True(t, ok)

// Create empty code graph for tests
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}

// Test Django models resolution
targetFQN, resolved := resolveCallTarget("models.User", importMap, registry, modulePath)
targetFQN, resolved := resolveCallTarget("models.User", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "Django models.User should be resolved")
assert.Equal(t, "django.db.models.User", targetFQN)

// Test REST framework resolution
targetFQN, resolved = resolveCallTarget("serializers.ModelSerializer", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("serializers.ModelSerializer", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "REST framework serializers should be resolved")
assert.Equal(t, "rest_framework.serializers.ModelSerializer", targetFQN)

// Test pytest resolution
targetFQN, resolved = resolveCallTarget("pytest.fixture", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("pytest.fixture", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "pytest.fixture should be resolved")
assert.Equal(t, "pytest.fixture", targetFQN)

// Test json (stdlib) resolution
targetFQN, resolved = resolveCallTarget("json.loads", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("json.loads", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "json.loads should be resolved")
assert.Equal(t, "json.loads", targetFQN)

// Test logging (stdlib) resolution
targetFQN, resolved = resolveCallTarget("logging.getLogger", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("logging.getLogger", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "logging.getLogger should be resolved")
assert.Equal(t, "logging.getLogger", targetFQN)
}
Expand Down Expand Up @@ -135,12 +139,15 @@ def process():
modulePath, ok := registry.FileToModule[testFile]
assert.True(t, ok)

// Create empty code graph for tests
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}

// Test local function resolution (should resolve to local module)
targetFQN, resolved := resolveCallTarget("sanitize", importMap, registry, modulePath)
targetFQN, resolved := resolveCallTarget("sanitize", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "Local function sanitize should be resolved")
assert.Contains(t, targetFQN, "utils.sanitize")

targetFQN, resolved = resolveCallTarget("validate", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("validate", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "Local function validate should be resolved")
assert.Contains(t, targetFQN, "utils.validate")
}
Expand Down Expand Up @@ -186,8 +193,11 @@ def process():
modulePath, ok := registry.FileToModule[testFile]
assert.True(t, ok)

// Create empty code graph for tests
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}

// Test that local json takes precedence over stdlib
targetFQN, resolved := resolveCallTarget("loads", importMap, registry, modulePath)
targetFQN, resolved := resolveCallTarget("loads", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "Local json.loads should be resolved")
// When there's a local module that shadows stdlib, it resolves to local
// The FQN will be json.loads but from the local module, not stdlib
Expand Down Expand Up @@ -243,13 +253,16 @@ def process():
modulePath, ok := registry.FileToModule[testFile]
assert.True(t, ok)

// Create empty code graph for tests
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}

// Test local function resolution
targetFQN, resolved := resolveCallTarget("helper", importMap, registry, modulePath)
targetFQN, resolved := resolveCallTarget("helper", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "Local helper should be resolved")
assert.Contains(t, targetFQN, "utils.helper")

// Test framework resolution
targetFQN, resolved = resolveCallTarget("json.loads", importMap, registry, modulePath)
targetFQN, resolved = resolveCallTarget("json.loads", importMap, registry, modulePath, codeGraph)
assert.True(t, resolved, "json.loads should be resolved as framework")
assert.Equal(t, "json.loads", targetFQN)
}
15 changes: 10 additions & 5 deletions sourcecode-parser/graph/callgraph/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func TestResolveCallTarget_SimpleImportedFunction(t *testing.T) {
importMap := NewImportMap("/project/myapp/views.py")
importMap.AddImport("sanitize", "myapp.utils.sanitize")

fqn, resolved := resolveCallTarget("sanitize", importMap, registry, "myapp.views")
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}
fqn, resolved := resolveCallTarget("sanitize", importMap, registry, "myapp.views", codeGraph)

assert.True(t, resolved)
assert.Equal(t, "myapp.utils.sanitize", fqn)
Expand All @@ -40,7 +41,8 @@ func TestResolveCallTarget_QualifiedImport(t *testing.T) {
importMap := NewImportMap("/project/myapp/views.py")
importMap.AddImport("utils", "myapp.utils")

fqn, resolved := resolveCallTarget("utils.sanitize", importMap, registry, "myapp.views")
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}
fqn, resolved := resolveCallTarget("utils.sanitize", importMap, registry, "myapp.views", codeGraph)

assert.True(t, resolved)
assert.Equal(t, "myapp.utils.sanitize", fqn)
Expand All @@ -55,7 +57,8 @@ func TestResolveCallTarget_SameModuleFunction(t *testing.T) {

importMap := NewImportMap("/project/myapp/views.py")

fqn, resolved := resolveCallTarget("helper", importMap, registry, "myapp.views")
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}
fqn, resolved := resolveCallTarget("helper", importMap, registry, "myapp.views", codeGraph)

assert.True(t, resolved)
assert.Equal(t, "myapp.views.helper", fqn)
Expand All @@ -70,7 +73,8 @@ func TestResolveCallTarget_UnresolvedMethodCall(t *testing.T) {

importMap := NewImportMap("/project/myapp/views.py")

fqn, resolved := resolveCallTarget("obj.method", importMap, registry, "myapp.views")
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}
fqn, resolved := resolveCallTarget("obj.method", importMap, registry, "myapp.views", codeGraph)

assert.False(t, resolved)
assert.Equal(t, "obj.method", fqn)
Expand All @@ -85,7 +89,8 @@ func TestResolveCallTarget_NonExistentFunction(t *testing.T) {
importMap := NewImportMap("/project/myapp/views.py")
importMap.AddImport("missing", "nonexistent.module.function")

fqn, resolved := resolveCallTarget("missing", importMap, registry, "myapp.views")
codeGraph := &graph.CodeGraph{Nodes: make(map[string]*graph.Node)}
fqn, resolved := resolveCallTarget("missing", importMap, registry, "myapp.views", codeGraph)

assert.False(t, resolved)
assert.Equal(t, "nonexistent.module.function", fqn)
Expand Down
Loading
Loading