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.
Files changed (194) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +200 -0
  3. package/configs/sinks/code_injection.yaml +672 -0
  4. package/configs/sinks/command.yaml +917 -0
  5. package/configs/sinks/deserialization.yaml +105 -0
  6. package/configs/sinks/ldap.yaml +136 -0
  7. package/configs/sinks/nodejs.json +629 -0
  8. package/configs/sinks/path.yaml +715 -0
  9. package/configs/sinks/python.json +501 -0
  10. package/configs/sinks/rust.json +339 -0
  11. package/configs/sinks/sql.yaml +233 -0
  12. package/configs/sinks/ssrf.yaml +160 -0
  13. package/configs/sinks/xpath.yaml +121 -0
  14. package/configs/sinks/xss.yaml +727 -0
  15. package/configs/sources/db_sources.yaml +90 -0
  16. package/configs/sources/env_sources.yaml +94 -0
  17. package/configs/sources/express.json +197 -0
  18. package/configs/sources/file_sources.yaml +164 -0
  19. package/configs/sources/http_sources.yaml +379 -0
  20. package/configs/sources/io_sources.yaml +519 -0
  21. package/configs/sources/network_sources.yaml +99 -0
  22. package/configs/sources/python.json +230 -0
  23. package/configs/sources/rust.json +286 -0
  24. package/configs/sources/spring.yaml +70 -0
  25. package/dist/analysis/advisory-db.d.ts +86 -0
  26. package/dist/analysis/advisory-db.js +104 -0
  27. package/dist/analysis/advisory-db.js.map +1 -0
  28. package/dist/analysis/cargo-parser.d.ts +42 -0
  29. package/dist/analysis/cargo-parser.js +102 -0
  30. package/dist/analysis/cargo-parser.js.map +1 -0
  31. package/dist/analysis/config-loader.d.ts +37 -0
  32. package/dist/analysis/config-loader.js +1561 -0
  33. package/dist/analysis/config-loader.js.map +1 -0
  34. package/dist/analysis/constant-propagation/ast-utils.d.ts +25 -0
  35. package/dist/analysis/constant-propagation/ast-utils.js +34 -0
  36. package/dist/analysis/constant-propagation/ast-utils.js.map +1 -0
  37. package/dist/analysis/constant-propagation/evaluator.d.ts +32 -0
  38. package/dist/analysis/constant-propagation/evaluator.js +296 -0
  39. package/dist/analysis/constant-propagation/evaluator.js.map +1 -0
  40. package/dist/analysis/constant-propagation/index.d.ts +62 -0
  41. package/dist/analysis/constant-propagation/index.js +152 -0
  42. package/dist/analysis/constant-propagation/index.js.map +1 -0
  43. package/dist/analysis/constant-propagation/patterns.d.ts +8 -0
  44. package/dist/analysis/constant-propagation/patterns.js +126 -0
  45. package/dist/analysis/constant-propagation/patterns.js.map +1 -0
  46. package/dist/analysis/constant-propagation/propagator.d.ts +180 -0
  47. package/dist/analysis/constant-propagation/propagator.js +1985 -0
  48. package/dist/analysis/constant-propagation/propagator.js.map +1 -0
  49. package/dist/analysis/constant-propagation/types.d.ts +63 -0
  50. package/dist/analysis/constant-propagation/types.js +5 -0
  51. package/dist/analysis/constant-propagation/types.js.map +1 -0
  52. package/dist/analysis/constant-propagation.d.ts +9 -0
  53. package/dist/analysis/constant-propagation.js +18 -0
  54. package/dist/analysis/constant-propagation.js.map +1 -0
  55. package/dist/analysis/dependency-scanner.d.ts +79 -0
  56. package/dist/analysis/dependency-scanner.js +122 -0
  57. package/dist/analysis/dependency-scanner.js.map +1 -0
  58. package/dist/analysis/dfg-verifier.d.ts +116 -0
  59. package/dist/analysis/dfg-verifier.js +399 -0
  60. package/dist/analysis/dfg-verifier.js.map +1 -0
  61. package/dist/analysis/findings.d.ts +11 -0
  62. package/dist/analysis/findings.js +228 -0
  63. package/dist/analysis/findings.js.map +1 -0
  64. package/dist/analysis/index.d.ts +16 -0
  65. package/dist/analysis/index.js +18 -0
  66. package/dist/analysis/index.js.map +1 -0
  67. package/dist/analysis/interprocedural.d.ts +99 -0
  68. package/dist/analysis/interprocedural.js +526 -0
  69. package/dist/analysis/interprocedural.js.map +1 -0
  70. package/dist/analysis/path-finder.d.ts +133 -0
  71. package/dist/analysis/path-finder.js +354 -0
  72. package/dist/analysis/path-finder.js.map +1 -0
  73. package/dist/analysis/rules.d.ts +75 -0
  74. package/dist/analysis/rules.js +332 -0
  75. package/dist/analysis/rules.js.map +1 -0
  76. package/dist/analysis/semver.d.ts +27 -0
  77. package/dist/analysis/semver.js +127 -0
  78. package/dist/analysis/semver.js.map +1 -0
  79. package/dist/analysis/taint-matcher.d.ts +15 -0
  80. package/dist/analysis/taint-matcher.js +634 -0
  81. package/dist/analysis/taint-matcher.js.map +1 -0
  82. package/dist/analysis/taint-propagation.d.ts +67 -0
  83. package/dist/analysis/taint-propagation.js +298 -0
  84. package/dist/analysis/taint-propagation.js.map +1 -0
  85. package/dist/analysis/unresolved.d.ts +14 -0
  86. package/dist/analysis/unresolved.js +202 -0
  87. package/dist/analysis/unresolved.js.map +1 -0
  88. package/dist/analyzer.d.ts +43 -0
  89. package/dist/analyzer.js +1010 -0
  90. package/dist/analyzer.js.map +1 -0
  91. package/dist/browser/circle-ir.js +16576 -0
  92. package/dist/browser.d.ts +38 -0
  93. package/dist/browser.js +38 -0
  94. package/dist/browser.js.map +1 -0
  95. package/dist/core/circle-ir-core.cjs +13626 -0
  96. package/dist/core/circle-ir-core.d.ts +59 -0
  97. package/dist/core/circle-ir-core.js +13591 -0
  98. package/dist/core/extractors/calls.d.ts +13 -0
  99. package/dist/core/extractors/calls.js +1429 -0
  100. package/dist/core/extractors/calls.js.map +1 -0
  101. package/dist/core/extractors/cfg.d.ts +9 -0
  102. package/dist/core/extractors/cfg.js +519 -0
  103. package/dist/core/extractors/cfg.js.map +1 -0
  104. package/dist/core/extractors/dfg.d.ts +12 -0
  105. package/dist/core/extractors/dfg.js +1081 -0
  106. package/dist/core/extractors/dfg.js.map +1 -0
  107. package/dist/core/extractors/exports.d.ts +14 -0
  108. package/dist/core/extractors/exports.js +80 -0
  109. package/dist/core/extractors/exports.js.map +1 -0
  110. package/dist/core/extractors/imports.d.ts +9 -0
  111. package/dist/core/extractors/imports.js +739 -0
  112. package/dist/core/extractors/imports.js.map +1 -0
  113. package/dist/core/extractors/index.d.ts +10 -0
  114. package/dist/core/extractors/index.js +11 -0
  115. package/dist/core/extractors/index.js.map +1 -0
  116. package/dist/core/extractors/meta.d.ts +10 -0
  117. package/dist/core/extractors/meta.js +109 -0
  118. package/dist/core/extractors/meta.js.map +1 -0
  119. package/dist/core/extractors/types.d.ts +10 -0
  120. package/dist/core/extractors/types.js +1479 -0
  121. package/dist/core/extractors/types.js.map +1 -0
  122. package/dist/core/index.d.ts +5 -0
  123. package/dist/core/index.js +8 -0
  124. package/dist/core/index.js.map +1 -0
  125. package/dist/core/parser.d.ts +84 -0
  126. package/dist/core/parser.js +250 -0
  127. package/dist/core/parser.js.map +1 -0
  128. package/dist/core-lib.d.ts +59 -0
  129. package/dist/core-lib.js +62 -0
  130. package/dist/core-lib.js.map +1 -0
  131. package/dist/index.d.ts +15 -0
  132. package/dist/index.js +20 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/languages/index.d.ts +11 -0
  135. package/dist/languages/index.js +14 -0
  136. package/dist/languages/index.js.map +1 -0
  137. package/dist/languages/plugins/base.d.ts +44 -0
  138. package/dist/languages/plugins/base.js +82 -0
  139. package/dist/languages/plugins/base.js.map +1 -0
  140. package/dist/languages/plugins/index.d.ts +14 -0
  141. package/dist/languages/plugins/index.js +25 -0
  142. package/dist/languages/plugins/index.js.map +1 -0
  143. package/dist/languages/plugins/java.d.ts +49 -0
  144. package/dist/languages/plugins/java.js +402 -0
  145. package/dist/languages/plugins/java.js.map +1 -0
  146. package/dist/languages/plugins/javascript.d.ts +48 -0
  147. package/dist/languages/plugins/javascript.js +445 -0
  148. package/dist/languages/plugins/javascript.js.map +1 -0
  149. package/dist/languages/plugins/python.d.ts +47 -0
  150. package/dist/languages/plugins/python.js +480 -0
  151. package/dist/languages/plugins/python.js.map +1 -0
  152. package/dist/languages/plugins/rust.d.ts +47 -0
  153. package/dist/languages/plugins/rust.js +405 -0
  154. package/dist/languages/plugins/rust.js.map +1 -0
  155. package/dist/languages/registry.d.ts +30 -0
  156. package/dist/languages/registry.js +80 -0
  157. package/dist/languages/registry.js.map +1 -0
  158. package/dist/languages/types.d.ts +184 -0
  159. package/dist/languages/types.js +8 -0
  160. package/dist/languages/types.js.map +1 -0
  161. package/dist/resolution/cross-file.d.ts +146 -0
  162. package/dist/resolution/cross-file.js +439 -0
  163. package/dist/resolution/cross-file.js.map +1 -0
  164. package/dist/resolution/index.d.ts +12 -0
  165. package/dist/resolution/index.js +10 -0
  166. package/dist/resolution/index.js.map +1 -0
  167. package/dist/resolution/symbol-table.d.ts +136 -0
  168. package/dist/resolution/symbol-table.js +336 -0
  169. package/dist/resolution/symbol-table.js.map +1 -0
  170. package/dist/resolution/type-hierarchy.d.ts +124 -0
  171. package/dist/resolution/type-hierarchy.js +515 -0
  172. package/dist/resolution/type-hierarchy.js.map +1 -0
  173. package/dist/types/config.d.ts +45 -0
  174. package/dist/types/config.js +5 -0
  175. package/dist/types/config.js.map +1 -0
  176. package/dist/types/index.d.ts +392 -0
  177. package/dist/types/index.js +7 -0
  178. package/dist/types/index.js.map +1 -0
  179. package/dist/utils/logger.d.ts +85 -0
  180. package/dist/utils/logger.js +198 -0
  181. package/dist/utils/logger.js.map +1 -0
  182. package/dist/wasm/tree-sitter-java.wasm +0 -0
  183. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  184. package/dist/wasm/tree-sitter-python.wasm +0 -0
  185. package/dist/wasm/tree-sitter-rust.wasm +0 -0
  186. package/dist/wasm/web-tree-sitter.wasm +0 -0
  187. package/docs/SPEC.md +1021 -0
  188. package/examples/browser-example.html +610 -0
  189. package/examples/node-example.ts +215 -0
  190. package/package.json +107 -0
  191. package/wasm/tree-sitter-java.wasm +0 -0
  192. package/wasm/tree-sitter-javascript.wasm +0 -0
  193. package/wasm/tree-sitter-python.wasm +0 -0
  194. 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