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

225 lines
5.4 KiB
Go

package cfg
import (
sitter "github.com/zzet/gortex/internal/parser/tsitter"
)
// extractDefUse walks one statement's subtree and returns the
// variable names it defines and reads, in source order, deduplicated.
// asDef seeds the walk in definition context (used for binding-only
// nodes: loop variables, catch parameters, match patterns).
//
// Nested function literals are opaque: their bodies run at an
// unknown later time, so neither their writes nor their reads are
// attributed to this statement. A nested definition that binds a
// name in the enclosing scope (Python `def inner`, JS `function g`)
// still defines that name.
func extractDefUse(spec *langSpec, src []byte, n *sitter.Node, asDef bool) (defs, uses []string) {
x := &duExtractor{spec: spec, src: src, seenDef: map[string]bool{}, seenUse: map[string]bool{}}
x.walk(n, asDef)
return x.defs, x.uses
}
type duExtractor struct {
spec *langSpec
src []byte
defs []string
uses []string
seenDef map[string]bool
seenUse map[string]bool
}
func (x *duExtractor) addDef(name string) {
if name == "" || name == "_" || x.seenDef[name] {
return
}
x.seenDef[name] = true
x.defs = append(x.defs, name)
}
func (x *duExtractor) addUse(name string) {
if name == "" || name == "_" || x.seenUse[name] {
return
}
x.seenUse[name] = true
x.uses = append(x.uses, name)
}
// walk visits n in read context (asDef=false) or binding context.
func (x *duExtractor) walk(n *sitter.Node, asDef bool) {
if n == nil {
return
}
t := n.Type()
if x.spec.nestedFuncs[t] {
// A named nested definition binds its name in this scope.
if nameNode := n.ChildByFieldName("name"); nameNode != nil && x.spec.identKinds[nameNode.Type()] {
x.addDef(nameNode.Content(x.src))
}
return
}
if x.spec.skipKinds[t] {
return
}
if x.spec.identKinds[t] {
if asDef {
x.addDef(n.Content(x.src))
} else {
x.addUse(n.Content(x.src))
}
return
}
if rule, ok := x.spec.assigns[t]; ok {
x.handleAssign(n, rule)
return
}
if rule, ok := x.spec.updates[t]; ok {
x.handleUpdate(n, rule)
return
}
x.walkChildren(n, asDef)
}
func (x *duExtractor) walkChildren(n *sitter.Node, asDef bool) {
t := n.Type()
skips := x.spec.skipFields[t]
cnt := int(n.ChildCount())
for i := 0; i < cnt; i++ {
c := n.Child(i)
if c == nil || !c.IsNamed() {
continue
}
if skips != nil {
if f := n.FieldNameForChild(i); f != "" && skips[f] {
continue
}
}
x.walk(c, asDef)
}
}
// handleAssign processes an assignment-shaped node: the LHS field
// holds binding targets, every other child is read.
func (x *duExtractor) handleAssign(n *sitter.Node, rule assignRule) {
alsoUse := false
switch rule.mode {
case augAlways:
alsoUse = true
case augIfOp:
alsoUse = hasAugmentedOperator(n)
}
skips := x.spec.skipFields[n.Type()]
cnt := int(n.ChildCount())
for i := 0; i < cnt; i++ {
c := n.Child(i)
if c == nil || !c.IsNamed() {
continue
}
f := n.FieldNameForChild(i)
if skips != nil && f != "" && skips[f] {
continue
}
if f == rule.lhsField {
x.walkLHS(c, alsoUse)
continue
}
// Type annotations and other non-value fields are pruned by
// skipKinds inside the recursive walk.
x.walk(c, false)
}
}
// handleUpdate processes increment/decrement-shaped nodes whose
// target is both read and written.
func (x *duExtractor) handleUpdate(n *sitter.Node, rule updateRule) {
var target *sitter.Node
if rule.field != "" {
target = n.ChildByFieldName(rule.field)
}
if target == nil {
target = n.NamedChild(0)
}
if target != nil {
x.walkLHS(target, true)
}
}
// walkLHS classifies an assignment target: a bare identifier is a
// definition (plus a use for augmented assigns); pattern containers
// recurse; anything else (member access, index expression) reads its
// base — writing x.f or x[i] mutates the object, not the binding.
func (x *duExtractor) walkLHS(n *sitter.Node, alsoUse bool) {
if n == nil {
return
}
t := n.Type()
if x.spec.identKinds[t] {
name := n.Content(x.src)
x.addDef(name)
if alsoUse {
x.addUse(name)
}
return
}
if x.spec.patternContainers[t] {
skips := x.spec.skipFields[t]
cnt := int(n.ChildCount())
for i := 0; i < cnt; i++ {
c := n.Child(i)
if c == nil || !c.IsNamed() {
continue
}
if skips != nil {
if f := n.FieldNameForChild(i); f != "" && skips[f] {
continue
}
}
x.walkLHS(c, alsoUse)
// Default values inside destructuring patterns are
// handled by the assignment_pattern rule during the
// recursive walk; nothing extra needed here.
}
return
}
if rule, ok := x.spec.assigns[t]; ok && t == "assignment_pattern" {
// Destructuring default: `{a = 1}` — a is a def, 1 is read.
if l := n.ChildByFieldName(rule.lhsField); l != nil {
x.walkLHS(l, alsoUse)
}
if r := n.ChildByFieldName("right"); r != nil {
x.walk(r, false)
}
return
}
// Non-binding target: reads flow normally.
x.walk(n, false)
}
// hasAugmentedOperator reports whether an assignment node carries a
// compound operator token (+=, -=, &&=, …) rather than plain "=".
func hasAugmentedOperator(n *sitter.Node) bool {
if op := n.ChildByFieldName("operator"); op != nil {
return op.Type() != "="
}
cnt := int(n.ChildCount())
for i := 0; i < cnt; i++ {
c := n.Child(i)
if c == nil || c.IsNamed() {
continue
}
t := c.Type()
if t == "=" {
return false
}
if len(t) >= 2 && t[len(t)-1] == '=' {
switch t {
case "==", "!=", "<=", ">=":
continue
}
return true
}
}
return false
}