@colbymchenry/codegraph-darwin-x64 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dist/bin/codegraph.js +111 -11
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/db/index.d.ts +22 -1
- package/lib/dist/db/index.d.ts.map +1 -1
- package/lib/dist/db/index.js +46 -1
- package/lib/dist/db/index.js.map +1 -1
- package/lib/dist/db/queries.d.ts +14 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +25 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/directory.d.ts +43 -0
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +121 -0
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/grammars.d.ts +11 -3
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +14 -5
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +161 -34
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +47 -2
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/csharp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/csharp.js +20 -0
- package/lib/dist/extraction/languages/csharp.js.map +1 -1
- package/lib/dist/extraction/languages/dart.d.ts.map +1 -1
- package/lib/dist/extraction/languages/dart.js +22 -0
- package/lib/dist/extraction/languages/dart.js.map +1 -1
- package/lib/dist/extraction/languages/java.d.ts.map +1 -1
- package/lib/dist/extraction/languages/java.js +213 -9
- package/lib/dist/extraction/languages/java.js.map +1 -1
- package/lib/dist/extraction/languages/kotlin.d.ts.map +1 -1
- package/lib/dist/extraction/languages/kotlin.js +51 -0
- package/lib/dist/extraction/languages/kotlin.js.map +1 -1
- package/lib/dist/extraction/languages/scala.d.ts.map +1 -1
- package/lib/dist/extraction/languages/scala.js +19 -9
- package/lib/dist/extraction/languages/scala.js.map +1 -1
- package/lib/dist/extraction/parse-worker.js +4 -1
- package/lib/dist/extraction/parse-worker.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +13 -0
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +119 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +890 -11
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/index.d.ts +33 -0
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +68 -7
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +33 -67
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/instructions-template.d.ts +3 -3
- package/lib/dist/installer/instructions-template.d.ts.map +1 -1
- package/lib/dist/installer/instructions-template.js +4 -4
- package/lib/dist/installer/targets/claude.d.ts +18 -12
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +78 -6
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/shared.d.ts +12 -2
- package/lib/dist/installer/targets/shared.d.ts.map +1 -1
- package/lib/dist/installer/targets/shared.js +13 -12
- package/lib/dist/installer/targets/shared.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +7 -0
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/mcp/engine.d.ts.map +1 -1
- package/lib/dist/mcp/engine.js +8 -0
- package/lib/dist/mcp/engine.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +18 -14
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +57 -52
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +23 -18
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +51 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +585 -151
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +19 -0
- package/lib/dist/project-config.d.ts.map +1 -0
- package/lib/dist/project-config.js +180 -0
- package/lib/dist/project-config.js.map +1 -0
- package/lib/dist/reasoning/config.d.ts +45 -0
- package/lib/dist/reasoning/config.d.ts.map +1 -0
- package/lib/dist/reasoning/config.js +171 -0
- package/lib/dist/reasoning/config.js.map +1 -0
- package/lib/dist/reasoning/credentials.d.ts +5 -0
- package/lib/dist/reasoning/credentials.d.ts.map +1 -0
- package/lib/dist/reasoning/credentials.js +83 -0
- package/lib/dist/reasoning/credentials.js.map +1 -0
- package/lib/dist/reasoning/login.d.ts +21 -0
- package/lib/dist/reasoning/login.d.ts.map +1 -0
- package/lib/dist/reasoning/login.js +85 -0
- package/lib/dist/reasoning/login.js.map +1 -0
- package/lib/dist/reasoning/reasoner.d.ts +43 -0
- package/lib/dist/reasoning/reasoner.d.ts.map +1 -0
- package/lib/dist/reasoning/reasoner.js +308 -0
- package/lib/dist/reasoning/reasoner.js.map +1 -0
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +33 -0
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
- package/lib/dist/resolution/c-fnptr-synthesizer.js +352 -0
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
- package/lib/dist/resolution/callback-synthesizer.d.ts +6 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +1109 -1
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/frameworks/goframe.d.ts +41 -0
- package/lib/dist/resolution/frameworks/goframe.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/goframe.js +112 -0
- package/lib/dist/resolution/frameworks/goframe.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +5 -1
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/react.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/react.js +17 -60
- package/lib/dist/resolution/frameworks/react.js.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts +28 -0
- package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
- package/lib/dist/resolution/goframe-synthesizer.js +158 -0
- package/lib/dist/resolution/goframe-synthesizer.js.map +1 -0
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +48 -8
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/strip-comments.d.ts +1 -1
- package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
- package/lib/dist/resolution/strip-comments.js +2 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/sync/watcher.d.ts +68 -1
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +212 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/telemetry/index.d.ts +0 -3
- package/lib/dist/telemetry/index.d.ts.map +1 -1
- package/lib/dist/telemetry/index.js +4 -7
- package/lib/dist/telemetry/index.js.map +1 -1
- package/lib/dist/upgrade/index.d.ts.map +1 -1
- package/lib/dist/upgrade/index.js +40 -4
- package/lib/dist/upgrade/index.js.map +1 -1
- package/lib/dist/utils.d.ts +14 -1
- package/lib/dist/utils.d.ts.map +1 -1
- package/lib/dist/utils.js +20 -2
- package/lib/dist/utils.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +2 -2
- package/package.json +1 -1
|
@@ -57,6 +57,25 @@ const frameworks_1 = require("../resolution/frameworks");
|
|
|
57
57
|
// Re-export for backward compatibility
|
|
58
58
|
var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
|
|
59
59
|
Object.defineProperty(exports, "generateNodeId", { enumerable: true, get: function () { return tree_sitter_helpers_2.generateNodeId; } });
|
|
60
|
+
/**
|
|
61
|
+
* RTK Query generated-hook naming convention: `use` + PascalCase endpoint (with
|
|
62
|
+
* an optional `Lazy` variant prefix) + `Query`/`Mutation`. Matches the hook
|
|
63
|
+
* bindings to extract from an `export const {...} = api` destructuring. Kept in
|
|
64
|
+
* sync with the same convention in `callback-synthesizer.ts` (the synth side).
|
|
65
|
+
*/
|
|
66
|
+
const RTK_HOOK_NAME_RE = /^use[A-Z][A-Za-z0-9]*(?:Query|Mutation)$/;
|
|
67
|
+
/** React HOC callees whose result is itself a component — a PascalCase const
|
|
68
|
+
* initialized with one of these is a component, not a constant (#841). */
|
|
69
|
+
const REACT_COMPONENT_HOCS = new Set(['forwardRef', 'memo', 'React.forwardRef', 'React.memo']);
|
|
70
|
+
/** Vue store collections whose object-literal members are the symbols an agent
|
|
71
|
+
* looks for. Extracted as function nodes so `actions`/`mutations`/`getters` are
|
|
72
|
+
* findable + readable (the foundation under any later dispatch-bridge synth). */
|
|
73
|
+
const VUE_STORE_COLLECTION_NAMES = new Set(['actions', 'mutations', 'getters']);
|
|
74
|
+
/** Store-definition callees whose config object carries those collections. */
|
|
75
|
+
const VUE_STORE_FACTORY_CALLEES = new Set(['defineStore', 'createStore']);
|
|
76
|
+
/** Distinct signals that a file is a Vuex/Pinia store (≥2 ⇒ treat a bare
|
|
77
|
+
* `const actions = {…}` as a store collection — see looksLikeVueStoreFile). */
|
|
78
|
+
const VUE_STORE_FILE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
|
|
60
79
|
/**
|
|
61
80
|
* Extract the name from a node based on language
|
|
62
81
|
*/
|
|
@@ -164,6 +183,77 @@ function scalaBaseTypeName(node, source) {
|
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the declared identifier inside a C declarator. A `declaration`'s
|
|
188
|
+
* `declarator` field nests the name through `init_declarator` (with value),
|
|
189
|
+
* `pointer_declarator`/`array_declarator`/`parenthesized_declarator`
|
|
190
|
+
* wrappers (each via their own `declarator` field) down to an `identifier`.
|
|
191
|
+
* A `function_declarator` means the declaration is a function prototype (or a
|
|
192
|
+
* function-pointer var) — return null so it isn't extracted as a variable.
|
|
193
|
+
*/
|
|
194
|
+
function cDeclaratorIdentifier(node) {
|
|
195
|
+
let cur = node;
|
|
196
|
+
let guard = 0;
|
|
197
|
+
while (cur && guard++ < 12) {
|
|
198
|
+
switch (cur.type) {
|
|
199
|
+
case 'identifier':
|
|
200
|
+
return cur;
|
|
201
|
+
case 'function_declarator':
|
|
202
|
+
return null;
|
|
203
|
+
case 'init_declarator':
|
|
204
|
+
case 'pointer_declarator':
|
|
205
|
+
case 'array_declarator':
|
|
206
|
+
case 'parenthesized_declarator':
|
|
207
|
+
cur = (0, tree_sitter_helpers_1.getChildByField)(cur, 'declarator');
|
|
208
|
+
break;
|
|
209
|
+
default:
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
/** First `simple_identifier` in `node`'s subtree (breadth-ish, first-found).
|
|
216
|
+
* Swift's property name nests as `property_declaration → <name> pattern →
|
|
217
|
+
* bound_identifier → simple_identifier`; this resolves it (and the bound name of
|
|
218
|
+
* a Kotlin/Swift property declarator for the shadow prune). For a tuple pattern
|
|
219
|
+
* (`let (a, b)`) it returns the first — acceptable, those are rare for consts. */
|
|
220
|
+
function firstSimpleIdentifier(node) {
|
|
221
|
+
const stack = node ? [node] : [];
|
|
222
|
+
let guard = 0;
|
|
223
|
+
while (stack.length > 0 && guard++ < 40) {
|
|
224
|
+
const n = stack.shift();
|
|
225
|
+
if (n.type === 'simple_identifier')
|
|
226
|
+
return n;
|
|
227
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
228
|
+
const c = n.namedChild(i);
|
|
229
|
+
if (c)
|
|
230
|
+
stack.push(c);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
/** Swift property facts: the bound name, whether it's a `let`, and whether it's
|
|
236
|
+
* a *computed* property (a getter block, no stored value — never a constant). */
|
|
237
|
+
function swiftPropertyInfo(node, source) {
|
|
238
|
+
const pattern = (0, tree_sitter_helpers_1.getChildByField)(node, 'name') ??
|
|
239
|
+
node.namedChildren.find((c) => c.type === 'value_binding_pattern' || c.type === 'pattern') ??
|
|
240
|
+
null;
|
|
241
|
+
const binding = node.namedChildren.find((c) => c.type === 'value_binding_pattern');
|
|
242
|
+
const isLet = binding != null && (0, tree_sitter_helpers_1.getNodeText)(binding, source).trimStart().startsWith('let');
|
|
243
|
+
const isComputed = node.namedChildren.some((c) => c.type === 'computed_property' || c.type === 'protocol_property_requirements');
|
|
244
|
+
return { nameNode: firstSimpleIdentifier(pattern), isLet, isComputed };
|
|
245
|
+
}
|
|
246
|
+
/** True when `node` is (transitively) inside a C function body — i.e. a local,
|
|
247
|
+
* not a file/namespace-scope declaration. Walks the parent chain to the root. */
|
|
248
|
+
function hasFunctionAncestor(node) {
|
|
249
|
+
let p = node.parent;
|
|
250
|
+
while (p) {
|
|
251
|
+
if (p.type === 'function_definition')
|
|
252
|
+
return true;
|
|
253
|
+
p = p.parent;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
167
257
|
/**
|
|
168
258
|
* PHP type-position wrapper node kinds (a type-hint is `named_type`,
|
|
169
259
|
* `?Foo` is `optional_type`, `A|B` is `union_type`, `A&B` is
|
|
@@ -230,6 +320,15 @@ class TreeSitterExtractor {
|
|
|
230
320
|
nodes = [];
|
|
231
321
|
edges = [];
|
|
232
322
|
unresolvedReferences = [];
|
|
323
|
+
// Value-reference edges (default ON; set CODEGRAPH_VALUE_REFS=0 to disable; see flushValueRefs).
|
|
324
|
+
// Same-file reads of file-scope const/var symbols → `references` edges so impact analysis catches
|
|
325
|
+
// value consumers ("change this constant/table, affect its readers").
|
|
326
|
+
static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
|
|
327
|
+
static MAX_VALUE_REF_NODES = 20_000;
|
|
328
|
+
valueRefsEnabled = process.env.CODEGRAPH_VALUE_REFS !== '0';
|
|
329
|
+
fileScopeValues = new Map();
|
|
330
|
+
fileScopeValueCounts = new Map(); // file-scope nodes per name (conditional-def detection)
|
|
331
|
+
valueRefScopes = [];
|
|
233
332
|
errors = [];
|
|
234
333
|
extractor = null;
|
|
235
334
|
nodeStack = []; // Stack of parent node IDs
|
|
@@ -239,6 +338,8 @@ class TreeSitterExtractor {
|
|
|
239
338
|
// (see flushFnRefCandidates).
|
|
240
339
|
fnRefSpec;
|
|
241
340
|
fnRefCandidates = [];
|
|
341
|
+
// Memoized "is this a Vue store file" verdict (per-extractor = per-file).
|
|
342
|
+
vueStoreFile = null;
|
|
242
343
|
constructor(filePath, source, language) {
|
|
243
344
|
this.filePath = filePath;
|
|
244
345
|
this.source = source;
|
|
@@ -326,6 +427,7 @@ class TreeSitterExtractor {
|
|
|
326
427
|
// Gate + flush function-as-value candidates (#756) while the file's
|
|
327
428
|
// nodes and import refs are complete and the file node is still pushed.
|
|
328
429
|
this.flushFnRefCandidates();
|
|
430
|
+
this.flushValueRefs();
|
|
329
431
|
if (packageNodeId)
|
|
330
432
|
this.nodeStack.pop();
|
|
331
433
|
this.nodeStack.pop();
|
|
@@ -514,6 +616,213 @@ class TreeSitterExtractor {
|
|
|
514
616
|
});
|
|
515
617
|
}
|
|
516
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Record value-reference bookkeeping as nodes are created: file-scope const/var symbols with
|
|
621
|
+
* distinctive names become reference targets; function/method/const/var symbols become reader
|
|
622
|
+
* scopes whose bodies flushValueRefs scans.
|
|
623
|
+
*/
|
|
624
|
+
captureValueRefScope(kind, name, id, node) {
|
|
625
|
+
// Pascal targets `constant` only: its extractor emits function PARAMETERS
|
|
626
|
+
// (`Dest: TBufferWriter`) and class fields (`declField`) as `variable` at the
|
|
627
|
+
// enclosing scope, which would otherwise become noisy targets (a param name
|
|
628
|
+
// shared across many procs collapses to one file-wide target). Genuine
|
|
629
|
+
// Pascal shared values are `const` (`constant`), so restrict to that. (Unit
|
|
630
|
+
// `var` globals are the rare cost; the parameter/field noise dominates.)
|
|
631
|
+
const targetKindOk = this.language === 'pascal' ? kind === 'constant' : kind === 'constant' || kind === 'variable';
|
|
632
|
+
if (targetKindOk && name.length >= 3 && /[A-Z_]/.test(name)) {
|
|
633
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
634
|
+
// file-scope OR class/module/struct/enum-scope constants are targets.
|
|
635
|
+
// Class/module scope matters for languages (Ruby) that keep nearly all
|
|
636
|
+
// constants inside a class or module; struct/enum scope matters for Swift,
|
|
637
|
+
// which namespaces shared constants in `struct`/`enum` (`enum Constants {
|
|
638
|
+
// static let X }`). Readers are same-file methods of that type.
|
|
639
|
+
if (parentId &&
|
|
640
|
+
(parentId.startsWith('file:') || parentId.startsWith('class:') ||
|
|
641
|
+
parentId.startsWith('module:') || parentId.startsWith('struct:') ||
|
|
642
|
+
parentId.startsWith('enum:'))) {
|
|
643
|
+
this.fileScopeValues.set(name, id);
|
|
644
|
+
// How many target nodes carry this name. A conditional def
|
|
645
|
+
// (`try: X = a; except: X = b`) makes >1 — distinct from a local shadow,
|
|
646
|
+
// which adds a binding the prune must catch (see flushValueRefs).
|
|
647
|
+
this.fileScopeValueCounts.set(name, (this.fileScopeValueCounts.get(name) ?? 0) + 1);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (kind === 'function' || kind === 'method' || kind === 'constant' || kind === 'variable') {
|
|
651
|
+
this.valueRefScopes.push({ id, node, name });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Emit same-file `references` edges from a symbol to the file-scope const/var it reads (TS/JS).
|
|
656
|
+
* The engine doesn't edge const→consumer, so impact analysis misses "change this table, affect
|
|
657
|
+
* its readers" (the ReScript-PR false positive). Same-file only (resolution is unambiguous),
|
|
658
|
+
* distinctive target names only (dodges the local-shadowing precision trap documented on
|
|
659
|
+
* function_ref), deduped per (reader, target). Default on (CODEGRAPH_VALUE_REFS=0 disables) +
|
|
660
|
+
* additive. Shadowed targets are pruned — see below.
|
|
661
|
+
*/
|
|
662
|
+
flushValueRefs() {
|
|
663
|
+
const scopes = this.valueRefScopes;
|
|
664
|
+
const targets = this.fileScopeValues;
|
|
665
|
+
const fileScopeCounts = this.fileScopeValueCounts;
|
|
666
|
+
this.valueRefScopes = [];
|
|
667
|
+
this.fileScopeValues = new Map();
|
|
668
|
+
this.fileScopeValueCounts = new Map();
|
|
669
|
+
if (!this.valueRefsEnabled || !TreeSitterExtractor.VALUE_REF_LANGS.has(this.language))
|
|
670
|
+
return;
|
|
671
|
+
if (targets.size === 0 || scopes.length === 0 || (0, generated_detection_1.isGeneratedFile)(this.filePath))
|
|
672
|
+
return;
|
|
673
|
+
// Prune SHADOWED targets. A target re-bound in an INNER scope (a
|
|
674
|
+
// bundled/Emscripten `const Module` re-declared as a nested `var Module`; a
|
|
675
|
+
// Go package `const Timeout` shadowed by a local `Timeout := …`; a Python
|
|
676
|
+
// module `CONFIG` shadowed by a local `CONFIG = …`) resolves to the inner
|
|
677
|
+
// binding for nested readers, so a file-scope edge is a false positive.
|
|
678
|
+
// Inner re-bindings aren't graph nodes, so detect them at the syntax level:
|
|
679
|
+
// count every declarator of the name across the tree and compare against how
|
|
680
|
+
// many FILE-SCOPE nodes carry it. A real shadow makes (declarators >
|
|
681
|
+
// file-scope nodes) — the excess is the local binding. A conditional
|
|
682
|
+
// module-level def (`try: X = a; except: X = b`) makes them EQUAL (both
|
|
683
|
+
// declarators are file-scope nodes), so it's correctly kept. Complements the
|
|
684
|
+
// path-based isGeneratedFile() check, which can't catch content-minified
|
|
685
|
+
// bundles.
|
|
686
|
+
//
|
|
687
|
+
// Declarator node types are per-grammar; a file only contains its own
|
|
688
|
+
// language's nodes, so matching all of them in one switch is safe.
|
|
689
|
+
if (this.tree) {
|
|
690
|
+
const declCounts = new Map();
|
|
691
|
+
const bump = (nameNode) => {
|
|
692
|
+
// `simple_identifier` is Kotlin's name node (a property declarator's name).
|
|
693
|
+
if (nameNode && (nameNode.type === 'identifier' || nameNode.type === 'simple_identifier')) {
|
|
694
|
+
const nm = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
695
|
+
if (targets.has(nm))
|
|
696
|
+
declCounts.set(nm, (declCounts.get(nm) ?? 0) + 1);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const dstack = [this.tree.rootNode];
|
|
700
|
+
let dvisited = 0;
|
|
701
|
+
while (dstack.length > 0 && dvisited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
|
|
702
|
+
const n = dstack.pop();
|
|
703
|
+
dvisited++;
|
|
704
|
+
switch (n.type) {
|
|
705
|
+
case 'variable_declarator': // TS/JS/tsx
|
|
706
|
+
case 'const_spec': // Go `const X = …`
|
|
707
|
+
case 'var_spec': // Go `var X = …`
|
|
708
|
+
bump(n.namedChild(0));
|
|
709
|
+
break;
|
|
710
|
+
case 'const_item': // Rust `const X: T = …`
|
|
711
|
+
case 'static_item': // Rust `static X: T = …`
|
|
712
|
+
bump((0, tree_sitter_helpers_1.getChildByField)(n, 'name'));
|
|
713
|
+
break;
|
|
714
|
+
case 'let_declaration': // Rust `let x = …` (locals — the shadow source)
|
|
715
|
+
case 'short_var_declaration': // Go `x, Y := …`
|
|
716
|
+
case 'assignment': { // Python `X = …` / `X: T = …` / `A, B = …`
|
|
717
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(n, 'left') ?? (0, tree_sitter_helpers_1.getChildByField)(n, 'pattern') ?? n.namedChild(0);
|
|
718
|
+
if (left?.type === 'identifier')
|
|
719
|
+
bump(left);
|
|
720
|
+
else if (left)
|
|
721
|
+
for (const c of left.namedChildren)
|
|
722
|
+
bump(c);
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
case 'init_declarator': // C `T X = …` (file-scope const AND the local that shadows it)
|
|
726
|
+
bump(cDeclaratorIdentifier(n));
|
|
727
|
+
break;
|
|
728
|
+
case 'val_definition': // Scala `val X = …` (object/top-level const AND a method-local that shadows it)
|
|
729
|
+
case 'var_definition': { // Scala `var X = …`
|
|
730
|
+
const pat = (0, tree_sitter_helpers_1.getChildByField)(n, 'pattern');
|
|
731
|
+
if (pat?.type === 'identifier')
|
|
732
|
+
bump(pat);
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
case 'static_final_declaration': // Dart top-level/`static` `const`/`final` (the target itself)
|
|
736
|
+
case 'initialized_identifier': // Dart instance field / `var`
|
|
737
|
+
case 'initialized_variable_definition': { // Dart a method-local `const`/`final`/`var` that shadows a const
|
|
738
|
+
const id = n.namedChildren.find((c) => c.type === 'identifier');
|
|
739
|
+
if (id)
|
|
740
|
+
bump(id);
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
case 'declConst': // Pascal unit/class `const` (the target itself) AND a function-local `const` that shadows it
|
|
744
|
+
case 'declVar': { // Pascal a function-local `var` that shadows a const
|
|
745
|
+
bump((0, tree_sitter_helpers_1.getChildByField)(n, 'name'));
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
case 'property_declaration': { // Kotlin / Swift `val`/`let X = …` (object/static const AND a method-local that shadows it)
|
|
749
|
+
// Kotlin: variable_declaration → simple_identifier; Swift: a `pattern`
|
|
750
|
+
// (`<name>` field) → simple_identifier. Resolve either shape.
|
|
751
|
+
const vd = n.namedChildren.find((c) => c.type === 'variable_declaration');
|
|
752
|
+
const id = vd
|
|
753
|
+
? vd.namedChildren.find((c) => c.type === 'simple_identifier')
|
|
754
|
+
: firstSimpleIdentifier((0, tree_sitter_helpers_1.getChildByField)(n, 'name') ??
|
|
755
|
+
n.namedChildren.find((c) => c.type === 'value_binding_pattern' || c.type === 'pattern') ??
|
|
756
|
+
null);
|
|
757
|
+
if (id)
|
|
758
|
+
bump(id);
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
763
|
+
const c = n.namedChild(i);
|
|
764
|
+
if (c)
|
|
765
|
+
dstack.push(c);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
for (const [nm, c] of declCounts)
|
|
769
|
+
if (c > (fileScopeCounts.get(nm) ?? 1))
|
|
770
|
+
targets.delete(nm);
|
|
771
|
+
if (targets.size === 0)
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
for (const scope of scopes) {
|
|
775
|
+
const seen = new Set();
|
|
776
|
+
const stack = [scope.node];
|
|
777
|
+
// Dart and Pascal attach a function/method BODY as a *next sibling* of the
|
|
778
|
+
// signature node that is stored as the reader scope (Dart `method_signature`
|
|
779
|
+
// ← `function_body`; Pascal `declProc` ← `block`, both under a `defProc`),
|
|
780
|
+
// not as a child — so the scope subtree is just the signature and the reads
|
|
781
|
+
// live in the sibling. Pull it in. (A body as a next sibling of the scope
|
|
782
|
+
// node is unique to Dart/Pascal among the value-ref languages — every other
|
|
783
|
+
// grammar nests the body inside the function node — so this is inert
|
|
784
|
+
// elsewhere.)
|
|
785
|
+
const sib = scope.node.nextNamedSibling;
|
|
786
|
+
if (sib && (sib.type === 'function_body' || sib.type === 'block'))
|
|
787
|
+
stack.push(sib);
|
|
788
|
+
let visited = 0;
|
|
789
|
+
while (stack.length > 0 && visited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
|
|
790
|
+
const n = stack.pop();
|
|
791
|
+
visited++;
|
|
792
|
+
// `constant` covers Ruby, where both a constant's definition and its
|
|
793
|
+
// references are `constant`-typed nodes, not `identifier`. `name` covers
|
|
794
|
+
// PHP, where a constant reference — bare `MAX_ITEMS` or the const half of
|
|
795
|
+
// `self::MAX_ITEMS` / `Foo::MAX_ITEMS` — is a `name` node (a `$var` local
|
|
796
|
+
// is a `variable_name`, a different namespace, so it can never shadow a
|
|
797
|
+
// bare constant — no prune wiring needed). `simple_identifier` covers
|
|
798
|
+
// Kotlin, whose every name reference (a const read included) is that
|
|
799
|
+
// node type. Safe across languages: a file only holds its own grammar's
|
|
800
|
+
// nodes; `name` is PHP-only and `simple_identifier` is Kotlin-only here.
|
|
801
|
+
if (n.type === 'identifier' || n.type === 'constant' ||
|
|
802
|
+
n.type === 'name' || n.type === 'simple_identifier') {
|
|
803
|
+
const refName = (0, tree_sitter_helpers_1.getNodeText)(n, this.source);
|
|
804
|
+
const targetId = targets.get(refName);
|
|
805
|
+
// Skip self and same-name targets: a symbol referencing a file-scope
|
|
806
|
+
// sibling of its own name (the two halves of a conditional `try: X=…;
|
|
807
|
+
// except: X=…`) is never a meaningful value read.
|
|
808
|
+
if (targetId && targetId !== scope.id && refName !== scope.name && !seen.has(targetId)) {
|
|
809
|
+
seen.add(targetId);
|
|
810
|
+
this.edges.push({
|
|
811
|
+
source: scope.id,
|
|
812
|
+
target: targetId,
|
|
813
|
+
kind: 'references',
|
|
814
|
+
metadata: { valueRef: true },
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
819
|
+
const c = n.namedChild(i);
|
|
820
|
+
if (c)
|
|
821
|
+
stack.push(c);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
517
826
|
/**
|
|
518
827
|
* Visit a node and extract information
|
|
519
828
|
*/
|
|
@@ -652,8 +961,13 @@ class TreeSitterExtractor {
|
|
|
652
961
|
skipChildren = true;
|
|
653
962
|
}
|
|
654
963
|
// Check for variable declarations (const, let, var, etc.)
|
|
655
|
-
// Only extract top-level variables (not inside functions/methods)
|
|
656
|
-
|
|
964
|
+
// Only extract top-level variables (not inside functions/methods) — plus
|
|
965
|
+
// class/module-scope CONSTANTS, which Ruby (and other const-in-class
|
|
966
|
+
// languages) keep almost exclusively inside a class/module. A Ruby `CONST =
|
|
967
|
+
// …` has a `constant`-typed LHS; other languages don't put one here, so this
|
|
968
|
+
// is effectively Ruby-only and doesn't disturb their class-internal locals.
|
|
969
|
+
else if (this.extractor.variableTypes.includes(nodeType) &&
|
|
970
|
+
(!this.isInsideClassLikeNode() || this.isClassScopeConstantAssignment(node))) {
|
|
657
971
|
this.extractVariable(node);
|
|
658
972
|
// extractVariable doesn't walk every initializer shape (object literals
|
|
659
973
|
// are deliberately skipped; Python/Ruby don't walk at all), so scan the
|
|
@@ -675,6 +989,20 @@ class TreeSitterExtractor {
|
|
|
675
989
|
nodeType === 'property_declaration' &&
|
|
676
990
|
this.isInsideClassLikeNode()) {
|
|
677
991
|
const ownerId = this.nodeStack[this.nodeStack.length - 1];
|
|
992
|
+
// A `static let`/`static var` member is a SHARED constant of the type
|
|
993
|
+
// (Swift's `static`-namespacing idiom, esp. in `enum`/`struct`) — extract
|
|
994
|
+
// it as `constant`/`variable` so value-reference edges can target it. An
|
|
995
|
+
// instance stored property stays a `field` (per-instance; Swift instance
|
|
996
|
+
// properties otherwise aren't own nodes — that's unchanged). A *computed*
|
|
997
|
+
// property (getter, no stored value) is never a constant — skip the node.
|
|
998
|
+
const { nameNode, isLet, isComputed } = swiftPropertyInfo(node, this.source);
|
|
999
|
+
if (nameNode && !isComputed) {
|
|
1000
|
+
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
1001
|
+
this.createNode(isStatic ? (isLet ? 'constant' : 'variable') : 'field', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node, {
|
|
1002
|
+
visibility: this.extractor.getVisibility?.(node),
|
|
1003
|
+
isStatic,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
678
1006
|
if (ownerId) {
|
|
679
1007
|
this.extractDecoratorsFor(node, ownerId);
|
|
680
1008
|
this.extractVariableTypeAnnotation(node, ownerId);
|
|
@@ -732,6 +1060,22 @@ class TreeSitterExtractor {
|
|
|
732
1060
|
if (parentId)
|
|
733
1061
|
this.emitReExportRefs(node, parentId);
|
|
734
1062
|
}
|
|
1063
|
+
// Vuex MODULE default export — `export default { namespaced, actions: {…},
|
|
1064
|
+
// mutations: {…} }` (the canonical Vuex module shape). Object-literal methods
|
|
1065
|
+
// aren't otherwise extracted, so scan the config's actions/mutations/getters
|
|
1066
|
+
// collections and extract their methods as nodes. Store-file gated (the
|
|
1067
|
+
// ≥2-signal heuristic) so a plain default-exported object is untouched; skip
|
|
1068
|
+
// the subtree afterward (the collection methods are now handled).
|
|
1069
|
+
else if (nodeType === 'export_statement' &&
|
|
1070
|
+
(this.language === 'typescript' || this.language === 'tsx' ||
|
|
1071
|
+
this.language === 'javascript' || this.language === 'jsx') &&
|
|
1072
|
+
this.looksLikeVueStoreFile()) {
|
|
1073
|
+
const exported = (0, tree_sitter_helpers_1.getChildByField)(node, 'value');
|
|
1074
|
+
if (exported && (exported.type === 'object' || exported.type === 'object_expression')) {
|
|
1075
|
+
this.extractStoreCollectionMethods(exported);
|
|
1076
|
+
skipChildren = true;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
735
1079
|
// Check for function calls
|
|
736
1080
|
else if (this.extractor.callTypes.includes(nodeType)) {
|
|
737
1081
|
this.extractCall(node);
|
|
@@ -843,6 +1187,8 @@ class TreeSitterExtractor {
|
|
|
843
1187
|
});
|
|
844
1188
|
}
|
|
845
1189
|
}
|
|
1190
|
+
if (this.valueRefsEnabled)
|
|
1191
|
+
this.captureValueRefScope(kind, name, id, node);
|
|
846
1192
|
return newNode;
|
|
847
1193
|
}
|
|
848
1194
|
/**
|
|
@@ -938,6 +1284,18 @@ class TreeSitterExtractor {
|
|
|
938
1284
|
parentNode.kind === 'enum' ||
|
|
939
1285
|
parentNode.kind === 'module');
|
|
940
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Ruby `CONST = …` assignment whose LHS is a `constant` node — a class/module
|
|
1289
|
+
* (or top-level) constant worth extracting as a symbol even inside a class.
|
|
1290
|
+
* Other languages don't give an assignment a `constant`-typed LHS, so this
|
|
1291
|
+
* gate is effectively Ruby-only.
|
|
1292
|
+
*/
|
|
1293
|
+
isClassScopeConstantAssignment(node) {
|
|
1294
|
+
if (node.type !== 'assignment')
|
|
1295
|
+
return false;
|
|
1296
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') ?? node.namedChild(0);
|
|
1297
|
+
return left?.type === 'constant';
|
|
1298
|
+
}
|
|
941
1299
|
/**
|
|
942
1300
|
* Extract a function
|
|
943
1301
|
*/
|
|
@@ -1026,6 +1384,70 @@ class TreeSitterExtractor {
|
|
|
1026
1384
|
}
|
|
1027
1385
|
this.nodeStack.pop();
|
|
1028
1386
|
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Detect a React component declared via an HOC wrapper whose result is itself a
|
|
1389
|
+
* component: `forwardRef(...)`, `memo(...)`, `React.forwardRef/memo(...)`, and
|
|
1390
|
+
* styled-components / emotion `styled.tag\`…\`` / `styled(Base)\`…\``. These
|
|
1391
|
+
* initializers are a call / tagged-template (not a bare arrow), so the const is
|
|
1392
|
+
* otherwise classified `constant` — and a constant is skipped by both the
|
|
1393
|
+
* JSX-render edge synthesizer and component resolution, so `<Button/>` usages
|
|
1394
|
+
* get no edge and callers/impact silently return empty (#841).
|
|
1395
|
+
*
|
|
1396
|
+
* Returns `{ inner }` — the inline render function to extract as the component
|
|
1397
|
+
* body, or `null` when the wrapper has no inline function (`memo(Imported)`,
|
|
1398
|
+
* `styled.button\`…\``) and only a bodyless component node is minted — or
|
|
1399
|
+
* `undefined` when this initializer is not a recognized component wrapper.
|
|
1400
|
+
*/
|
|
1401
|
+
reactComponentHoc(valueNode) {
|
|
1402
|
+
if (valueNode.type !== 'call_expression')
|
|
1403
|
+
return undefined;
|
|
1404
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(valueNode, 'function');
|
|
1405
|
+
if (!callee)
|
|
1406
|
+
return undefined;
|
|
1407
|
+
const calleeText = (0, tree_sitter_helpers_1.getNodeText)(callee, this.source);
|
|
1408
|
+
// styled-components / emotion: `styled.button\`…\`` / `styled(Base)\`…\``.
|
|
1409
|
+
// tree-sitter models these tagged templates as a call_expression whose callee
|
|
1410
|
+
// is the `styled.x` / `styled(Base)` tag (\b avoids matching `styledFoo`).
|
|
1411
|
+
// No inline render fn — the argument is the CSS template.
|
|
1412
|
+
if (/^styled\b/.test(calleeText))
|
|
1413
|
+
return { inner: null };
|
|
1414
|
+
// React HOCs: `forwardRef`/`memo`/`React.forwardRef`/`React.memo`.
|
|
1415
|
+
if (!REACT_COMPONENT_HOCS.has(calleeText))
|
|
1416
|
+
return undefined;
|
|
1417
|
+
// The first arrow / function-expression argument is the render fn (if inline;
|
|
1418
|
+
// `memo(Imported)` passes a bare identifier and has none).
|
|
1419
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(valueNode, 'arguments');
|
|
1420
|
+
let inner = null;
|
|
1421
|
+
if (args) {
|
|
1422
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
1423
|
+
const a = args.namedChild(i);
|
|
1424
|
+
if (a && (a.type === 'arrow_function' || a.type === 'function_expression')) {
|
|
1425
|
+
inner = a;
|
|
1426
|
+
break;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return { inner };
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Emit a `component` node for an HOC-wrapped React component declaration (see
|
|
1434
|
+
* reactComponentHoc). Named by the declarator (`Button`) and located at it so
|
|
1435
|
+
* the node range spans the body. When the wrapper has an inline render
|
|
1436
|
+
* function, its body is walked so the component's callees (hooks, helpers) are
|
|
1437
|
+
* captured under the component node — matching how a plain
|
|
1438
|
+
* `const Foo = () => …` arrow component already behaves.
|
|
1439
|
+
*/
|
|
1440
|
+
extractReactComponentNode(name, declarator, innerFn, extra) {
|
|
1441
|
+
const compNode = this.createNode('component', name, declarator, extra);
|
|
1442
|
+
if (!compNode || !innerFn || !this.extractor)
|
|
1443
|
+
return;
|
|
1444
|
+
this.nodeStack.push(compNode.id);
|
|
1445
|
+
const body = this.extractor.resolveBody?.(innerFn, this.extractor.bodyField)
|
|
1446
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(innerFn, this.extractor.bodyField);
|
|
1447
|
+
if (body)
|
|
1448
|
+
this.visitFunctionBody(body, compNode.id);
|
|
1449
|
+
this.nodeStack.pop();
|
|
1450
|
+
}
|
|
1029
1451
|
/**
|
|
1030
1452
|
* Extract a class
|
|
1031
1453
|
*/
|
|
@@ -1062,6 +1484,12 @@ class TreeSitterExtractor {
|
|
|
1062
1484
|
this.visitNode(child);
|
|
1063
1485
|
}
|
|
1064
1486
|
}
|
|
1487
|
+
// Synthesize compile-time-generated members (Lombok accessors, #912). Runs
|
|
1488
|
+
// after the body so the hook can dedup against hand-written members, and
|
|
1489
|
+
// while the class is still on the stack so containment/QNs attach.
|
|
1490
|
+
if (this.extractor.synthesizeMembers) {
|
|
1491
|
+
this.extractor.synthesizeMembers(node, this.makeExtractorContext());
|
|
1492
|
+
}
|
|
1065
1493
|
this.nodeStack.pop();
|
|
1066
1494
|
}
|
|
1067
1495
|
/**
|
|
@@ -1350,6 +1778,15 @@ class TreeSitterExtractor {
|
|
|
1350
1778
|
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1351
1779
|
const visibility = this.extractor.getVisibility?.(node);
|
|
1352
1780
|
const isStatic = this.extractor.isStatic?.(node) ?? false;
|
|
1781
|
+
// A class field that is actually a CONSTANT (Java `static final`, C# `const`
|
|
1782
|
+
// / `static readonly`) is extracted as `constant` kind, not `field`, so
|
|
1783
|
+
// value-reference edges treat it as a target (the gate accepts
|
|
1784
|
+
// constant/variable, not field). Scoped to languages whose `isConst`
|
|
1785
|
+
// predicate is field-shaped — other languages' fields stay `field`.
|
|
1786
|
+
const fieldKind = (this.language === 'java' || this.language === 'csharp') &&
|
|
1787
|
+
(this.extractor.isConst?.(node) ?? false)
|
|
1788
|
+
? 'constant'
|
|
1789
|
+
: 'field';
|
|
1353
1790
|
// Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children
|
|
1354
1791
|
// C# field_declaration: wraps in variable_declaration → variable_declarator(s)
|
|
1355
1792
|
let declarators = node.namedChildren.filter(c => c.type === 'variable_declarator');
|
|
@@ -1402,7 +1839,7 @@ class TreeSitterExtractor {
|
|
|
1402
1839
|
continue;
|
|
1403
1840
|
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1404
1841
|
const signature = typeText ? `${typeText} ${name}` : name;
|
|
1405
|
-
const fieldNode = this.createNode(
|
|
1842
|
+
const fieldNode = this.createNode(fieldKind, name, decl, {
|
|
1406
1843
|
docstring,
|
|
1407
1844
|
signature,
|
|
1408
1845
|
visibility,
|
|
@@ -1427,7 +1864,7 @@ class TreeSitterExtractor {
|
|
|
1427
1864
|
|| node.namedChildren.find(c => c.type === 'identifier');
|
|
1428
1865
|
if (nameNode) {
|
|
1429
1866
|
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
1430
|
-
this.createNode(
|
|
1867
|
+
this.createNode(fieldKind, name, node, {
|
|
1431
1868
|
docstring,
|
|
1432
1869
|
visibility,
|
|
1433
1870
|
isStatic,
|
|
@@ -1543,6 +1980,317 @@ class TreeSitterExtractor {
|
|
|
1543
1980
|
}
|
|
1544
1981
|
return null;
|
|
1545
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* RTK Query: from a `createApi({ ..., endpoints: build => ({...}) })` or a
|
|
1985
|
+
* `baseApi.injectEndpoints({ endpoints: build => ({...}) })` call initializer,
|
|
1986
|
+
* return the object literal of endpoint definitions (the object the `endpoints`
|
|
1987
|
+
* arrow returns). Returns null for any other call — the common case — so this
|
|
1988
|
+
* stays cheap and silent. Keyed on the RTK entry-point names (`createApi` /
|
|
1989
|
+
* `injectEndpoints`) like the framework extractors key on their library APIs.
|
|
1990
|
+
*/
|
|
1991
|
+
findRtkEndpointsObject(callNode) {
|
|
1992
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function');
|
|
1993
|
+
if (!callee)
|
|
1994
|
+
return null;
|
|
1995
|
+
const calleeName = callee.type === 'identifier'
|
|
1996
|
+
? (0, tree_sitter_helpers_1.getNodeText)(callee, this.source)
|
|
1997
|
+
: callee.type === 'member_expression'
|
|
1998
|
+
? (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source)
|
|
1999
|
+
: '';
|
|
2000
|
+
if (calleeName !== 'createApi' && calleeName !== 'injectEndpoints')
|
|
2001
|
+
return null;
|
|
2002
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
|
|
2003
|
+
if (!args)
|
|
2004
|
+
return null;
|
|
2005
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2006
|
+
const arg = args.namedChild(i);
|
|
2007
|
+
if (arg?.type !== 'object' && arg?.type !== 'object_expression')
|
|
2008
|
+
continue;
|
|
2009
|
+
for (let j = 0; j < arg.namedChildCount; j++) {
|
|
2010
|
+
const member = arg.namedChild(j);
|
|
2011
|
+
// Two equally-common spellings: `endpoints: build => ({...})` (pair with an
|
|
2012
|
+
// arrow value) and `endpoints(build) { return {...} }` (method shorthand).
|
|
2013
|
+
if (member?.type === 'pair') {
|
|
2014
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
2015
|
+
if (!key || (0, tree_sitter_helpers_1.getNodeText)(key, this.source) !== 'endpoints')
|
|
2016
|
+
continue;
|
|
2017
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2018
|
+
if (value && (value.type === 'arrow_function' || value.type === 'function_expression')) {
|
|
2019
|
+
return this.functionReturnedObject(value);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
else if (member?.type === 'method_definition') {
|
|
2023
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'name');
|
|
2024
|
+
if (!key || (0, tree_sitter_helpers_1.getNodeText)(key, this.source) !== 'endpoints')
|
|
2025
|
+
continue;
|
|
2026
|
+
return this.functionReturnedObject(member);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Extract each RTK Query endpoint (`getX: build.query({...})` / `build.mutation`)
|
|
2034
|
+
* as a function node named by the endpoint key, spanning its primary handler
|
|
2035
|
+
* (the `queryFn`/`query` arrow) so the fetch logic's calls attribute to the
|
|
2036
|
+
* endpoint. Without this an endpoint exists only as an object-literal property —
|
|
2037
|
+
* never a node — so the generated `useXQuery` hook can't be bridged to it.
|
|
2038
|
+
*/
|
|
2039
|
+
extractRtkEndpoints(obj) {
|
|
2040
|
+
for (let i = 0; i < obj.namedChildCount; i++) {
|
|
2041
|
+
const member = obj.namedChild(i);
|
|
2042
|
+
if (member?.type !== 'pair')
|
|
2043
|
+
continue;
|
|
2044
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
2045
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2046
|
+
if (!key || value?.type !== 'call_expression')
|
|
2047
|
+
continue;
|
|
2048
|
+
// The value must be a builder dispatch `<builder>.query|mutation(...)`.
|
|
2049
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(value, 'function');
|
|
2050
|
+
if (callee?.type !== 'member_expression')
|
|
2051
|
+
continue;
|
|
2052
|
+
const method = (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source);
|
|
2053
|
+
if (method !== 'query' && method !== 'mutation' && method !== 'infiniteQuery')
|
|
2054
|
+
continue;
|
|
2055
|
+
const handler = this.rtkEndpointHandler(value);
|
|
2056
|
+
if (handler) {
|
|
2057
|
+
this.extractFunction(handler, this.objectKeyName(key));
|
|
2058
|
+
}
|
|
2059
|
+
else {
|
|
2060
|
+
// Factory / config-only handler (`queryFn: makeQueryFn(url)`): no function
|
|
2061
|
+
// literal to name. Mint a bare endpoint node spanning the builder call so
|
|
2062
|
+
// the generated hook still bridges to it, and walk the call so its handler
|
|
2063
|
+
// factory (and any inline transform) is captured as an outgoing edge.
|
|
2064
|
+
const epNode = this.createNode('function', this.objectKeyName(key), value, {
|
|
2065
|
+
signature: (0, tree_sitter_helpers_1.getNodeText)(value, this.source).slice(0, 80),
|
|
2066
|
+
});
|
|
2067
|
+
if (epNode) {
|
|
2068
|
+
this.nodeStack.push(epNode.id);
|
|
2069
|
+
this.visitFunctionBody(value, epNode.id);
|
|
2070
|
+
this.nodeStack.pop();
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* The primary handler arrow of a `build.query({ queryFn|query: (…) => … })`
|
|
2077
|
+
* endpoint — prefers `queryFn`, then `query`, else the first function-valued
|
|
2078
|
+
* property. Returns null when the endpoint is config-only (no handler arrow).
|
|
2079
|
+
*/
|
|
2080
|
+
rtkEndpointHandler(callNode) {
|
|
2081
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
|
|
2082
|
+
if (!args)
|
|
2083
|
+
return null;
|
|
2084
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2085
|
+
const arg = args.namedChild(i);
|
|
2086
|
+
if (arg?.type !== 'object' && arg?.type !== 'object_expression')
|
|
2087
|
+
continue;
|
|
2088
|
+
let queryFn = null;
|
|
2089
|
+
let query = null;
|
|
2090
|
+
let firstFn = null;
|
|
2091
|
+
for (let j = 0; j < arg.namedChildCount; j++) {
|
|
2092
|
+
const member = arg.namedChild(j);
|
|
2093
|
+
// The handler may be `queryFn: () => …` / `query: () => …` (pair) or the
|
|
2094
|
+
// method-shorthand `query(arg) { … }` / `queryFn(arg) { … }`.
|
|
2095
|
+
let fn = null;
|
|
2096
|
+
let kn = '';
|
|
2097
|
+
if (member?.type === 'pair') {
|
|
2098
|
+
const v = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2099
|
+
if (v?.type === 'arrow_function' || v?.type === 'function_expression') {
|
|
2100
|
+
fn = v;
|
|
2101
|
+
const k = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
2102
|
+
kn = k ? (0, tree_sitter_helpers_1.getNodeText)(k, this.source) : '';
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
else if (member?.type === 'method_definition') {
|
|
2106
|
+
fn = member;
|
|
2107
|
+
const k = (0, tree_sitter_helpers_1.getChildByField)(member, 'name');
|
|
2108
|
+
kn = k ? (0, tree_sitter_helpers_1.getNodeText)(k, this.source) : '';
|
|
2109
|
+
}
|
|
2110
|
+
if (!fn)
|
|
2111
|
+
continue;
|
|
2112
|
+
if (kn === 'queryFn')
|
|
2113
|
+
queryFn = fn;
|
|
2114
|
+
else if (kn === 'query')
|
|
2115
|
+
query = fn;
|
|
2116
|
+
if (!firstFn)
|
|
2117
|
+
firstFn = fn;
|
|
2118
|
+
}
|
|
2119
|
+
if (queryFn)
|
|
2120
|
+
return queryFn;
|
|
2121
|
+
if (query)
|
|
2122
|
+
return query;
|
|
2123
|
+
if (firstFn)
|
|
2124
|
+
return firstFn;
|
|
2125
|
+
}
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* RTK Query generated-hook bindings. `export const { useGetXQuery,
|
|
2130
|
+
* useUpdateYMutation } = someApi` destructures the hooks RTK generates per
|
|
2131
|
+
* endpoint off a createApi result. They are real exported symbols that
|
|
2132
|
+
* components import, but destructured bindings aren't otherwise extracted —
|
|
2133
|
+
* mint a function node per binding matching the RTK hook convention so the hook
|
|
2134
|
+
* resolves and the synthesizer can bridge it to its endpoint. Gated tight by the
|
|
2135
|
+
* caller (object-pattern off a bare identifier) + the name convention here, so
|
|
2136
|
+
* ordinary destructures stay unextracted.
|
|
2137
|
+
*/
|
|
2138
|
+
extractRtkHookBindings(pattern, isExported) {
|
|
2139
|
+
for (let i = 0; i < pattern.namedChildCount; i++) {
|
|
2140
|
+
const binding = pattern.namedChild(i);
|
|
2141
|
+
if (binding?.type !== 'shorthand_property_identifier_pattern')
|
|
2142
|
+
continue;
|
|
2143
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(binding, this.source);
|
|
2144
|
+
if (!RTK_HOOK_NAME_RE.test(name))
|
|
2145
|
+
continue;
|
|
2146
|
+
this.createNode('function', name, binding, {
|
|
2147
|
+
isExported,
|
|
2148
|
+
signature: '= RTK Query generated hook',
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
/** Cheap per-file heuristic: the file carries ≥2 distinct Vue-store signals
|
|
2153
|
+
* (defineStore/createStore/Vuex, or the actions/mutations/getters/namespaced
|
|
2154
|
+
* vocabulary). Gates the non-exported `const actions = {…}` Vuex-module form so
|
|
2155
|
+
* a stray `const actions` in unrelated code is never mistaken for a store. */
|
|
2156
|
+
looksLikeVueStoreFile() {
|
|
2157
|
+
if (this.vueStoreFile !== null)
|
|
2158
|
+
return this.vueStoreFile;
|
|
2159
|
+
const seen = new Set();
|
|
2160
|
+
VUE_STORE_FILE_SIGNAL.lastIndex = 0;
|
|
2161
|
+
let m;
|
|
2162
|
+
while ((m = VUE_STORE_FILE_SIGNAL.exec(this.source))) {
|
|
2163
|
+
seen.add(m[0]);
|
|
2164
|
+
if (seen.size >= 2)
|
|
2165
|
+
break;
|
|
2166
|
+
}
|
|
2167
|
+
this.vueStoreFile = seen.size >= 2;
|
|
2168
|
+
return this.vueStoreFile;
|
|
2169
|
+
}
|
|
2170
|
+
/** True if an object literal has ≥1 inline function member (`key: () => …` /
|
|
2171
|
+
* `method(){}`) — distinguishes an inline action map (zustand/SvelteKit form
|
|
2172
|
+
* actions) from a Pinia SETUP store's all-shorthand `return { foo, bar }`
|
|
2173
|
+
* (whose functions are body-local consts, walked normally instead). */
|
|
2174
|
+
objectHasInlineFunctions(obj) {
|
|
2175
|
+
for (let i = 0; i < obj.namedChildCount; i++) {
|
|
2176
|
+
const member = obj.namedChild(i);
|
|
2177
|
+
if (member?.type === 'method_definition')
|
|
2178
|
+
return true;
|
|
2179
|
+
if (member?.type === 'pair') {
|
|
2180
|
+
const v = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2181
|
+
if (v?.type === 'arrow_function' || v?.type === 'function_expression')
|
|
2182
|
+
return true;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
/** Vue store action/mutation/getter collections defined INLINE in a store call:
|
|
2188
|
+
* `defineStore({ actions: {…}, getters: {…} })` (Pinia options form),
|
|
2189
|
+
* `defineStore('id', { actions: {…} })`, `createStore({ mutations: {…} })`,
|
|
2190
|
+
* `new Vuex.Store({ actions: {…} })`. Returns the object literals under those
|
|
2191
|
+
* keys so their methods become nodes. Gated on the store-factory callee. */
|
|
2192
|
+
findVueStoreCollectionObjects(callNode) {
|
|
2193
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function') ?? (0, tree_sitter_helpers_1.getChildByField)(callNode, 'constructor');
|
|
2194
|
+
if (!callee)
|
|
2195
|
+
return [];
|
|
2196
|
+
const calleeName = callee.type === 'identifier'
|
|
2197
|
+
? (0, tree_sitter_helpers_1.getNodeText)(callee, this.source)
|
|
2198
|
+
: callee.type === 'member_expression'
|
|
2199
|
+
? (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source)
|
|
2200
|
+
: '';
|
|
2201
|
+
if (!VUE_STORE_FACTORY_CALLEES.has(calleeName) && calleeName !== 'Store')
|
|
2202
|
+
return [];
|
|
2203
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
|
|
2204
|
+
if (!args)
|
|
2205
|
+
return [];
|
|
2206
|
+
const objects = [];
|
|
2207
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2208
|
+
const arg = args.namedChild(i);
|
|
2209
|
+
if (arg?.type !== 'object' && arg?.type !== 'object_expression')
|
|
2210
|
+
continue;
|
|
2211
|
+
for (let j = 0; j < arg.namedChildCount; j++) {
|
|
2212
|
+
const member = arg.namedChild(j);
|
|
2213
|
+
if (member?.type !== 'pair')
|
|
2214
|
+
continue;
|
|
2215
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
2216
|
+
if (!key || !VUE_STORE_COLLECTION_NAMES.has((0, tree_sitter_helpers_1.getNodeText)(key, this.source)))
|
|
2217
|
+
continue;
|
|
2218
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2219
|
+
if (value && (value.type === 'object' || value.type === 'object_expression')) {
|
|
2220
|
+
objects.push(value);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
return objects;
|
|
2225
|
+
}
|
|
2226
|
+
/** Extract the methods of a store-config object's `actions`/`mutations`/`getters`
|
|
2227
|
+
* properties. Used for the canonical Vuex MODULE shape `export default {
|
|
2228
|
+
* namespaced, actions: {…}, mutations: {…} }` — object-literal methods aren't
|
|
2229
|
+
* otherwise extracted, so the actions/mutations would never be nodes. */
|
|
2230
|
+
extractStoreCollectionMethods(configObj) {
|
|
2231
|
+
for (let j = 0; j < configObj.namedChildCount; j++) {
|
|
2232
|
+
const member = configObj.namedChild(j);
|
|
2233
|
+
if (member?.type !== 'pair')
|
|
2234
|
+
continue;
|
|
2235
|
+
const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
|
|
2236
|
+
if (!key || !VUE_STORE_COLLECTION_NAMES.has((0, tree_sitter_helpers_1.getNodeText)(key, this.source)))
|
|
2237
|
+
continue;
|
|
2238
|
+
const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
|
|
2239
|
+
if (value && (value.type === 'object' || value.type === 'object_expression')) {
|
|
2240
|
+
this.extractObjectLiteralFunctions(value);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
/** The SETUP function of a Pinia setup store (`defineStore('id', () => {…})`)
|
|
2245
|
+
* — an arrow/function arg with a block body. Returns null for the options form
|
|
2246
|
+
* (`defineStore({…})`) and for any non-defineStore call. The setup body's local
|
|
2247
|
+
* function consts are the store's actions; the generic body walk doesn't reach
|
|
2248
|
+
* them (nested functions are separate scopes), so they're extracted explicitly. */
|
|
2249
|
+
findPiniaSetupFn(callNode) {
|
|
2250
|
+
const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function');
|
|
2251
|
+
if (!callee || callee.type !== 'identifier' || (0, tree_sitter_helpers_1.getNodeText)(callee, this.source) !== 'defineStore')
|
|
2252
|
+
return null;
|
|
2253
|
+
const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
|
|
2254
|
+
if (!args)
|
|
2255
|
+
return null;
|
|
2256
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
2257
|
+
const arg = args.namedChild(i);
|
|
2258
|
+
if (arg?.type !== 'arrow_function' && arg?.type !== 'function_expression')
|
|
2259
|
+
continue;
|
|
2260
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(arg, 'body');
|
|
2261
|
+
if (body?.type === 'statement_block')
|
|
2262
|
+
return arg; // block body ⇒ setup form
|
|
2263
|
+
}
|
|
2264
|
+
return null;
|
|
2265
|
+
}
|
|
2266
|
+
/** Extract a Pinia setup store's actions: the body-local `const foo = () => …`
|
|
2267
|
+
* / `function foo(){}` declarations, named by the binding. (State refs and other
|
|
2268
|
+
* consts are left to the normal value-extraction; only the functions matter as
|
|
2269
|
+
* the store's callable surface.) */
|
|
2270
|
+
extractPiniaSetupBody(setupFn) {
|
|
2271
|
+
const body = (0, tree_sitter_helpers_1.getChildByField)(setupFn, 'body');
|
|
2272
|
+
if (!body || body.type !== 'statement_block')
|
|
2273
|
+
return;
|
|
2274
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
2275
|
+
const stmt = body.namedChild(i);
|
|
2276
|
+
if (!stmt)
|
|
2277
|
+
continue;
|
|
2278
|
+
if (stmt.type === 'function_declaration') {
|
|
2279
|
+
this.extractFunction(stmt);
|
|
2280
|
+
}
|
|
2281
|
+
else if (this.extractor.variableTypes.includes(stmt.type)) {
|
|
2282
|
+
for (let j = 0; j < stmt.namedChildCount; j++) {
|
|
2283
|
+
const decl = stmt.namedChild(j);
|
|
2284
|
+
if (decl?.type !== 'variable_declarator')
|
|
2285
|
+
continue;
|
|
2286
|
+
const v = (0, tree_sitter_helpers_1.getChildByField)(decl, 'value');
|
|
2287
|
+
if (v?.type === 'arrow_function' || v?.type === 'function_expression') {
|
|
2288
|
+
this.extractFunction(v); // name resolved from the parent declarator
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
1546
2294
|
/**
|
|
1547
2295
|
* Extract a variable declaration (const, let, var, etc.)
|
|
1548
2296
|
*
|
|
@@ -1572,8 +2320,15 @@ class TreeSitterExtractor {
|
|
|
1572
2320
|
const valueNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'value');
|
|
1573
2321
|
if (nameNode) {
|
|
1574
2322
|
// Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
|
|
1575
|
-
// These produce ugly multi-line names like "{ class: className }"
|
|
2323
|
+
// These produce ugly multi-line names like "{ class: className }".
|
|
2324
|
+
// EXCEPT `export const { useGetXQuery } = someApi` — the RTK Query
|
|
2325
|
+
// generated hooks: real exported symbols destructured off a createApi
|
|
2326
|
+
// result. Mint a node per binding matching the hook convention (gated
|
|
2327
|
+
// on a bare-identifier RHS so ordinary destructures stay skipped).
|
|
1576
2328
|
if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
|
|
2329
|
+
if (nameNode.type === 'object_pattern' && valueNode?.type === 'identifier') {
|
|
2330
|
+
this.extractRtkHookBindings(nameNode, isExported);
|
|
2331
|
+
}
|
|
1577
2332
|
continue;
|
|
1578
2333
|
}
|
|
1579
2334
|
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
@@ -1585,6 +2340,25 @@ class TreeSitterExtractor {
|
|
|
1585
2340
|
// Capture first 100 chars of initializer for context (stored in signature for searchability)
|
|
1586
2341
|
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
1587
2342
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
2343
|
+
// React HOC-wrapped components (`forwardRef`/`memo`/`styled`) — see
|
|
2344
|
+
// reactComponentHoc. The initializer is a call / tagged-template (not
|
|
2345
|
+
// a bare arrow), so without this the const is a plain `constant`,
|
|
2346
|
+
// which the JSX-render synthesizer and component resolution both skip
|
|
2347
|
+
// → `<Button/>` usages get no edge and callers/impact return empty
|
|
2348
|
+
// (the whole shadcn/ui design-system pattern, #841). PascalCase-gated
|
|
2349
|
+
// to the component naming convention so a memoization util
|
|
2350
|
+
// (`const cache = memo(fn)`) stays a constant.
|
|
2351
|
+
if (valueNode && /^[A-Z]/.test(name)) {
|
|
2352
|
+
const hoc = this.reactComponentHoc(valueNode);
|
|
2353
|
+
if (hoc) {
|
|
2354
|
+
this.extractReactComponentNode(name, child, hoc.inner, {
|
|
2355
|
+
docstring,
|
|
2356
|
+
signature: initSignature,
|
|
2357
|
+
isExported,
|
|
2358
|
+
});
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
1588
2362
|
const varNode = this.createNode(kind, name, child, {
|
|
1589
2363
|
docstring,
|
|
1590
2364
|
signature: initSignature,
|
|
@@ -1615,21 +2389,65 @@ class TreeSitterExtractor {
|
|
|
1615
2389
|
: valueNode?.type === 'call_expression'
|
|
1616
2390
|
? this.findInitializerReturnedObject(valueNode)
|
|
1617
2391
|
: null;
|
|
1618
|
-
|
|
2392
|
+
// Only treat as an inline object-of-functions when the object actually
|
|
2393
|
+
// HAS inline functions. A Pinia SETUP store `defineStore('id', () => {
|
|
2394
|
+
// const foo = …; return { foo } })` returns an ALL-SHORTHAND object
|
|
2395
|
+
// whose functions are body-local consts — it must fall through to a
|
|
2396
|
+
// normal body walk (extracting those consts), not be skipped here.
|
|
2397
|
+
const hasInlineFns = !!objectOfFns && this.objectHasInlineFunctions(objectOfFns);
|
|
2398
|
+
const extractObjectMethods = isExported && !!objectOfFns && hasInlineFns;
|
|
2399
|
+
// RTK Query: `createApi`/`injectEndpoints` define endpoints as
|
|
2400
|
+
// object-literal properties whose values are `build.query/mutation(...)`
|
|
2401
|
+
// calls — nested under an `endpoints` arrow, so neither the
|
|
2402
|
+
// object-of-functions path above nor the normal walk extracts them.
|
|
2403
|
+
// Extract each endpoint as a function node (named by its key), and skip
|
|
2404
|
+
// walking the createApi call body (its handler arrows are extracted
|
|
2405
|
+
// individually below, exactly like the store-factory case).
|
|
2406
|
+
const rtkEndpoints = valueNode?.type === 'call_expression' ? this.findRtkEndpointsObject(valueNode) : null;
|
|
2407
|
+
// Pinia SETUP store: `defineStore('id', () => { const foo = …; return {…} })`.
|
|
2408
|
+
// Its actions are body-local consts the generic walk can't reach.
|
|
2409
|
+
const piniaSetup = valueNode?.type === 'call_expression' ? this.findPiniaSetupFn(valueNode) : null;
|
|
2410
|
+
// Vue store collections — make `actions`/`mutations`/`getters` findable
|
|
2411
|
+
// function nodes (the foundation under any later dispatch-bridge synth).
|
|
2412
|
+
// Two positions: INLINE in a store call (`defineStore({ actions: {…} })`
|
|
2413
|
+
// / `createStore` / `new Vuex.Store`), and the non-exported Vuex-MODULE
|
|
2414
|
+
// form (`const actions = {…}` at a store file's top level, wired via a
|
|
2415
|
+
// `export default { actions }`). The Pinia SETUP form is handled by the
|
|
2416
|
+
// body walk above (its actions are local consts).
|
|
2417
|
+
const storeCollections = [];
|
|
2418
|
+
if (valueNode?.type === 'call_expression' || valueNode?.type === 'new_expression') {
|
|
2419
|
+
storeCollections.push(...this.findVueStoreCollectionObjects(valueNode));
|
|
2420
|
+
}
|
|
2421
|
+
if (objectOfFns && !extractObjectMethods &&
|
|
2422
|
+
VUE_STORE_COLLECTION_NAMES.has(name) && this.looksLikeVueStoreFile()) {
|
|
2423
|
+
storeCollections.push(objectOfFns);
|
|
2424
|
+
}
|
|
1619
2425
|
// Visit the initializer body for calls — EXCEPT object literals (their
|
|
1620
2426
|
// function-valued properties are extracted below) and the store-factory
|
|
1621
|
-
// call whose
|
|
1622
|
-
// the whole call would re-visit those
|
|
1623
|
-
// their inner calls to the file
|
|
2427
|
+
// / createApi / store-collection call whose nested objects we extract
|
|
2428
|
+
// method-by-method below (walking the whole call would re-visit those
|
|
2429
|
+
// method arrows and mis-attribute their inner calls to the file scope).
|
|
1624
2430
|
if (valueNode &&
|
|
1625
2431
|
valueNode.type !== 'object' &&
|
|
1626
2432
|
valueNode.type !== 'object_expression' &&
|
|
1627
|
-
!(extractObjectMethods && valueNode.type === 'call_expression')
|
|
2433
|
+
!(extractObjectMethods && valueNode.type === 'call_expression') &&
|
|
2434
|
+
!rtkEndpoints &&
|
|
2435
|
+
!piniaSetup &&
|
|
2436
|
+
storeCollections.length === 0) {
|
|
1628
2437
|
this.visitFunctionBody(valueNode, '');
|
|
1629
2438
|
}
|
|
1630
2439
|
if (extractObjectMethods && objectOfFns) {
|
|
1631
2440
|
this.extractObjectLiteralFunctions(objectOfFns);
|
|
1632
2441
|
}
|
|
2442
|
+
if (rtkEndpoints) {
|
|
2443
|
+
this.extractRtkEndpoints(rtkEndpoints);
|
|
2444
|
+
}
|
|
2445
|
+
if (piniaSetup) {
|
|
2446
|
+
this.extractPiniaSetupBody(piniaSetup);
|
|
2447
|
+
}
|
|
2448
|
+
for (const coll of storeCollections) {
|
|
2449
|
+
this.extractObjectLiteralFunctions(coll);
|
|
2450
|
+
}
|
|
1633
2451
|
}
|
|
1634
2452
|
}
|
|
1635
2453
|
}
|
|
@@ -1638,7 +2456,9 @@ class TreeSitterExtractor {
|
|
|
1638
2456
|
// Python/Ruby assignment: left = right
|
|
1639
2457
|
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') || node.namedChild(0);
|
|
1640
2458
|
const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right') || node.namedChild(1);
|
|
1641
|
-
|
|
2459
|
+
// Ruby constant assignments (`MAX = 3`) have a `constant`-typed LHS, not
|
|
2460
|
+
// `identifier`; without this they were never extracted as symbols at all.
|
|
2461
|
+
if (left && (left.type === 'identifier' || left.type === 'constant')) {
|
|
1642
2462
|
const name = (0, tree_sitter_helpers_1.getNodeText)(left, this.source);
|
|
1643
2463
|
// Skip if name starts with lowercase and looks like a function call result
|
|
1644
2464
|
// Python constants are usually UPPER_CASE
|
|
@@ -1725,6 +2545,65 @@ class TreeSitterExtractor {
|
|
|
1725
2545
|
this.createNode(kind, name, nameNode, { docstring, signature: initSignature, isExported });
|
|
1726
2546
|
});
|
|
1727
2547
|
}
|
|
2548
|
+
else if (this.language === 'c') {
|
|
2549
|
+
// C: a `declaration` node's name nests inside the `declarator` field —
|
|
2550
|
+
// `init_declarator` (with value) or bare/pointer/array declarators (no
|
|
2551
|
+
// value); a `function_declarator` is a prototype, not a variable. The
|
|
2552
|
+
// generic fallback below only finds a *direct* identifier child, which C
|
|
2553
|
+
// never has, so file-scope consts/globals went unextracted entirely (and
|
|
2554
|
+
// so had no impact-radius edges). Only file-scope declarations are tracked
|
|
2555
|
+
// — locals inside a function body are skipped (a `static const` table read
|
|
2556
|
+
// by same-file functions is the value the impact graph wants, not every
|
|
2557
|
+
// block-local). C allows several declarators per declaration
|
|
2558
|
+
// (`int a = 1, b = 2;`), so iterate them.
|
|
2559
|
+
if (!hasFunctionAncestor(node)) {
|
|
2560
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2561
|
+
const child = node.namedChild(i);
|
|
2562
|
+
if (!child)
|
|
2563
|
+
continue;
|
|
2564
|
+
// Accept only `init_declarator` (has a value) and pointer/array
|
|
2565
|
+
// declarators. A *bare* `identifier` declarator is deliberately
|
|
2566
|
+
// skipped: an unknown leading macro (`CURL_EXTERN`, `XXH_PUBLIC_API`)
|
|
2567
|
+
// makes tree-sitter-c misparse a prototype `MACRO RetType fn(args);`
|
|
2568
|
+
// as a declaration whose "variable" is the bare return-type
|
|
2569
|
+
// identifier, splitting `fn(args)` off as a bogus expression — minting
|
|
2570
|
+
// a spurious type-named global for every macro-prefixed prototype in a
|
|
2571
|
+
// header. Those misparses are always bare identifiers; real
|
|
2572
|
+
// consts/tables always carry an initializer. The only legit loss is
|
|
2573
|
+
// uninitialized scalar globals (`static int g;`).
|
|
2574
|
+
if (child.type !== 'init_declarator' &&
|
|
2575
|
+
child.type !== 'pointer_declarator' &&
|
|
2576
|
+
child.type !== 'array_declarator') {
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
const nameNode = cDeclaratorIdentifier(child);
|
|
2580
|
+
if (!nameNode)
|
|
2581
|
+
continue;
|
|
2582
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
2583
|
+
if (!name)
|
|
2584
|
+
continue;
|
|
2585
|
+
const valueNode = child.type === 'init_declarator' ? (0, tree_sitter_helpers_1.getChildByField)(child, 'value') : null;
|
|
2586
|
+
const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
|
|
2587
|
+
const initSignature = initValue
|
|
2588
|
+
? `= ${initValue}${initValue.length >= 100 ? '...' : ''}`
|
|
2589
|
+
: undefined;
|
|
2590
|
+
this.createNode(kind, name, child, { docstring, signature: initSignature, isExported });
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
else if (this.language === 'swift') {
|
|
2595
|
+
// Swift top-level property (`let X = …` / `var Y = …`). The name nests in
|
|
2596
|
+
// a `pattern`, which the generic fallback can't read, so top-level Swift
|
|
2597
|
+
// constants/globals went unextracted. A top-level `let`→`constant`,
|
|
2598
|
+
// `var`→`variable`; a computed property (getter, no value) is skipped.
|
|
2599
|
+
const { nameNode, isLet, isComputed } = swiftPropertyInfo(node, this.source);
|
|
2600
|
+
if (nameNode && !isComputed) {
|
|
2601
|
+
this.createNode(isLet ? 'constant' : 'variable', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node, {
|
|
2602
|
+
docstring,
|
|
2603
|
+
isExported,
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
1728
2607
|
else {
|
|
1729
2608
|
// Generic fallback for other languages
|
|
1730
2609
|
// Try to find identifier children
|