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
94 lines
2.7 KiB
Go
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
|
|
}
|