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
221 lines
6.8 KiB
Go
221 lines
6.8 KiB
Go
package languages
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
)
|
|
|
|
// RTK Query createApi endpoint + generated-hook detection. Redux Toolkit
|
|
// Query declares data endpoints inside a `createApi({ endpoints: (builder)
|
|
// => ({ getUser: builder.query(...) }) })` call and auto-generates a React
|
|
// hook per endpoint (`useGetUserQuery`) that has no source node. This pass
|
|
// mints a node per endpoint and per generated hook, and stamps a
|
|
// placeholder EdgeCalls hook → endpoint, which the resolver's
|
|
// ResolveRTKQueryCalls binds. A component's `useGetUserQuery()` call then
|
|
// reaches the endpoint's query body. Shared by the JS and TS extractors.
|
|
|
|
// rtkQueryViaTag must match resolver.rtkQueryVia — the languages package
|
|
// does not import resolver, so the two agree by value.
|
|
const rtkQueryViaTag = "rtk-query"
|
|
|
|
// captureRTKQueryEndpoints finds createApi endpoint definitions, mints
|
|
// endpoint + generated-hook nodes, and stamps the hook → endpoint
|
|
// placeholder. Runs at the tail of Extract so a hand-written hook of the
|
|
// same name (already in result.Nodes) suppresses the synthetic one.
|
|
func captureRTKQueryEndpoints(result *parser.ExtractionResult, root *sitter.Node, filePath, language string, src []byte) {
|
|
if root == nil || result == nil {
|
|
return
|
|
}
|
|
// Names already defined in source — a hand-written useXQuery look-alike
|
|
// must not be shadowed by a synthetic hook node.
|
|
sourceNames := map[string]bool{}
|
|
for _, n := range result.Nodes {
|
|
if n != nil && n.Name != "" {
|
|
sourceNames[n.Name] = true
|
|
}
|
|
}
|
|
|
|
rtkWalk(root, func(call *sitter.Node) {
|
|
if call.Type() != "call_expression" {
|
|
return
|
|
}
|
|
if jsCalleeLastName(call.ChildByFieldName("function"), src) != "createApi" {
|
|
return
|
|
}
|
|
arrow := rtkEndpointsArrow(call, src)
|
|
if arrow == nil {
|
|
return
|
|
}
|
|
obj := rtkReturnedObject(arrow)
|
|
if obj == nil {
|
|
return
|
|
}
|
|
for i, _nc := 0, int(obj.NamedChildCount()); i < _nc; i++ {
|
|
pair := obj.NamedChild(i)
|
|
if pair == nil || pair.Type() != "pair" {
|
|
continue
|
|
}
|
|
endpoint := rtkPairKey(pair, src)
|
|
val := pair.ChildByFieldName("value")
|
|
if endpoint == "" || val == nil || val.Type() != "call_expression" {
|
|
continue
|
|
}
|
|
kind := jsCalleeLastName(val.ChildByFieldName("function"), src)
|
|
if kind != "query" && kind != "mutation" {
|
|
continue
|
|
}
|
|
rtkEmitEndpoint(result, filePath, language, endpoint, kind, val, sourceNames)
|
|
}
|
|
})
|
|
}
|
|
|
|
// rtkEmitEndpoint mints the endpoint node, the generated-hook node (unless
|
|
// a source hook of that name exists), and the hook → endpoint placeholder.
|
|
func rtkEmitEndpoint(result *parser.ExtractionResult, filePath, language, endpoint, kind string, def *sitter.Node, sourceNames map[string]bool) {
|
|
endpointID := filePath + "::api." + endpoint
|
|
if sourceNames["api."+endpoint] {
|
|
return // already materialised (re-run idempotency)
|
|
}
|
|
startLn := int(def.StartPoint().Row) + 1
|
|
endLn := int(def.EndPoint().Row) + 1
|
|
result.Nodes = append(result.Nodes, &graph.Node{
|
|
ID: endpointID, Kind: graph.KindFunction, Name: "api." + endpoint,
|
|
FilePath: filePath, StartLine: startLn, EndLine: endLn, Language: language,
|
|
Meta: map[string]any{"rtk_endpoint": endpoint, "rtk_kind": kind, "signature": "api." + endpoint + "()"},
|
|
})
|
|
sourceNames["api."+endpoint] = true
|
|
|
|
suffix := "Query"
|
|
if kind == "mutation" {
|
|
suffix = "Mutation"
|
|
}
|
|
hookName := "use" + rtkPascal(endpoint) + suffix
|
|
// A hand-written hook of this name in source wins — skip synthesis.
|
|
if sourceNames[hookName] {
|
|
return
|
|
}
|
|
hookID := filePath + "::" + hookName
|
|
result.Nodes = append(result.Nodes, &graph.Node{
|
|
ID: hookID, Kind: graph.KindFunction, Name: hookName,
|
|
FilePath: filePath, StartLine: startLn, EndLine: endLn, Language: language,
|
|
Meta: map[string]any{"rtk_generated_hook": true, "generated": true, "codegen_tool": "rtk-query", "signature": hookName + "()"},
|
|
})
|
|
sourceNames[hookName] = true
|
|
result.Edges = append(result.Edges, &graph.Edge{
|
|
From: hookID, To: "unresolved::*." + endpoint, Kind: graph.EdgeCalls,
|
|
FilePath: filePath, Line: startLn,
|
|
Meta: map[string]any{"via": rtkQueryViaTag, "rtk_endpoint": endpoint},
|
|
})
|
|
}
|
|
|
|
// rtkEndpointsArrow returns the `endpoints: (builder) => ({...})` arrow (or
|
|
// function) value from a createApi config object.
|
|
func rtkEndpointsArrow(call *sitter.Node, src []byte) *sitter.Node {
|
|
args := call.ChildByFieldName("arguments")
|
|
if args == nil {
|
|
return nil
|
|
}
|
|
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
|
|
obj := args.NamedChild(i)
|
|
if obj == nil || obj.Type() != "object" {
|
|
continue
|
|
}
|
|
for j, _nc := 0, int(obj.NamedChildCount()); j < _nc; j++ {
|
|
pair := obj.NamedChild(j)
|
|
if pair == nil || pair.Type() != "pair" {
|
|
continue
|
|
}
|
|
if rtkPairKey(pair, src) != "endpoints" {
|
|
continue
|
|
}
|
|
val := pair.ChildByFieldName("value")
|
|
if val == nil {
|
|
return nil
|
|
}
|
|
switch val.Type() {
|
|
case "arrow_function", "function_expression", "function":
|
|
return val
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// rtkReturnedObject returns the object literal a builder arrow/function
|
|
// returns — `(b) => ({...})`, `(b) => {...}` (parenthesized), or
|
|
// `(b) => { return {...} }`.
|
|
func rtkReturnedObject(fn *sitter.Node) *sitter.Node {
|
|
body := fn.ChildByFieldName("body")
|
|
if body == nil {
|
|
return nil
|
|
}
|
|
return rtkUnwrapObject(body)
|
|
}
|
|
|
|
func rtkUnwrapObject(n *sitter.Node) *sitter.Node {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
switch n.Type() {
|
|
case "object":
|
|
return n
|
|
case "parenthesized_expression":
|
|
for i, _nc := 0, int(n.NamedChildCount()); i < _nc; i++ {
|
|
if o := rtkUnwrapObject(n.NamedChild(i)); o != nil {
|
|
return o
|
|
}
|
|
}
|
|
case "statement_block":
|
|
for i, _nc := 0, int(n.NamedChildCount()); i < _nc; i++ {
|
|
c := n.NamedChild(i)
|
|
if c != nil && c.Type() == "return_statement" {
|
|
for j, _nc := 0, int(c.NamedChildCount()); j < _nc; j++ {
|
|
if o := rtkUnwrapObject(c.NamedChild(j)); o != nil {
|
|
return o
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// rtkPairKey returns the property name of an object pair, for both plain
|
|
// (`getUser:`) and string (`'getUser':`) keys.
|
|
func rtkPairKey(pair *sitter.Node, src []byte) string {
|
|
key := pair.ChildByFieldName("key")
|
|
if key == nil {
|
|
return ""
|
|
}
|
|
switch key.Type() {
|
|
case "property_identifier", "identifier":
|
|
return key.Content(src)
|
|
case "string":
|
|
return jsStringLiteralContent(key, src)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// rtkPascal upper-cases the first byte of an identifier (ASCII endpoint
|
|
// names): getUser → GetUser.
|
|
func rtkPascal(s string) string {
|
|
if s == "" {
|
|
return s
|
|
}
|
|
return strings.ToUpper(s[:1]) + s[1:]
|
|
}
|
|
|
|
// rtkWalk visits n and all its named descendants.
|
|
func rtkWalk(n *sitter.Node, fn func(*sitter.Node)) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
fn(n)
|
|
for i, _nc := 0, int(n.NamedChildCount()); i < _nc; i++ {
|
|
rtkWalk(n.NamedChild(i), fn)
|
|
}
|
|
}
|