Repository navigation
Expand file tree
/
Copy pathreferences.go
More file actions
140 lines (135 loc) · 5.52 KB
/
Copy pathreferences.go
File metadata and controls
140 lines (135 loc) · 5.52 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
package codegraph
import (
"context"
"fmt"
"github.com/compforge/codegraph/internal/analysis"
)
// A binding is located at a source use, even when other uses share its name.
// Owner participates in the key so a resolver cannot silently reparent a use.
type referenceKey struct {
owner string
kind RelationKind
path string
start, end int
}
// publishReferences is the production-side handoff from extraction to graph
// facts. Uses exist before target binding; they never create external symbols.
// +spec=Every extracted call/reference has a source-use node even without a target; candidate targets retain the resolver's evidence.
// +why=Consumers must be able to inspect a partial graph without reading Facts or treating diagnostics as code facts.
func publishReferences(ctx context.Context, files map[string]analysis.Facts, ids map[analysis.Ref]string, nodes map[string]Node, relations map[string]Relation, opts Options) error {
bindings := make(map[referenceKey][]Relation)
evidenceCount := 0
for _, r := range relations {
if err := ctx.Err(); err != nil {
return err
}
evidenceCount += len(r.Evidence)
if r.Kind == Calls || r.Kind == References || r.Kind == Extends || r.Kind == Implements || r.Kind == Decorates {
key := referenceKey{r.Source, r.Kind, r.Location.Path, r.Location.StartByte, r.Location.EndByte}
bindings[key] = append(bindings[key], r)
if r.Kind == Decorates {
delete(relations, r.ID)
evidenceCount -= len(r.Evidence)
}
}
}
addRelation := func(r Relation) error {
r.ID = identity(r.Source, r.Target, r.Kind, r.Location.Path, r.Location.StartByte, r.Location.EndByte)
if _, exists := relations[r.ID]; exists {
return nil
}
if len(relations) >= opts.MaxRelations || evidenceCount+len(r.Evidence) > opts.MaxEvidence {
return fmt.Errorf("%w: source-use relations or evidence", ErrBuildBudget)
}
relations[r.ID] = r
evidenceCount += len(r.Evidence)
return nil
}
addReference := func(f analysis.Facts, kind RelationKind, name, receiver string, span analysis.Span, owner int, direct analysis.Ref, decorated int) error {
if err := ctx.Err(); err != nil {
return err
}
// Source identity depends on this occurrence, never on available targets
// or precision, so supplementing documents does not rename existing uses.
id := "node:" + identity(f.Path, Reference, kind, span.Start, span.End, name, receiver)
if _, exists := nodes[id]; exists {
return nil
}
if len(nodes) >= opts.MaxNodes {
return fmt.Errorf("%w: source-use nodes", ErrBuildBudget)
}
parent := ids[analysis.SourceRef(f.Path, owner)]
if parent == "" {
return fmt.Errorf("unpublished source-use owner: %s at %d", f.Path, span.Start)
}
loc := location(f, span)
nodes[id] = Node{ID: id, Kind: Reference, ReferenceKind: ReferenceKind(kind), Name: name, Receiver: receiver, Language: f.Language, Location: &loc}
if err := addRelation(Relation{Source: id, Target: parent, Kind: OccursIn, Location: loc,
Confidence: Exact, Evidence: []Evidence{{Basis: "source_occurrence", Confidence: Exact}}}); err != nil {
return err
}
if decorated >= 0 {
if err := addRelation(Relation{Source: id, Target: ids[analysis.DeclarationRef(f.Path, decorated)], Kind: Decorates, Location: loc, Confidence: Exact, Evidence: []Evidence{{Basis: "source_modifier", Confidence: Exact}}}); err != nil {
return err
}
}
if target := ids[direct]; target != "" {
return addRelation(Relation{Source: id, Target: target, Kind: References, Location: loc, Confidence: Exact, Evidence: []Evidence{{Basis: "local_import_binding", Confidence: Exact}}})
}
key := referenceKey{parent, kind, f.Path, span.Start, span.End}
for _, binding := range bindings[key] {
// Derive both graph views from the same resolver output. This is a
// projection of evidence, never an independent name-based resolution.
binding = cloneRelation(binding)
binding.Source, binding.Kind = id, References
if err := addRelation(binding); err != nil {
return err
}
}
return nil
}
for _, path := range sortedFiles(files) {
f := files[path]
for _, call := range f.Calls {
if err := addReference(f, Calls, call.Name, call.Receiver, call.Span, analysis.EnclosingDeclaration(f, call.Span), directImport(nodes, ids, f, call.Name, call.Receiver, call.Span, call.Imported), -1); err != nil {
return err
}
}
for _, ref := range f.References {
kind := References
if ref.Kind != "" {
kind = RelationKind(ref.Kind)
}
decorated := -1
if kind == Decorates {
decorated = ref.Decorated
}
if err := addReference(f, kind, ref.Name, ref.Receiver, ref.Span, ref.Owner, directImport(nodes, ids, f, ref.Name, ref.Receiver, ref.Span, !ref.Bound), decorated); err != nil {
return err
}
}
for _, hint := range f.TypeRelations {
if err := addReference(f, RelationKind(hint.Kind), hint.Name, hint.Module, hint.Span, hint.Owner, directImport(nodes, ids, f, hint.Name, hint.Module, hint.Span, !hint.Blocked), -1); err != nil {
return err
}
}
}
return nil
}
func directImport(nodes map[string]Node, ids map[analysis.Ref]string, f analysis.Facts, name, receiver string, span analysis.Span, allowed bool) analysis.Ref {
if allowed && receiver == "" {
if ref, ok := analysis.LocalImport(f, name, span); ok {
return ref
}
if f.Language == "go" {
for i := range f.Imports {
ref := analysis.ImportItemRef(f.Path, i, -1)
item := nodes[ids[ref]]
if item.Binding != nil && item.Binding.Form == "namespace" && item.Binding.LocalName == name {
return ref
}
}
}
}
return analysis.Ref{}
}