-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfacts.go
More file actions
260 lines (242 loc) · 9.14 KB
/
Copy pathfacts.go
File metadata and controls
260 lines (242 loc) · 9.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package codegraph
import (
"context"
"fmt"
"github.com/compforge/codegraph/internal/analysis"
)
// Facts is the detached extraction result for one source document, projected
// from the same facts AddDocuments consumes. Extraction succeeds without
// publishing any graph state, so consumers can explore dependencies and still
// hand the same documents to AddDocuments without a second parse.
type Facts struct {
Path, Package, Language string
// Gitlink retains the pinned commit for an opaque gitlink document.
Gitlink string
Declarations []FactDeclaration
Imports []FactImport
Calls []FactCall
References []FactReference
TypeRelations []FactTypeRelation
Issues []Diagnostic
// Exports maps explicit public names to local declarations or imported bindings.
// Cross-module re-exports are recorded on Imports.Bindings.
Exports map[string]string
// Statements preserve execution order and scope with a bounded expression
// vocabulary, for consumer-side interpretation; the graph model never
// depends on statement-level facts. Captured for Python sources today,
// empty for languages where statement capture is not implemented.
Statements []Statement
}
// Statement preserves execution order and scope without evaluating code.
// Kind values are grammar node types of the capturing language.
type Statement struct {
Kind, Name string
Line int // One-based source line.
Imports []FactImport
Target, Value Expression
Prelude []Expression
Body, Else []Statement
}
type Expression struct {
Kind, Text string
Children []Expression
}
type FactDeclaration struct {
Name, QualifiedName string
Kind NodeKind
Location Location
Markers []Marker
}
type FactImport struct {
Alias, Path, From string
Relative int
// Binding is the name the import introduces in this lexical scope.
Binding string
// Names are the imported names before caller aliases; empty means the
// whole module is imported.
Names []string
Bindings []FactImportBinding
Location Location
}
// FactImportBinding preserves a source name, its local alias and its statement scope.
type FactImportBinding struct {
Name, Local string
Namespace, ReExport bool
Location Location
}
// FactReference records one identifier use. Owner indexes Declarations, or is
// -1 for file scope. A missing target does not discard the lexical fact.
type FactReference struct {
Name, Receiver string
Location Location
Owner int
}
// FactCallTarget records a syntax-based callable candidate before graph binding.
// Kind describes the callable form; Constructor candidates bind to Class nodes.
// Module is a source import qualifier, not a fetched dependency identity.
type FactCallTarget struct {
Name, ReceiverType, Module string
Kind NodeKind
Basis string
}
// FactTypeRelation records explicit inheritance/interface syntax. Owner indexes
// Declarations; Name and Module retain the unbound source spelling.
type FactTypeRelation struct {
Owner int
Name, Module string
Kind RelationKind
Basis string
Location Location
}
type FactCall struct {
Name, Receiver string
Location Location
// Blocked excludes the static-function path; Targets may still carry dispatch candidates.
Blocked, Builtin bool
Targets []FactCallTarget
}
// factCacheEntry keys extracted facts by content identity, so a cache hit
// cannot confuse two different sources sharing one logical path.
type factCacheEntry struct {
hash [32]byte
facts analysis.Facts
}
// Extract returns detached source facts for one document without publishing
// graph state. Results are cached by path and content identity: a later
// AddDocuments of the same path and content reuses them instead of parsing
// again, and facts of already loaded documents are projected without parsing.
// Source documents without a registered grammar yield file-level facts carrying an
// unsupported_language issue, mirroring how AddDocuments records them.
// Gitlinks yield only their path and commit, without attempting language parsing.
// +spec=`Exploration extraction never reparses identical snapshot content`
func (g *Graph) Extract(ctx context.Context, document Document) (Facts, error) {
if err := document.validate(); err != nil {
return Facts{}, err
}
hash := document.digest()
g.factCacheMu.Lock()
entry, cached := g.factCache[document.Path]
g.factCacheMu.Unlock()
if cached && entry.hash == hash {
return projectFacts(entry.facts)
}
g.mu.RLock()
staged, loaded := g.documents[document.Path]
g.mu.RUnlock()
if loaded && document.matches(staged) {
return projectFacts(staged)
}
facts, err := g.extractMaterial(ctx, document)
if err != nil {
return Facts{}, err
}
if err := ctx.Err(); err != nil {
return Facts{}, err
}
g.factCacheMu.Lock()
g.factCache[document.Path] = factCacheEntry{hash: hash, facts: facts}
g.factCacheMu.Unlock()
return projectFacts(facts)
}
// cachedFacts returns a cached extraction for identical content and consumes
// the entry: once staged, the graph's retained facts become the authoritative
// copy, so the cache stays bounded to explored-but-not-yet-added documents.
func (g *Graph) cachedFacts(document Document) (analysis.Facts, bool) {
g.factCacheMu.Lock()
defer g.factCacheMu.Unlock()
entry, ok := g.factCache[document.Path]
if !ok || entry.hash != document.digest() {
return analysis.Facts{}, false
}
delete(g.factCache, document.Path)
return entry.facts, true
}
func projectFacts(f analysis.Facts) (Facts, error) {
out := Facts{Path: f.Path, Package: f.Package, Language: f.Language, Gitlink: f.Gitlink}
for _, d := range f.Declarations {
kind, err := declarationKind(d.Kind)
if err != nil {
return Facts{}, fmt.Errorf("%s: %w", f.Path, err)
}
decl := FactDeclaration{Name: d.Name, QualifiedName: d.QualifiedName, Kind: kind, Location: location(f, d.Span)}
for _, m := range d.Comments {
decl.Markers = append(decl.Markers, Marker{Kind: MarkerKind(m.Kind), Text: m.Text, Location: location(f, m.Span)})
}
out.Declarations = append(out.Declarations, decl)
}
imports := make([]FactImport, 0, len(f.Imports))
byStart := map[int][]int{}
for j, i := range f.Imports {
byStart[i.Span.Start] = append(byStart[i.Span.Start], j)
bindings := make([]FactImportBinding, 0, len(i.Bindings))
for _, b := range i.Bindings {
bindings = append(bindings, FactImportBinding{Name: b.Name, Local: b.Local, Namespace: b.Namespace, ReExport: b.ReExport, Location: location(f, b.Span)})
}
imports = append(imports, FactImport{Bindings: bindings, Alias: i.Alias, Path: i.Path, From: i.From, Relative: i.Relative, Binding: i.Binding, Names: append([]string(nil), i.Names...), Location: location(f, i.Span)})
}
out.Imports = imports
for _, s := range f.Statements {
out.Statements = append(out.Statements, projectStatement(s, imports, byStart))
}
for public, local := range f.Exports {
if out.Exports == nil {
out.Exports = map[string]string{}
}
out.Exports[public] = local
}
for _, c := range f.Calls {
targets := make([]FactCallTarget, 0, len(c.Targets))
for _, target := range c.Targets {
kind := Function
if target.Kind == "method" {
kind = Method
}
if target.Kind == "constructor" {
kind = Constructor
}
targets = append(targets, FactCallTarget{Name: target.Name, ReceiverType: target.ReceiverType, Module: target.Module, Kind: kind, Basis: target.Basis})
}
out.Calls = append(out.Calls, FactCall{Targets: targets, Name: c.Name, Receiver: c.Receiver, Location: location(f, c.Span), Blocked: c.Blocked, Builtin: c.Builtin})
}
for _, r := range f.References {
out.References = append(out.References, FactReference{Name: r.Name, Receiver: r.Receiver, Location: location(f, r.Span), Owner: r.Owner})
}
for _, r := range f.TypeRelations {
out.TypeRelations = append(out.TypeRelations, FactTypeRelation{Owner: r.Owner, Name: r.Name, Module: r.Module, Kind: RelationKind(r.Kind), Basis: r.Basis, Location: location(f, r.Span)})
}
for _, issue := range f.Issues {
out.Issues = append(out.Issues, extractionDiagnostic(f, issue))
}
return out, nil
}
func projectExpression(e analysis.Expression) Expression {
out := Expression{Kind: e.Kind, Text: e.Text}
for _, child := range e.Children {
out.Children = append(out.Children, projectExpression(child))
}
return out
}
func projectStatement(s analysis.Statement, imports []FactImport, byStart map[int][]int) Statement {
out := Statement{Kind: s.Kind, Name: s.Name, Line: s.Line, Target: projectExpression(s.Target), Value: projectExpression(s.Value)}
// One statement can hold several imports sharing its start offset; attach
// each projected import once.
seen := map[int]bool{}
for _, i := range s.Imports {
for _, j := range byStart[i.Span.Start] {
if !seen[j] {
seen[j] = true
out.Imports = append(out.Imports, imports[j])
}
}
}
for _, p := range s.Prelude {
out.Prelude = append(out.Prelude, projectExpression(p))
}
for _, b := range s.Body {
out.Body = append(out.Body, projectStatement(b, imports, byStart))
}
for _, e := range s.Else {
out.Else = append(out.Else, projectStatement(e, imports, byStart))
}
return out
}