@colbymchenry/codegraph-win32-x64 1.1.6 → 1.3.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.d.ts +1 -1
- package/lib/dist/bin/codegraph.d.ts.map +1 -1
- package/lib/dist/bin/codegraph.js +232 -61
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts.map +1 -1
- package/lib/dist/bin/command-supervision.js +14 -1
- package/lib/dist/bin/command-supervision.js.map +1 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +19 -1
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts +51 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +141 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +19 -0
- package/lib/dist/directory.d.ts +9 -5
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +210 -19
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
- package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/cfml-extractor.js +494 -0
- package/lib/dist/extraction/cfml-extractor.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +9 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +115 -3
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +46 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +254 -3
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/arkts.d.ts +3 -0
- package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/lib/dist/extraction/languages/arkts.js +127 -0
- package/lib/dist/extraction/languages/arkts.js.map +1 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts +59 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +499 -3
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
- package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfquery.js +28 -0
- package/lib/dist/extraction/languages/cfquery.js.map +1 -0
- package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
- package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfscript.js +73 -0
- package/lib/dist/extraction/languages/cfscript.js.map +1 -0
- package/lib/dist/extraction/languages/cobol.d.ts +33 -0
- package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cobol.js +499 -0
- package/lib/dist/extraction/languages/cobol.js.map +1 -0
- package/lib/dist/extraction/languages/erlang.d.ts +3 -0
- package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/lib/dist/extraction/languages/erlang.js +350 -0
- package/lib/dist/extraction/languages/erlang.js.map +1 -0
- package/lib/dist/extraction/languages/index.d.ts.map +1 -1
- package/lib/dist/extraction/languages/index.js +18 -0
- package/lib/dist/extraction/languages/index.js.map +1 -1
- package/lib/dist/extraction/languages/nix.d.ts +3 -0
- package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
- package/lib/dist/extraction/languages/nix.js +294 -0
- package/lib/dist/extraction/languages/nix.js.map +1 -0
- package/lib/dist/extraction/languages/solidity.d.ts +3 -0
- package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/lib/dist/extraction/languages/solidity.js +293 -0
- package/lib/dist/extraction/languages/solidity.js.map +1 -0
- package/lib/dist/extraction/languages/terraform.d.ts +3 -0
- package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/lib/dist/extraction/languages/terraform.js +641 -0
- package/lib/dist/extraction/languages/terraform.js.map +1 -0
- package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
- package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/lib/dist/extraction/languages/vbnet.js +141 -0
- package/lib/dist/extraction/languages/vbnet.js.map +1 -0
- package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/mybatis-extractor.js +140 -38
- package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
- package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +38 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +845 -16
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/lib/dist/graph/traversal.d.ts.map +1 -1
- package/lib/dist/graph/traversal.js +76 -17
- package/lib/dist/graph/traversal.js.map +1 -1
- package/lib/dist/index.d.ts +63 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +310 -6
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +3 -1
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +25 -3
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +39 -7
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.js +2 -1
- package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/lib/dist/mcp/early-ppid.d.ts +26 -0
- package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
- package/lib/dist/mcp/early-ppid.js +29 -0
- package/lib/dist/mcp/early-ppid.js.map +1 -0
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +21 -4
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +21 -3
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/startup-handshake.d.ts +44 -0
- package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/lib/dist/mcp/startup-handshake.js +73 -0
- package/lib/dist/mcp/startup-handshake.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +22 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +74 -9
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +38 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +101 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +743 -11
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
- package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/lib/dist/resolution/cooperative-yield.js +42 -0
- package/lib/dist/resolution/cooperative-yield.js.map +1 -0
- package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
- package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/cics.js +90 -0
- package/lib/dist/resolution/frameworks/cics.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +6 -0
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/java.js +14 -6
- package/lib/dist/resolution/frameworks/java.js.map +1 -1
- package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/terraform.js +277 -0
- package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
- package/lib/dist/resolution/import-resolver.d.ts +7 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +129 -4
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +44 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +335 -30
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +22 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +484 -39
- 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 +49 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +20 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.d.ts +10 -0
- package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.js +142 -4
- package/lib/dist/resolution/workspace-packages.js.map +1 -1
- package/lib/dist/search/identifier-segments.d.ts +60 -0
- package/lib/dist/search/identifier-segments.d.ts.map +1 -0
- package/lib/dist/search/identifier-segments.js +176 -0
- package/lib/dist/search/identifier-segments.js.map +1 -0
- package/lib/dist/sync/git-hooks.d.ts.map +1 -1
- package/lib/dist/sync/git-hooks.js +2 -0
- package/lib/dist/sync/git-hooks.js.map +1 -1
- package/lib/dist/sync/watcher.d.ts +10 -5
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +51 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +5 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +19 -1
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +10 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/dist/upgrade/index.js +1 -1
- package/lib/dist/upgrade/index.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/lib/dist/reasoning/config.d.ts +0 -45
- package/lib/dist/reasoning/config.d.ts.map +0 -1
- package/lib/dist/reasoning/config.js +0 -171
- package/lib/dist/reasoning/config.js.map +0 -1
- package/lib/dist/reasoning/credentials.d.ts +0 -5
- package/lib/dist/reasoning/credentials.d.ts.map +0 -1
- package/lib/dist/reasoning/credentials.js +0 -83
- package/lib/dist/reasoning/credentials.js.map +0 -1
- package/lib/dist/reasoning/login.d.ts +0 -21
- package/lib/dist/reasoning/login.d.ts.map +0 -1
- package/lib/dist/reasoning/login.js +0 -85
- package/lib/dist/reasoning/login.js.map +0 -1
- package/lib/dist/reasoning/reasoner.d.ts +0 -43
- package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
- package/lib/dist/reasoning/reasoner.js +0 -308
- package/lib/dist/reasoning/reasoner.js.map +0 -1
|
@@ -54,6 +54,7 @@ const astro_extractor_1 = require("./astro-extractor");
|
|
|
54
54
|
const dfm_extractor_1 = require("./dfm-extractor");
|
|
55
55
|
const vue_extractor_1 = require("./vue-extractor");
|
|
56
56
|
const mybatis_extractor_1 = require("./mybatis-extractor");
|
|
57
|
+
const cfml_extractor_1 = require("./cfml-extractor");
|
|
57
58
|
const frameworks_1 = require("../resolution/frameworks");
|
|
58
59
|
// Re-export for backward compatibility
|
|
59
60
|
var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
|
|
@@ -77,24 +78,68 @@ const VUE_STORE_FACTORY_CALLEES = new Set(['defineStore', 'createStore']);
|
|
|
77
78
|
/** Distinct signals that a file is a Vuex/Pinia store (≥2 ⇒ treat a bare
|
|
78
79
|
* `const actions = {…}` as a store collection — see looksLikeVueStoreFile). */
|
|
79
80
|
const VUE_STORE_FILE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
|
|
81
|
+
/**
|
|
82
|
+
* Erlang calls that take their real callee as (Module, Function, Args)
|
|
83
|
+
* ARGUMENTS — the spawn/apply family. Keys are the callee as the call site
|
|
84
|
+
* spells it: bare for auto-imported BIFs, `module:function` for remote calls.
|
|
85
|
+
* Used by the erlang branch of extractCall to lift a static MFA pair into a
|
|
86
|
+
* call edge (the spawned/applied function is otherwise invisible to the graph).
|
|
87
|
+
*/
|
|
88
|
+
/** Compiler-predefined Erlang macros — no `-define` exists to link a use to. */
|
|
89
|
+
const ERLANG_PREDEFINED_MACROS = new Set([
|
|
90
|
+
'MODULE', 'MODULE_STRING', 'FILE', 'LINE', 'MACHINE',
|
|
91
|
+
'FUNCTION_NAME', 'FUNCTION_ARITY', 'OTP_RELEASE',
|
|
92
|
+
'FEATURE_AVAILABLE', 'FEATURE_ENABLED',
|
|
93
|
+
]);
|
|
94
|
+
const ERLANG_MFA_CALLS = new Set([
|
|
95
|
+
'spawn', 'spawn_link', 'spawn_monitor', 'spawn_opt', 'apply',
|
|
96
|
+
'erlang:spawn', 'erlang:spawn_link', 'erlang:spawn_monitor', 'erlang:spawn_opt', 'erlang:apply',
|
|
97
|
+
'proc_lib:spawn', 'proc_lib:spawn_link', 'proc_lib:spawn_opt', 'proc_lib:start', 'proc_lib:start_link',
|
|
98
|
+
'timer:apply_after', 'timer:apply_interval',
|
|
99
|
+
'rpc:call', 'rpc:cast', 'rpc:async_call',
|
|
100
|
+
'erpc:call', 'erpc:cast',
|
|
101
|
+
]);
|
|
80
102
|
/**
|
|
81
103
|
* Extract the name from a node based on language
|
|
82
104
|
*/
|
|
83
105
|
function extractName(node, source, extractor) {
|
|
106
|
+
const name = extractNameRaw(node, source, extractor);
|
|
107
|
+
// Universal fallback: recover a real identifier from a name still mangled by a
|
|
108
|
+
// macro the pre-parse didn't blank (C/C++ only — see recoverMangledName). A
|
|
109
|
+
// no-op on well-formed names, so a clean name is never altered.
|
|
110
|
+
return extractor.recoverMangledName ? extractor.recoverMangledName(name) : name;
|
|
111
|
+
}
|
|
112
|
+
function extractNameRaw(node, source, extractor) {
|
|
84
113
|
const hookName = extractor.resolveName?.(node, source);
|
|
85
114
|
if (hookName)
|
|
86
115
|
return hookName;
|
|
87
116
|
// Try field name first
|
|
88
117
|
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, extractor.nameField);
|
|
89
118
|
if (nameNode) {
|
|
90
|
-
// Unwrap pointer_declarator
|
|
119
|
+
// Unwrap pointer_declarator / reference_declarator for C/C++ pointer and
|
|
120
|
+
// reference return types (`int* f()`, `int& f()`, `int&& f()`). Without
|
|
121
|
+
// unwrapping the reference wrapper an inline reference-returning method is
|
|
122
|
+
// named "& f() const" instead of "f" — common in Unreal Engine gameplay
|
|
123
|
+
// headers (`const FGameplayTagContainer& GetActiveTags() const`). Out-of-line
|
|
124
|
+
// defs (`T& C::f()`) already resolve via the qualified-name hook. A
|
|
125
|
+
// pointer_declarator exposes its inner through a `declarator` field; a
|
|
126
|
+
// reference_declarator has none, so it's reached via namedChild(0).
|
|
91
127
|
let resolved = nameNode;
|
|
92
|
-
while (resolved.type === 'pointer_declarator') {
|
|
128
|
+
while (resolved.type === 'pointer_declarator' || resolved.type === 'reference_declarator') {
|
|
93
129
|
const inner = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
94
130
|
if (!inner)
|
|
95
131
|
break;
|
|
96
132
|
resolved = inner;
|
|
97
133
|
}
|
|
134
|
+
// C++ user-defined conversion operator: the declarator is an `operator_cast`
|
|
135
|
+
// whose first child is the target type and second is the `() const` tail. Name
|
|
136
|
+
// it `operator <type>` (the conventional spelling) rather than the whole
|
|
137
|
+
// `operator EALSMovementState() const` declarator, so it matches symbolic
|
|
138
|
+
// overloads (`operator+`) and is findable by the type name.
|
|
139
|
+
if (resolved.type === 'operator_cast') {
|
|
140
|
+
const typeNode = resolved.namedChild(0);
|
|
141
|
+
return typeNode ? `operator ${(0, tree_sitter_helpers_1.getNodeText)(typeNode, source).trim()}` : (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
|
|
142
|
+
}
|
|
98
143
|
// Handle complex declarators (C/C++)
|
|
99
144
|
if (resolved.type === 'function_declarator' || resolved.type === 'declarator') {
|
|
100
145
|
const innerName = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
|
|
@@ -324,7 +369,7 @@ class TreeSitterExtractor {
|
|
|
324
369
|
// Value-reference edges (default ON; set CODEGRAPH_VALUE_REFS=0 to disable; see flushValueRefs).
|
|
325
370
|
// Same-file reads of file-scope const/var symbols → `references` edges so impact analysis catches
|
|
326
371
|
// value consumers ("change this constant/table, affect its readers").
|
|
327
|
-
static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
|
|
372
|
+
static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'arkts', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
|
|
328
373
|
static MAX_VALUE_REF_NODES = 20_000;
|
|
329
374
|
valueRefsEnabled = process.env.CODEGRAPH_VALUE_REFS !== '0';
|
|
330
375
|
fileScopeValues = new Map();
|
|
@@ -333,6 +378,20 @@ class TreeSitterExtractor {
|
|
|
333
378
|
errors = [];
|
|
334
379
|
extractor = null;
|
|
335
380
|
nodeStack = []; // Stack of parent node IDs
|
|
381
|
+
// C/C++ enclosing `namespace ns { … }` names, prepended to every contained
|
|
382
|
+
// symbol's qualifiedName (see visitNode). Prefix-only by design — no
|
|
383
|
+
// namespace NODE is created: `namespace cutlass {` opens in thousands of
|
|
384
|
+
// files, and a node per block would flood search with same-named symbols
|
|
385
|
+
// (the #1093 crowd-out failure mode). Always empty outside C/C++.
|
|
386
|
+
namespacePrefix = [];
|
|
387
|
+
// C++ local function-pointer bindings, per enclosing symbol:
|
|
388
|
+
// `auto kernel = &flash_fwd_kernel<…>;` recorded as callerId → kernel →
|
|
389
|
+
// {flash_fwd_kernel}, so a later `kernel<<<grid, block>>>(params)` (or plain
|
|
390
|
+
// `kernel(args)`) in the same body emits calls refs to the real target(s)
|
|
391
|
+
// instead of an unresolvable local name. Branch reassignments accumulate —
|
|
392
|
+
// each assigned target is a genuine possible callee. Same-body locality is
|
|
393
|
+
// the precision guard (the #932 table-dispatch philosophy scoped to locals).
|
|
394
|
+
cppLocalFnPtrs = new Map();
|
|
336
395
|
methodIndex = null; // lookup key → node ID for Pascal defProc lookup
|
|
337
396
|
// Function-as-value capture (#756): per-language spec + candidates collected
|
|
338
397
|
// during the walk, gated & flushed into unresolvedReferences at end-of-file
|
|
@@ -393,7 +452,7 @@ class TreeSitterExtractor {
|
|
|
393
452
|
// this.source so downstream getNodeText reads the same bytes the parser
|
|
394
453
|
// saw (identical outside the blanked directive lines).
|
|
395
454
|
if (this.extractor?.preParse) {
|
|
396
|
-
this.source = this.extractor.preParse(this.source);
|
|
455
|
+
this.source = this.extractor.preParse(this.source, this.filePath);
|
|
397
456
|
}
|
|
398
457
|
this.tree = parser.parse(this.source) ?? null;
|
|
399
458
|
if (!this.tree) {
|
|
@@ -851,6 +910,31 @@ class TreeSitterExtractor {
|
|
|
851
910
|
if (skipChildren)
|
|
852
911
|
return;
|
|
853
912
|
}
|
|
913
|
+
// C++ namespace blocks: carry the namespace name as a qualifiedName prefix
|
|
914
|
+
// while walking the body, so `namespace flash { void compute_attn(); }`
|
|
915
|
+
// indexes compute_attn with qualifiedName `flash::compute_attn` and a
|
|
916
|
+
// namespace-qualified call (`flash::compute_attn(...)`) resolves by exact
|
|
917
|
+
// qualified match instead of never resolving — C++ namespaces previously
|
|
918
|
+
// left no trace in qualifiedNames at all, so every `ns::fn()` call site
|
|
919
|
+
// was a permanently dead edge (surfaced by #387 flow validation on
|
|
920
|
+
// flash-attention/cutlass, whose kernel dispatch is namespace-qualified).
|
|
921
|
+
// C++17 nested forms (`namespace a::b {`) prefix as written. An anonymous
|
|
922
|
+
// namespace falls through to the generic walk — its contents stay bare,
|
|
923
|
+
// matching how call sites spell them.
|
|
924
|
+
if (this.language === 'cpp' && nodeType === 'namespace_definition') {
|
|
925
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
926
|
+
const nsName = nameNode ? (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source) : '';
|
|
927
|
+
if (nsName) {
|
|
928
|
+
this.namespacePrefix.push(nsName);
|
|
929
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
930
|
+
const child = node.namedChild(i);
|
|
931
|
+
if (child)
|
|
932
|
+
this.visitNode(child);
|
|
933
|
+
}
|
|
934
|
+
this.namespacePrefix.pop();
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
854
938
|
// Function-as-value capture (#756) — independent of the dispatch ladder
|
|
855
939
|
// below (the captured container types have no other handler there), so it
|
|
856
940
|
// can never shadow or be shadowed by an extraction branch.
|
|
@@ -1084,7 +1168,8 @@ class TreeSitterExtractor {
|
|
|
1084
1168
|
// falls through to its normal declaration extraction).
|
|
1085
1169
|
else if (nodeType === 'export_statement' &&
|
|
1086
1170
|
(this.language === 'typescript' || this.language === 'tsx' ||
|
|
1087
|
-
this.language === 'javascript' || this.language === 'jsx'
|
|
1171
|
+
this.language === 'javascript' || this.language === 'jsx' ||
|
|
1172
|
+
this.language === 'arkts') &&
|
|
1088
1173
|
(0, tree_sitter_helpers_1.getChildByField)(node, 'source')) {
|
|
1089
1174
|
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1090
1175
|
if (parentId)
|
|
@@ -1114,7 +1199,7 @@ class TreeSitterExtractor {
|
|
|
1114
1199
|
// produce an `instantiates` reference. Children still walked so
|
|
1115
1200
|
// nested calls inside the constructor args (`new Foo(bar())`) get
|
|
1116
1201
|
// their own `calls` refs.
|
|
1117
|
-
else if (INSTANTIATION_KINDS.has(nodeType)) {
|
|
1202
|
+
else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
|
|
1118
1203
|
this.extractInstantiation(node);
|
|
1119
1204
|
// Java/C# `new T(...) { ... }` — anonymous class with body. Without
|
|
1120
1205
|
// extracting it as a class node + its methods, the interface→impl
|
|
@@ -1265,7 +1350,8 @@ class TreeSitterExtractor {
|
|
|
1265
1350
|
buildQualifiedName(name) {
|
|
1266
1351
|
// Build a qualified name from the semantic hierarchy only (no file path).
|
|
1267
1352
|
// The file path is stored separately in filePath and pollutes FTS if included here.
|
|
1268
|
-
|
|
1353
|
+
// C/C++ enclosing namespaces prefix first (empty for every other language).
|
|
1354
|
+
const parts = [...this.namespacePrefix];
|
|
1269
1355
|
for (const nodeId of this.nodeStack) {
|
|
1270
1356
|
const node = this.nodes.find((n) => n.id === nodeId);
|
|
1271
1357
|
if (node && node.kind !== 'file') {
|
|
@@ -1484,6 +1570,15 @@ class TreeSitterExtractor {
|
|
|
1484
1570
|
extractClass(node, kind = 'class') {
|
|
1485
1571
|
if (!this.extractor)
|
|
1486
1572
|
return;
|
|
1573
|
+
// Skip forward declarations / elaborated type references (`class Foo;`) in
|
|
1574
|
+
// languages that opt in — bodiless there means "not a definition", so it
|
|
1575
|
+
// would otherwise mint a phantom node competing with the real definition
|
|
1576
|
+
// (#1093). Languages where a bodiless class is complete (Kotlin, Scala)
|
|
1577
|
+
// leave the flag unset. Resolved once here and reused for the body walk.
|
|
1578
|
+
const resolvedBody = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
|
1579
|
+
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1580
|
+
if (this.extractor.skipBodilessClass && !resolvedBody)
|
|
1581
|
+
return;
|
|
1487
1582
|
const name = extractName(node, this.source, this.extractor);
|
|
1488
1583
|
const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
|
|
1489
1584
|
const visibility = this.extractor.getVisibility?.(node);
|
|
@@ -1503,10 +1598,7 @@ class TreeSitterExtractor {
|
|
|
1503
1598
|
this.extractDecoratorsFor(node, classNode.id);
|
|
1504
1599
|
// Push to stack and visit body
|
|
1505
1600
|
this.nodeStack.push(classNode.id);
|
|
1506
|
-
|
|
1507
|
-
?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
|
|
1508
|
-
if (!body)
|
|
1509
|
-
body = node;
|
|
1601
|
+
const body = resolvedBody ?? node;
|
|
1510
1602
|
// Visit all children for methods and properties
|
|
1511
1603
|
for (let i = 0; i < body.namedChildCount; i++) {
|
|
1512
1604
|
const child = body.namedChild(i);
|
|
@@ -2340,7 +2432,8 @@ class TreeSitterExtractor {
|
|
|
2340
2432
|
const isExported = this.extractor.isExported?.(node, this.source) ?? false;
|
|
2341
2433
|
// Extract variable declarators based on language
|
|
2342
2434
|
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
2343
|
-
this.language === 'tsx' || this.language === 'jsx'
|
|
2435
|
+
this.language === 'tsx' || this.language === 'jsx' || this.language === 'cfscript' ||
|
|
2436
|
+
this.language === 'arkts') {
|
|
2344
2437
|
// Handle lexical_declaration and variable_declaration
|
|
2345
2438
|
// These contain one or more variable_declarator children
|
|
2346
2439
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2754,7 +2847,7 @@ class TreeSitterExtractor {
|
|
|
2754
2847
|
// property/method nodes under the type alias so `recorder.stop()`
|
|
2755
2848
|
// can attach the call edge to `RecorderHandle.stop` instead of
|
|
2756
2849
|
// an unrelated class method picked by path-proximity (#359).
|
|
2757
|
-
if (this.language === 'typescript' || this.language === 'tsx') {
|
|
2850
|
+
if (this.language === 'typescript' || this.language === 'tsx' || this.language === 'arkts') {
|
|
2758
2851
|
this.extractTsTypeAliasMembers(value, typeAliasNode);
|
|
2759
2852
|
// `type List = [ Service<'name', Req, Resp>, … ]` — surface each
|
|
2760
2853
|
// entry's string-literal name as a searchable member (issue #634).
|
|
@@ -2975,7 +3068,8 @@ class TreeSitterExtractor {
|
|
|
2975
3068
|
// Link each imported binding to its definition so imported-but-not-
|
|
2976
3069
|
// called/typed symbols still record a cross-file dependency (TS/JS only).
|
|
2977
3070
|
if (this.language === 'typescript' || this.language === 'tsx' ||
|
|
2978
|
-
this.language === 'javascript' || this.language === 'jsx'
|
|
3071
|
+
this.language === 'javascript' || this.language === 'jsx' ||
|
|
3072
|
+
this.language === 'arkts') {
|
|
2979
3073
|
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
2980
3074
|
if (parentId)
|
|
2981
3075
|
this.emitImportBindingRefs(node, parentId);
|
|
@@ -3414,12 +3508,522 @@ class TreeSitterExtractor {
|
|
|
3414
3508
|
/**
|
|
3415
3509
|
* Extract a function call
|
|
3416
3510
|
*/
|
|
3511
|
+
/**
|
|
3512
|
+
* The module an Erlang gen_server target expression statically names, or
|
|
3513
|
+
* null when it's dynamic (pid/var/tuple form). Static shapes:
|
|
3514
|
+
* - a bare atom — either this module or another one; OTP's dominant
|
|
3515
|
+
* registration convention (`{local, ?MODULE}`) names a server process
|
|
3516
|
+
* after its module, so `gen_server:call(other_mod, …)` reaches
|
|
3517
|
+
* `other_mod`'s handlers. A registered name that matches no module
|
|
3518
|
+
* resolves to nothing downstream (the qualified ref just drops).
|
|
3519
|
+
* - `?MODULE`, or a macro the file defines as `?MODULE`
|
|
3520
|
+
* (`-define(SERVER, ?MODULE)` — the standard self idiom)
|
|
3521
|
+
* - a macro the file defines as a bare atom
|
|
3522
|
+
* (`-define(STORE, kv_store)` — the cross-module variant)
|
|
3523
|
+
* The macro tables are memoized per file (single entry — extraction is
|
|
3524
|
+
* file-sequential).
|
|
3525
|
+
*/
|
|
3526
|
+
erlangServerMacroFile = '';
|
|
3527
|
+
erlangSelfMacros = new Set();
|
|
3528
|
+
erlangAtomMacros = new Map();
|
|
3529
|
+
resolveErlangGenServerTarget(target) {
|
|
3530
|
+
const ownModule = (this.filePath.split('/').pop() ?? '').replace(/\.erl$/, '');
|
|
3531
|
+
if (target.type === 'atom') {
|
|
3532
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source).replace(/^'([\s\S]*)'$/, '$1');
|
|
3533
|
+
return name || null;
|
|
3534
|
+
}
|
|
3535
|
+
if (target.type !== 'macro_call_expr')
|
|
3536
|
+
return null;
|
|
3537
|
+
const nameNode = (0, tree_sitter_helpers_1.getChildByField)(target, 'name');
|
|
3538
|
+
if (!nameNode)
|
|
3539
|
+
return null;
|
|
3540
|
+
const macroName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3541
|
+
if (macroName === 'MODULE')
|
|
3542
|
+
return ownModule || null;
|
|
3543
|
+
if (this.erlangServerMacroFile !== this.filePath) {
|
|
3544
|
+
this.erlangServerMacroFile = this.filePath;
|
|
3545
|
+
this.erlangSelfMacros = new Set();
|
|
3546
|
+
this.erlangAtomMacros = new Map();
|
|
3547
|
+
let root = target;
|
|
3548
|
+
while (root.parent)
|
|
3549
|
+
root = root.parent;
|
|
3550
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
3551
|
+
const form = root.namedChild(i);
|
|
3552
|
+
if (form?.type !== 'pp_define')
|
|
3553
|
+
continue;
|
|
3554
|
+
const lhs = (0, tree_sitter_helpers_1.getChildByField)(form, 'lhs');
|
|
3555
|
+
const defName = lhs ? (0, tree_sitter_helpers_1.getChildByField)(lhs, 'name') : null;
|
|
3556
|
+
const replacement = (0, tree_sitter_helpers_1.getChildByField)(form, 'replacement');
|
|
3557
|
+
if (!defName || !replacement)
|
|
3558
|
+
continue;
|
|
3559
|
+
if (replacement.type === 'macro_call_expr' &&
|
|
3560
|
+
(0, tree_sitter_helpers_1.getChildByField)(replacement, 'name') &&
|
|
3561
|
+
(0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(replacement, 'name'), this.source) === 'MODULE') {
|
|
3562
|
+
this.erlangSelfMacros.add((0, tree_sitter_helpers_1.getNodeText)(defName, this.source));
|
|
3563
|
+
}
|
|
3564
|
+
else if (replacement.type === 'atom') {
|
|
3565
|
+
this.erlangAtomMacros.set((0, tree_sitter_helpers_1.getNodeText)(defName, this.source), (0, tree_sitter_helpers_1.getNodeText)(replacement, this.source).replace(/^'([\s\S]*)'$/, '$1'));
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
if (this.erlangSelfMacros.has(macroName))
|
|
3570
|
+
return ownModule || null;
|
|
3571
|
+
return this.erlangAtomMacros.get(macroName) ?? null;
|
|
3572
|
+
}
|
|
3417
3573
|
extractCall(node) {
|
|
3418
3574
|
if (this.nodeStack.length === 0)
|
|
3419
3575
|
return;
|
|
3420
3576
|
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
3421
3577
|
if (!callerId)
|
|
3422
3578
|
return;
|
|
3579
|
+
// VB.NET: `foo(args)` is syntactically ambiguous between a call and an
|
|
3580
|
+
// index read, so the grammar parses non-empty parens as
|
|
3581
|
+
// array_access_expression (field `array`, not `function`) — even Roslyn
|
|
3582
|
+
// parses both as InvocationExpression and resolves during binding. Treat
|
|
3583
|
+
// all three shapes as call sites: the callee is the member/identifier
|
|
3584
|
+
// under the array/function field, qualified with a simple-identifier
|
|
3585
|
+
// receiver for resolution. Index reads on collections simply never
|
|
3586
|
+
// resolve to a callable, so they cost nothing.
|
|
3587
|
+
if (this.language === 'vbnet' &&
|
|
3588
|
+
(node.type === 'array_access_expression' ||
|
|
3589
|
+
node.type === 'invocation_expression' ||
|
|
3590
|
+
node.type === 'generic_invocation_expression')) {
|
|
3591
|
+
const fn = (0, tree_sitter_helpers_1.getChildByField)(node, 'function') || (0, tree_sitter_helpers_1.getChildByField)(node, 'array');
|
|
3592
|
+
if (!fn)
|
|
3593
|
+
return;
|
|
3594
|
+
let calleeName = '';
|
|
3595
|
+
if (fn.type === 'member_access_expression') {
|
|
3596
|
+
const member = (0, tree_sitter_helpers_1.getChildByField)(fn, 'member');
|
|
3597
|
+
const memberName = member ? (0, tree_sitter_helpers_1.getNodeText)(member, this.source) : '';
|
|
3598
|
+
if (!memberName)
|
|
3599
|
+
return;
|
|
3600
|
+
const receiver = (0, tree_sitter_helpers_1.getChildByField)(fn, 'object');
|
|
3601
|
+
const SKIP = new Set(['me', 'mybase', 'myclass']);
|
|
3602
|
+
if (receiver && receiver.type === 'identifier' && !SKIP.has((0, tree_sitter_helpers_1.getNodeText)(receiver, this.source).toLowerCase())) {
|
|
3603
|
+
calleeName = `${(0, tree_sitter_helpers_1.getNodeText)(receiver, this.source)}.${memberName}`;
|
|
3604
|
+
}
|
|
3605
|
+
else {
|
|
3606
|
+
calleeName = memberName;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
else if (fn.type === 'identifier') {
|
|
3610
|
+
calleeName = (0, tree_sitter_helpers_1.getNodeText)(fn, this.source);
|
|
3611
|
+
}
|
|
3612
|
+
else {
|
|
3613
|
+
return; // parenthesized/chained receivers: no static name to link
|
|
3614
|
+
}
|
|
3615
|
+
if (calleeName) {
|
|
3616
|
+
this.unresolvedReferences.push({
|
|
3617
|
+
fromNodeId: callerId,
|
|
3618
|
+
referenceName: calleeName,
|
|
3619
|
+
referenceKind: 'calls',
|
|
3620
|
+
line: node.startPosition.row + 1,
|
|
3621
|
+
column: node.startPosition.column,
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
// Erlang: a local call is `call(expr: atom, args)`; a remote call nests it
|
|
3627
|
+
// under `remote(module: remote_module, fun: call)` — the module qualifier
|
|
3628
|
+
// lives on the PARENT. Remote calls are emitted as `mod::fn`, which is
|
|
3629
|
+
// byte-identical to the qualifiedName the module namespace gives every
|
|
3630
|
+
// function (see packageTypes in languages/erlang.ts), so they resolve via
|
|
3631
|
+
// matchByQualifiedName. A var/macro callee or module (`F(X)`, `?M(X)`,
|
|
3632
|
+
// `Mod:handle(X)`) has no static target — except `?MODULE:fn(X)`, which the
|
|
3633
|
+
// bare name + same-file preference resolves correctly. `fun name/1` /
|
|
3634
|
+
// `fun mod:name/1` values are function REFERENCES (callback registration),
|
|
3635
|
+
// and record construction/update/index/field-access are `references` to the
|
|
3636
|
+
// record's struct node.
|
|
3637
|
+
if (this.language === 'erlang') {
|
|
3638
|
+
const line = node.startPosition.row + 1;
|
|
3639
|
+
const column = node.startPosition.column;
|
|
3640
|
+
const erlAtom = (n) => (0, tree_sitter_helpers_1.getNodeText)(n, this.source).replace(/^'([\s\S]*)'$/, '$1');
|
|
3641
|
+
if (node.type === 'call') {
|
|
3642
|
+
let callee = (0, tree_sitter_helpers_1.getChildByField)(node, 'expr');
|
|
3643
|
+
let moduleNode = null;
|
|
3644
|
+
// remote(module, fun: call) — the shape the grammar produces today; the
|
|
3645
|
+
// node-types also permit call(expr: remote), so handle both nestings.
|
|
3646
|
+
if (node.parent?.type === 'remote') {
|
|
3647
|
+
moduleNode = (0, tree_sitter_helpers_1.getChildByField)(node.parent, 'module');
|
|
3648
|
+
}
|
|
3649
|
+
else if (callee?.type === 'remote') {
|
|
3650
|
+
moduleNode = (0, tree_sitter_helpers_1.getChildByField)(callee, 'module');
|
|
3651
|
+
callee = (0, tree_sitter_helpers_1.getChildByField)(callee, 'fun');
|
|
3652
|
+
}
|
|
3653
|
+
if (callee?.type === 'atom') {
|
|
3654
|
+
const fnBare = erlAtom(callee);
|
|
3655
|
+
let calleeName = fnBare;
|
|
3656
|
+
const moduleExpr = moduleNode ? (0, tree_sitter_helpers_1.getChildByField)(moduleNode, 'module') : null;
|
|
3657
|
+
if (moduleExpr?.type === 'atom') {
|
|
3658
|
+
calleeName = `${erlAtom(moduleExpr)}::${calleeName}`;
|
|
3659
|
+
}
|
|
3660
|
+
else if (moduleExpr) {
|
|
3661
|
+
// Non-atom module qualifier. `?MODULE:f(X)` targets THIS module —
|
|
3662
|
+
// keep the bare name so same-file preference resolves it. Anything
|
|
3663
|
+
// else (`Mod:f(X)`) is behaviour-style dynamic dispatch with no
|
|
3664
|
+
// static target: emitting the bare name would link an arbitrary
|
|
3665
|
+
// same-named function, so stay silent instead.
|
|
3666
|
+
const macroName = moduleExpr.type === 'macro_call_expr' ? (0, tree_sitter_helpers_1.getChildByField)(moduleExpr, 'name') : null;
|
|
3667
|
+
if (!macroName || (0, tree_sitter_helpers_1.getNodeText)(macroName, this.source) !== 'MODULE')
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
this.unresolvedReferences.push({
|
|
3671
|
+
fromNodeId: callerId,
|
|
3672
|
+
referenceName: calleeName,
|
|
3673
|
+
referenceKind: 'calls',
|
|
3674
|
+
line,
|
|
3675
|
+
column,
|
|
3676
|
+
});
|
|
3677
|
+
// gen_server dispatch: `gen_server:call(?SERVER, Msg)` /
|
|
3678
|
+
// `gen_server:cast(other_mod, Msg)` — a request routes to the TARGET
|
|
3679
|
+
// module's handle_call/handle_cast. The target is static when the
|
|
3680
|
+
// first argument names a module: ?MODULE or a ?MODULE-defined macro
|
|
3681
|
+
// (the self API-wrapper idiom), a bare atom (OTP's `{local, ?MODULE}`
|
|
3682
|
+
// convention names a server after its module, so a cross-module
|
|
3683
|
+
// registered name reaches that module's handlers — and a registered
|
|
3684
|
+
// name matching no module resolves to nothing), or a macro defined
|
|
3685
|
+
// as a bare atom. Pid/var/tuple targets stay silent.
|
|
3686
|
+
if (moduleExpr?.type === 'atom' &&
|
|
3687
|
+
erlAtom(moduleExpr) === 'gen_server' &&
|
|
3688
|
+
(fnBare === 'call' || fnBare === 'cast' || fnBare === 'send_request')) {
|
|
3689
|
+
const argsNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'args');
|
|
3690
|
+
const target = argsNode?.namedChild(0) ?? null;
|
|
3691
|
+
const targetModule = target ? this.resolveErlangGenServerTarget(target) : null;
|
|
3692
|
+
if (targetModule) {
|
|
3693
|
+
this.unresolvedReferences.push({
|
|
3694
|
+
fromNodeId: callerId,
|
|
3695
|
+
referenceName: `${targetModule}::${fnBare === 'cast' ? 'handle_cast' : 'handle_call'}`,
|
|
3696
|
+
referenceKind: 'calls',
|
|
3697
|
+
line,
|
|
3698
|
+
column,
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
// MFA-in-argument dispatch: the spawn/apply family names its real
|
|
3703
|
+
// callee in ARGUMENT position — `proc_lib:spawn_link(?MODULE,
|
|
3704
|
+
// request_process, [Req, Env, Middlewares])` — so the walker above
|
|
3705
|
+
// sees only the spawn itself and the spawned function ends up with
|
|
3706
|
+
// zero callers (measured on cowboy: request_process had no incoming
|
|
3707
|
+
// edges and the agent Read the file to find it). When the (Module,
|
|
3708
|
+
// Function) pair is static, lift it as a call edge. The pair is
|
|
3709
|
+
// found positionally-agnostically (first adjacent module-atom/
|
|
3710
|
+
// ?MODULE + atom pair) so every arity variant works: spawn/3,
|
|
3711
|
+
// spawn(Node,M,F,A)/4, timer:apply_after(Time,M,F,A),
|
|
3712
|
+
// rpc:call(Node,M,F,A). A var module or fun stays silent.
|
|
3713
|
+
const familyKey = moduleExpr?.type === 'atom' ? `${erlAtom(moduleExpr)}:${fnBare}` : fnBare;
|
|
3714
|
+
if (ERLANG_MFA_CALLS.has(familyKey)) {
|
|
3715
|
+
const argsNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'args');
|
|
3716
|
+
const argExprs = argsNode ? argsNode.namedChildren : [];
|
|
3717
|
+
for (let i = 0; i + 1 < argExprs.length; i++) {
|
|
3718
|
+
const m = argExprs[i];
|
|
3719
|
+
const f = argExprs[i + 1];
|
|
3720
|
+
if (f.type !== 'atom')
|
|
3721
|
+
continue;
|
|
3722
|
+
const isLocalModule = m.type === 'macro_call_expr' &&
|
|
3723
|
+
(0, tree_sitter_helpers_1.getChildByField)(m, 'name') !== null &&
|
|
3724
|
+
(0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(m, 'name'), this.source) === 'MODULE';
|
|
3725
|
+
if (m.type !== 'atom' && !isLocalModule)
|
|
3726
|
+
continue;
|
|
3727
|
+
this.unresolvedReferences.push({
|
|
3728
|
+
fromNodeId: callerId,
|
|
3729
|
+
referenceName: isLocalModule ? erlAtom(f) : `${erlAtom(m)}::${erlAtom(f)}`,
|
|
3730
|
+
referenceKind: 'calls',
|
|
3731
|
+
line: f.startPosition.row + 1,
|
|
3732
|
+
column: f.startPosition.column,
|
|
3733
|
+
});
|
|
3734
|
+
break;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
if (node.type === 'internal_fun' || node.type === 'external_fun') {
|
|
3741
|
+
const funNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'fun');
|
|
3742
|
+
if (funNode?.type !== 'atom')
|
|
3743
|
+
return; // fun Mod:F/A with var parts — dynamic
|
|
3744
|
+
let refName = erlAtom(funNode);
|
|
3745
|
+
if (node.type === 'external_fun') {
|
|
3746
|
+
const moduleWrapper = (0, tree_sitter_helpers_1.getChildByField)(node, 'module');
|
|
3747
|
+
const moduleAtom = moduleWrapper ? (0, tree_sitter_helpers_1.getChildByField)(moduleWrapper, 'name') : null;
|
|
3748
|
+
if (moduleAtom?.type !== 'atom')
|
|
3749
|
+
return;
|
|
3750
|
+
refName = `${erlAtom(moduleAtom)}::${refName}`;
|
|
3751
|
+
}
|
|
3752
|
+
this.unresolvedReferences.push({
|
|
3753
|
+
fromNodeId: callerId,
|
|
3754
|
+
referenceName: refName,
|
|
3755
|
+
referenceKind: 'references',
|
|
3756
|
+
line,
|
|
3757
|
+
column,
|
|
3758
|
+
});
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
if (node.type === 'macro_call_expr') {
|
|
3762
|
+
// Macro use site → the `-define` constant node. Function-like uses
|
|
3763
|
+
// (`?LOG_AUDIT(X)` — args present) are inlined code, so they join the
|
|
3764
|
+
// call chain and connect through the macro node to the body's calls
|
|
3765
|
+
// (attributed there by handlePpDefine); bare reads (`?TIMEOUT`) are
|
|
3766
|
+
// `references`, answering "where is this macro used" without
|
|
3767
|
+
// polluting call paths. Compiler-predefined macros carry no
|
|
3768
|
+
// definition to link. The use site's ARGUMENTS are children and keep
|
|
3769
|
+
// walking, so a call nested in `?assertEqual(ok, do_thing())` still
|
|
3770
|
+
// attributes to the enclosing function.
|
|
3771
|
+
const macroName = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3772
|
+
if (!macroName)
|
|
3773
|
+
return;
|
|
3774
|
+
const name = (0, tree_sitter_helpers_1.getNodeText)(macroName, this.source);
|
|
3775
|
+
if (ERLANG_PREDEFINED_MACROS.has(name))
|
|
3776
|
+
return;
|
|
3777
|
+
this.unresolvedReferences.push({
|
|
3778
|
+
fromNodeId: callerId,
|
|
3779
|
+
referenceName: name,
|
|
3780
|
+
referenceKind: (0, tree_sitter_helpers_1.getChildByField)(node, 'args') ? 'calls' : 'references',
|
|
3781
|
+
line,
|
|
3782
|
+
column,
|
|
3783
|
+
});
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
// record_expr / record_update_expr / record_index_expr / record_field_expr
|
|
3787
|
+
const recordName = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
|
|
3788
|
+
const recordAtom = recordName?.type === 'record_name' ? (0, tree_sitter_helpers_1.getChildByField)(recordName, 'name') : null;
|
|
3789
|
+
if (recordAtom?.type === 'atom') {
|
|
3790
|
+
this.unresolvedReferences.push({
|
|
3791
|
+
fromNodeId: callerId,
|
|
3792
|
+
referenceName: erlAtom(recordAtom),
|
|
3793
|
+
referenceKind: 'references',
|
|
3794
|
+
line,
|
|
3795
|
+
column,
|
|
3796
|
+
});
|
|
3797
|
+
}
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
// Ruby `call` nodes use `receiver` + `method` fields (tree-sitter-ruby), not
|
|
3801
|
+
// the `object`/`name`/`function` fields the branches below expect — so
|
|
3802
|
+
// without this they fell through to the generic path, which took the
|
|
3803
|
+
// receiver as the callee and DROPPED the method name: `lg.log()` produced a
|
|
3804
|
+
// `calls` ref to `lg` (unresolvable) and no method edge was ever recorded,
|
|
3805
|
+
// so a Ruby method's callers/impact were invisible (#1108 follow-up). Build
|
|
3806
|
+
// `receiver.method` so the resolver — and local-variable type inference —
|
|
3807
|
+
// can link it; `Foo.new` stays an instantiation.
|
|
3808
|
+
if (this.language === 'ruby' && (node.type === 'call' || node.type === 'method_call')) {
|
|
3809
|
+
const methodNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'method');
|
|
3810
|
+
const methodName = methodNode ? (0, tree_sitter_helpers_1.getNodeText)(methodNode, this.source) : '';
|
|
3811
|
+
if (!methodName)
|
|
3812
|
+
return; // operator/element-reference call with no method name
|
|
3813
|
+
const receiverNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'receiver');
|
|
3814
|
+
const line = node.startPosition.row + 1;
|
|
3815
|
+
const column = node.startPosition.column;
|
|
3816
|
+
if (!receiverNode) {
|
|
3817
|
+
// Bare `foo(...)` — just the method name (unchanged behavior).
|
|
3818
|
+
this.unresolvedReferences.push({ fromNodeId: callerId, referenceName: methodName, referenceKind: 'calls', line, column });
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiverNode, this.source);
|
|
3822
|
+
// `Foo.new` / `Foo::Bar.new` is construction — emit an `instantiates` ref to
|
|
3823
|
+
// the class (last `::` segment), preserving the "what creates X" edge.
|
|
3824
|
+
if (methodName === 'new') {
|
|
3825
|
+
const className = receiverName.includes('::')
|
|
3826
|
+
? receiverName.slice(receiverName.lastIndexOf('::') + 2)
|
|
3827
|
+
: receiverName;
|
|
3828
|
+
if (/^[A-Z]/.test(className)) {
|
|
3829
|
+
this.unresolvedReferences.push({ fromNodeId: callerId, referenceName: className, referenceKind: 'instantiates', line, column });
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
const SKIP_RECEIVERS = new Set(['self', 'super']);
|
|
3834
|
+
const skip = SKIP_RECEIVERS.has(receiverName);
|
|
3835
|
+
this.unresolvedReferences.push({
|
|
3836
|
+
fromNodeId: callerId,
|
|
3837
|
+
referenceName: skip ? methodName : `${receiverName}.${methodName}`,
|
|
3838
|
+
referenceKind: 'calls',
|
|
3839
|
+
line,
|
|
3840
|
+
column,
|
|
3841
|
+
});
|
|
3842
|
+
// A capitalized (constant) receiver — `Foo.bar`, a class/module method call
|
|
3843
|
+
// — is itself a dependency on that constant; emit a `references` ref so a
|
|
3844
|
+
// class used only via its class methods still records a dependent (the edge
|
|
3845
|
+
// the old receiver-only callee happened to provide, now made explicit).
|
|
3846
|
+
if (!skip && receiverNode.type === 'constant') {
|
|
3847
|
+
this.unresolvedReferences.push({
|
|
3848
|
+
fromNodeId: callerId,
|
|
3849
|
+
referenceName: receiverName,
|
|
3850
|
+
referenceKind: 'references',
|
|
3851
|
+
line: receiverNode.startPosition.row + 1,
|
|
3852
|
+
column: receiverNode.startPosition.column,
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
// ArkTS build()-DSL handling. Three shapes carry UI-attribute chains, and
|
|
3858
|
+
// all of their attribute names are emitted with a LEADING DOT
|
|
3859
|
+
// (`.titleStyle`, `.width`) — an impossible identifier that routes them to
|
|
3860
|
+
// a dedicated matcher strategy resolving ONLY to decorator-marked
|
|
3861
|
+
// attribute helpers (`@Extend`/`@Styles`/`@AnimatableExtend`/`@Builder`
|
|
3862
|
+
// functions). Bare names would go through global name matching, where
|
|
3863
|
+
// framework attributes (`.width`, `.fontSize`, appearing on nearly every
|
|
3864
|
+
// UI line) hit arbitrary same-named symbols — measured on the OpenHarmony
|
|
3865
|
+
// samples monorepo, that produced 36k wrong edges (17% of all calls),
|
|
3866
|
+
// including single properties with 3,400+ false callers.
|
|
3867
|
+
//
|
|
3868
|
+
// 1. `Column({space:8}) { … }.height('100%')` — ONE
|
|
3869
|
+
// arkui_component_expression: `function:` = the component, chained
|
|
3870
|
+
// attributes as repeated `property:`/`arguments:` field pairs.
|
|
3871
|
+
// The component ref (`Column`, `TodoRow`) stays a PLAIN name — it
|
|
3872
|
+
// resolves to the child `@Component struct`, giving the parent→child
|
|
3873
|
+
// component-tree edge the way JSX children do for React.
|
|
3874
|
+
// 2. `Image(x).width(10).onClick(this.f)` — ordinary nested
|
|
3875
|
+
// call_expressions whose `function:` is a member_expression chained
|
|
3876
|
+
// on a CALL RESULT (never a named receiver, so `svc.save()` /
|
|
3877
|
+
// `this.vm.load()` are untouched and fall through to the generic
|
|
3878
|
+
// paths below).
|
|
3879
|
+
// 3. A nested component whose chain starts on the line AFTER its
|
|
3880
|
+
// closing `}` inside arkui_children — the grammar detaches the chain
|
|
3881
|
+
// into sibling `leading_dot_expression(identifier)` +
|
|
3882
|
+
// `parenthesized_expression(args)` statement pairs; reassemble from
|
|
3883
|
+
// the siblings.
|
|
3884
|
+
//
|
|
3885
|
+
// `.onXxx(this.handler)` METHOD-REFERENCE bindings (no call parens, so
|
|
3886
|
+
// nothing else records them) additionally emit a call ref to the bare
|
|
3887
|
+
// handler name — same-class resolution links the tap→handler hop.
|
|
3888
|
+
// Arrow-function handlers need nothing: their bodies' calls already
|
|
3889
|
+
// attribute to the enclosing build(). Children/argument subtrees are
|
|
3890
|
+
// still walked by the caller, so nested components extract normally.
|
|
3891
|
+
if (this.language === 'arkts') {
|
|
3892
|
+
const emitAttr = (nameNode) => {
|
|
3893
|
+
const attrName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
|
|
3894
|
+
if (!attrName)
|
|
3895
|
+
return;
|
|
3896
|
+
this.unresolvedReferences.push({
|
|
3897
|
+
fromNodeId: callerId,
|
|
3898
|
+
referenceName: '.' + attrName,
|
|
3899
|
+
referenceKind: 'calls',
|
|
3900
|
+
line: nameNode.startPosition.row + 1,
|
|
3901
|
+
column: nameNode.startPosition.column,
|
|
3902
|
+
});
|
|
3903
|
+
};
|
|
3904
|
+
// Emit `handler` for each bare `this.handler` among an on-attribute's
|
|
3905
|
+
// arguments.
|
|
3906
|
+
const emitThisHandlers = (args) => {
|
|
3907
|
+
if (!args)
|
|
3908
|
+
return;
|
|
3909
|
+
for (let j = 0; j < args.namedChildCount; j++) {
|
|
3910
|
+
const arg = args.namedChild(j);
|
|
3911
|
+
if (arg?.type !== 'member_expression')
|
|
3912
|
+
continue;
|
|
3913
|
+
const obj = (0, tree_sitter_helpers_1.getChildByField)(arg, 'object');
|
|
3914
|
+
const prop = (0, tree_sitter_helpers_1.getChildByField)(arg, 'property');
|
|
3915
|
+
if (obj?.type === 'this' && prop) {
|
|
3916
|
+
this.unresolvedReferences.push({
|
|
3917
|
+
fromNodeId: callerId,
|
|
3918
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(prop, this.source),
|
|
3919
|
+
referenceKind: 'calls',
|
|
3920
|
+
line: arg.startPosition.row + 1,
|
|
3921
|
+
column: arg.startPosition.column,
|
|
3922
|
+
});
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
};
|
|
3926
|
+
// Shape 1: arkui_component_expression with property/arguments pairs.
|
|
3927
|
+
if (node.type === 'arkui_component_expression') {
|
|
3928
|
+
const componentField = (0, tree_sitter_helpers_1.getChildByField)(node, 'function');
|
|
3929
|
+
if (componentField && componentField.type === 'identifier') {
|
|
3930
|
+
this.unresolvedReferences.push({
|
|
3931
|
+
fromNodeId: callerId,
|
|
3932
|
+
referenceName: (0, tree_sitter_helpers_1.getNodeText)(componentField, this.source),
|
|
3933
|
+
referenceKind: 'calls',
|
|
3934
|
+
line: node.startPosition.row + 1,
|
|
3935
|
+
column: node.startPosition.column,
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3939
|
+
const child = node.child(i);
|
|
3940
|
+
if (!child || child.type !== 'property_identifier')
|
|
3941
|
+
continue;
|
|
3942
|
+
emitAttr(child);
|
|
3943
|
+
if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(child, this.source))) {
|
|
3944
|
+
// The attribute's arguments node is the next `arguments`-typed
|
|
3945
|
+
// child before the following attribute name.
|
|
3946
|
+
let args = null;
|
|
3947
|
+
for (let k = i + 1; k < node.childCount; k++) {
|
|
3948
|
+
const next = node.child(k);
|
|
3949
|
+
if (!next)
|
|
3950
|
+
continue;
|
|
3951
|
+
if (next.type === 'property_identifier')
|
|
3952
|
+
break;
|
|
3953
|
+
if (next.type === 'arguments') {
|
|
3954
|
+
args = next;
|
|
3955
|
+
break;
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
emitThisHandlers(args);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
// Shape 2: fluent chain on a call result —
|
|
3964
|
+
// call_expression(function: member_expression(object: <call>)), or the
|
|
3965
|
+
// grammar's DSL-specific arkui_dsl_decorator_member_expression (same
|
|
3966
|
+
// object/property fields; produced e.g. by `Column() { … }.alignItems(x)`
|
|
3967
|
+
// in some chain positions — it ONLY occurs in attribute chains).
|
|
3968
|
+
if (node.type === 'call_expression') {
|
|
3969
|
+
const fn = (0, tree_sitter_helpers_1.getChildByField)(node, 'function');
|
|
3970
|
+
if (fn?.type === 'member_expression' || fn?.type === 'arkui_dsl_decorator_member_expression') {
|
|
3971
|
+
const obj = (0, tree_sitter_helpers_1.getChildByField)(fn, 'object');
|
|
3972
|
+
const prop = (0, tree_sitter_helpers_1.getChildByField)(fn, 'property');
|
|
3973
|
+
if (prop &&
|
|
3974
|
+
(fn.type === 'arkui_dsl_decorator_member_expression' ||
|
|
3975
|
+
obj?.type === 'call_expression' ||
|
|
3976
|
+
obj?.type === 'arkui_component_expression')) {
|
|
3977
|
+
emitAttr(prop);
|
|
3978
|
+
if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(prop, this.source))) {
|
|
3979
|
+
emitThisHandlers((0, tree_sitter_helpers_1.getChildByField)(node, 'arguments'));
|
|
3980
|
+
}
|
|
3981
|
+
return;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
// The INNERMOST call of a proper-form detached chain
|
|
3985
|
+
// (`.alignItems(x).layoutWeight(1)…` under a leading_dot_expression)
|
|
3986
|
+
// has a BARE IDENTIFIER function — the leading dot was consumed by
|
|
3987
|
+
// the wrapper, so it masquerades as a plain `alignItems(...)` call.
|
|
3988
|
+
// Walk up the member/call alternation; topping out at
|
|
3989
|
+
// leading_dot_expression means the dot belongs to this chain.
|
|
3990
|
+
if (fn?.type === 'identifier') {
|
|
3991
|
+
let p = node.parent;
|
|
3992
|
+
while (p && (p.type === 'member_expression' || p.type === 'call_expression')) {
|
|
3993
|
+
p = p.parent;
|
|
3994
|
+
}
|
|
3995
|
+
if (p?.type === 'leading_dot_expression') {
|
|
3996
|
+
emitAttr(fn);
|
|
3997
|
+
if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(fn, this.source))) {
|
|
3998
|
+
emitThisHandlers((0, tree_sitter_helpers_1.getChildByField)(node, 'arguments'));
|
|
3999
|
+
}
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
// Not a chained attribute — fall through to the generic call paths.
|
|
4004
|
+
}
|
|
4005
|
+
// Shape 3: detached chain segment — leading_dot_expression whose only
|
|
4006
|
+
// named child is a bare identifier; its arguments sit in the NEXT
|
|
4007
|
+
// sibling statement as a parenthesized_expression.
|
|
4008
|
+
if (node.type === 'leading_dot_expression') {
|
|
4009
|
+
const only = node.namedChildCount === 1 ? node.namedChild(0) : null;
|
|
4010
|
+
if (only && only.type === 'identifier') {
|
|
4011
|
+
emitAttr(only);
|
|
4012
|
+
if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(only, this.source))) {
|
|
4013
|
+
const stmt = node.parent; // expression_statement
|
|
4014
|
+
const nextStmt = stmt?.nextNamedSibling;
|
|
4015
|
+
const paren = nextStmt?.namedChild(0);
|
|
4016
|
+
if (paren?.type === 'parenthesized_expression') {
|
|
4017
|
+
emitThisHandlers(paren);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
// The proper form (child is a call_expression chain, as inside
|
|
4022
|
+
// `@Extend` bodies) needs nothing here — the walker descends into it
|
|
4023
|
+
// and the inner call_expressions take the paths above.
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
3423
4027
|
// Get the function/method being called
|
|
3424
4028
|
let calleeName = '';
|
|
3425
4029
|
// Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
|
|
@@ -3711,6 +4315,20 @@ class TreeSitterExtractor {
|
|
|
3711
4315
|
}
|
|
3712
4316
|
calleeName = reencode ? `${innerCallee}().${methodName}` : methodName;
|
|
3713
4317
|
}
|
|
4318
|
+
else if (this.language === 'cfscript' &&
|
|
4319
|
+
receiver &&
|
|
4320
|
+
receiver.type === 'member_expression' &&
|
|
4321
|
+
/^(variables|this|local|arguments)\.[A-Za-z_][\w]*$/i.test((0, tree_sitter_helpers_1.getNodeText)(receiver, this.source))) {
|
|
4322
|
+
// CFML scope-prefixed member call — `variables.svc.save()` /
|
|
4323
|
+
// `arguments.svc.save()`: the receiver is a component field,
|
|
4324
|
+
// injected property, or typed argument reached through one of
|
|
4325
|
+
// CFML's file-local scopes. Keep the full receiver chain so
|
|
4326
|
+
// resolution can strip the scope prefix and infer the field's
|
|
4327
|
+
// component type from its declaration (#1108). Gated to these
|
|
4328
|
+
// scope keywords: such calls previously emitted a bare method
|
|
4329
|
+
// name, which either failed to resolve or resolved ambiguously.
|
|
4330
|
+
calleeName = `${(0, tree_sitter_helpers_1.getNodeText)(receiver, this.source)}.${methodName}`;
|
|
4331
|
+
}
|
|
3714
4332
|
else {
|
|
3715
4333
|
calleeName = methodName;
|
|
3716
4334
|
}
|
|
@@ -3753,6 +4371,40 @@ class TreeSitterExtractor {
|
|
|
3753
4371
|
if (conv && conv[1])
|
|
3754
4372
|
calleeName = conv[1];
|
|
3755
4373
|
}
|
|
4374
|
+
// C/C++ templated callees — a direct templated call (`fn<T, 256>(args)`,
|
|
4375
|
+
// the shape every CUDA kernel-launch site takes once its `<<<…>>>` config
|
|
4376
|
+
// is blanked) or a qualified one (`ns::fn<T>(args)`) — carry template
|
|
4377
|
+
// arguments in the callee text, which can never match the bare name the
|
|
4378
|
+
// function was DEFINED as, so the call edge silently never resolves. Strip
|
|
4379
|
+
// them: the same normalization base-class `extends` refs already get
|
|
4380
|
+
// (#1043). `operator<`/`operator<<` callees are excluded — their `<` is the
|
|
4381
|
+
// operator itself, not a template-argument list.
|
|
4382
|
+
if (calleeName &&
|
|
4383
|
+
calleeName.includes('<') &&
|
|
4384
|
+
(this.language === 'cpp' || this.language === 'c') &&
|
|
4385
|
+
!calleeName.includes('operator')) {
|
|
4386
|
+
calleeName = (0, c_cpp_1.stripCppTemplateArgs)(calleeName);
|
|
4387
|
+
}
|
|
4388
|
+
// C++ call/launch through a local function pointer: `auto kernel =
|
|
4389
|
+
// &flash_fwd_kernel<…>; … kernel<<<grid, block>>>(params);` — the callee
|
|
4390
|
+
// is an unresolvable local name. When the same enclosing symbol bound the
|
|
4391
|
+
// local from `&fn` (each branch assignment counts), emit the call against
|
|
4392
|
+
// every recorded target instead of the local.
|
|
4393
|
+
if (calleeName && this.language === 'cpp' && /^[A-Za-z_]\w*$/.test(calleeName)) {
|
|
4394
|
+
const targets = this.cppLocalFnPtrs.get(callerId)?.get(calleeName);
|
|
4395
|
+
if (targets && targets.size > 0) {
|
|
4396
|
+
for (const target of targets) {
|
|
4397
|
+
this.unresolvedReferences.push({
|
|
4398
|
+
fromNodeId: callerId,
|
|
4399
|
+
referenceName: target,
|
|
4400
|
+
referenceKind: 'calls',
|
|
4401
|
+
line: node.startPosition.row + 1,
|
|
4402
|
+
column: node.startPosition.column,
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
3756
4408
|
if (calleeName) {
|
|
3757
4409
|
this.unresolvedReferences.push({
|
|
3758
4410
|
fromNodeId: callerId,
|
|
@@ -3772,6 +4424,26 @@ class TreeSitterExtractor {
|
|
|
3772
4424
|
* Children are still walked so nested calls inside the constructor
|
|
3773
4425
|
* arguments (`new Foo(bar())`) get their own `calls` references.
|
|
3774
4426
|
*/
|
|
4427
|
+
/**
|
|
4428
|
+
* VB.NET `New Invoice(1)` is syntactically ambiguous between constructing
|
|
4429
|
+
* Invoice with an argument and allocating an Invoice array of bound 1; the
|
|
4430
|
+
* grammar parses the parenthesized form as array_creation_expression. A
|
|
4431
|
+
* user-defined type with no `{...}` array initializer is overwhelmingly a
|
|
4432
|
+
* constructor call, so treat it as an instantiation. Predefined element
|
|
4433
|
+
* types (`New Byte(1023)`) and brace-initialized forms stay arrays.
|
|
4434
|
+
*/
|
|
4435
|
+
isVbnetConstructorShapedArrayCreation(node) {
|
|
4436
|
+
if (this.language !== 'vbnet' || node.type !== 'array_creation_expression')
|
|
4437
|
+
return false;
|
|
4438
|
+
const typeNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
|
|
4439
|
+
if (!typeNode || typeNode.type === 'predefined_type' || typeNode.type === 'array_type')
|
|
4440
|
+
return false;
|
|
4441
|
+
for (const child of node.namedChildren) {
|
|
4442
|
+
if (child?.type === 'array_initializer')
|
|
4443
|
+
return false;
|
|
4444
|
+
}
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
3775
4447
|
extractInstantiation(node) {
|
|
3776
4448
|
if (this.nodeStack.length === 0)
|
|
3777
4449
|
return;
|
|
@@ -3834,6 +4506,14 @@ class TreeSitterExtractor {
|
|
|
3834
4506
|
const ltIdx = className.indexOf('<');
|
|
3835
4507
|
if (ltIdx > 0)
|
|
3836
4508
|
className = className.slice(0, ltIdx);
|
|
4509
|
+
// VB.NET spells generics with parentheses: `New List(Of String)` /
|
|
4510
|
+
// `New Dictionary(Of K, V)(cap)` — strip from the `(` so the bare
|
|
4511
|
+
// type name is what resolution matches.
|
|
4512
|
+
if (this.language === 'vbnet') {
|
|
4513
|
+
const parenIdx = className.indexOf('(');
|
|
4514
|
+
if (parenIdx > 0)
|
|
4515
|
+
className = className.slice(0, parenIdx);
|
|
4516
|
+
}
|
|
3837
4517
|
// For namespaced/qualified constructors (`new ns.Foo()`,
|
|
3838
4518
|
// `new ns::Foo()`) keep the trailing identifier — that's what
|
|
3839
4519
|
// matches a class node in the index.
|
|
@@ -4048,6 +4728,28 @@ class TreeSitterExtractor {
|
|
|
4048
4728
|
const consider = (n) => {
|
|
4049
4729
|
if (!n)
|
|
4050
4730
|
return;
|
|
4731
|
+
// Solidity `modifier_invocation` (unique to that grammar) sits
|
|
4732
|
+
// decorator-position in the function header — OUTSIDE the `body:` field
|
|
4733
|
+
// the call walker descends — but its body executes around the function
|
|
4734
|
+
// via `_;`, so it is a real call-flow hop (`withdraw → onlyOwner →
|
|
4735
|
+
// _checkRole` is the canonical audit trace). The same node type carries
|
|
4736
|
+
// base-constructor invocations (`constructor() ERC20("T","TOK")`), the
|
|
4737
|
+
// constructor-chain hop. Emit `calls`, not `decorates`, so flow
|
|
4738
|
+
// traversal rides it.
|
|
4739
|
+
if (n.type === 'modifier_invocation') {
|
|
4740
|
+
const target = n.namedChild(0);
|
|
4741
|
+
const name = target?.type === 'identifier' ? (0, tree_sitter_helpers_1.getNodeText)(target, this.source) : undefined;
|
|
4742
|
+
if (name) {
|
|
4743
|
+
this.unresolvedReferences.push({
|
|
4744
|
+
fromNodeId: decoratedId,
|
|
4745
|
+
referenceName: name,
|
|
4746
|
+
referenceKind: 'calls',
|
|
4747
|
+
line: n.startPosition.row + 1,
|
|
4748
|
+
column: n.startPosition.column,
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4051
4753
|
// `marker_annotation` is Java's grammar for arg-less annotations
|
|
4052
4754
|
// (`@Override`, `@Deprecated`); `attribute` is Swift's grammar for
|
|
4053
4755
|
// attributes and PROPERTY WRAPPERS (`@objc`, `@Argument`, `@Published`,
|
|
@@ -4225,6 +4927,43 @@ class TreeSitterExtractor {
|
|
|
4225
4927
|
}
|
|
4226
4928
|
flush();
|
|
4227
4929
|
}
|
|
4930
|
+
/**
|
|
4931
|
+
* Record a C++ local function-pointer binding (`local = &fn` / `&fn<…>` /
|
|
4932
|
+
* `&ns::fn<…>`) for the CURRENT enclosing symbol, so calls through the local
|
|
4933
|
+
* resolve to the real target (see cppLocalFnPtrs). Only the address-of shape
|
|
4934
|
+
* is accepted — a bare-identifier RHS (`auto x = y;`) is any value copy, and
|
|
4935
|
+
* linking through it would guess.
|
|
4936
|
+
*/
|
|
4937
|
+
recordCppFnPtrBinding(localName, value) {
|
|
4938
|
+
if (!value || value.type !== 'pointer_expression')
|
|
4939
|
+
return;
|
|
4940
|
+
if (value.child(0)?.type !== '&')
|
|
4941
|
+
return; // `*p` dereference, not address-of
|
|
4942
|
+
const arg = (0, tree_sitter_helpers_1.getChildByField)(value, 'argument') ?? value.namedChild(0);
|
|
4943
|
+
if (!arg ||
|
|
4944
|
+
(arg.type !== 'identifier' &&
|
|
4945
|
+
arg.type !== 'template_function' &&
|
|
4946
|
+
arg.type !== 'qualified_identifier')) {
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
4949
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
4950
|
+
if (!callerId)
|
|
4951
|
+
return;
|
|
4952
|
+
const target = (0, c_cpp_1.stripCppTemplateArgs)((0, tree_sitter_helpers_1.getNodeText)(arg, this.source));
|
|
4953
|
+
if (!target || target === localName)
|
|
4954
|
+
return;
|
|
4955
|
+
let locals = this.cppLocalFnPtrs.get(callerId);
|
|
4956
|
+
if (!locals) {
|
|
4957
|
+
locals = new Map();
|
|
4958
|
+
this.cppLocalFnPtrs.set(callerId, locals);
|
|
4959
|
+
}
|
|
4960
|
+
let targets = locals.get(localName);
|
|
4961
|
+
if (!targets) {
|
|
4962
|
+
targets = new Set();
|
|
4963
|
+
locals.set(localName, targets);
|
|
4964
|
+
}
|
|
4965
|
+
targets.add(target);
|
|
4966
|
+
}
|
|
4228
4967
|
visitFunctionBody(body, _functionId) {
|
|
4229
4968
|
if (!this.extractor)
|
|
4230
4969
|
return;
|
|
@@ -4240,7 +4979,7 @@ class TreeSitterExtractor {
|
|
|
4240
4979
|
if (this.extractor.callTypes.includes(nodeType)) {
|
|
4241
4980
|
this.extractCall(node);
|
|
4242
4981
|
}
|
|
4243
|
-
else if (INSTANTIATION_KINDS.has(nodeType)) {
|
|
4982
|
+
else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
|
|
4244
4983
|
// `new Foo()` inside a function body — emit an `instantiates`
|
|
4245
4984
|
// reference. Without this branch the body walker only knew
|
|
4246
4985
|
// about `call_expression`, so constructor invocations
|
|
@@ -4282,6 +5021,31 @@ class TreeSitterExtractor {
|
|
|
4282
5021
|
if (nodeType === 'declaration' && this.language === 'cpp' && this.isCppStackConstruction(node)) {
|
|
4283
5022
|
this.extractInstantiation(node);
|
|
4284
5023
|
}
|
|
5024
|
+
// C++ local function-pointer bindings (see cppLocalFnPtrs): record
|
|
5025
|
+
// `auto kernel = &fn<…>;` declarations and `kernel = &other_fn<…>;`
|
|
5026
|
+
// branch reassignments so a call/launch through the local links to the
|
|
5027
|
+
// real target(s). The body walker sees these in source order, and C++
|
|
5028
|
+
// requires declaration-before-use, so the map is always populated before
|
|
5029
|
+
// the call that consumes it.
|
|
5030
|
+
if (this.language === 'cpp' && this.nodeStack.length > 0) {
|
|
5031
|
+
if (nodeType === 'declaration') {
|
|
5032
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5033
|
+
const child = node.namedChild(i);
|
|
5034
|
+
if (child?.type !== 'init_declarator')
|
|
5035
|
+
continue;
|
|
5036
|
+
const decl = (0, tree_sitter_helpers_1.getChildByField)(child, 'declarator');
|
|
5037
|
+
if (decl?.type !== 'identifier')
|
|
5038
|
+
continue;
|
|
5039
|
+
this.recordCppFnPtrBinding((0, tree_sitter_helpers_1.getNodeText)(decl, this.source), (0, tree_sitter_helpers_1.getChildByField)(child, 'value'));
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
else if (nodeType === 'assignment_expression') {
|
|
5043
|
+
const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left');
|
|
5044
|
+
if (left?.type === 'identifier') {
|
|
5045
|
+
this.recordCppFnPtrBinding((0, tree_sitter_helpers_1.getNodeText)(left, this.source), (0, tree_sitter_helpers_1.getChildByField)(node, 'right'));
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
4285
5049
|
// Static-member / value-read: `Enum.value`, `Type.CONST`, `Foo::BAR`.
|
|
4286
5050
|
this.extractStaticMemberRef(node);
|
|
4287
5051
|
// Local variable type annotations inside a body — `const items: Foo[] = []`,
|
|
@@ -4606,6 +5370,33 @@ class TreeSitterExtractor {
|
|
|
4606
5370
|
}
|
|
4607
5371
|
}
|
|
4608
5372
|
}
|
|
5373
|
+
// VB.NET: `Inherits Base` / `Implements IFoo, IBar(Of T)` are STATEMENTS
|
|
5374
|
+
// inside the class body (children of the class node), not header clauses.
|
|
5375
|
+
// Each name is a simple/qualified/generic reference; generics unwrap to
|
|
5376
|
+
// the base identifier and dotted paths keep the trailing segment.
|
|
5377
|
+
if (this.language === 'vbnet' &&
|
|
5378
|
+
(child.type === 'inherits_statement' || child.type === 'implements_statement')) {
|
|
5379
|
+
const kind = child.type === 'inherits_statement' ? 'extends' : 'implements';
|
|
5380
|
+
for (const ref of child.namedChildren) {
|
|
5381
|
+
if (!ref || (ref.type !== 'simple_name' && ref.type !== 'qualified_name' && ref.type !== 'generic_name' && ref.type !== 'global_qualified_name'))
|
|
5382
|
+
continue;
|
|
5383
|
+
let name = (0, tree_sitter_helpers_1.getNodeText)(ref, this.source);
|
|
5384
|
+
name = name.replace(/\(\s*Of\b[^)]*\)/gi, '');
|
|
5385
|
+
const lastDot = name.lastIndexOf('.');
|
|
5386
|
+
if (lastDot >= 0)
|
|
5387
|
+
name = name.slice(lastDot + 1);
|
|
5388
|
+
name = name.trim();
|
|
5389
|
+
if (!name)
|
|
5390
|
+
continue;
|
|
5391
|
+
this.unresolvedReferences.push({
|
|
5392
|
+
fromNodeId: classId,
|
|
5393
|
+
referenceName: name,
|
|
5394
|
+
referenceKind: kind,
|
|
5395
|
+
line: ref.startPosition.row + 1,
|
|
5396
|
+
column: ref.startPosition.column,
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
4609
5400
|
// C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
|
|
4610
5401
|
// base_list combines both base class and interfaces in a single colon-separated list.
|
|
4611
5402
|
// We emit all as 'extends' since the syntax doesn't distinguish them.
|
|
@@ -4681,6 +5472,33 @@ class TreeSitterExtractor {
|
|
|
4681
5472
|
if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
|
|
4682
5473
|
this.extractInheritance(child, classId);
|
|
4683
5474
|
}
|
|
5475
|
+
// CFML cfscript `component extends="Base" implements="IFoo,IBar" { ... }`
|
|
5476
|
+
// (also covers `interface extends="IBase" { ... }`, which reuses the same
|
|
5477
|
+
// component_attribute shape). Attributes are generic name=value pairs —
|
|
5478
|
+
// (identifier label, expression value) — not a dedicated extends_clause,
|
|
5479
|
+
// so filter by the label text. `implements` is a comma-separated list.
|
|
5480
|
+
if (child.type === 'component_attribute' && node.type === 'component') {
|
|
5481
|
+
const label = child.namedChildren.find((c) => c.type === 'identifier');
|
|
5482
|
+
const value = child.namedChildren.find((c) => c.type !== 'identifier');
|
|
5483
|
+
if (label && value) {
|
|
5484
|
+
const labelText = (0, tree_sitter_helpers_1.getNodeText)(label, this.source).toLowerCase();
|
|
5485
|
+
if (labelText === 'extends' || labelText === 'implements') {
|
|
5486
|
+
const valueText = (0, tree_sitter_helpers_1.getNodeText)(value, this.source).replace(/^["']|["']$/g, '');
|
|
5487
|
+
const names = labelText === 'implements'
|
|
5488
|
+
? valueText.split(',').map((s) => s.trim()).filter(Boolean)
|
|
5489
|
+
: [valueText.trim()].filter(Boolean);
|
|
5490
|
+
for (const name of names) {
|
|
5491
|
+
this.unresolvedReferences.push({
|
|
5492
|
+
fromNodeId: classId,
|
|
5493
|
+
referenceName: name,
|
|
5494
|
+
referenceKind: labelText === 'implements' ? 'implements' : 'extends',
|
|
5495
|
+
line: value.startPosition.row + 1,
|
|
5496
|
+
column: value.startPosition.column,
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
4684
5502
|
}
|
|
4685
5503
|
}
|
|
4686
5504
|
/**
|
|
@@ -4740,7 +5558,7 @@ class TreeSitterExtractor {
|
|
|
4740
5558
|
* Languages that support type annotations (TypeScript, etc.)
|
|
4741
5559
|
*/
|
|
4742
5560
|
TYPE_ANNOTATION_LANGUAGES = new Set([
|
|
4743
|
-
'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
|
|
5561
|
+
'typescript', 'tsx', 'arkts', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
|
|
4744
5562
|
]);
|
|
4745
5563
|
/**
|
|
4746
5564
|
* PHP pseudo-types and `self`/`static`/`parent` that aren't project symbols.
|
|
@@ -5634,6 +6452,17 @@ function extractFromSource(filePath, source, language, frameworkNames) {
|
|
|
5634
6452
|
const extractor = new mybatis_extractor_1.MyBatisExtractor(filePath, source);
|
|
5635
6453
|
result = extractor.extract();
|
|
5636
6454
|
}
|
|
6455
|
+
else if (detectedLanguage === 'cfml' || detectedLanguage === 'cfscript') {
|
|
6456
|
+
// Custom extractor for CFML (.cfc/.cfm) — dialect-switches between the
|
|
6457
|
+
// tag-based cfml grammar and the bare-script cfscript grammar. Standalone
|
|
6458
|
+
// `.cfs` files (language 'cfscript') are always pure script (never `<`-led),
|
|
6459
|
+
// so routing them through here too gets them the same anonymous-component
|
|
6460
|
+
// filename fallback as a bare-script `.cfc` — without it a `.cfs` whose
|
|
6461
|
+
// `component { ... }` declares no name (the grammar has no `name` field;
|
|
6462
|
+
// CFML never spells one in source) stays `<anonymous>`.
|
|
6463
|
+
const extractor = new cfml_extractor_1.CfmlExtractor(filePath, source, detectedLanguage);
|
|
6464
|
+
result = extractor.extract();
|
|
6465
|
+
}
|
|
5637
6466
|
else if ((0, grammars_1.isFileLevelOnlyLanguage)(detectedLanguage)) {
|
|
5638
6467
|
// No symbol extraction at this stage — files are tracked at the file-record
|
|
5639
6468
|
// level only. Framework extractors (Drupal routing yml, Spring `@Value`
|