chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user