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

94 lines
2.7 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"
)
// captureReactContextRefs emits a placeholder reference edge from a component
// to the context object it reads via `useContext(SomeContext)`. The TS/JS
// extractor records the `useContext` callee but drops the context-object
// argument, so without this the React resolver has nothing to bind a
// `useContext(AuthContext)` site to its /context/ definition. The edge is
// tagged `via=react_context` so the resolver treats it as a context reference
// regardless of the identifier's suffix. Runs at the tail of Extract.
func captureReactContextRefs(result *parser.ExtractionResult, root *sitter.Node, filePath string, src []byte) {
if root == nil || result == nil {
return
}
seen := map[string]bool{}
expressWalk(root, func(c *sitter.Node) {
if c.Type() != "call_expression" {
return
}
fn := c.ChildByFieldName("function")
if fn == nil || fn.Type() != "identifier" || fn.Content(src) != "useContext" {
return
}
args := c.ChildByFieldName("arguments")
if args == nil {
return
}
ctx := reactFirstIdentArg(args, src)
if ctx == "" {
return
}
line := int(c.StartPoint().Row) + 1
from := reactEnclosingFuncID(result.Nodes, line)
if from == "" {
return
}
key := from + "\x00" + ctx
if seen[key] {
return
}
seen[key] = true
result.Edges = append(result.Edges, &graph.Edge{
From: from, To: "unresolved::" + ctx, Kind: graph.EdgeReferences,
FilePath: filePath, Line: line, Origin: graph.OriginASTInferred,
Meta: map[string]any{"via": "react_context", "context_name": ctx},
})
})
}
// reactFirstIdentArg returns the first call argument when it is a bare
// identifier (the context object passed to useContext), else "".
func reactFirstIdentArg(args *sitter.Node, src []byte) string {
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
a := args.NamedChild(i)
if a == nil {
continue
}
if a.Type() == "identifier" {
if name := a.Content(src); isSimpleJSIdent(name) {
return name
}
}
return "" // first arg isn't a plain identifier (e.g. a member expression)
}
return ""
}
// reactEnclosingFuncID returns the ID of the innermost function/method node
// whose line range contains line, or "" when none does.
func reactEnclosingFuncID(nodes []*graph.Node, line int) string {
best := ""
bestSpan := 1 << 30
for _, n := range nodes {
if n == nil {
continue
}
if n.Kind != graph.KindFunction && n.Kind != graph.KindMethod {
continue
}
if n.StartLine <= line && line <= n.EndLine {
if span := n.EndLine - n.StartLine; span < bestSpan {
bestSpan = span
best = n.ID
}
}
}
return best
}