Skip to content

Commit 7dcc6d7

Browse files
shivasuryaclaude
andcommitted
feat(dsl): Add variable_matcher executor for Go
Implements variable matcher consuming Python DSL JSON IR. Searches variable references in callgraph arguments with wildcard support for patterns like user_*, *_input, and *data*. 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
1 parent 138989c commit 7dcc6d7

3 files changed

Lines changed: 194 additions & 0 deletions

File tree

sourcecode-parser/dsl/ir_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ func (c *CallMatcherIR) GetType() IRType {
3030
return IRTypeCallMatcher
3131
}
3232

33+
// VariableMatcherIR represents variable_matcher JSON IR.
34+
type VariableMatcherIR struct {
35+
Type string `json:"type"` // "variable_matcher"
36+
Pattern string `json:"pattern"` // "user_input" or "user_*"
37+
Wildcard bool `json:"wildcard"` // true if pattern has *
38+
}
39+
40+
// GetType returns the IR type.
41+
func (v *VariableMatcherIR) GetType() IRType {
42+
return IRTypeVariableMatcher
43+
}
44+
3345
// RuleIR represents a complete rule with metadata.
3446
type RuleIR struct {
3547
Rule struct {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package dsl
2+
3+
import (
4+
"strings"
5+
6+
"github.com/shivasurya/code-pathfinder/sourcecode-parser/graph/callgraph"
7+
)
8+
9+
// VariableMatcherExecutor executes variable_matcher IR.
10+
type VariableMatcherExecutor struct {
11+
IR *VariableMatcherIR
12+
CallGraph *callgraph.CallGraph
13+
}
14+
15+
// NewVariableMatcherExecutor creates a new executor.
16+
func NewVariableMatcherExecutor(ir *VariableMatcherIR, cg *callgraph.CallGraph) *VariableMatcherExecutor {
17+
return &VariableMatcherExecutor{
18+
IR: ir,
19+
CallGraph: cg,
20+
}
21+
}
22+
23+
// Execute finds all variable references matching the pattern.
24+
//
25+
// Algorithm:
26+
// 1. Iterate over callGraph.CallSites
27+
// 2. For each call site, check arguments for variable references
28+
// 3. Match argument values against pattern (with wildcard support)
29+
// 4. Return list of matching call sites with argument positions
30+
func (e *VariableMatcherExecutor) Execute() []VariableMatchResult {
31+
matches := []VariableMatchResult{}
32+
33+
for functionFQN, callSites := range e.CallGraph.CallSites {
34+
for _, callSite := range callSites {
35+
// Check each argument
36+
for _, arg := range callSite.Arguments {
37+
if arg.IsVariable && e.matchesPattern(arg.Value) {
38+
matches = append(matches, VariableMatchResult{
39+
CallSite: callSite,
40+
VariableName: arg.Value,
41+
ArgumentPos: arg.Position,
42+
FunctionFQN: functionFQN,
43+
SourceFile: callSite.Location.File,
44+
Line: callSite.Location.Line,
45+
})
46+
}
47+
}
48+
}
49+
}
50+
51+
return matches
52+
}
53+
54+
// VariableMatchResult contains match information.
55+
type VariableMatchResult struct {
56+
CallSite callgraph.CallSite
57+
VariableName string // The matched variable name
58+
ArgumentPos int // Position in argument list
59+
FunctionFQN string
60+
SourceFile string
61+
Line int
62+
}
63+
64+
// matchesPattern checks if variable name matches pattern.
65+
func (e *VariableMatcherExecutor) matchesPattern(varName string) bool {
66+
pattern := e.IR.Pattern
67+
68+
if !e.IR.Wildcard {
69+
return varName == pattern
70+
}
71+
72+
// Wildcard matching (same as CallMatcher)
73+
if pattern == "*" {
74+
return true
75+
}
76+
77+
if strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*") {
78+
substr := strings.Trim(pattern, "*")
79+
return strings.Contains(varName, substr)
80+
}
81+
82+
if strings.HasPrefix(pattern, "*") {
83+
suffix := strings.TrimPrefix(pattern, "*")
84+
return strings.HasSuffix(varName, suffix)
85+
}
86+
87+
if strings.HasSuffix(pattern, "*") {
88+
prefix := strings.TrimSuffix(pattern, "*")
89+
return strings.HasPrefix(varName, prefix)
90+
}
91+
92+
return varName == pattern
93+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package dsl
2+
3+
import (
4+
"testing"
5+
6+
"github.com/shivasurya/code-pathfinder/sourcecode-parser/graph/callgraph"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestVariableMatcherExecutor_Execute(t *testing.T) {
11+
cg := callgraph.NewCallGraph()
12+
13+
cg.CallSites["test.main"] = []callgraph.CallSite{
14+
{
15+
Target: "eval",
16+
Arguments: []callgraph.Argument{
17+
{Value: "user_input", IsVariable: true, Position: 0},
18+
},
19+
Location: callgraph.Location{File: "test.py", Line: 10},
20+
},
21+
{
22+
Target: "print",
23+
Arguments: []callgraph.Argument{
24+
{Value: "\"hello\"", IsVariable: false, Position: 0},
25+
},
26+
Location: callgraph.Location{File: "test.py", Line: 15},
27+
},
28+
}
29+
30+
t.Run("exact match", func(t *testing.T) {
31+
ir := &VariableMatcherIR{
32+
Pattern: "user_input",
33+
Wildcard: false,
34+
}
35+
36+
executor := NewVariableMatcherExecutor(ir, cg)
37+
matches := executor.Execute()
38+
39+
assert.Len(t, matches, 1)
40+
assert.Equal(t, "user_input", matches[0].VariableName)
41+
assert.Equal(t, 0, matches[0].ArgumentPos)
42+
})
43+
44+
t.Run("wildcard prefix", func(t *testing.T) {
45+
cg2 := callgraph.NewCallGraph()
46+
cg2.CallSites["test.main"] = []callgraph.CallSite{
47+
{
48+
Target: "process",
49+
Arguments: []callgraph.Argument{
50+
{Value: "user_input", IsVariable: true},
51+
{Value: "user_id", IsVariable: true},
52+
{Value: "admin_name", IsVariable: true},
53+
},
54+
},
55+
}
56+
57+
ir := &VariableMatcherIR{
58+
Pattern: "user_*",
59+
Wildcard: true,
60+
}
61+
62+
executor := NewVariableMatcherExecutor(ir, cg2)
63+
matches := executor.Execute()
64+
65+
assert.Len(t, matches, 2) // user_input, user_id
66+
})
67+
68+
t.Run("no matches - literal argument", func(t *testing.T) {
69+
ir := &VariableMatcherIR{
70+
Pattern: "user_input",
71+
Wildcard: false,
72+
}
73+
74+
cg2 := callgraph.NewCallGraph()
75+
cg2.CallSites["test.main"] = []callgraph.CallSite{
76+
{
77+
Target: "print",
78+
Arguments: []callgraph.Argument{
79+
{Value: "\"literal\"", IsVariable: false}, // NOT a variable
80+
},
81+
},
82+
}
83+
84+
executor := NewVariableMatcherExecutor(ir, cg2)
85+
matches := executor.Execute()
86+
87+
assert.Len(t, matches, 0)
88+
})
89+
}

0 commit comments

Comments
 (0)