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
159 lines
4.9 KiB
Go
159 lines
4.9 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"
|
|
)
|
|
|
|
// NgRx effects dispatch detection. An effect declares the action(s) it reacts
|
|
// to via `createEffect(() => this.actions$.pipe(ofType(SomeAction), ...))`. The
|
|
// effect is registered with the EffectsModule, not called, so the static call
|
|
// graph never links it to the action it handles. This pass tags the effect node
|
|
// with Meta["ngrx_effect"] and stamps a placeholder EdgeCalls per ofType action,
|
|
// which the resolver's ResolveNgRxEffects binds to the action node. Shared by
|
|
// the JavaScript and TypeScript extractors.
|
|
|
|
// ngrxEffectViaTag must match resolver.ngrxEffectVia -- the languages package
|
|
// does not import resolver, so the two agree on the via tag by value.
|
|
const ngrxEffectViaTag = "ngrx-effect"
|
|
|
|
// ngrxEffectActionCap bounds the ofType actions mined from one effect.
|
|
const ngrxEffectActionCap = 16
|
|
|
|
// captureNgRxEffects finds `name$ = createEffect(() => actions$.pipe(
|
|
// ofType(Action), ...))` class properties (and `const name$ = createEffect(...)`
|
|
// functional effects), tags the effect node with Meta["ngrx_effect"], and stamps
|
|
// a placeholder EdgeCalls from it to each ofType action. Runs at the tail of
|
|
// Extract, after all nodes exist, so it can find and tag the effect node.
|
|
func captureNgRxEffects(result *parser.ExtractionResult, root *sitter.Node, filePath string, src []byte) {
|
|
if root == nil || result == nil {
|
|
return
|
|
}
|
|
ngrxEffectWalk(root, func(n *sitter.Node) {
|
|
var nameNode, init *sitter.Node
|
|
switch n.Type() {
|
|
case "public_field_definition", "field_definition", "variable_declarator":
|
|
nameNode = n.ChildByFieldName("name")
|
|
init = n.ChildByFieldName("value")
|
|
default:
|
|
return
|
|
}
|
|
if nameNode == nil || init == nil || init.Type() != "call_expression" {
|
|
return
|
|
}
|
|
if jsCalleeLastName(init.ChildByFieldName("function"), src) != "createEffect" {
|
|
return
|
|
}
|
|
name := nameNode.Content(src)
|
|
if name == "" {
|
|
return
|
|
}
|
|
var actions []string
|
|
seen := map[string]bool{}
|
|
ngrxEffectWalk(init, func(c *sitter.Node) {
|
|
if len(actions) >= ngrxEffectActionCap || c.Type() != "call_expression" {
|
|
return
|
|
}
|
|
if jsCalleeLastName(c.ChildByFieldName("function"), src) != "ofType" {
|
|
return
|
|
}
|
|
args := c.ChildByFieldName("arguments")
|
|
if args == nil {
|
|
return
|
|
}
|
|
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
|
|
act := ngrxActionName(args.NamedChild(i), src)
|
|
if act == "" || seen[act] {
|
|
continue
|
|
}
|
|
seen[act] = true
|
|
actions = append(actions, act)
|
|
}
|
|
})
|
|
if len(actions) == 0 {
|
|
return
|
|
}
|
|
effectID := ngrxEffectTag(result, filePath, name)
|
|
line := int(n.StartPoint().Row) + 1
|
|
for _, act := range actions {
|
|
result.Edges = append(result.Edges, &graph.Edge{
|
|
From: effectID,
|
|
To: "unresolved::*." + act,
|
|
Kind: graph.EdgeCalls,
|
|
FilePath: filePath,
|
|
Line: line,
|
|
Meta: map[string]any{
|
|
"via": ngrxEffectViaTag,
|
|
"ngrx_action": act,
|
|
},
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// ngrxActionName returns the action name an ofType argument references: a bare
|
|
// identifier (`LoadUsers`) or the trailing property of a member access
|
|
// (`UserActions.load` -> "load").
|
|
func ngrxActionName(n *sitter.Node, src []byte) string {
|
|
if n == nil {
|
|
return ""
|
|
}
|
|
switch n.Type() {
|
|
case "identifier":
|
|
return n.Content(src)
|
|
case "member_expression":
|
|
if p := n.ChildByFieldName("property"); p != nil {
|
|
return p.Content(src)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ngrxEffectTag stamps Meta["ngrx_effect"] on the effect's node and returns its
|
|
// ID. It prefers the exact-ID node (a functional effect `const name$ = ...`),
|
|
// then a same-named class property / variable, and mints a node as a last resort
|
|
// so the placeholder edge's From is always real.
|
|
func ngrxEffectTag(result *parser.ExtractionResult, filePath, name string) string {
|
|
want := filePath + "::" + name
|
|
var fallback *graph.Node
|
|
for _, nd := range result.Nodes {
|
|
if nd == nil {
|
|
continue
|
|
}
|
|
if nd.ID == want {
|
|
ngrxStampEffect(nd, name)
|
|
return nd.ID
|
|
}
|
|
if fallback == nil && nd.Name == name &&
|
|
(nd.Kind == graph.KindVariable || nd.Kind == graph.KindConstant || nd.Kind == graph.KindField || nd.Kind == graph.KindFunction) {
|
|
fallback = nd
|
|
}
|
|
}
|
|
if fallback != nil {
|
|
ngrxStampEffect(fallback, name)
|
|
return fallback.ID
|
|
}
|
|
node := &graph.Node{ID: want, Kind: graph.KindVariable, Name: name, FilePath: filePath, Meta: map[string]any{"ngrx_effect": name}}
|
|
result.Nodes = append(result.Nodes, node)
|
|
return want
|
|
}
|
|
|
|
func ngrxStampEffect(n *graph.Node, name string) {
|
|
if n.Meta == nil {
|
|
n.Meta = map[string]any{}
|
|
}
|
|
n.Meta["ngrx_effect"] = name
|
|
}
|
|
|
|
// ngrxEffectWalk visits n and all its named descendants.
|
|
func ngrxEffectWalk(n *sitter.Node, fn func(*sitter.Node)) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
fn(n)
|
|
for i, _nc := 0, int(n.NamedChildCount()); i < _nc; i++ {
|
|
ngrxEffectWalk(n.NamedChild(i), fn)
|
|
}
|
|
}
|