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
114 lines
4.3 KiB
Go
114 lines
4.3 KiB
Go
package languages
|
|
|
|
import (
|
|
"regexp"
|
|
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
)
|
|
|
|
// Store-factory action resolution (Zustand / Redux Toolkit / Pinia / MobX).
|
|
// In these libraries an action is declared inside a factory call and invoked
|
|
// indirectly — `useStore.getState().fetchUser()` or
|
|
// `const {fetchUser} = useStore.getState(); fetchUser()`. The extractor stamps
|
|
// each store-action node with Meta["store_factory"]=<binding> and resolves the
|
|
// indirect call against the same-file store binding (or hands a provenance-
|
|
// tagged placeholder to the store-factory synthesizer for cross-file binding).
|
|
// Shared by the JavaScript and TypeScript extractors.
|
|
|
|
// jsStoreFactoryCallNames is the set of factory callees that mark an object
|
|
// literal as a store definition. Matched on the callee's last identifier so
|
|
// `redux.createStore` and `create` both qualify. The name gate is what keeps
|
|
// an ordinary `ctx.set({...})` object from being mistaken for a store.
|
|
var jsStoreFactoryCallNames = map[string]bool{
|
|
"create": true, // zustand
|
|
"createStore": true, // redux / zustand vanilla
|
|
"configureStore": true, // redux toolkit
|
|
"createSlice": true, // redux toolkit
|
|
"defineStore": true, // pinia
|
|
"makeAutoObservable": true, // mobx
|
|
"makeObservable": true, // mobx
|
|
"observable": true, // mobx
|
|
}
|
|
|
|
// jsGetStateChainRE recognises a store-accessor receiver:
|
|
// `useStore.getState()`, `.getStore()`, `.getActions()`, `.use()`.
|
|
var jsGetStateChainRE = regexp.MustCompile(`^(\w+)\.(?:getState|getStore|getActions|use)\(\)$`)
|
|
|
|
// jsDestructureGetStateRE recognises `const {a, b} = useStore.getState()` so a
|
|
// later bare `a()` / `b()` call binds to the store action.
|
|
var jsDestructureGetStateRE = regexp.MustCompile(`(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(\w+)\.(?:getState|getStore|getActions|use)\(\)`)
|
|
|
|
// jsStoreFactoryBinding reports whether the given object-literal member node is
|
|
// an action of a store-factory binding, returning the binding name and the
|
|
// factory callee. It walks up from the member through the (possibly
|
|
// middleware-wrapped) factory call to the variable_declarator that names the
|
|
// store, requiring a recognised factory callee along the way.
|
|
func jsStoreFactoryBinding(member *sitter.Node, src []byte) (binding, factory string, ok bool) {
|
|
if member == nil {
|
|
return "", "", false
|
|
}
|
|
saw := ""
|
|
for cur := member.Parent(); cur != nil; cur = cur.Parent() {
|
|
switch cur.Type() {
|
|
case "call_expression":
|
|
if fn := cur.ChildByFieldName("function"); fn != nil {
|
|
if name := jsCalleeLastName(fn, src); jsStoreFactoryCallNames[name] {
|
|
saw = name
|
|
}
|
|
}
|
|
case "variable_declarator":
|
|
if saw == "" {
|
|
return "", "", false
|
|
}
|
|
if name := cur.ChildByFieldName("name"); name != nil && name.Type() == "identifier" {
|
|
return name.Content(src), saw, true
|
|
}
|
|
return "", "", false
|
|
case "program", "class_body":
|
|
return "", "", false
|
|
}
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
// jsCalleeLastName returns the trailing identifier of a call's function
|
|
// expression: "create" for `create`, "createStore" for `redux.createStore`.
|
|
func jsCalleeLastName(fn *sitter.Node, src []byte) string {
|
|
if fn == nil {
|
|
return ""
|
|
}
|
|
switch fn.Type() {
|
|
case "identifier":
|
|
return fn.Content(src)
|
|
case "member_expression":
|
|
if p := fn.ChildByFieldName("property"); p != nil {
|
|
return p.Content(src)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// jsStoreOptionKeys are the object-literal keys under which Pinia and Redux
|
|
// Toolkit nest their action functions (`defineStore('id',{actions:{...}})`,
|
|
// `createSlice({reducers:{...}})`). When jsObjectOwnerName resolves a member's
|
|
// owner to one of these, the member is still a store action — the factory walk
|
|
// confirms it — so the extractor reruns store-factory detection for them.
|
|
var jsStoreOptionKeys = map[string]bool{
|
|
"actions": true, // pinia / vuex
|
|
"reducers": true, // redux toolkit
|
|
"methods": true, // generic
|
|
}
|
|
|
|
// jsIsStoreOptionKey reports whether an owner name is a store option key.
|
|
func jsIsStoreOptionKey(owner string) bool { return jsStoreOptionKeys[owner] }
|
|
|
|
// jsParseGetStateChain extracts the store binding from a member-call receiver
|
|
// of the form `<binding>.getState()`.
|
|
func jsParseGetStateChain(receiver string) (binding string, ok bool) {
|
|
m := jsGetStateChainRE.FindStringSubmatch(receiver)
|
|
if m == nil {
|
|
return "", false
|
|
}
|
|
return m[1], true
|
|
}
|