-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmerger.go
More file actions
135 lines (122 loc) · 3.56 KB
/
Copy pathmerger.go
File metadata and controls
135 lines (122 loc) · 3.56 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
package analyzer
import (
"sort"
"github.com/randomcodespace/codeiq/internal/model"
)
// mergeNode merges incoming into existing, picking the higher-confidence
// node as the survivor, then filling gaps and unioning properties /
// annotations. Returns the survivor (which is mutated in place).
//
// Plan §1.1 — semantics:
// - Higher Confidence wins; ties keep existing.
// - Non-empty FQN / Module / FilePath / LineStart / LineEnd / Layer
// fill in from whichever side has them.
// - Properties: incoming wins per-key only when existing's value is nil
// or missing (do not clobber framework/auth_type already stamped by a
// higher-confidence detector).
// - Annotations are unioned and sorted for determinism.
func mergeNode(existing, incoming *model.CodeNode) *model.CodeNode {
if existing == nil {
return incoming
}
if incoming == nil {
return existing
}
survivor := existing
donor := incoming
if incoming.Confidence > existing.Confidence {
survivor = incoming
donor = existing
}
// Gap-fill scalar fields from the donor when the survivor has none.
if survivor.FQN == "" && donor.FQN != "" {
survivor.FQN = donor.FQN
}
if survivor.Module == "" && donor.Module != "" {
survivor.Module = donor.Module
}
if survivor.FilePath == "" && donor.FilePath != "" {
survivor.FilePath = donor.FilePath
}
if survivor.LineStart == 0 && donor.LineStart != 0 {
survivor.LineStart = donor.LineStart
}
if survivor.LineEnd == 0 && donor.LineEnd != 0 {
survivor.LineEnd = donor.LineEnd
}
if survivor.Layer == model.LayerUnknown && donor.Layer != model.LayerUnknown {
survivor.Layer = donor.Layer
}
if survivor.Source == "" && donor.Source != "" {
survivor.Source = donor.Source
}
// Property union: donor fills missing keys; never clobbers existing.
if survivor.Properties == nil {
survivor.Properties = map[string]any{}
}
for k, v := range donor.Properties {
if _, exists := survivor.Properties[k]; exists {
continue
}
survivor.Properties[k] = v
}
// Annotation union — dedup + sort for determinism.
survivor.Annotations = unionSorted(survivor.Annotations, donor.Annotations)
return survivor
}
// mergeEdge merges two edges with the same EdgeKey (src, tgt, kind).
// Higher-confidence wins; ties keep existing. Properties unioned with
// non-clobber semantics.
func mergeEdge(existing, incoming *model.CodeEdge) *model.CodeEdge {
if existing == nil {
return incoming
}
if incoming == nil {
return existing
}
survivor := existing
donor := incoming
if incoming.Confidence > existing.Confidence {
survivor = incoming
donor = existing
}
if survivor.Source == "" && donor.Source != "" {
survivor.Source = donor.Source
}
if survivor.Properties == nil {
survivor.Properties = map[string]any{}
}
for k, v := range donor.Properties {
if _, exists := survivor.Properties[k]; exists {
continue
}
survivor.Properties[k] = v
}
return survivor
}
func unionSorted(a, b []string) []string {
seen := make(map[string]struct{}, len(a)+len(b))
for _, s := range a {
seen[s] = struct{}{}
}
for _, s := range b {
seen[s] = struct{}{}
}
out := make([]string, 0, len(seen))
for s := range seen {
out = append(out, s)
}
sort.Strings(out)
return out
}
// edgeKey is the canonical key used to dedupe edges. Two edges with the
// same (source, target, kind) are considered the same edge regardless of
// detector-assigned ID strings.
type edgeKey struct {
source string
target string
kind model.EdgeKind
}
func makeEdgeKey(e *model.CodeEdge) edgeKey {
return edgeKey{source: e.SourceID, target: e.TargetID, kind: e.Kind}
}