Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

86 lines
2.6 KiB
Go

package graph
import (
"sort"
"strconv"
"strings"
)
// Edge call-site multiplicity.
//
// The graph natively keys edges by (From, To, Kind, FilePath, Line), so an AST
// extractor that emits one edge per call site preserves in-file multiplicity
// on its own. A *synthesized* producer that can only mint one edge per
// (From, To) — e.g. the LSP references-add pass, which sees N reference sites
// for one declaration — would otherwise collapse those N sites to one. Rather
// than mint N near-identical edges, such a producer keeps one edge (its
// primary site in FilePath/Line) and records the additional sites in
// Meta["call_sites"]; find_usages expands them back into one row per site.
//
// Meta-only: no Edge struct field is added, so no wire-contract / GCX encoder
// churn. Edge.Meta round-trips through the store, so the sites survive a warm
// restart on the sqlite backend (via AddBatch / PersistEdge).
// AppendCallSite records an additional call/reference site on an edge whose
// primary site stays in FilePath/Line. Extra sites are stored in
// Meta["call_sites"] as sorted, deduped "<file>:<line>" strings; the primary
// site is never duplicated there.
func AppendCallSite(e *Edge, filePath string, line int) {
if e == nil || filePath == "" || line <= 0 {
return
}
if filePath == e.FilePath && line == e.Line {
return // the primary site lives in FilePath/Line, not call_sites
}
site := filePath + ":" + strconv.Itoa(line)
sites := CallSites(e)
for _, s := range sites {
if s == site {
return
}
}
sites = append(sites, site)
sort.Strings(sites)
if e.Meta == nil {
e.Meta = map[string]any{}
}
e.Meta["call_sites"] = sites
}
// CallSites returns the extra "<file>:<line>" sites recorded on an edge,
// tolerating both the in-memory []string form and the []any form a JSON meta
// round-trip (disk backend) produces.
func CallSites(e *Edge) []string {
if e == nil || e.Meta == nil {
return nil
}
switch v := e.Meta["call_sites"].(type) {
case []string:
return v
case []any:
out := make([]string, 0, len(v))
for _, x := range v {
if s, ok := x.(string); ok {
out = append(out, s)
}
}
return out
}
return nil
}
// SplitCallSite splits a "<file>:<line>" call-site string into its file and
// 1-based line, returning ("", 0) when malformed. It splits on the last colon
// so a path that itself contains a colon is handled.
func SplitCallSite(site string) (string, int) {
i := strings.LastIndexByte(site, ':')
if i <= 0 || i == len(site)-1 {
return "", 0
}
line, err := strconv.Atoi(site[i+1:])
if err != nil || line <= 0 {
return "", 0
}
return site[:i], line
}