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
97 lines
3.1 KiB
Go
97 lines
3.1 KiB
Go
package languages
|
|
|
|
import (
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
)
|
|
|
|
// captureValueRefCandidates records a value-reference candidate read for every
|
|
// distinctive identifier in the parse tree that names a file-scope constant or
|
|
// variable this extractor just emitted, attributed to its enclosing function.
|
|
// The resolver's ResolveValueRefs then binds (or shadow-prunes) each candidate
|
|
// into a tiered EdgeReads, so a config constant's blast radius reaches its
|
|
// readers — closing a change-impact gap that plain call/arg extraction misses.
|
|
//
|
|
// It is grammar-agnostic: it keys on `identifier` leaf nodes (the value-position
|
|
// token in nearly every tree-sitter grammar) and pre-filters to names the file
|
|
// actually declares, so it never emits a candidate that cannot bind. Call it
|
|
// once at the end of an extractor's Extract with the parse-tree root.
|
|
func captureValueRefCandidates(result *parser.ExtractionResult, root *sitter.Node, filePath string, src []byte) {
|
|
if root == nil || result == nil {
|
|
return
|
|
}
|
|
consts := map[string]bool{}
|
|
for _, n := range result.Nodes {
|
|
if n == nil || n.FilePath != filePath {
|
|
continue
|
|
}
|
|
if n.Kind != graph.KindConstant && n.Kind != graph.KindVariable {
|
|
continue
|
|
}
|
|
if isDistinctiveConstName(n.Name) {
|
|
consts[n.Name] = true
|
|
}
|
|
}
|
|
if len(consts) == 0 {
|
|
return
|
|
}
|
|
funcRanges := buildFuncRanges(result)
|
|
if len(funcRanges) == 0 {
|
|
return
|
|
}
|
|
seen := map[string]bool{}
|
|
walkNodes(root, func(n *sitter.Node) {
|
|
if !isValueRefIdentNode(n.Type()) {
|
|
return
|
|
}
|
|
name := n.Content(src)
|
|
if !consts[name] {
|
|
return
|
|
}
|
|
line := int(n.StartPoint().Row) + 1
|
|
fromID := findEnclosingFunc(funcRanges, line)
|
|
if fromID == "" || fromID == filePath {
|
|
return // not inside a function (e.g. the declaration itself)
|
|
}
|
|
key := fromID + "\x00" + name
|
|
if seen[key] {
|
|
return
|
|
}
|
|
seen[key] = true
|
|
result.Edges = append(result.Edges, &graph.Edge{
|
|
From: fromID, To: "unresolved::valueref::" + name, Kind: graph.EdgeReads,
|
|
FilePath: filePath, Line: line, Origin: graph.OriginSpeculative,
|
|
Meta: map[string]any{"via": "value_ref_candidate", "name": name},
|
|
})
|
|
})
|
|
}
|
|
|
|
// isValueRefIdentNode reports whether a node type is a value-position
|
|
// identifier leaf the constant-read scan should consider. Beyond the common
|
|
// `identifier`, Kotlin and Swift use `simple_identifier` and PHP a bare `name`,
|
|
// so a constant read in those grammars is captured too.
|
|
func isValueRefIdentNode(t string) bool {
|
|
switch t {
|
|
case "identifier", "simple_identifier", "name", "constant":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isDistinctiveConstName mirrors the resolver's distinctive-name gate: at least
|
|
// 3 characters with an uppercase letter or underscore — the config-constant
|
|
// shape, unlikely to collide with an ordinary lowerCamelCase local.
|
|
func isDistinctiveConstName(name string) bool {
|
|
if len(name) < 3 {
|
|
return false
|
|
}
|
|
for i := 0; i < len(name); i++ {
|
|
c := name[i]
|
|
if c == '_' || (c >= 'A' && c <= 'Z') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|