Skip to content

Commit 8cba51b

Browse files
committed
add support for analyzing imports, dependency graph
1 parent 020d9e2 commit 8cba51b

8 files changed

+229
-1
lines changed

depgraph.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package errgoengine
2+
3+
import "fmt"
4+
5+
type DepGraph map[string]*DepNode
6+
7+
type DepNode struct {
8+
Graph DepGraph
9+
Path string // path where the module/library/package is located
10+
Dependencies map[string]string // mapped as map[label]depPath
11+
}
12+
13+
func (node *DepNode) GetDependencies() []*DepNode {
14+
deps := make([]*DepNode, len(node.Dependencies))
15+
16+
i := 0
17+
for _, path := range node.Dependencies {
18+
deps[i] = node.Graph[path]
19+
i++
20+
}
21+
22+
return deps
23+
}
24+
25+
func (node *DepNode) Dependents() []*DepNode {
26+
deps := []*DepNode{}
27+
28+
for _, cnode := range node.Graph {
29+
if cnode.HasDependency(node.Path) {
30+
deps = append(deps, cnode)
31+
}
32+
}
33+
34+
return deps
35+
}
36+
37+
func (node *DepNode) DependentPaths() []string {
38+
deps := node.Dependents()
39+
depPaths := make([]string, len(deps))
40+
for i, dep := range deps {
41+
depPaths[i] = dep.Path
42+
}
43+
return depPaths
44+
}
45+
46+
func (node *DepNode) HasDependency(path string) bool {
47+
for _, depPath := range node.Dependencies {
48+
if depPath == path {
49+
return true
50+
}
51+
}
52+
return false
53+
}
54+
55+
func (node *DepNode) Detach(path string) error {
56+
if !node.HasDependency(path) {
57+
return fmt.Errorf(
58+
"dependency '%s' not found in %s's dependencies",
59+
path,
60+
node.Path,
61+
)
62+
}
63+
64+
for k, v := range node.Dependencies {
65+
if v == path {
66+
delete(node.Dependencies, k)
67+
node.Graph.Delete(path)
68+
break
69+
}
70+
}
71+
72+
return nil
73+
}
74+
75+
func (graph DepGraph) Add(path string, deps map[string]string) {
76+
if node, ok := graph[path]; ok {
77+
for label, depPath := range deps {
78+
if !graph.Has(depPath) {
79+
graph.Add(depPath, map[string]string{})
80+
}
81+
82+
node.Dependencies[label] = depPath
83+
}
84+
} else {
85+
graph[path] = &DepNode{
86+
Graph: graph,
87+
Path: path,
88+
Dependencies: map[string]string{},
89+
}
90+
91+
graph.Add(path, deps)
92+
}
93+
}
94+
95+
func (graph DepGraph) Has(path string) bool {
96+
_, hasDep := graph[path]
97+
return hasDep
98+
}
99+
100+
func (graph DepGraph) Delete(path string) {
101+
if node, ok := graph[path]; !ok {
102+
return
103+
} else if len(node.Dependents()) > 0 {
104+
return
105+
}
106+
delete(graph, path)
107+
}
108+
109+
func (graph DepGraph) Detach(path string, dep string) error {
110+
return graph[path].Detach(dep)
111+
}

depgraph_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package errgoengine
2+
3+
import (
4+
"testing"
5+
6+
testutils "github.com/nedpals/errgoengine/test_utils"
7+
)
8+
9+
func TestDepGraph(t *testing.T) {
10+
graph := DepGraph{}
11+
12+
// Adding
13+
graph.Add("a", map[string]string{"b": "c"})
14+
graph.Add("d", map[string]string{"e": "c"})
15+
16+
testutils.Equals(t, graph.Has("a"), true)
17+
testutils.Equals(t, graph.Has("c"), true)
18+
19+
testutils.Equals(t, graph["a"].Path, "a")
20+
testutils.EqualsMap(t, graph["a"].Graph, graph)
21+
testutils.EqualsMap(t, graph["a"].Dependencies, map[string]string{"b": "c"})
22+
23+
testutils.Equals(t, graph["d"].Path, "d")
24+
testutils.EqualsMap(t, graph["d"].Graph, graph)
25+
testutils.EqualsMap(t, graph["d"].Dependencies, map[string]string{"e": "c"})
26+
27+
testutils.EqualsList(t, graph["c"].DependentPaths(), []string{"a", "d"})
28+
29+
// Deleting
30+
testutils.ExpectNoError(t, graph.Detach("d", "c"))
31+
testutils.Equals(t, graph.Has("d"), true)
32+
testutils.Equals(t, graph.Has("c"), true)
33+
testutils.EqualsList(t, graph["c"].DependentPaths(), []string{"a"})
34+
35+
testutils.ExpectNoError(t, graph.Detach("a", "c"))
36+
testutils.Equals(t, graph.Has("a"), true)
37+
testutils.Equals(t, graph.Has("c"), false)
38+
}

errgoengine.go

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ErrgoEngine struct {
1919
func New() *ErrgoEngine {
2020
return &ErrgoEngine{
2121
SharedStore: &Store{
22+
DepGraph: DepGraph{},
2223
Documents: map[string]*Document{},
2324
Symbols: map[string]*SymbolTree{},
2425
},

imports.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package errgoengine
2+
3+
type ImportParams struct {
4+
Node Node
5+
CurrentDir string
6+
}
7+
8+
type ResolvedImport struct {
9+
Path string
10+
Name string
11+
Symbols []string
12+
}

language.go

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Language struct {
1919
SymbolsToCapture ISymbolCaptureList
2020
LocationConverter func(path, pos string) Location
2121
ValueAnalyzer func(NodeValueAnalyzer, Node) Symbol
22+
ImportResolver func(NodeValueAnalyzer, ImportParams) ResolvedImport
2223
}
2324

2425
func (lang *Language) MatchPath(path string) bool {
@@ -43,6 +44,10 @@ func (lang *Language) Compile() {
4344
lang.stackTraceRegex = regexp.MustCompile("(?m)" + lang.StackTracePattern)
4445
}
4546

47+
if lang.ImportResolver == nil {
48+
panic(fmt.Sprintf("[Language -> %s] ImportResolver must not be nil", lang.Name))
49+
}
50+
4651
lang.isCompiled = true
4752
}
4853

store.go

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package errgoengine
33
import sitter "github.com/smacker/go-tree-sitter"
44

55
type Store struct {
6+
DepGraph DepGraph
67
Documents map[string]*Document
78
Symbols map[string]*SymbolTree
89
}

symbol_analyzer.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,35 @@ func (an *SymbolAnalyzer) captureAndAnalyze(parent *SymbolTree, rootNode *sitter
8080
// - content
8181
// - position
8282
// - item name (sym.children.0.name for example)
83-
if body, ok := captured["body"]; ok {
83+
if identifiedKind == SymbolKindImport {
84+
name, ok := captured["name"]
85+
if !ok {
86+
// TODO: error
87+
continue
88+
}
89+
90+
resolvedImport := an.doc.Language.ImportResolver(an.contextData, ImportParams{
91+
Node: name,
92+
CurrentDir: an.contextData.WorkingPath,
93+
})
94+
95+
if len(resolvedImport.Path) == 0 {
96+
// TODO: error
97+
continue
98+
}
99+
100+
an.contextData.DepGraph.Add(
101+
an.contextData.CurrentDocumentPath,
102+
map[string]string{
103+
resolvedImport.Name: resolvedImport.Path,
104+
})
105+
106+
parent.Add(&ImportSymbol{
107+
Alias: resolvedImport.Name,
108+
Node: an.contextData.DepGraph[resolvedImport.Path],
109+
ImportedSymbols: resolvedImport.Symbols,
110+
})
111+
} else if body, ok := captured["body"]; ok {
84112
// returnSym = an.contextData.AnalyzeValue(body)
85113
childTree := parent.CreateChildFromNode(body)
86114

symbols.go

+32
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const (
99
SymbolKindFunction SymbolKind = iota
1010
SymbolKindVariable SymbolKind = iota
1111
SymbolKindArray SymbolKind = iota
12+
SymbolKindImport SymbolKind = iota
1213
)
1314

1415
type Symbol interface {
@@ -116,3 +117,34 @@ func (sym BuiltinSymbol) Location() Location {
116117
func Builtin(name string) Symbol {
117118
return BuiltinSymbol{Name_: name}
118119
}
120+
121+
type ImportSymbol struct {
122+
Alias string
123+
Node *DepNode
124+
ImportedSymbols []string
125+
}
126+
127+
func (sym ImportSymbol) Name() string {
128+
return sym.Alias
129+
}
130+
131+
func (sym ImportSymbol) Kind() SymbolKind {
132+
return SymbolKindImport
133+
}
134+
135+
func (sym ImportSymbol) Location() Location {
136+
return Location{
137+
DocumentPath: sym.Node.Path,
138+
Position: Position{
139+
Line: 0,
140+
Column: 0,
141+
Index: 0,
142+
},
143+
}
144+
}
145+
146+
// TODO:
147+
// func (sym ImportSymbol) Children() *SymbolTree {
148+
// // TODO:
149+
// return nil
150+
// }

0 commit comments

Comments
 (0)