circle-ir 3.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/LICENSE +15 -0
- package/README.md +200 -0
- package/configs/sinks/code_injection.yaml +672 -0
- package/configs/sinks/command.yaml +917 -0
- package/configs/sinks/deserialization.yaml +105 -0
- package/configs/sinks/ldap.yaml +136 -0
- package/configs/sinks/nodejs.json +629 -0
- package/configs/sinks/path.yaml +715 -0
- package/configs/sinks/python.json +501 -0
- package/configs/sinks/rust.json +339 -0
- package/configs/sinks/sql.yaml +233 -0
- package/configs/sinks/ssrf.yaml +160 -0
- package/configs/sinks/xpath.yaml +121 -0
- package/configs/sinks/xss.yaml +727 -0
- package/configs/sources/db_sources.yaml +90 -0
- package/configs/sources/env_sources.yaml +94 -0
- package/configs/sources/express.json +197 -0
- package/configs/sources/file_sources.yaml +164 -0
- package/configs/sources/http_sources.yaml +379 -0
- package/configs/sources/io_sources.yaml +519 -0
- package/configs/sources/network_sources.yaml +99 -0
- package/configs/sources/python.json +230 -0
- package/configs/sources/rust.json +286 -0
- package/configs/sources/spring.yaml +70 -0
- package/dist/analysis/advisory-db.d.ts +86 -0
- package/dist/analysis/advisory-db.js +104 -0
- package/dist/analysis/advisory-db.js.map +1 -0
- package/dist/analysis/cargo-parser.d.ts +42 -0
- package/dist/analysis/cargo-parser.js +102 -0
- package/dist/analysis/cargo-parser.js.map +1 -0
- package/dist/analysis/config-loader.d.ts +37 -0
- package/dist/analysis/config-loader.js +1561 -0
- package/dist/analysis/config-loader.js.map +1 -0
- package/dist/analysis/constant-propagation/ast-utils.d.ts +25 -0
- package/dist/analysis/constant-propagation/ast-utils.js +34 -0
- package/dist/analysis/constant-propagation/ast-utils.js.map +1 -0
- package/dist/analysis/constant-propagation/evaluator.d.ts +32 -0
- package/dist/analysis/constant-propagation/evaluator.js +296 -0
- package/dist/analysis/constant-propagation/evaluator.js.map +1 -0
- package/dist/analysis/constant-propagation/index.d.ts +62 -0
- package/dist/analysis/constant-propagation/index.js +152 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -0
- package/dist/analysis/constant-propagation/patterns.d.ts +8 -0
- package/dist/analysis/constant-propagation/patterns.js +126 -0
- package/dist/analysis/constant-propagation/patterns.js.map +1 -0
- package/dist/analysis/constant-propagation/propagator.d.ts +180 -0
- package/dist/analysis/constant-propagation/propagator.js +1985 -0
- package/dist/analysis/constant-propagation/propagator.js.map +1 -0
- package/dist/analysis/constant-propagation/types.d.ts +63 -0
- package/dist/analysis/constant-propagation/types.js +5 -0
- package/dist/analysis/constant-propagation/types.js.map +1 -0
- package/dist/analysis/constant-propagation.d.ts +9 -0
- package/dist/analysis/constant-propagation.js +18 -0
- package/dist/analysis/constant-propagation.js.map +1 -0
- package/dist/analysis/dependency-scanner.d.ts +79 -0
- package/dist/analysis/dependency-scanner.js +122 -0
- package/dist/analysis/dependency-scanner.js.map +1 -0
- package/dist/analysis/dfg-verifier.d.ts +116 -0
- package/dist/analysis/dfg-verifier.js +399 -0
- package/dist/analysis/dfg-verifier.js.map +1 -0
- package/dist/analysis/findings.d.ts +11 -0
- package/dist/analysis/findings.js +228 -0
- package/dist/analysis/findings.js.map +1 -0
- package/dist/analysis/index.d.ts +16 -0
- package/dist/analysis/index.js +18 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts +99 -0
- package/dist/analysis/interprocedural.js +526 -0
- package/dist/analysis/interprocedural.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +133 -0
- package/dist/analysis/path-finder.js +354 -0
- package/dist/analysis/path-finder.js.map +1 -0
- package/dist/analysis/rules.d.ts +75 -0
- package/dist/analysis/rules.js +332 -0
- package/dist/analysis/rules.js.map +1 -0
- package/dist/analysis/semver.d.ts +27 -0
- package/dist/analysis/semver.js +127 -0
- package/dist/analysis/semver.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +15 -0
- package/dist/analysis/taint-matcher.js +634 -0
- package/dist/analysis/taint-matcher.js.map +1 -0
- package/dist/analysis/taint-propagation.d.ts +67 -0
- package/dist/analysis/taint-propagation.js +298 -0
- package/dist/analysis/taint-propagation.js.map +1 -0
- package/dist/analysis/unresolved.d.ts +14 -0
- package/dist/analysis/unresolved.js +202 -0
- package/dist/analysis/unresolved.js.map +1 -0
- package/dist/analyzer.d.ts +43 -0
- package/dist/analyzer.js +1010 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/browser/circle-ir.js +16576 -0
- package/dist/browser.d.ts +38 -0
- package/dist/browser.js +38 -0
- package/dist/browser.js.map +1 -0
- package/dist/core/circle-ir-core.cjs +13626 -0
- package/dist/core/circle-ir-core.d.ts +59 -0
- package/dist/core/circle-ir-core.js +13591 -0
- package/dist/core/extractors/calls.d.ts +13 -0
- package/dist/core/extractors/calls.js +1429 -0
- package/dist/core/extractors/calls.js.map +1 -0
- package/dist/core/extractors/cfg.d.ts +9 -0
- package/dist/core/extractors/cfg.js +519 -0
- package/dist/core/extractors/cfg.js.map +1 -0
- package/dist/core/extractors/dfg.d.ts +12 -0
- package/dist/core/extractors/dfg.js +1081 -0
- package/dist/core/extractors/dfg.js.map +1 -0
- package/dist/core/extractors/exports.d.ts +14 -0
- package/dist/core/extractors/exports.js +80 -0
- package/dist/core/extractors/exports.js.map +1 -0
- package/dist/core/extractors/imports.d.ts +9 -0
- package/dist/core/extractors/imports.js +739 -0
- package/dist/core/extractors/imports.js.map +1 -0
- package/dist/core/extractors/index.d.ts +10 -0
- package/dist/core/extractors/index.js +11 -0
- package/dist/core/extractors/index.js.map +1 -0
- package/dist/core/extractors/meta.d.ts +10 -0
- package/dist/core/extractors/meta.js +109 -0
- package/dist/core/extractors/meta.js.map +1 -0
- package/dist/core/extractors/types.d.ts +10 -0
- package/dist/core/extractors/types.js +1479 -0
- package/dist/core/extractors/types.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/parser.d.ts +84 -0
- package/dist/core/parser.js +250 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core-lib.d.ts +59 -0
- package/dist/core-lib.js +62 -0
- package/dist/core-lib.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +14 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/plugins/base.d.ts +44 -0
- package/dist/languages/plugins/base.js +82 -0
- package/dist/languages/plugins/base.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +14 -0
- package/dist/languages/plugins/index.js +25 -0
- package/dist/languages/plugins/index.js.map +1 -0
- package/dist/languages/plugins/java.d.ts +49 -0
- package/dist/languages/plugins/java.js +402 -0
- package/dist/languages/plugins/java.js.map +1 -0
- package/dist/languages/plugins/javascript.d.ts +48 -0
- package/dist/languages/plugins/javascript.js +445 -0
- package/dist/languages/plugins/javascript.js.map +1 -0
- package/dist/languages/plugins/python.d.ts +47 -0
- package/dist/languages/plugins/python.js +480 -0
- package/dist/languages/plugins/python.js.map +1 -0
- package/dist/languages/plugins/rust.d.ts +47 -0
- package/dist/languages/plugins/rust.js +405 -0
- package/dist/languages/plugins/rust.js.map +1 -0
- package/dist/languages/registry.d.ts +30 -0
- package/dist/languages/registry.js +80 -0
- package/dist/languages/registry.js.map +1 -0
- package/dist/languages/types.d.ts +184 -0
- package/dist/languages/types.js +8 -0
- package/dist/languages/types.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +146 -0
- package/dist/resolution/cross-file.js +439 -0
- package/dist/resolution/cross-file.js.map +1 -0
- package/dist/resolution/index.d.ts +12 -0
- package/dist/resolution/index.js +10 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/symbol-table.d.ts +136 -0
- package/dist/resolution/symbol-table.js +336 -0
- package/dist/resolution/symbol-table.js.map +1 -0
- package/dist/resolution/type-hierarchy.d.ts +124 -0
- package/dist/resolution/type-hierarchy.js +515 -0
- package/dist/resolution/type-hierarchy.js.map +1 -0
- package/dist/types/config.d.ts +45 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +392 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +85 -0
- package/dist/utils/logger.js +198 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/docs/SPEC.md +1021 -0
- package/examples/browser-example.html +610 -0
- package/examples/node-example.ts +215 -0
- package/package.json +107 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DFG (Data Flow Graph) builder
|
|
3
|
+
*
|
|
4
|
+
* Tracks variable definitions and uses for data flow analysis.
|
|
5
|
+
*/
|
|
6
|
+
import { findNodes, walkTree, getNodeText, getNodesFromCache } from '../parser.js';
|
|
7
|
+
/**
|
|
8
|
+
* Detect language from tree structure.
|
|
9
|
+
*/
|
|
10
|
+
function detectLanguage(tree) {
|
|
11
|
+
const root = tree.rootNode;
|
|
12
|
+
// Check for JavaScript-specific nodes
|
|
13
|
+
const jsNodeTypes = new Set([
|
|
14
|
+
'arrow_function', 'lexical_declaration', 'function_declaration',
|
|
15
|
+
'export_statement', 'import_statement', 'const', 'let'
|
|
16
|
+
]);
|
|
17
|
+
// Check for Java-specific nodes
|
|
18
|
+
const javaNodeTypes = new Set([
|
|
19
|
+
'package_declaration', 'import_declaration', 'class_declaration',
|
|
20
|
+
'method_declaration', 'annotation', 'throws'
|
|
21
|
+
]);
|
|
22
|
+
let jsScore = 0;
|
|
23
|
+
let javaScore = 0;
|
|
24
|
+
// Walk first level children to detect language
|
|
25
|
+
for (let i = 0; i < Math.min(root.childCount, 20); i++) {
|
|
26
|
+
const child = root.child(i);
|
|
27
|
+
if (!child)
|
|
28
|
+
continue;
|
|
29
|
+
if (jsNodeTypes.has(child.type))
|
|
30
|
+
jsScore++;
|
|
31
|
+
if (javaNodeTypes.has(child.type))
|
|
32
|
+
javaScore++;
|
|
33
|
+
// Check deeper for function bodies
|
|
34
|
+
if (child.type === 'expression_statement') {
|
|
35
|
+
const expr = child.child(0);
|
|
36
|
+
if (expr?.type === 'call_expression')
|
|
37
|
+
jsScore++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return jsScore > javaScore ? 'javascript' : 'java';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build DFG for all methods in the tree.
|
|
44
|
+
*/
|
|
45
|
+
export function buildDFG(tree, cache, language) {
|
|
46
|
+
// Auto-detect language if not provided
|
|
47
|
+
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
48
|
+
const isJavaScript = effectiveLanguage === 'javascript' || effectiveLanguage === 'typescript';
|
|
49
|
+
if (isJavaScript) {
|
|
50
|
+
return buildJavaScriptDFG(tree, cache);
|
|
51
|
+
}
|
|
52
|
+
if (effectiveLanguage === 'rust') {
|
|
53
|
+
return buildRustDFG(tree, cache);
|
|
54
|
+
}
|
|
55
|
+
return buildJavaDFG(tree, cache);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build DFG for Java code.
|
|
59
|
+
*/
|
|
60
|
+
function buildJavaDFG(tree, cache) {
|
|
61
|
+
const defs = [];
|
|
62
|
+
const uses = [];
|
|
63
|
+
let defIdCounter = 1;
|
|
64
|
+
let useIdCounter = 1;
|
|
65
|
+
// Track definitions by variable name and scope for reaching definitions
|
|
66
|
+
const scopeStack = [new Map()];
|
|
67
|
+
// Find all method/constructor bodies
|
|
68
|
+
const methods = [
|
|
69
|
+
...getNodesFromCache(tree.rootNode, 'method_declaration', cache),
|
|
70
|
+
...getNodesFromCache(tree.rootNode, 'constructor_declaration', cache),
|
|
71
|
+
];
|
|
72
|
+
for (const method of methods) {
|
|
73
|
+
// Start new scope for method
|
|
74
|
+
scopeStack.push(new Map());
|
|
75
|
+
// Extract parameters as definitions
|
|
76
|
+
const params = method.childForFieldName('parameters');
|
|
77
|
+
if (params) {
|
|
78
|
+
const paramDefs = extractParameterDefs(params, defIdCounter);
|
|
79
|
+
for (const def of paramDefs) {
|
|
80
|
+
defs.push(def);
|
|
81
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
82
|
+
defIdCounter++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Process method body
|
|
86
|
+
const body = method.childForFieldName('body');
|
|
87
|
+
if (body) {
|
|
88
|
+
const result = processBlock(body, defIdCounter, useIdCounter, scopeStack, false);
|
|
89
|
+
defs.push(...result.defs);
|
|
90
|
+
uses.push(...result.uses);
|
|
91
|
+
defIdCounter = result.nextDefId;
|
|
92
|
+
useIdCounter = result.nextUseId;
|
|
93
|
+
}
|
|
94
|
+
// Pop method scope
|
|
95
|
+
scopeStack.pop();
|
|
96
|
+
}
|
|
97
|
+
// Also extract field definitions
|
|
98
|
+
const classes = getNodesFromCache(tree.rootNode, 'class_declaration', cache);
|
|
99
|
+
for (const cls of classes) {
|
|
100
|
+
const body = cls.childForFieldName('body');
|
|
101
|
+
if (body) {
|
|
102
|
+
const fieldDefs = extractFieldDefs(body, defIdCounter);
|
|
103
|
+
for (const def of fieldDefs) {
|
|
104
|
+
defs.push(def);
|
|
105
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
106
|
+
defIdCounter++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Compute def-use chains
|
|
111
|
+
const chains = computeChains(defs, uses);
|
|
112
|
+
return { defs, uses, chains };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build DFG for JavaScript/TypeScript code.
|
|
116
|
+
*/
|
|
117
|
+
function buildJavaScriptDFG(tree, cache) {
|
|
118
|
+
const defs = [];
|
|
119
|
+
const uses = [];
|
|
120
|
+
let defIdCounter = 1;
|
|
121
|
+
let useIdCounter = 1;
|
|
122
|
+
// Track definitions by variable name and scope for reaching definitions
|
|
123
|
+
const scopeStack = [new Map()];
|
|
124
|
+
// Find all function bodies (function declarations, arrow functions, method definitions)
|
|
125
|
+
const functions = [
|
|
126
|
+
...getNodesFromCache(tree.rootNode, 'function_declaration', cache),
|
|
127
|
+
...getNodesFromCache(tree.rootNode, 'arrow_function', cache),
|
|
128
|
+
...getNodesFromCache(tree.rootNode, 'method_definition', cache),
|
|
129
|
+
...getNodesFromCache(tree.rootNode, 'function', cache),
|
|
130
|
+
...getNodesFromCache(tree.rootNode, 'function_expression', cache),
|
|
131
|
+
];
|
|
132
|
+
for (const func of functions) {
|
|
133
|
+
// Start new scope for function
|
|
134
|
+
scopeStack.push(new Map());
|
|
135
|
+
// Extract parameters as definitions
|
|
136
|
+
const params = func.childForFieldName('parameters') ?? func.childForFieldName('parameter');
|
|
137
|
+
if (params) {
|
|
138
|
+
const paramDefs = extractJSParameterDefs(params, defIdCounter);
|
|
139
|
+
for (const def of paramDefs) {
|
|
140
|
+
defs.push(def);
|
|
141
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
142
|
+
defIdCounter++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Process function body
|
|
146
|
+
const body = func.childForFieldName('body');
|
|
147
|
+
if (body) {
|
|
148
|
+
if (body.type === 'statement_block') {
|
|
149
|
+
const result = processBlock(body, defIdCounter, useIdCounter, scopeStack, true);
|
|
150
|
+
defs.push(...result.defs);
|
|
151
|
+
uses.push(...result.uses);
|
|
152
|
+
defIdCounter = result.nextDefId;
|
|
153
|
+
useIdCounter = result.nextUseId;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Arrow function with expression body
|
|
157
|
+
const exprUses = extractUses(body, useIdCounter, scopeStack, true);
|
|
158
|
+
uses.push(...exprUses.uses);
|
|
159
|
+
useIdCounter = exprUses.nextId;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Pop function scope
|
|
163
|
+
scopeStack.pop();
|
|
164
|
+
}
|
|
165
|
+
// Process top-level variable declarations
|
|
166
|
+
const topLevelDecls = [
|
|
167
|
+
...getNodesFromCache(tree.rootNode, 'lexical_declaration', cache),
|
|
168
|
+
...getNodesFromCache(tree.rootNode, 'variable_declaration', cache),
|
|
169
|
+
].filter(node => node.parent?.type === 'program' || node.parent?.type === 'export_statement');
|
|
170
|
+
for (const decl of topLevelDecls) {
|
|
171
|
+
const declarators = findNodes(decl, 'variable_declarator');
|
|
172
|
+
for (const declarator of declarators) {
|
|
173
|
+
const nameNode = declarator.childForFieldName('name');
|
|
174
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
175
|
+
const name = getNodeText(nameNode);
|
|
176
|
+
const def = {
|
|
177
|
+
id: defIdCounter++,
|
|
178
|
+
variable: name,
|
|
179
|
+
line: declarator.startPosition.row + 1,
|
|
180
|
+
kind: 'local',
|
|
181
|
+
};
|
|
182
|
+
defs.push(def);
|
|
183
|
+
currentScope(scopeStack).set(name, def.id);
|
|
184
|
+
// Process initializer for uses
|
|
185
|
+
const init = declarator.childForFieldName('value');
|
|
186
|
+
if (init) {
|
|
187
|
+
const initUses = extractUses(init, useIdCounter, scopeStack, true);
|
|
188
|
+
uses.push(...initUses.uses);
|
|
189
|
+
useIdCounter = initUses.nextId;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Also extract class field definitions
|
|
195
|
+
const classes = getNodesFromCache(tree.rootNode, 'class_declaration', cache);
|
|
196
|
+
for (const cls of classes) {
|
|
197
|
+
const body = cls.childForFieldName('body');
|
|
198
|
+
if (body) {
|
|
199
|
+
const fieldDefs = extractJSFieldDefs(body, defIdCounter);
|
|
200
|
+
for (const def of fieldDefs) {
|
|
201
|
+
defs.push(def);
|
|
202
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
203
|
+
defIdCounter++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Compute def-use chains
|
|
208
|
+
const chains = computeChains(defs, uses);
|
|
209
|
+
return { defs, uses, chains };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Process a block of statements.
|
|
213
|
+
*/
|
|
214
|
+
function processBlock(block, startDefId, startUseId, scopeStack, isJavaScript) {
|
|
215
|
+
const defs = [];
|
|
216
|
+
const uses = [];
|
|
217
|
+
let defId = startDefId;
|
|
218
|
+
let useId = startUseId;
|
|
219
|
+
// Process each statement
|
|
220
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
221
|
+
const stmt = block.child(i);
|
|
222
|
+
if (!stmt)
|
|
223
|
+
continue;
|
|
224
|
+
const result = processStatement(stmt, defId, useId, scopeStack, isJavaScript);
|
|
225
|
+
defs.push(...result.defs);
|
|
226
|
+
uses.push(...result.uses);
|
|
227
|
+
defId = result.nextDefId;
|
|
228
|
+
useId = result.nextUseId;
|
|
229
|
+
}
|
|
230
|
+
return { defs, uses, nextDefId: defId, nextUseId: useId };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Process a single statement.
|
|
234
|
+
*/
|
|
235
|
+
function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript) {
|
|
236
|
+
const defs = [];
|
|
237
|
+
const uses = [];
|
|
238
|
+
let defId = startDefId;
|
|
239
|
+
let useId = startUseId;
|
|
240
|
+
switch (stmt.type) {
|
|
241
|
+
case 'local_variable_declaration':
|
|
242
|
+
case 'lexical_declaration':
|
|
243
|
+
case 'variable_declaration': {
|
|
244
|
+
// Extract variable definitions (Java: local_variable_declaration, JS: lexical_declaration/variable_declaration)
|
|
245
|
+
const declarators = findNodes(stmt, 'variable_declarator');
|
|
246
|
+
for (const decl of declarators) {
|
|
247
|
+
const nameNode = decl.childForFieldName('name');
|
|
248
|
+
if (nameNode) {
|
|
249
|
+
// Handle simple identifiers
|
|
250
|
+
if (nameNode.type === 'identifier') {
|
|
251
|
+
const name = getNodeText(nameNode);
|
|
252
|
+
const def = {
|
|
253
|
+
id: defId++,
|
|
254
|
+
variable: name,
|
|
255
|
+
line: decl.startPosition.row + 1,
|
|
256
|
+
kind: 'local',
|
|
257
|
+
};
|
|
258
|
+
defs.push(def);
|
|
259
|
+
currentScope(scopeStack).set(name, def.id);
|
|
260
|
+
}
|
|
261
|
+
else if (isJavaScript) {
|
|
262
|
+
// Handle destructuring patterns in JavaScript
|
|
263
|
+
const destructDefs = extractDestructuringDefs(nameNode, defId);
|
|
264
|
+
for (const def of destructDefs) {
|
|
265
|
+
defs.push(def);
|
|
266
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
267
|
+
defId++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Process initializer for uses
|
|
271
|
+
const init = decl.childForFieldName('value');
|
|
272
|
+
if (init) {
|
|
273
|
+
const initUses = extractUses(init, useId, scopeStack, isJavaScript);
|
|
274
|
+
uses.push(...initUses.uses);
|
|
275
|
+
useId = initUses.nextId;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case 'expression_statement': {
|
|
282
|
+
// Process the expression for defs and uses
|
|
283
|
+
const expr = stmt.child(0);
|
|
284
|
+
if (expr) {
|
|
285
|
+
const result = processExpression(expr, defId, useId, scopeStack, isJavaScript);
|
|
286
|
+
defs.push(...result.defs);
|
|
287
|
+
uses.push(...result.uses);
|
|
288
|
+
defId = result.nextDefId;
|
|
289
|
+
useId = result.nextUseId;
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'return_statement': {
|
|
294
|
+
// Extract uses from return expression
|
|
295
|
+
const returnExpr = stmt.child(1); // Skip 'return' keyword
|
|
296
|
+
if (returnExpr && returnExpr.type !== ';') {
|
|
297
|
+
const exprUses = extractUses(returnExpr, useId, scopeStack, isJavaScript);
|
|
298
|
+
uses.push(...exprUses.uses);
|
|
299
|
+
useId = exprUses.nextId;
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'if_statement': {
|
|
304
|
+
// Process condition
|
|
305
|
+
const condition = stmt.childForFieldName('condition');
|
|
306
|
+
if (condition) {
|
|
307
|
+
const condUses = extractUses(condition, useId, scopeStack, isJavaScript);
|
|
308
|
+
uses.push(...condUses.uses);
|
|
309
|
+
useId = condUses.nextId;
|
|
310
|
+
}
|
|
311
|
+
// Process branches
|
|
312
|
+
const consequence = stmt.childForFieldName('consequence');
|
|
313
|
+
if (consequence) {
|
|
314
|
+
const result = processStatement(consequence, defId, useId, scopeStack, isJavaScript);
|
|
315
|
+
defs.push(...result.defs);
|
|
316
|
+
uses.push(...result.uses);
|
|
317
|
+
defId = result.nextDefId;
|
|
318
|
+
useId = result.nextUseId;
|
|
319
|
+
}
|
|
320
|
+
const alternative = stmt.childForFieldName('alternative');
|
|
321
|
+
if (alternative) {
|
|
322
|
+
const result = processStatement(alternative, defId, useId, scopeStack, isJavaScript);
|
|
323
|
+
defs.push(...result.defs);
|
|
324
|
+
uses.push(...result.uses);
|
|
325
|
+
defId = result.nextDefId;
|
|
326
|
+
useId = result.nextUseId;
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case 'for_statement':
|
|
331
|
+
case 'enhanced_for_statement':
|
|
332
|
+
case 'for_in_statement':
|
|
333
|
+
case 'for_of_statement':
|
|
334
|
+
case 'while_statement':
|
|
335
|
+
case 'do_statement': {
|
|
336
|
+
// Process loop variable definitions (Java: enhanced for, JS: for-in/for-of)
|
|
337
|
+
if (stmt.type === 'enhanced_for_statement') {
|
|
338
|
+
const varName = stmt.childForFieldName('name');
|
|
339
|
+
if (varName) {
|
|
340
|
+
const def = {
|
|
341
|
+
id: defId++,
|
|
342
|
+
variable: getNodeText(varName),
|
|
343
|
+
line: varName.startPosition.row + 1,
|
|
344
|
+
kind: 'local',
|
|
345
|
+
};
|
|
346
|
+
defs.push(def);
|
|
347
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
348
|
+
}
|
|
349
|
+
const value = stmt.childForFieldName('value');
|
|
350
|
+
if (value) {
|
|
351
|
+
const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
|
|
352
|
+
uses.push(...valueUses.uses);
|
|
353
|
+
useId = valueUses.nextId;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (isJavaScript && (stmt.type === 'for_in_statement' || stmt.type === 'for_of_statement')) {
|
|
357
|
+
// Handle for-in/for-of in JavaScript
|
|
358
|
+
const left = stmt.childForFieldName('left');
|
|
359
|
+
if (left) {
|
|
360
|
+
if (left.type === 'identifier') {
|
|
361
|
+
const def = {
|
|
362
|
+
id: defId++,
|
|
363
|
+
variable: getNodeText(left),
|
|
364
|
+
line: left.startPosition.row + 1,
|
|
365
|
+
kind: 'local',
|
|
366
|
+
};
|
|
367
|
+
defs.push(def);
|
|
368
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
369
|
+
}
|
|
370
|
+
else if (left.type === 'lexical_declaration' || left.type === 'variable_declaration') {
|
|
371
|
+
// const x of array
|
|
372
|
+
const declarators = findNodes(left, 'variable_declarator');
|
|
373
|
+
for (const decl of declarators) {
|
|
374
|
+
const nameNode = decl.childForFieldName('name');
|
|
375
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
376
|
+
const def = {
|
|
377
|
+
id: defId++,
|
|
378
|
+
variable: getNodeText(nameNode),
|
|
379
|
+
line: nameNode.startPosition.row + 1,
|
|
380
|
+
kind: 'local',
|
|
381
|
+
};
|
|
382
|
+
defs.push(def);
|
|
383
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const right = stmt.childForFieldName('right');
|
|
389
|
+
if (right) {
|
|
390
|
+
const rightUses = extractUses(right, useId, scopeStack, isJavaScript);
|
|
391
|
+
uses.push(...rightUses.uses);
|
|
392
|
+
useId = rightUses.nextId;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Process condition
|
|
396
|
+
const condition = stmt.childForFieldName('condition');
|
|
397
|
+
if (condition) {
|
|
398
|
+
const condUses = extractUses(condition, useId, scopeStack, isJavaScript);
|
|
399
|
+
uses.push(...condUses.uses);
|
|
400
|
+
useId = condUses.nextId;
|
|
401
|
+
}
|
|
402
|
+
// Process body
|
|
403
|
+
const body = stmt.childForFieldName('body');
|
|
404
|
+
if (body) {
|
|
405
|
+
const result = processStatement(body, defId, useId, scopeStack, isJavaScript);
|
|
406
|
+
defs.push(...result.defs);
|
|
407
|
+
uses.push(...result.uses);
|
|
408
|
+
defId = result.nextDefId;
|
|
409
|
+
useId = result.nextUseId;
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case 'block':
|
|
414
|
+
case 'statement_block': {
|
|
415
|
+
// New scope for block
|
|
416
|
+
scopeStack.push(new Map());
|
|
417
|
+
const result = processBlock(stmt, defId, useId, scopeStack, isJavaScript);
|
|
418
|
+
defs.push(...result.defs);
|
|
419
|
+
uses.push(...result.uses);
|
|
420
|
+
defId = result.nextDefId;
|
|
421
|
+
useId = result.nextUseId;
|
|
422
|
+
scopeStack.pop();
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
default: {
|
|
426
|
+
// For other statements, just extract uses
|
|
427
|
+
const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
|
|
428
|
+
uses.push(...stmtUses.uses);
|
|
429
|
+
useId = stmtUses.nextId;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { defs, uses, nextDefId: defId, nextUseId: useId };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Process an expression for definitions and uses.
|
|
436
|
+
*/
|
|
437
|
+
function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScript) {
|
|
438
|
+
const defs = [];
|
|
439
|
+
const uses = [];
|
|
440
|
+
let defId = startDefId;
|
|
441
|
+
let useId = startUseId;
|
|
442
|
+
if (expr.type === 'assignment_expression') {
|
|
443
|
+
// For chained assignments like o1 = o2 = o3 = value, we need to:
|
|
444
|
+
// 1. Process the right side first (to handle nested assignments)
|
|
445
|
+
// 2. Then create a def for the left side
|
|
446
|
+
const left = expr.childForFieldName('left');
|
|
447
|
+
const right = expr.childForFieldName('right');
|
|
448
|
+
// Process right side first - may contain nested assignments
|
|
449
|
+
if (right) {
|
|
450
|
+
if (right.type === 'assignment_expression') {
|
|
451
|
+
// Recursively process nested assignment expression
|
|
452
|
+
const result = processExpression(right, defId, useId, scopeStack, isJavaScript);
|
|
453
|
+
defs.push(...result.defs);
|
|
454
|
+
uses.push(...result.uses);
|
|
455
|
+
defId = result.nextDefId;
|
|
456
|
+
useId = result.nextUseId;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
// Normal right side - extract uses
|
|
460
|
+
const rightUses = extractUses(right, useId, scopeStack, isJavaScript);
|
|
461
|
+
uses.push(...rightUses.uses);
|
|
462
|
+
useId = rightUses.nextId;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Now process left side as a definition
|
|
466
|
+
if (left && left.type === 'identifier') {
|
|
467
|
+
const name = getNodeText(left);
|
|
468
|
+
const def = {
|
|
469
|
+
id: defId++,
|
|
470
|
+
variable: name,
|
|
471
|
+
line: left.startPosition.row + 1,
|
|
472
|
+
kind: 'local',
|
|
473
|
+
};
|
|
474
|
+
defs.push(def);
|
|
475
|
+
currentScope(scopeStack).set(name, def.id);
|
|
476
|
+
}
|
|
477
|
+
else if (left) {
|
|
478
|
+
// Field access or array access - extract uses
|
|
479
|
+
const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
|
|
480
|
+
uses.push(...leftUses.uses);
|
|
481
|
+
useId = leftUses.nextId;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else if (expr.type === 'update_expression') {
|
|
485
|
+
// ++i or i++ is both a use and a def
|
|
486
|
+
const operand = expr.childForFieldName('operand') ?? expr.child(0) ?? expr.child(1);
|
|
487
|
+
if (operand && operand.type === 'identifier') {
|
|
488
|
+
const name = getNodeText(operand);
|
|
489
|
+
// Use first
|
|
490
|
+
const reachingDef = findReachingDef(name, scopeStack);
|
|
491
|
+
const use = {
|
|
492
|
+
id: useId++,
|
|
493
|
+
variable: name,
|
|
494
|
+
line: operand.startPosition.row + 1,
|
|
495
|
+
def_id: reachingDef,
|
|
496
|
+
};
|
|
497
|
+
uses.push(use);
|
|
498
|
+
// Then def
|
|
499
|
+
const def = {
|
|
500
|
+
id: defId++,
|
|
501
|
+
variable: name,
|
|
502
|
+
line: operand.startPosition.row + 1,
|
|
503
|
+
kind: 'local',
|
|
504
|
+
};
|
|
505
|
+
defs.push(def);
|
|
506
|
+
currentScope(scopeStack).set(name, def.id);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Other expressions - just extract uses
|
|
511
|
+
const exprUses = extractUses(expr, useId, scopeStack, isJavaScript);
|
|
512
|
+
uses.push(...exprUses.uses);
|
|
513
|
+
useId = exprUses.nextId;
|
|
514
|
+
}
|
|
515
|
+
return { defs, uses, nextDefId: defId, nextUseId: useId };
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Extract uses from an expression.
|
|
519
|
+
*/
|
|
520
|
+
function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
521
|
+
const uses = [];
|
|
522
|
+
let id = startId;
|
|
523
|
+
walkTree(node, (n) => {
|
|
524
|
+
if (n.type === 'identifier') {
|
|
525
|
+
// Check if it's not a method name or type name
|
|
526
|
+
const parent = n.parent;
|
|
527
|
+
if (parent) {
|
|
528
|
+
// Skip if it's a method name (Java: method_invocation, JS: call_expression)
|
|
529
|
+
if (parent.type === 'method_invocation') {
|
|
530
|
+
const nameNode = parent.childForFieldName('name');
|
|
531
|
+
if (nameNode === n)
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (isJavaScript && parent.type === 'call_expression') {
|
|
535
|
+
const funcNode = parent.childForFieldName('function');
|
|
536
|
+
if (funcNode === n)
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
// Skip if it's a property name in member expression (JS)
|
|
540
|
+
if (isJavaScript && parent.type === 'member_expression') {
|
|
541
|
+
const propNode = parent.childForFieldName('property');
|
|
542
|
+
if (propNode === n)
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// Skip if it's a type name
|
|
546
|
+
if (parent.type === 'type_identifier' ||
|
|
547
|
+
parent.type === 'class_declaration' ||
|
|
548
|
+
parent.type === 'interface_declaration') {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
// Skip if it's a field name in declaration
|
|
552
|
+
if (parent.type === 'variable_declarator') {
|
|
553
|
+
const nameNode = parent.childForFieldName('name');
|
|
554
|
+
if (nameNode === n)
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
// Skip if it's a property name in object (JS)
|
|
558
|
+
if (isJavaScript && parent.type === 'pair') {
|
|
559
|
+
const keyNode = parent.childForFieldName('key');
|
|
560
|
+
if (keyNode === n)
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// Skip if it's a shorthand property in object (JS)
|
|
564
|
+
if (isJavaScript && parent.type === 'shorthand_property_identifier_pattern') {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
// Skip if it's a function/method name (JS)
|
|
568
|
+
if (isJavaScript && (parent.type === 'function_declaration' || parent.type === 'method_definition')) {
|
|
569
|
+
const nameNode = parent.childForFieldName('name');
|
|
570
|
+
if (nameNode === n)
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
// Skip if it's a formal parameter name
|
|
574
|
+
if (parent.type === 'formal_parameter' || parent.type === 'required_parameter' || parent.type === 'optional_parameter') {
|
|
575
|
+
const nameNode = parent.childForFieldName('name') ?? parent.childForFieldName('pattern');
|
|
576
|
+
if (nameNode === n)
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const name = getNodeText(n);
|
|
581
|
+
const reachingDef = findReachingDef(name, scopeStack);
|
|
582
|
+
uses.push({
|
|
583
|
+
id: id++,
|
|
584
|
+
variable: name,
|
|
585
|
+
line: n.startPosition.row + 1,
|
|
586
|
+
def_id: reachingDef,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
return { uses, nextId: id };
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Extract parameter definitions.
|
|
594
|
+
*/
|
|
595
|
+
function extractParameterDefs(params, startId) {
|
|
596
|
+
const defs = [];
|
|
597
|
+
let id = startId;
|
|
598
|
+
for (let i = 0; i < params.childCount; i++) {
|
|
599
|
+
const param = params.child(i);
|
|
600
|
+
if (!param)
|
|
601
|
+
continue;
|
|
602
|
+
if (param.type === 'formal_parameter' || param.type === 'spread_parameter') {
|
|
603
|
+
const nameNode = param.childForFieldName('name');
|
|
604
|
+
if (nameNode) {
|
|
605
|
+
defs.push({
|
|
606
|
+
id: id++,
|
|
607
|
+
variable: getNodeText(nameNode),
|
|
608
|
+
line: param.startPosition.row + 1,
|
|
609
|
+
kind: 'param',
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return defs;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Extract field definitions from a class body.
|
|
618
|
+
*/
|
|
619
|
+
function extractFieldDefs(body, startId) {
|
|
620
|
+
const defs = [];
|
|
621
|
+
let id = startId;
|
|
622
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
623
|
+
const child = body.child(i);
|
|
624
|
+
if (!child)
|
|
625
|
+
continue;
|
|
626
|
+
if (child.type === 'field_declaration') {
|
|
627
|
+
const declarators = findNodes(child, 'variable_declarator');
|
|
628
|
+
for (const decl of declarators) {
|
|
629
|
+
const nameNode = decl.childForFieldName('name');
|
|
630
|
+
if (nameNode) {
|
|
631
|
+
defs.push({
|
|
632
|
+
id: id++,
|
|
633
|
+
variable: getNodeText(nameNode),
|
|
634
|
+
line: decl.startPosition.row + 1,
|
|
635
|
+
kind: 'field',
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return defs;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Extract JavaScript parameter definitions.
|
|
645
|
+
* Handles: simple params, default params, rest params, destructuring patterns
|
|
646
|
+
*/
|
|
647
|
+
function extractJSParameterDefs(params, startId) {
|
|
648
|
+
const defs = [];
|
|
649
|
+
let id = startId;
|
|
650
|
+
for (let i = 0; i < params.childCount; i++) {
|
|
651
|
+
const param = params.child(i);
|
|
652
|
+
if (!param)
|
|
653
|
+
continue;
|
|
654
|
+
// Skip punctuation
|
|
655
|
+
if (param.type === ',' || param.type === '(' || param.type === ')')
|
|
656
|
+
continue;
|
|
657
|
+
// Simple identifier parameter
|
|
658
|
+
if (param.type === 'identifier') {
|
|
659
|
+
defs.push({
|
|
660
|
+
id: id++,
|
|
661
|
+
variable: getNodeText(param),
|
|
662
|
+
line: param.startPosition.row + 1,
|
|
663
|
+
kind: 'param',
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
// Rest parameter (...args)
|
|
667
|
+
else if (param.type === 'rest_pattern' || param.type === 'rest_element') {
|
|
668
|
+
const nameNode = param.namedChildCount > 0 ? param.namedChild(0) : null;
|
|
669
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
670
|
+
defs.push({
|
|
671
|
+
id: id++,
|
|
672
|
+
variable: getNodeText(nameNode),
|
|
673
|
+
line: param.startPosition.row + 1,
|
|
674
|
+
kind: 'param',
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Assignment pattern (default value): name = defaultValue
|
|
679
|
+
else if (param.type === 'assignment_pattern') {
|
|
680
|
+
const leftNode = param.childForFieldName('left');
|
|
681
|
+
if (leftNode && leftNode.type === 'identifier') {
|
|
682
|
+
defs.push({
|
|
683
|
+
id: id++,
|
|
684
|
+
variable: getNodeText(leftNode),
|
|
685
|
+
line: param.startPosition.row + 1,
|
|
686
|
+
kind: 'param',
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
else if (leftNode) {
|
|
690
|
+
// Could be destructuring with default
|
|
691
|
+
const destructDefs = extractDestructuringDefs(leftNode, id);
|
|
692
|
+
for (const def of destructDefs) {
|
|
693
|
+
def.kind = 'param';
|
|
694
|
+
defs.push(def);
|
|
695
|
+
id++;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Destructuring pattern
|
|
700
|
+
else if (param.type === 'object_pattern' || param.type === 'array_pattern') {
|
|
701
|
+
const destructDefs = extractDestructuringDefs(param, id);
|
|
702
|
+
for (const def of destructDefs) {
|
|
703
|
+
def.kind = 'param';
|
|
704
|
+
defs.push(def);
|
|
705
|
+
id++;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
// TypeScript formal parameters
|
|
709
|
+
else if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
|
|
710
|
+
const patternNode = param.childForFieldName('pattern');
|
|
711
|
+
if (patternNode && patternNode.type === 'identifier') {
|
|
712
|
+
defs.push({
|
|
713
|
+
id: id++,
|
|
714
|
+
variable: getNodeText(patternNode),
|
|
715
|
+
line: param.startPosition.row + 1,
|
|
716
|
+
kind: 'param',
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return defs;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Extract JavaScript field definitions from a class body.
|
|
725
|
+
*/
|
|
726
|
+
function extractJSFieldDefs(body, startId) {
|
|
727
|
+
const defs = [];
|
|
728
|
+
let id = startId;
|
|
729
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
730
|
+
const child = body.child(i);
|
|
731
|
+
if (!child)
|
|
732
|
+
continue;
|
|
733
|
+
// Class field definition (public_field_definition in tree-sitter-javascript)
|
|
734
|
+
if (child.type === 'public_field_definition' || child.type === 'field_definition') {
|
|
735
|
+
const nameNode = child.childForFieldName('name');
|
|
736
|
+
if (nameNode && nameNode.type === 'property_identifier') {
|
|
737
|
+
defs.push({
|
|
738
|
+
id: id++,
|
|
739
|
+
variable: getNodeText(nameNode),
|
|
740
|
+
line: child.startPosition.row + 1,
|
|
741
|
+
kind: 'field',
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return defs;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Extract definitions from destructuring patterns.
|
|
750
|
+
*/
|
|
751
|
+
function extractDestructuringDefs(node, startId) {
|
|
752
|
+
const defs = [];
|
|
753
|
+
let id = startId;
|
|
754
|
+
if (node.type === 'object_pattern') {
|
|
755
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
756
|
+
const child = node.namedChild(i);
|
|
757
|
+
if (!child)
|
|
758
|
+
continue;
|
|
759
|
+
if (child.type === 'shorthand_property_identifier_pattern') {
|
|
760
|
+
// { foo } shorthand
|
|
761
|
+
defs.push({
|
|
762
|
+
id: id++,
|
|
763
|
+
variable: getNodeText(child),
|
|
764
|
+
line: child.startPosition.row + 1,
|
|
765
|
+
kind: 'local',
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
else if (child.type === 'pair_pattern') {
|
|
769
|
+
// { key: value }
|
|
770
|
+
const valueNode = child.childForFieldName('value');
|
|
771
|
+
if (valueNode && valueNode.type === 'identifier') {
|
|
772
|
+
defs.push({
|
|
773
|
+
id: id++,
|
|
774
|
+
variable: getNodeText(valueNode),
|
|
775
|
+
line: valueNode.startPosition.row + 1,
|
|
776
|
+
kind: 'local',
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else if (valueNode) {
|
|
780
|
+
// Nested destructuring
|
|
781
|
+
const nestedDefs = extractDestructuringDefs(valueNode, id);
|
|
782
|
+
defs.push(...nestedDefs);
|
|
783
|
+
id += nestedDefs.length;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
else if (child.type === 'rest_pattern') {
|
|
787
|
+
// { ...rest }
|
|
788
|
+
const nameNode = child.namedChildCount > 0 ? child.namedChild(0) : null;
|
|
789
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
790
|
+
defs.push({
|
|
791
|
+
id: id++,
|
|
792
|
+
variable: getNodeText(nameNode),
|
|
793
|
+
line: child.startPosition.row + 1,
|
|
794
|
+
kind: 'local',
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
else if (child.type === 'assignment_pattern') {
|
|
799
|
+
// { foo = defaultValue }
|
|
800
|
+
const leftNode = child.childForFieldName('left');
|
|
801
|
+
if (leftNode && leftNode.type === 'shorthand_property_identifier_pattern') {
|
|
802
|
+
defs.push({
|
|
803
|
+
id: id++,
|
|
804
|
+
variable: getNodeText(leftNode),
|
|
805
|
+
line: leftNode.startPosition.row + 1,
|
|
806
|
+
kind: 'local',
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
else if (leftNode && leftNode.type === 'identifier') {
|
|
810
|
+
defs.push({
|
|
811
|
+
id: id++,
|
|
812
|
+
variable: getNodeText(leftNode),
|
|
813
|
+
line: leftNode.startPosition.row + 1,
|
|
814
|
+
kind: 'local',
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
else if (node.type === 'array_pattern') {
|
|
821
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
822
|
+
const child = node.namedChild(i);
|
|
823
|
+
if (!child)
|
|
824
|
+
continue;
|
|
825
|
+
if (child.type === 'identifier') {
|
|
826
|
+
defs.push({
|
|
827
|
+
id: id++,
|
|
828
|
+
variable: getNodeText(child),
|
|
829
|
+
line: child.startPosition.row + 1,
|
|
830
|
+
kind: 'local',
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
else if (child.type === 'rest_pattern') {
|
|
834
|
+
const nameNode = child.namedChildCount > 0 ? child.namedChild(0) : null;
|
|
835
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
836
|
+
defs.push({
|
|
837
|
+
id: id++,
|
|
838
|
+
variable: getNodeText(nameNode),
|
|
839
|
+
line: child.startPosition.row + 1,
|
|
840
|
+
kind: 'local',
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else if (child.type === 'assignment_pattern') {
|
|
845
|
+
// [x = defaultValue]
|
|
846
|
+
const leftNode = child.childForFieldName('left');
|
|
847
|
+
if (leftNode && leftNode.type === 'identifier') {
|
|
848
|
+
defs.push({
|
|
849
|
+
id: id++,
|
|
850
|
+
variable: getNodeText(leftNode),
|
|
851
|
+
line: leftNode.startPosition.row + 1,
|
|
852
|
+
kind: 'local',
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
else if (child.type === 'object_pattern' || child.type === 'array_pattern') {
|
|
857
|
+
// Nested destructuring
|
|
858
|
+
const nestedDefs = extractDestructuringDefs(child, id);
|
|
859
|
+
defs.push(...nestedDefs);
|
|
860
|
+
id += nestedDefs.length;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return defs;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Get the current scope.
|
|
868
|
+
*/
|
|
869
|
+
function currentScope(scopeStack) {
|
|
870
|
+
return scopeStack[scopeStack.length - 1];
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Find the reaching definition for a variable.
|
|
874
|
+
*/
|
|
875
|
+
function findReachingDef(name, scopeStack) {
|
|
876
|
+
// Search from innermost scope to outermost
|
|
877
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
878
|
+
const defId = scopeStack[i].get(name);
|
|
879
|
+
if (defId !== undefined) {
|
|
880
|
+
return defId;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Compute def-use chains.
|
|
887
|
+
*
|
|
888
|
+
* A chain connects a definition to another definition when the first
|
|
889
|
+
* definition's value flows to create the second definition.
|
|
890
|
+
*
|
|
891
|
+
* Example:
|
|
892
|
+
* int x = getInput(); // def1: x
|
|
893
|
+
* int y = x + 1; // use1: x (def_id=1), def2: y
|
|
894
|
+
*
|
|
895
|
+
* Chain: { from_def: 1, to_def: 2, via: "x" }
|
|
896
|
+
*/
|
|
897
|
+
function computeChains(defs, uses) {
|
|
898
|
+
const chains = [];
|
|
899
|
+
// Create a map of def_id -> def for quick lookup
|
|
900
|
+
const defById = new Map();
|
|
901
|
+
for (const def of defs) {
|
|
902
|
+
defById.set(def.id, def);
|
|
903
|
+
}
|
|
904
|
+
// Group uses by line to find uses in definitions
|
|
905
|
+
const usesByLine = new Map();
|
|
906
|
+
for (const use of uses) {
|
|
907
|
+
const existing = usesByLine.get(use.line) ?? [];
|
|
908
|
+
existing.push(use);
|
|
909
|
+
usesByLine.set(use.line, existing);
|
|
910
|
+
}
|
|
911
|
+
// For each definition, find uses on the same line that might be in its initializer
|
|
912
|
+
// This is a heuristic - more precise would require tracking def-use relationships in AST
|
|
913
|
+
for (const def of defs) {
|
|
914
|
+
if (def.kind === 'local') {
|
|
915
|
+
const usesOnLine = usesByLine.get(def.line) ?? [];
|
|
916
|
+
for (const use of usesOnLine) {
|
|
917
|
+
// If this use has a reaching def, create a chain
|
|
918
|
+
if (use.def_id !== null && use.def_id !== def.id) {
|
|
919
|
+
// Avoid duplicate chains
|
|
920
|
+
const exists = chains.some(c => c.from_def === use.def_id && c.to_def === def.id && c.via === use.variable);
|
|
921
|
+
if (!exists) {
|
|
922
|
+
chains.push({
|
|
923
|
+
from_def: use.def_id,
|
|
924
|
+
to_def: def.id,
|
|
925
|
+
via: use.variable,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
// Sort chains for consistent output
|
|
933
|
+
chains.sort((a, b) => a.from_def - b.from_def || a.to_def - b.to_def);
|
|
934
|
+
return chains;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Build DFG for Rust code.
|
|
938
|
+
*/
|
|
939
|
+
function buildRustDFG(tree, cache) {
|
|
940
|
+
const defs = [];
|
|
941
|
+
const uses = [];
|
|
942
|
+
let defIdCounter = 1;
|
|
943
|
+
let useIdCounter = 1;
|
|
944
|
+
// Track definitions by variable name and scope for reaching definitions
|
|
945
|
+
const scopeStack = [new Map()];
|
|
946
|
+
// Find all function bodies
|
|
947
|
+
const functions = findNodes(tree.rootNode, 'function_item');
|
|
948
|
+
for (const func of functions) {
|
|
949
|
+
// Start new scope for function
|
|
950
|
+
scopeStack.push(new Map());
|
|
951
|
+
// Extract parameters as definitions
|
|
952
|
+
const params = func.childForFieldName('parameters');
|
|
953
|
+
if (params) {
|
|
954
|
+
for (let i = 0; i < params.childCount; i++) {
|
|
955
|
+
const param = params.child(i);
|
|
956
|
+
if (param?.type === 'parameter') {
|
|
957
|
+
const patternNode = param.childForFieldName('pattern');
|
|
958
|
+
if (patternNode) {
|
|
959
|
+
const varName = getNodeText(patternNode);
|
|
960
|
+
const def = {
|
|
961
|
+
id: defIdCounter++,
|
|
962
|
+
variable: varName,
|
|
963
|
+
kind: 'param',
|
|
964
|
+
line: param.startPosition.row + 1,
|
|
965
|
+
column: param.startPosition.column,
|
|
966
|
+
expression: getNodeText(param),
|
|
967
|
+
};
|
|
968
|
+
defs.push(def);
|
|
969
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Process function body
|
|
975
|
+
const body = func.childForFieldName('body');
|
|
976
|
+
if (body) {
|
|
977
|
+
processRustBlock(body, defs, uses, defIdCounter, useIdCounter, scopeStack);
|
|
978
|
+
}
|
|
979
|
+
// Pop function scope
|
|
980
|
+
scopeStack.pop();
|
|
981
|
+
}
|
|
982
|
+
// Build chains from defs and uses
|
|
983
|
+
const chains = computeChains(defs, uses);
|
|
984
|
+
return { defs, uses, chains };
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Process a Rust block and extract definitions and uses.
|
|
988
|
+
*/
|
|
989
|
+
function processRustBlock(node, defs, uses, defIdCounter, useIdCounter, scopeStack) {
|
|
990
|
+
walkTree(node, (child) => {
|
|
991
|
+
if (child.type === 'let_declaration') {
|
|
992
|
+
// Extract variable definition from let binding
|
|
993
|
+
const patternNode = child.childForFieldName('pattern');
|
|
994
|
+
const valueNode = child.childForFieldName('value');
|
|
995
|
+
if (patternNode) {
|
|
996
|
+
const varName = getNodeText(patternNode);
|
|
997
|
+
const def = {
|
|
998
|
+
id: defIdCounter++,
|
|
999
|
+
variable: varName,
|
|
1000
|
+
kind: 'local',
|
|
1001
|
+
line: child.startPosition.row + 1,
|
|
1002
|
+
column: child.startPosition.column,
|
|
1003
|
+
expression: valueNode ? getNodeText(valueNode) : undefined,
|
|
1004
|
+
};
|
|
1005
|
+
defs.push(def);
|
|
1006
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
1007
|
+
}
|
|
1008
|
+
// Extract uses from the value expression
|
|
1009
|
+
if (valueNode) {
|
|
1010
|
+
extractRustUses(valueNode, uses, useIdCounter, scopeStack);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
else if (child.type === 'assignment_expression') {
|
|
1014
|
+
// Handle reassignment
|
|
1015
|
+
const leftNode = child.childForFieldName('left');
|
|
1016
|
+
const rightNode = child.childForFieldName('right');
|
|
1017
|
+
if (leftNode && leftNode.type === 'identifier') {
|
|
1018
|
+
const varName = getNodeText(leftNode);
|
|
1019
|
+
const def = {
|
|
1020
|
+
id: defIdCounter++,
|
|
1021
|
+
variable: varName,
|
|
1022
|
+
kind: 'local',
|
|
1023
|
+
line: child.startPosition.row + 1,
|
|
1024
|
+
column: child.startPosition.column,
|
|
1025
|
+
expression: rightNode ? getNodeText(rightNode) : undefined,
|
|
1026
|
+
};
|
|
1027
|
+
defs.push(def);
|
|
1028
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
1029
|
+
}
|
|
1030
|
+
// Extract uses from right side
|
|
1031
|
+
if (rightNode) {
|
|
1032
|
+
extractRustUses(rightNode, uses, useIdCounter, scopeStack);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
else if (child.type === 'call_expression') {
|
|
1036
|
+
// Extract uses from call arguments
|
|
1037
|
+
const argsNode = child.childForFieldName('arguments');
|
|
1038
|
+
if (argsNode) {
|
|
1039
|
+
extractRustUses(argsNode, uses, useIdCounter, scopeStack);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
return { defId: defIdCounter, useId: useIdCounter };
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Extract variable uses from a Rust expression.
|
|
1047
|
+
*/
|
|
1048
|
+
function extractRustUses(node, uses, useIdCounter, scopeStack) {
|
|
1049
|
+
walkTree(node, (child) => {
|
|
1050
|
+
if (child.type === 'identifier') {
|
|
1051
|
+
const varName = getNodeText(child);
|
|
1052
|
+
// Skip keywords and type names (simple heuristic: starts with uppercase = type)
|
|
1053
|
+
if (varName.length > 0 && !isRustKeyword(varName) && varName[0] === varName[0].toLowerCase()) {
|
|
1054
|
+
const defId = findReachingDef(varName, scopeStack);
|
|
1055
|
+
uses.push({
|
|
1056
|
+
id: useIdCounter++,
|
|
1057
|
+
variable: varName,
|
|
1058
|
+
line: child.startPosition.row + 1,
|
|
1059
|
+
column: child.startPosition.column,
|
|
1060
|
+
def_id: defId,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
return useIdCounter;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if a name is a Rust keyword.
|
|
1069
|
+
*/
|
|
1070
|
+
function isRustKeyword(name) {
|
|
1071
|
+
const keywords = new Set([
|
|
1072
|
+
'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern',
|
|
1073
|
+
'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod',
|
|
1074
|
+
'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static', 'struct',
|
|
1075
|
+
'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while',
|
|
1076
|
+
'async', 'await', 'dyn', 'abstract', 'become', 'box', 'do', 'final',
|
|
1077
|
+
'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual', 'yield',
|
|
1078
|
+
]);
|
|
1079
|
+
return keywords.has(name);
|
|
1080
|
+
}
|
|
1081
|
+
//# sourceMappingURL=dfg.js.map
|