@grafema/core 0.1.1-alpha → 0.2.0-beta

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 (319) hide show
  1. package/dist/Orchestrator.d.ts +7 -0
  2. package/dist/Orchestrator.d.ts.map +1 -1
  3. package/dist/Orchestrator.js +25 -3
  4. package/dist/config/ConfigLoader.d.ts +18 -0
  5. package/dist/config/ConfigLoader.d.ts.map +1 -1
  6. package/dist/config/ConfigLoader.js +65 -3
  7. package/dist/core/FileExplainer.d.ts +101 -0
  8. package/dist/core/FileExplainer.d.ts.map +1 -0
  9. package/dist/core/FileExplainer.js +139 -0
  10. package/dist/core/NodeFactory.d.ts +44 -5
  11. package/dist/core/NodeFactory.d.ts.map +1 -1
  12. package/dist/core/NodeFactory.js +52 -7
  13. package/dist/core/nodes/ArrayLiteralNode.d.ts.map +1 -1
  14. package/dist/core/nodes/ArrayLiteralNode.js +4 -2
  15. package/dist/core/nodes/BranchNode.d.ts +41 -0
  16. package/dist/core/nodes/BranchNode.d.ts.map +1 -0
  17. package/dist/core/nodes/BranchNode.js +82 -0
  18. package/dist/core/nodes/CallSiteNode.d.ts +2 -2
  19. package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
  20. package/dist/core/nodes/CallSiteNode.js +9 -5
  21. package/dist/core/nodes/CaseNode.d.ts +43 -0
  22. package/dist/core/nodes/CaseNode.d.ts.map +1 -0
  23. package/dist/core/nodes/CaseNode.js +81 -0
  24. package/dist/core/nodes/ClassNode.d.ts +2 -2
  25. package/dist/core/nodes/ClassNode.d.ts.map +1 -1
  26. package/dist/core/nodes/ClassNode.js +8 -4
  27. package/dist/core/nodes/ConstantNode.d.ts +2 -2
  28. package/dist/core/nodes/ConstantNode.d.ts.map +1 -1
  29. package/dist/core/nodes/ConstantNode.js +6 -4
  30. package/dist/core/nodes/ConstructorCallNode.d.ts +51 -0
  31. package/dist/core/nodes/ConstructorCallNode.d.ts.map +1 -0
  32. package/dist/core/nodes/ConstructorCallNode.js +171 -0
  33. package/dist/core/nodes/DatabaseQueryNode.d.ts +3 -2
  34. package/dist/core/nodes/DatabaseQueryNode.d.ts.map +1 -1
  35. package/dist/core/nodes/DatabaseQueryNode.js +5 -2
  36. package/dist/core/nodes/DecoratorNode.d.ts +2 -2
  37. package/dist/core/nodes/DecoratorNode.d.ts.map +1 -1
  38. package/dist/core/nodes/DecoratorNode.js +5 -3
  39. package/dist/core/nodes/EnumNode.d.ts +2 -2
  40. package/dist/core/nodes/EnumNode.d.ts.map +1 -1
  41. package/dist/core/nodes/EnumNode.js +5 -3
  42. package/dist/core/nodes/EventListenerNode.d.ts +4 -4
  43. package/dist/core/nodes/EventListenerNode.d.ts.map +1 -1
  44. package/dist/core/nodes/EventListenerNode.js +7 -4
  45. package/dist/core/nodes/ExportNode.d.ts +2 -2
  46. package/dist/core/nodes/ExportNode.d.ts.map +1 -1
  47. package/dist/core/nodes/ExportNode.js +8 -4
  48. package/dist/core/nodes/ExpressionNode.d.ts +2 -2
  49. package/dist/core/nodes/ExpressionNode.d.ts.map +1 -1
  50. package/dist/core/nodes/ExpressionNode.js +6 -4
  51. package/dist/core/nodes/ExternalModuleNode.d.ts +4 -0
  52. package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -1
  53. package/dist/core/nodes/ExternalModuleNode.js +10 -2
  54. package/dist/core/nodes/HttpRequestNode.d.ts +4 -4
  55. package/dist/core/nodes/HttpRequestNode.d.ts.map +1 -1
  56. package/dist/core/nodes/HttpRequestNode.js +7 -4
  57. package/dist/core/nodes/ImportNode.d.ts +10 -2
  58. package/dist/core/nodes/ImportNode.d.ts.map +1 -1
  59. package/dist/core/nodes/ImportNode.js +21 -4
  60. package/dist/core/nodes/InterfaceNode.d.ts +2 -2
  61. package/dist/core/nodes/InterfaceNode.d.ts.map +1 -1
  62. package/dist/core/nodes/InterfaceNode.js +5 -3
  63. package/dist/core/nodes/LiteralNode.d.ts +2 -2
  64. package/dist/core/nodes/LiteralNode.d.ts.map +1 -1
  65. package/dist/core/nodes/LiteralNode.js +6 -4
  66. package/dist/core/nodes/MethodCallNode.d.ts +2 -2
  67. package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
  68. package/dist/core/nodes/MethodCallNode.js +9 -5
  69. package/dist/core/nodes/MethodNode.d.ts +2 -2
  70. package/dist/core/nodes/MethodNode.d.ts.map +1 -1
  71. package/dist/core/nodes/MethodNode.js +8 -4
  72. package/dist/core/nodes/ObjectLiteralNode.d.ts.map +1 -1
  73. package/dist/core/nodes/ObjectLiteralNode.js +4 -2
  74. package/dist/core/nodes/ParameterNode.d.ts +2 -2
  75. package/dist/core/nodes/ParameterNode.d.ts.map +1 -1
  76. package/dist/core/nodes/ParameterNode.js +5 -3
  77. package/dist/core/nodes/TypeNode.d.ts +2 -2
  78. package/dist/core/nodes/TypeNode.d.ts.map +1 -1
  79. package/dist/core/nodes/TypeNode.js +5 -3
  80. package/dist/core/nodes/VariableDeclarationNode.d.ts +2 -2
  81. package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
  82. package/dist/core/nodes/VariableDeclarationNode.js +9 -5
  83. package/dist/core/nodes/index.d.ts +3 -0
  84. package/dist/core/nodes/index.d.ts.map +1 -1
  85. package/dist/core/nodes/index.js +3 -0
  86. package/dist/data/builtins/BuiltinRegistry.d.ts +78 -0
  87. package/dist/data/builtins/BuiltinRegistry.d.ts.map +1 -0
  88. package/dist/data/builtins/BuiltinRegistry.js +110 -0
  89. package/dist/data/builtins/definitions.d.ts +28 -0
  90. package/dist/data/builtins/definitions.d.ts.map +1 -0
  91. package/dist/data/builtins/definitions.js +250 -0
  92. package/dist/data/builtins/index.d.ts +10 -0
  93. package/dist/data/builtins/index.d.ts.map +1 -0
  94. package/dist/data/builtins/index.js +8 -0
  95. package/dist/data/builtins/jsGlobals.d.ts +18 -0
  96. package/dist/data/builtins/jsGlobals.d.ts.map +1 -0
  97. package/dist/data/builtins/jsGlobals.js +26 -0
  98. package/dist/data/builtins/types.d.ts +34 -0
  99. package/dist/data/builtins/types.d.ts.map +1 -0
  100. package/dist/data/builtins/types.js +7 -0
  101. package/dist/data/globals/definitions.d.ts +27 -0
  102. package/dist/data/globals/definitions.d.ts.map +1 -0
  103. package/dist/data/globals/definitions.js +117 -0
  104. package/dist/data/globals/index.d.ts +36 -0
  105. package/dist/data/globals/index.d.ts.map +1 -0
  106. package/dist/data/globals/index.js +52 -0
  107. package/dist/diagnostics/DiagnosticReporter.d.ts +23 -0
  108. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -1
  109. package/dist/diagnostics/DiagnosticReporter.js +88 -0
  110. package/dist/diagnostics/index.d.ts +1 -1
  111. package/dist/diagnostics/index.d.ts.map +1 -1
  112. package/dist/errors/GrafemaError.d.ts +43 -0
  113. package/dist/errors/GrafemaError.d.ts.map +1 -1
  114. package/dist/errors/GrafemaError.js +50 -0
  115. package/dist/index.d.ts +17 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +17 -1
  118. package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
  119. package/dist/plugins/analysis/DatabaseAnalyzer.js +3 -2
  120. package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
  121. package/dist/plugins/analysis/ExpressAnalyzer.js +3 -1
  122. package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts +148 -0
  123. package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts.map +1 -0
  124. package/dist/plugins/analysis/ExpressResponseAnalyzer.js +495 -0
  125. package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
  126. package/dist/plugins/analysis/ExpressRouteAnalyzer.js +53 -18
  127. package/dist/plugins/analysis/FetchAnalyzer.d.ts +40 -0
  128. package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
  129. package/dist/plugins/analysis/FetchAnalyzer.js +163 -15
  130. package/dist/plugins/analysis/JSASTAnalyzer.d.ts +157 -26
  131. package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
  132. package/dist/plugins/analysis/JSASTAnalyzer.js +2418 -191
  133. package/dist/plugins/analysis/RustAnalyzer.js +4 -4
  134. package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
  135. package/dist/plugins/analysis/SQLiteAnalyzer.js +4 -2
  136. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts +9 -0
  137. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
  138. package/dist/plugins/analysis/SocketIOAnalyzer.js +91 -7
  139. package/dist/plugins/analysis/ast/GraphBuilder.d.ts +173 -0
  140. package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
  141. package/dist/plugins/analysis/ast/GraphBuilder.js +1256 -65
  142. package/dist/plugins/analysis/ast/types.d.ts +294 -0
  143. package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
  144. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +5 -1
  145. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -1
  146. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +1 -0
  147. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
  148. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +12 -1
  149. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +10 -0
  150. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
  151. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +62 -0
  152. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts +4 -0
  153. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
  154. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +101 -0
  155. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +16 -1
  156. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
  157. package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +233 -39
  158. package/dist/plugins/discovery/WorkspaceDiscovery.d.ts.map +1 -1
  159. package/dist/plugins/discovery/WorkspaceDiscovery.js +9 -4
  160. package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
  161. package/dist/plugins/enrichment/AliasTracker.js +16 -1
  162. package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts +32 -0
  163. package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts.map +1 -0
  164. package/dist/plugins/enrichment/ArgumentParameterLinker.js +175 -0
  165. package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts +51 -0
  166. package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts.map +1 -0
  167. package/dist/plugins/enrichment/ClosureCaptureEnricher.js +205 -0
  168. package/dist/plugins/enrichment/ExternalCallResolver.d.ts +42 -0
  169. package/dist/plugins/enrichment/ExternalCallResolver.d.ts.map +1 -0
  170. package/dist/plugins/enrichment/ExternalCallResolver.js +213 -0
  171. package/dist/plugins/enrichment/FunctionCallResolver.d.ts +58 -0
  172. package/dist/plugins/enrichment/FunctionCallResolver.d.ts.map +1 -0
  173. package/dist/plugins/enrichment/FunctionCallResolver.js +340 -0
  174. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts +16 -3
  175. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
  176. package/dist/plugins/enrichment/HTTPConnectionEnricher.js +64 -20
  177. package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
  178. package/dist/plugins/enrichment/MethodCallResolver.js +15 -1
  179. package/dist/plugins/enrichment/MountPointResolver.d.ts +14 -12
  180. package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
  181. package/dist/plugins/enrichment/MountPointResolver.js +172 -151
  182. package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts +44 -0
  183. package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts.map +1 -0
  184. package/dist/plugins/enrichment/NodejsBuiltinsResolver.js +271 -0
  185. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +5 -27
  186. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
  187. package/dist/plugins/enrichment/ValueDomainAnalyzer.js +62 -139
  188. package/dist/plugins/indexing/JSModuleIndexer.d.ts +15 -0
  189. package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
  190. package/dist/plugins/indexing/JSModuleIndexer.js +58 -0
  191. package/dist/plugins/indexing/RustModuleIndexer.d.ts +1 -1
  192. package/dist/plugins/indexing/RustModuleIndexer.js +4 -4
  193. package/dist/plugins/validation/BrokenImportValidator.d.ts +31 -0
  194. package/dist/plugins/validation/BrokenImportValidator.d.ts.map +1 -0
  195. package/dist/plugins/validation/BrokenImportValidator.js +249 -0
  196. package/dist/plugins/validation/CallResolverValidator.d.ts +21 -10
  197. package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -1
  198. package/dist/plugins/validation/CallResolverValidator.js +101 -76
  199. package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
  200. package/dist/plugins/validation/DataFlowValidator.js +49 -41
  201. package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -1
  202. package/dist/plugins/validation/GraphConnectivityValidator.js +25 -1
  203. package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -1
  204. package/dist/plugins/validation/SQLInjectionValidator.js +2 -3
  205. package/dist/queries/findCallsInFunction.d.ts +52 -0
  206. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  207. package/dist/queries/findCallsInFunction.js +135 -0
  208. package/dist/queries/findContainingFunction.d.ts +45 -0
  209. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  210. package/dist/queries/findContainingFunction.js +54 -0
  211. package/dist/queries/index.d.ts +14 -0
  212. package/dist/queries/index.d.ts.map +1 -0
  213. package/dist/queries/index.js +11 -0
  214. package/dist/queries/traceValues.d.ts +70 -0
  215. package/dist/queries/traceValues.d.ts.map +1 -0
  216. package/dist/queries/traceValues.js +299 -0
  217. package/dist/queries/types.d.ts +163 -0
  218. package/dist/queries/types.d.ts.map +1 -0
  219. package/dist/queries/types.js +9 -0
  220. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  221. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  222. package/dist/schema/GraphSchemaExtractor.js +124 -0
  223. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  224. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  225. package/dist/schema/InterfaceSchemaExtractor.js +112 -0
  226. package/dist/schema/index.d.ts +5 -0
  227. package/dist/schema/index.d.ts.map +1 -0
  228. package/dist/schema/index.js +2 -0
  229. package/dist/storage/backends/RFDBServerBackend.d.ts +12 -18
  230. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
  231. package/dist/storage/backends/RFDBServerBackend.js +41 -52
  232. package/dist/storage/backends/typeValidation.d.ts.map +1 -1
  233. package/dist/storage/backends/typeValidation.js +1 -0
  234. package/package.json +3 -3
  235. package/src/Orchestrator.ts +35 -3
  236. package/src/config/ConfigLoader.ts +94 -3
  237. package/src/core/FileExplainer.ts +179 -0
  238. package/src/core/NodeFactory.ts +72 -8
  239. package/src/core/nodes/ArrayLiteralNode.ts +3 -2
  240. package/src/core/nodes/BranchNode.ts +113 -0
  241. package/src/core/nodes/CallSiteNode.ts +7 -5
  242. package/src/core/nodes/CaseNode.ts +123 -0
  243. package/src/core/nodes/ClassNode.ts +6 -4
  244. package/src/core/nodes/ConstantNode.ts +5 -4
  245. package/src/core/nodes/ConstructorCallNode.ts +217 -0
  246. package/src/core/nodes/DatabaseQueryNode.ts +5 -1
  247. package/src/core/nodes/DecoratorNode.ts +4 -3
  248. package/src/core/nodes/EnumNode.ts +4 -3
  249. package/src/core/nodes/EventListenerNode.ts +7 -4
  250. package/src/core/nodes/ExportNode.ts +6 -4
  251. package/src/core/nodes/ExpressionNode.ts +5 -4
  252. package/src/core/nodes/ExternalModuleNode.ts +11 -2
  253. package/src/core/nodes/HttpRequestNode.ts +7 -4
  254. package/src/core/nodes/ImportNode.ts +31 -4
  255. package/src/core/nodes/InterfaceNode.ts +4 -3
  256. package/src/core/nodes/LiteralNode.ts +5 -4
  257. package/src/core/nodes/MethodCallNode.ts +7 -5
  258. package/src/core/nodes/MethodNode.ts +6 -4
  259. package/src/core/nodes/ObjectLiteralNode.ts +3 -2
  260. package/src/core/nodes/ParameterNode.ts +4 -3
  261. package/src/core/nodes/TypeNode.ts +4 -3
  262. package/src/core/nodes/VariableDeclarationNode.ts +7 -5
  263. package/src/core/nodes/index.ts +3 -0
  264. package/src/data/builtins/BuiltinRegistry.ts +124 -0
  265. package/src/data/builtins/definitions.ts +267 -0
  266. package/src/data/builtins/index.ts +10 -0
  267. package/src/data/builtins/jsGlobals.ts +28 -0
  268. package/src/data/builtins/types.ts +36 -0
  269. package/src/data/globals/definitions.ts +156 -0
  270. package/src/data/globals/index.ts +66 -0
  271. package/src/diagnostics/DiagnosticReporter.ts +120 -0
  272. package/src/diagnostics/index.ts +1 -1
  273. package/src/errors/GrafemaError.ts +65 -0
  274. package/src/index.ts +45 -0
  275. package/src/plugins/analysis/DatabaseAnalyzer.ts +4 -2
  276. package/src/plugins/analysis/ExpressAnalyzer.ts +5 -1
  277. package/src/plugins/analysis/ExpressResponseAnalyzer.ts +636 -0
  278. package/src/plugins/analysis/ExpressRouteAnalyzer.ts +57 -18
  279. package/src/plugins/analysis/FetchAnalyzer.ts +204 -16
  280. package/src/plugins/analysis/JSASTAnalyzer.ts +2958 -260
  281. package/src/plugins/analysis/RustAnalyzer.ts +4 -4
  282. package/src/plugins/analysis/SQLiteAnalyzer.ts +5 -2
  283. package/src/plugins/analysis/SocketIOAnalyzer.ts +121 -7
  284. package/src/plugins/analysis/ast/GraphBuilder.ts +1578 -70
  285. package/src/plugins/analysis/ast/types.ts +387 -0
  286. package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +8 -0
  287. package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +16 -1
  288. package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +77 -2
  289. package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +112 -1
  290. package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +272 -47
  291. package/src/plugins/discovery/WorkspaceDiscovery.ts +11 -4
  292. package/src/plugins/enrichment/AliasTracker.ts +22 -1
  293. package/src/plugins/enrichment/ArgumentParameterLinker.ts +240 -0
  294. package/src/plugins/enrichment/ClosureCaptureEnricher.ts +267 -0
  295. package/src/plugins/enrichment/ExternalCallResolver.ts +262 -0
  296. package/src/plugins/enrichment/FunctionCallResolver.ts +456 -0
  297. package/src/plugins/enrichment/HTTPConnectionEnricher.ts +70 -20
  298. package/src/plugins/enrichment/MethodCallResolver.ts +21 -1
  299. package/src/plugins/enrichment/MountPointResolver.ts +206 -198
  300. package/src/plugins/enrichment/NodejsBuiltinsResolver.ts +365 -0
  301. package/src/plugins/enrichment/ValueDomainAnalyzer.ts +67 -184
  302. package/src/plugins/indexing/JSModuleIndexer.ts +66 -0
  303. package/src/plugins/indexing/RustModuleIndexer.ts +4 -4
  304. package/src/plugins/validation/BrokenImportValidator.ts +325 -0
  305. package/src/plugins/validation/CallResolverValidator.ts +129 -109
  306. package/src/plugins/validation/DataFlowValidator.ts +75 -58
  307. package/src/plugins/validation/GraphConnectivityValidator.ts +39 -1
  308. package/src/plugins/validation/SQLInjectionValidator.ts +2 -5
  309. package/src/queries/README.md +46 -0
  310. package/src/queries/findCallsInFunction.ts +206 -0
  311. package/src/queries/findContainingFunction.ts +83 -0
  312. package/src/queries/index.ts +23 -0
  313. package/src/queries/traceValues.ts +398 -0
  314. package/src/queries/types.ts +187 -0
  315. package/src/schema/GraphSchemaExtractor.ts +177 -0
  316. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  317. package/src/schema/index.ts +5 -0
  318. package/src/storage/backends/RFDBServerBackend.ts +58 -70
  319. package/src/storage/backends/typeValidation.ts +1 -0
@@ -9,6 +9,7 @@ import { EnumNode } from '../../../core/nodes/EnumNode.js';
9
9
  import { DecoratorNode } from '../../../core/nodes/DecoratorNode.js';
10
10
  import { NetworkRequestNode } from '../../../core/nodes/NetworkRequestNode.js';
11
11
  import { NodeFactory } from '../../../core/NodeFactory.js';
12
+ import { computeSemanticId, parseSemanticId } from '../../../core/SemanticId.js';
12
13
  export class GraphBuilder {
13
14
  // Track singleton nodes to avoid duplicates (net:stdio, net:request, etc.)
14
15
  _createdSingletons = new Set();
@@ -57,15 +58,29 @@ export class GraphBuilder {
57
58
  * Создаёт ноды и рёбра в графе (BATCHED VERSION)
58
59
  */
59
60
  async build(module, graph, projectPath, data) {
60
- const { functions, parameters = [], scopes, variableDeclarations, callSites, methodCalls = [], eventListeners = [], classInstantiations = [], classDeclarations = [], methodCallbacks = [], callArguments = [], imports = [], exports = [], httpRequests = [], literals = [], variableAssignments = [],
61
+ const { functions, parameters = [], scopes,
62
+ // Branching
63
+ branches = [], cases = [],
64
+ // Control flow (loops)
65
+ loops = [],
66
+ // Control flow (try/catch/finally) - Phase 4
67
+ tryBlocks = [], catchBlocks = [], finallyBlocks = [], variableDeclarations, callSites, methodCalls = [], eventListeners = [], classInstantiations = [], constructorCalls = [], classDeclarations = [], methodCallbacks = [], callArguments = [], imports = [], exports = [], httpRequests = [], literals = [], variableAssignments = [],
61
68
  // TypeScript-specific collections
62
69
  interfaces = [], typeAliases = [], enums = [], decorators = [],
63
70
  // Array mutation tracking for FLOWS_INTO edges
64
71
  arrayMutations = [],
65
72
  // Object mutation tracking for FLOWS_INTO edges
66
73
  objectMutations = [],
74
+ // Variable reassignment tracking for FLOWS_INTO edges (REG-290)
75
+ variableReassignments = [],
76
+ // Return statement tracking for RETURNS edges
77
+ returnStatements = [],
78
+ // Update expression tracking for MODIFIES edges (REG-288, REG-312)
79
+ updateExpressions = [],
80
+ // Promise resolution tracking for RESOLVES_TO edges (REG-334)
81
+ promiseResolutions = [],
67
82
  // Object/Array literal tracking
68
- objectLiterals = [], arrayLiterals = [] } = data;
83
+ objectLiterals = [], objectProperties = [], arrayLiterals = [] } = data;
69
84
  // Reset buffers for this build
70
85
  this._nodeBuffer = [];
71
86
  this._edgeBuffer = [];
@@ -79,10 +94,40 @@ export class GraphBuilder {
79
94
  const { parentFunctionId, parentScopeId, capturesFrom, modifies, ...scopeData } = scope;
80
95
  this._bufferNode(scopeData);
81
96
  }
82
- // 3. Buffer variables
97
+ // 2.5. Buffer BRANCH nodes
98
+ // Note: parentScopeId is kept on node for query support (REG-275 test requirement)
99
+ for (const branch of branches) {
100
+ const { discriminantExpressionId, discriminantExpressionType, discriminantLine, discriminantColumn, ...branchData } = branch;
101
+ this._bufferNode(branchData);
102
+ }
103
+ // 2.6. Buffer CASE nodes
104
+ for (const caseInfo of cases) {
105
+ const { parentBranchId, ...caseData } = caseInfo;
106
+ this._bufferNode(caseData);
107
+ }
108
+ // 2.7. Buffer LOOP nodes
109
+ for (const loop of loops) {
110
+ // Exclude metadata used for edge creation (not stored on node)
111
+ const { iteratesOverName, iteratesOverLine, iteratesOverColumn, conditionExpressionId, conditionExpressionType, conditionLine, conditionColumn, ...loopData } = loop;
112
+ this._bufferNode(loopData);
113
+ }
114
+ // 2.8. Buffer TRY_BLOCK nodes (Phase 4)
115
+ for (const tryBlock of tryBlocks) {
116
+ this._bufferNode(tryBlock);
117
+ }
118
+ // 2.9. Buffer CATCH_BLOCK nodes (Phase 4)
119
+ for (const catchBlock of catchBlocks) {
120
+ const { parentTryBlockId, ...catchData } = catchBlock;
121
+ this._bufferNode(catchData);
122
+ }
123
+ // 2.10. Buffer FINALLY_BLOCK nodes (Phase 4)
124
+ for (const finallyBlock of finallyBlocks) {
125
+ const { parentTryBlockId, ...finallyData } = finallyBlock;
126
+ this._bufferNode(finallyData);
127
+ }
128
+ // 3. Buffer variables (keep parentScopeId on node for queries)
83
129
  for (const varDecl of variableDeclarations) {
84
- const { parentScopeId, ...varData } = varDecl;
85
- this._bufferNode(varData);
130
+ this._bufferNode(varDecl);
86
131
  }
87
132
  // 3.5. Buffer PARAMETER nodes and HAS_PARAMETER edges
88
133
  for (const param of parameters) {
@@ -98,21 +143,49 @@ export class GraphBuilder {
98
143
  });
99
144
  }
100
145
  }
101
- // 4. Buffer CALL_SITE
146
+ // 4. Buffer CALL_SITE (keep parentScopeId on node for queries)
102
147
  for (const callSite of callSites) {
103
- const { parentScopeId, targetFunctionName, ...callData } = callSite;
148
+ const { targetFunctionName, ...callData } = callSite;
104
149
  this._bufferNode(callData);
105
150
  }
151
+ // 4.5 Buffer CONSTRUCTOR_CALL nodes
152
+ for (const constructorCall of constructorCalls) {
153
+ this._bufferNode({
154
+ id: constructorCall.id,
155
+ type: constructorCall.type,
156
+ name: `new ${constructorCall.className}()`,
157
+ className: constructorCall.className,
158
+ isBuiltin: constructorCall.isBuiltin,
159
+ file: constructorCall.file,
160
+ line: constructorCall.line,
161
+ column: constructorCall.column
162
+ });
163
+ }
106
164
  // 5. Buffer edges for functions
107
165
  this.bufferFunctionEdges(module, functions);
108
166
  // 6. Buffer edges for SCOPE
109
167
  this.bufferScopeEdges(scopes, variableDeclarations);
168
+ // 6.3. Buffer edges for LOOP (HAS_BODY, ITERATES_OVER, CONTAINS)
169
+ this.bufferLoopEdges(loops, scopes, variableDeclarations, parameters);
170
+ // 6.35. Buffer HAS_CONDITION edges for LOOP (REG-280)
171
+ this.bufferLoopConditionEdges(loops, callSites);
172
+ // 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
173
+ this.bufferLoopConditionExpressions(loops);
174
+ // 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
175
+ // Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
176
+ this.bufferBranchEdges(branches, callSites, scopes);
177
+ // 6.6. Buffer edges for CASE
178
+ this.bufferCaseEdges(cases);
179
+ // 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
180
+ this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
181
+ // 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
182
+ this.bufferDiscriminantExpressions(branches, callSites);
110
183
  // 7. Buffer edges for variables
111
184
  this.bufferVariableEdges(variableDeclarations);
112
185
  // 8. Buffer edges for CALL_SITE
113
186
  this.bufferCallSiteEdges(callSites, functions);
114
- // 9. Buffer METHOD_CALL nodes and CONTAINS edges
115
- this.bufferMethodCalls(methodCalls);
187
+ // 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
188
+ this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
116
189
  // 10. Buffer net:stdio and WRITES_TO edges for console.log/error
117
190
  this.bufferStdioNodes(methodCalls);
118
191
  // 11. Buffer CLASS nodes for class declarations and CONTAINS edges
@@ -131,6 +204,13 @@ export class GraphBuilder {
131
204
  this.bufferHttpRequests(httpRequests, functions);
132
205
  // 18. Buffer LITERAL nodes
133
206
  this.bufferLiterals(literals);
207
+ // 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
208
+ this.bufferObjectLiteralNodes(objectLiterals);
209
+ // 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
210
+ this.bufferArrayLiteralNodes(arrayLiterals);
211
+ // 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
212
+ // REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
213
+ this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
134
214
  // 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
135
215
  this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
136
216
  // 20. Buffer PASSES_ARGUMENT edges (CALL -> argument)
@@ -150,10 +230,14 @@ export class GraphBuilder {
150
230
  // 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
151
231
  // REG-152: Now includes classDeclarations for this.prop = value patterns
152
232
  this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
153
- // 28. Buffer OBJECT_LITERAL nodes
154
- this.bufferObjectLiteralNodes(objectLiterals);
155
- // 29. Buffer ARRAY_LITERAL nodes
156
- this.bufferArrayLiteralNodes(arrayLiterals);
233
+ // 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
234
+ this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
235
+ // 29. Buffer RETURNS edges for return statements
236
+ this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
237
+ // 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
238
+ this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
239
+ // 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
240
+ this.bufferPromiseResolutionEdges(promiseResolutions);
157
241
  // FLUSH: Write all nodes first, then edges in single batch calls
158
242
  const nodesCreated = await this._flushNodes(graph);
159
243
  const edgesCreated = await this._flushEdges(graph);
@@ -225,6 +309,371 @@ export class GraphBuilder {
225
309
  }
226
310
  }
227
311
  }
312
+ /**
313
+ * Buffer LOOP edges (CONTAINS, HAS_BODY, ITERATES_OVER)
314
+ *
315
+ * Creates edges for:
316
+ * - Parent -> CONTAINS -> LOOP
317
+ * - LOOP -> HAS_BODY -> body SCOPE
318
+ * - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
319
+ *
320
+ * Scope-aware variable lookup for ITERATES_OVER:
321
+ * For for-of/for-in, finds the iterated variable preferring:
322
+ * 1. Variables declared before the loop on same or earlier line (closest first)
323
+ * 2. Parameters (function arguments)
324
+ */
325
+ bufferLoopEdges(loops, scopes, variableDeclarations, parameters) {
326
+ for (const loop of loops) {
327
+ // Parent -> CONTAINS -> LOOP
328
+ if (loop.parentScopeId) {
329
+ this._bufferEdge({
330
+ type: 'CONTAINS',
331
+ src: loop.parentScopeId,
332
+ dst: loop.id
333
+ });
334
+ }
335
+ // LOOP -> HAS_BODY -> body SCOPE
336
+ // Find the body scope by matching parentScopeId to loop.id
337
+ const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
338
+ if (bodyScope) {
339
+ this._bufferEdge({
340
+ type: 'HAS_BODY',
341
+ src: loop.id,
342
+ dst: bodyScope.id
343
+ });
344
+ }
345
+ // LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
346
+ if (loop.iteratesOverName && (loop.loopType === 'for-in' || loop.loopType === 'for-of')) {
347
+ // For MemberExpression iterables (obj.items), extract base object
348
+ const iterableName = loop.iteratesOverName.includes('.')
349
+ ? loop.iteratesOverName.split('.')[0]
350
+ : loop.iteratesOverName;
351
+ // Scope-aware lookup: prefer parameters over variables
352
+ // Parameters are function-local and shadow outer variables
353
+ const param = parameters.find(p => p.name === iterableName && p.file === loop.file);
354
+ // Determine iteration type: for-in iterates keys, for-of iterates values
355
+ const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
356
+ if (param) {
357
+ // Parameter found - most local binding
358
+ this._bufferEdge({
359
+ type: 'ITERATES_OVER',
360
+ src: loop.id,
361
+ dst: param.id,
362
+ metadata: { iterates }
363
+ });
364
+ }
365
+ else {
366
+ // Find variable by name and line proximity (scope-aware heuristic)
367
+ // Prefer variables declared before the loop in the same file
368
+ const candidateVars = variableDeclarations.filter(v => v.name === iterableName &&
369
+ v.file === loop.file &&
370
+ (v.line ?? 0) <= loop.line // Declared before or on loop line
371
+ );
372
+ // Sort by line descending to find closest declaration
373
+ candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
374
+ if (candidateVars.length > 0) {
375
+ this._bufferEdge({
376
+ type: 'ITERATES_OVER',
377
+ src: loop.id,
378
+ dst: candidateVars[0].id,
379
+ metadata: { iterates }
380
+ });
381
+ }
382
+ }
383
+ }
384
+ // REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
385
+ if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
386
+ // Find the variable declared in the init on this line
387
+ const initVar = variableDeclarations.find(v => v.name === loop.initVariableName &&
388
+ v.file === loop.file &&
389
+ v.line === loop.initLine);
390
+ if (initVar) {
391
+ this._bufferEdge({
392
+ type: 'HAS_INIT',
393
+ src: loop.id,
394
+ dst: initVar.id
395
+ });
396
+ }
397
+ }
398
+ // REG-282: LOOP -> HAS_CONDITION -> EXPRESSION (i < 10 or condition for while/do-while)
399
+ if (loop.testExpressionId && loop.testExpressionType) {
400
+ // Create EXPRESSION node for the test
401
+ this._bufferNode({
402
+ id: loop.testExpressionId,
403
+ type: 'EXPRESSION',
404
+ name: loop.testExpressionType,
405
+ file: loop.file,
406
+ line: loop.testLine,
407
+ column: loop.testColumn,
408
+ expressionType: loop.testExpressionType
409
+ });
410
+ this._bufferEdge({
411
+ type: 'HAS_CONDITION',
412
+ src: loop.id,
413
+ dst: loop.testExpressionId
414
+ });
415
+ }
416
+ // REG-282: LOOP (for) -> HAS_UPDATE -> EXPRESSION (i++)
417
+ if (loop.loopType === 'for' && loop.updateExpressionId && loop.updateExpressionType) {
418
+ // Create EXPRESSION node for the update
419
+ this._bufferNode({
420
+ id: loop.updateExpressionId,
421
+ type: 'EXPRESSION',
422
+ name: loop.updateExpressionType,
423
+ file: loop.file,
424
+ line: loop.updateLine,
425
+ column: loop.updateColumn,
426
+ expressionType: loop.updateExpressionType
427
+ });
428
+ this._bufferEdge({
429
+ type: 'HAS_UPDATE',
430
+ src: loop.id,
431
+ dst: loop.updateExpressionId
432
+ });
433
+ }
434
+ }
435
+ }
436
+ /**
437
+ * Buffer HAS_CONDITION edges from LOOP to condition EXPRESSION/CALL nodes.
438
+ * Also creates EXPRESSION nodes for non-CallExpression conditions.
439
+ *
440
+ * REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
441
+ * condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
442
+ *
443
+ * For CallExpression conditions, links to existing CALL_SITE node by coordinates.
444
+ */
445
+ bufferLoopConditionEdges(loops, callSites) {
446
+ for (const loop of loops) {
447
+ // Skip for-in/for-of loops - they don't have test expressions
448
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
449
+ continue;
450
+ }
451
+ // Skip if no condition (e.g., infinite for loop: for(;;))
452
+ if (!loop.conditionExpressionId) {
453
+ continue;
454
+ }
455
+ // LOOP -> HAS_CONDITION -> EXPRESSION/CALL
456
+ let targetId = loop.conditionExpressionId;
457
+ // For CallExpression conditions, look up the actual CALL_SITE by coordinates
458
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
459
+ if (loop.conditionExpressionType === 'CallExpression' && loop.conditionLine && loop.conditionColumn !== undefined) {
460
+ const callSite = callSites.find(cs => cs.file === loop.file &&
461
+ cs.line === loop.conditionLine &&
462
+ cs.column === loop.conditionColumn);
463
+ if (callSite) {
464
+ targetId = callSite.id;
465
+ }
466
+ }
467
+ this._bufferEdge({
468
+ type: 'HAS_CONDITION',
469
+ src: loop.id,
470
+ dst: targetId
471
+ });
472
+ }
473
+ }
474
+ /**
475
+ * Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
476
+ * Similar to bufferDiscriminantExpressions but for loops.
477
+ *
478
+ * REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
479
+ * CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
480
+ */
481
+ bufferLoopConditionExpressions(loops) {
482
+ for (const loop of loops) {
483
+ // Skip for-in/for-of loops - they don't have test expressions
484
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
485
+ continue;
486
+ }
487
+ if (loop.conditionExpressionId && loop.conditionExpressionType) {
488
+ // Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
489
+ if (loop.conditionExpressionType === 'CallExpression') {
490
+ continue;
491
+ }
492
+ // Only create if it looks like an EXPRESSION ID
493
+ if (loop.conditionExpressionId.includes(':EXPRESSION:')) {
494
+ this._bufferNode({
495
+ id: loop.conditionExpressionId,
496
+ type: 'EXPRESSION',
497
+ name: loop.conditionExpressionType,
498
+ file: loop.file,
499
+ line: loop.conditionLine,
500
+ column: loop.conditionColumn,
501
+ expressionType: loop.conditionExpressionType
502
+ });
503
+ }
504
+ }
505
+ }
506
+ }
507
+ /**
508
+ * Buffer BRANCH edges (CONTAINS, HAS_CONDITION, HAS_CONSEQUENT, HAS_ALTERNATE)
509
+ *
510
+ * REG-275: For CallExpression discriminants (switch(getType())), looks up the
511
+ * actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
512
+ *
513
+ * Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
514
+ * pointing to the if-body and else-body SCOPEs.
515
+ */
516
+ bufferBranchEdges(branches, callSites, scopes) {
517
+ for (const branch of branches) {
518
+ // Parent SCOPE -> CONTAINS -> BRANCH
519
+ if (branch.parentScopeId) {
520
+ this._bufferEdge({
521
+ type: 'CONTAINS',
522
+ src: branch.parentScopeId,
523
+ dst: branch.id
524
+ });
525
+ }
526
+ // BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
527
+ if (branch.discriminantExpressionId) {
528
+ let targetId = branch.discriminantExpressionId;
529
+ // For CallExpression discriminants, look up the actual CALL_SITE by coordinates
530
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
531
+ if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
532
+ const callSite = callSites.find(cs => cs.file === branch.file &&
533
+ cs.line === branch.discriminantLine &&
534
+ cs.column === branch.discriminantColumn);
535
+ if (callSite) {
536
+ targetId = callSite.id;
537
+ }
538
+ }
539
+ this._bufferEdge({
540
+ type: 'HAS_CONDITION',
541
+ src: branch.id,
542
+ dst: targetId
543
+ });
544
+ }
545
+ // Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
546
+ if (branch.branchType === 'if') {
547
+ // Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
548
+ const consequentScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'if_statement');
549
+ if (consequentScope) {
550
+ this._bufferEdge({
551
+ type: 'HAS_CONSEQUENT',
552
+ src: branch.id,
553
+ dst: consequentScope.id
554
+ });
555
+ }
556
+ // Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
557
+ const alternateScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'else_statement');
558
+ if (alternateScope) {
559
+ this._bufferEdge({
560
+ type: 'HAS_ALTERNATE',
561
+ src: branch.id,
562
+ dst: alternateScope.id
563
+ });
564
+ }
565
+ // For else-if chains: if this branch is the alternate of another branch
566
+ // This is handled differently - see below
567
+ }
568
+ // REG-287: For ternary branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges to expressions
569
+ if (branch.branchType === 'ternary') {
570
+ if (branch.consequentExpressionId) {
571
+ this._bufferEdge({
572
+ type: 'HAS_CONSEQUENT',
573
+ src: branch.id,
574
+ dst: branch.consequentExpressionId
575
+ });
576
+ }
577
+ if (branch.alternateExpressionId) {
578
+ this._bufferEdge({
579
+ type: 'HAS_ALTERNATE',
580
+ src: branch.id,
581
+ dst: branch.alternateExpressionId
582
+ });
583
+ }
584
+ }
585
+ // Phase 3: For else-if chains, create HAS_ALTERNATE from parent branch to this branch
586
+ if (branch.isAlternateOfBranchId) {
587
+ this._bufferEdge({
588
+ type: 'HAS_ALTERNATE',
589
+ src: branch.isAlternateOfBranchId,
590
+ dst: branch.id
591
+ });
592
+ }
593
+ }
594
+ }
595
+ /**
596
+ * Buffer CASE edges (HAS_CASE, HAS_DEFAULT)
597
+ */
598
+ bufferCaseEdges(cases) {
599
+ for (const caseInfo of cases) {
600
+ // BRANCH -> HAS_CASE or HAS_DEFAULT -> CASE
601
+ const edgeType = caseInfo.isDefault ? 'HAS_DEFAULT' : 'HAS_CASE';
602
+ this._bufferEdge({
603
+ type: edgeType,
604
+ src: caseInfo.parentBranchId,
605
+ dst: caseInfo.id
606
+ });
607
+ }
608
+ }
609
+ /**
610
+ * Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK nodes (Phase 4)
611
+ *
612
+ * Creates edges for:
613
+ * - Parent -> CONTAINS -> TRY_BLOCK
614
+ * - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
615
+ * - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
616
+ */
617
+ bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks) {
618
+ // Buffer TRY_BLOCK edges
619
+ for (const tryBlock of tryBlocks) {
620
+ // Parent -> CONTAINS -> TRY_BLOCK
621
+ if (tryBlock.parentScopeId) {
622
+ this._bufferEdge({
623
+ type: 'CONTAINS',
624
+ src: tryBlock.parentScopeId,
625
+ dst: tryBlock.id
626
+ });
627
+ }
628
+ }
629
+ // Buffer CATCH_BLOCK edges (HAS_CATCH from TRY_BLOCK)
630
+ for (const catchBlock of catchBlocks) {
631
+ // TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
632
+ this._bufferEdge({
633
+ type: 'HAS_CATCH',
634
+ src: catchBlock.parentTryBlockId,
635
+ dst: catchBlock.id
636
+ });
637
+ }
638
+ // Buffer FINALLY_BLOCK edges (HAS_FINALLY from TRY_BLOCK)
639
+ for (const finallyBlock of finallyBlocks) {
640
+ // TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
641
+ this._bufferEdge({
642
+ type: 'HAS_FINALLY',
643
+ src: finallyBlock.parentTryBlockId,
644
+ dst: finallyBlock.id
645
+ });
646
+ }
647
+ }
648
+ /**
649
+ * Buffer EXPRESSION nodes for switch discriminants
650
+ * Uses stored metadata directly instead of parsing from ID (Linus improvement)
651
+ *
652
+ * REG-275: For CallExpression discriminants, we don't create nodes here since
653
+ * bufferBranchEdges links to the existing CALL_SITE node by coordinates.
654
+ */
655
+ bufferDiscriminantExpressions(branches, callSites) {
656
+ for (const branch of branches) {
657
+ if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
658
+ // Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
659
+ if (branch.discriminantExpressionType === 'CallExpression') {
660
+ continue;
661
+ }
662
+ // Only create if it looks like an EXPRESSION ID
663
+ if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
664
+ this._bufferNode({
665
+ id: branch.discriminantExpressionId,
666
+ type: 'EXPRESSION',
667
+ name: branch.discriminantExpressionType,
668
+ file: branch.file,
669
+ line: branch.discriminantLine,
670
+ column: branch.discriminantColumn,
671
+ expressionType: branch.discriminantExpressionType
672
+ });
673
+ }
674
+ }
675
+ }
676
+ }
228
677
  bufferVariableEdges(variableDeclarations) {
229
678
  for (const varDecl of variableDeclarations) {
230
679
  const { parentScopeId, ...varData } = varDecl;
@@ -256,17 +705,44 @@ export class GraphBuilder {
256
705
  }
257
706
  }
258
707
  }
259
- bufferMethodCalls(methodCalls) {
708
+ bufferMethodCalls(methodCalls, variableDeclarations, parameters) {
260
709
  for (const methodCall of methodCalls) {
261
- const { parentScopeId, ...methodData } = methodCall;
262
- // Buffer METHOD_CALL node
263
- this._bufferNode(methodData);
710
+ // Keep parentScopeId on node for queries
711
+ this._bufferNode(methodCall);
264
712
  // SCOPE -> CONTAINS -> METHOD_CALL
265
713
  this._bufferEdge({
266
714
  type: 'CONTAINS',
267
- src: parentScopeId,
268
- dst: methodData.id
715
+ src: methodCall.parentScopeId,
716
+ dst: methodCall.id
269
717
  });
718
+ // REG-262: Create USES edge from METHOD_CALL to receiver variable
719
+ // Skip 'this' - it's not a variable node
720
+ if (methodCall.object && methodCall.object !== 'this') {
721
+ // Handle nested member expressions: obj.nested.method() -> use base 'obj'
722
+ const receiverName = methodCall.object.includes('.')
723
+ ? methodCall.object.split('.')[0]
724
+ : methodCall.object;
725
+ // Find receiver variable in current file
726
+ const receiverVar = variableDeclarations.find(v => v.name === receiverName && v.file === methodCall.file);
727
+ if (receiverVar) {
728
+ this._bufferEdge({
729
+ type: 'USES',
730
+ src: methodCall.id,
731
+ dst: receiverVar.id
732
+ });
733
+ }
734
+ else {
735
+ // Check parameters (function arguments)
736
+ const receiverParam = parameters.find(p => p.name === receiverName && p.file === methodCall.file);
737
+ if (receiverParam) {
738
+ this._bufferEdge({
739
+ type: 'USES',
740
+ src: methodCall.id,
741
+ dst: receiverParam.id
742
+ });
743
+ }
744
+ }
745
+ }
270
746
  }
271
747
  }
272
748
  bufferStdioNodes(methodCalls) {
@@ -311,10 +787,11 @@ export class GraphBuilder {
311
787
  }
312
788
  // If superClass, buffer DERIVES_FROM edge with computed ID
313
789
  if (superClass) {
314
- // Compute superclass ID using same format as ClassNode (line 0 = unknown location)
315
- // Assume superclass is in same file (most common case)
790
+ // Compute superclass ID using semantic ID format
791
+ // Assume superclass is in same file at global scope (most common case)
316
792
  // When superclass is in different file, edge will be dangling until that file analyzed
317
- const superClassId = `${file}:CLASS:${superClass}:0`;
793
+ const globalContext = { file, scopePath: [] };
794
+ const superClassId = computeSemanticId('CLASS', superClass, globalContext);
318
795
  this._bufferEdge({
319
796
  type: 'DERIVES_FROM',
320
797
  src: id,
@@ -325,9 +802,11 @@ export class GraphBuilder {
325
802
  }
326
803
  bufferClassNodes(module, classInstantiations, classDeclarations) {
327
804
  // Create lookup map: className → declaration ID
805
+ // Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
806
+ const moduleBasename = basename(module.file);
328
807
  const declarationMap = new Map();
329
808
  for (const decl of classDeclarations) {
330
- if (decl.file === module.file) {
809
+ if (decl.file === moduleBasename) {
331
810
  declarationMap.set(decl.name, decl.id);
332
811
  }
333
812
  }
@@ -335,10 +814,11 @@ export class GraphBuilder {
335
814
  const { variableId, className, line } = instantiation;
336
815
  let classId = declarationMap.get(className);
337
816
  if (!classId) {
338
- // External class - compute ID using ClassNode format (line 0 = unknown location)
339
- // Assume class is in same file (most common case)
817
+ // External class - compute semantic ID
818
+ // Use basename to match CLASS node format (scopeTracker uses basename)
340
819
  // When class is in different file, edge will be dangling until that file analyzed
341
- classId = `${module.file}:CLASS:${className}:0`;
820
+ const globalContext = { file: moduleBasename, scopePath: [] };
821
+ classId = computeSemanticId('CLASS', className, globalContext);
342
822
  // NO node creation - node will exist when class file analyzed
343
823
  }
344
824
  // Buffer INSTANCE_OF edge
@@ -364,18 +844,19 @@ export class GraphBuilder {
364
844
  }
365
845
  bufferImportNodes(module, imports) {
366
846
  for (const imp of imports) {
367
- const { source, specifiers, line, column } = imp;
368
- for (const spec of specifiers) {
369
- // Use ImportNode factory for proper semantic IDs and field population
370
- const importNode = ImportNode.create(spec.local, // name = local binding
847
+ const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
848
+ // REG-273: Handle side-effect-only imports (no specifiers)
849
+ if (specifiers.length === 0) {
850
+ // Side-effect import: import './polyfill.js'
851
+ const importNode = ImportNode.create(source, // name = source (no local binding)
371
852
  module.file, // file
372
853
  line, // line (stored as field, not in ID)
373
854
  column || 0, // column
374
855
  source, // source module
375
856
  {
376
- imported: spec.imported,
377
- local: spec.local
378
- // importType is auto-detected from imported field
857
+ imported: '*', // no specific export
858
+ local: source, // source becomes local
859
+ sideEffect: true // mark as side-effect import
379
860
  });
380
861
  this._bufferNode(importNode);
381
862
  // MODULE -> CONTAINS -> IMPORT
@@ -400,6 +881,49 @@ export class GraphBuilder {
400
881
  });
401
882
  }
402
883
  }
884
+ else {
885
+ // Regular imports with specifiers
886
+ for (const spec of specifiers) {
887
+ // Use ImportNode factory for proper semantic IDs and field population
888
+ const importNode = ImportNode.create(spec.local, // name = local binding
889
+ module.file, // file
890
+ line, // line (stored as field, not in ID)
891
+ column || 0, // column
892
+ source, // source module
893
+ {
894
+ imported: spec.imported,
895
+ local: spec.local,
896
+ sideEffect: false, // regular imports are not side-effects
897
+ // importType is auto-detected from imported field
898
+ // Dynamic import fields
899
+ isDynamic,
900
+ isResolvable,
901
+ dynamicPath
902
+ });
903
+ this._bufferNode(importNode);
904
+ // MODULE -> CONTAINS -> IMPORT
905
+ this._bufferEdge({
906
+ type: 'CONTAINS',
907
+ src: module.id,
908
+ dst: importNode.id
909
+ });
910
+ // Create EXTERNAL_MODULE node for external modules
911
+ const isRelative = source.startsWith('./') || source.startsWith('../');
912
+ if (!isRelative) {
913
+ const externalModule = NodeFactory.createExternalModule(source);
914
+ // Avoid duplicate EXTERNAL_MODULE nodes
915
+ if (!this._createdSingletons.has(externalModule.id)) {
916
+ this._bufferNode(externalModule);
917
+ this._createdSingletons.add(externalModule.id);
918
+ }
919
+ this._bufferEdge({
920
+ type: 'IMPORTS',
921
+ src: module.id,
922
+ dst: externalModule.id
923
+ });
924
+ }
925
+ }
926
+ }
403
927
  }
404
928
  }
405
929
  bufferExportNodes(module, exports) {
@@ -518,11 +1042,26 @@ export class GraphBuilder {
518
1042
  }
519
1043
  bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters) {
520
1044
  for (const assignment of variableAssignments) {
521
- const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, className } = assignment;
1045
+ const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, column, className } = assignment;
522
1046
  // Skip CLASS sourceType - handled async in createClassAssignmentEdges
523
1047
  if (sourceType === 'CLASS') {
524
1048
  continue;
525
1049
  }
1050
+ // CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
1051
+ // Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
1052
+ if (sourceType === 'CONSTRUCTOR_CALL' && className) {
1053
+ const constructorLine = line ?? 0;
1054
+ const constructorColumn = column ?? 0;
1055
+ const constructorFile = assignment.file ?? '';
1056
+ // Generate ID matching the one created in NewExpression visitor
1057
+ const constructorCallId = NodeFactory.generateConstructorCallId(className, constructorFile, constructorLine, constructorColumn);
1058
+ this._bufferEdge({
1059
+ type: 'ASSIGNED_FROM',
1060
+ src: variableId,
1061
+ dst: constructorCallId
1062
+ });
1063
+ continue;
1064
+ }
526
1065
  // Direct LITERAL assignment
527
1066
  if (sourceId && sourceType !== 'EXPRESSION') {
528
1067
  this._bufferEdge({
@@ -600,7 +1139,9 @@ export class GraphBuilder {
600
1139
  }
601
1140
  // EXPRESSION node creation using NodeFactory
602
1141
  else if (sourceType === 'EXPRESSION' && sourceId) {
603
- const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine, column: exprColumn } = assignment;
1142
+ const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine, column: exprColumn,
1143
+ // Destructuring support (REG-201)
1144
+ path, baseName, propertyPath, arrayIndex } = assignment;
604
1145
  // Create node from upstream metadata using factory
605
1146
  const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', exprFile || '', exprLine || 0, exprColumn || 0, {
606
1147
  id: sourceId, // ID from JSASTAnalyzer
@@ -608,7 +1149,12 @@ export class GraphBuilder {
608
1149
  property,
609
1150
  computed,
610
1151
  computedPropertyVar: computedPropertyVar ?? undefined,
611
- operator
1152
+ operator,
1153
+ // Destructuring support (REG-201)
1154
+ path,
1155
+ baseName,
1156
+ propertyPath,
1157
+ arrayIndex
612
1158
  });
613
1159
  this._bufferNode(expressionNode);
614
1160
  this._bufferEdge({
@@ -629,6 +1175,41 @@ export class GraphBuilder {
629
1175
  });
630
1176
  }
631
1177
  }
1178
+ // Call-based source lookup (REG-223)
1179
+ else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
1180
+ const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
1181
+ // Try CALL_SITE first (direct function calls)
1182
+ const callSite = callSites.find(cs => cs.line === callSourceLine &&
1183
+ cs.column === callSourceColumn &&
1184
+ (callSourceName ? cs.name === callSourceName : true));
1185
+ if (callSite) {
1186
+ this._bufferEdge({
1187
+ type: 'DERIVES_FROM',
1188
+ src: sourceId,
1189
+ dst: callSite.id
1190
+ });
1191
+ }
1192
+ // Fall back to methodCalls (arr.map(), obj.getConfig())
1193
+ else {
1194
+ const methodCall = methodCalls.find(mc => mc.line === callSourceLine &&
1195
+ mc.column === callSourceColumn &&
1196
+ (callSourceName ? mc.name === callSourceName : true));
1197
+ if (methodCall) {
1198
+ this._bufferEdge({
1199
+ type: 'DERIVES_FROM',
1200
+ src: sourceId,
1201
+ dst: methodCall.id
1202
+ });
1203
+ }
1204
+ // Log warning when lookup fails (per Linus review - no silent failures)
1205
+ else {
1206
+ console.warn(`[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
1207
+ `at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
1208
+ `Expected CALL_SITE or methodCall for "${callSourceName}". ` +
1209
+ `This indicates a coordinate mismatch or missing call node.`);
1210
+ }
1211
+ }
1212
+ }
632
1213
  if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
633
1214
  if (leftSourceName) {
634
1215
  const leftVar = variableDeclarations.find(v => v.name === leftSourceName && (!varFile || v.file === varFile));
@@ -737,6 +1318,12 @@ export class GraphBuilder {
737
1318
  targetNodeId = nestedCall.id;
738
1319
  }
739
1320
  }
1321
+ else if (targetType === 'LITERAL' ||
1322
+ targetType === 'OBJECT_LITERAL' ||
1323
+ targetType === 'ARRAY_LITERAL') {
1324
+ // targetId is already set by CallExpressionVisitor
1325
+ targetNodeId = targetId;
1326
+ }
740
1327
  if (targetNodeId) {
741
1328
  const edgeData = {
742
1329
  type: 'PASSES_ARGUMENT',
@@ -902,36 +1489,27 @@ export class GraphBuilder {
902
1489
  * OPTIMIZED: Uses Map-based lookup cache for O(1) variable lookups instead of O(n) find()
903
1490
  */
904
1491
  bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters) {
905
- // Build lookup cache once: O(n) instead of O(n*m) with find() per mutation
906
- const varLookup = new Map();
907
- for (const v of variableDeclarations) {
908
- varLookup.set(`${v.file}:${v.name}`, v);
909
- }
910
- // Build parameter lookup cache for function-level mutations
911
- const paramLookup = new Map();
912
- for (const p of parameters) {
913
- paramLookup.set(`${p.file}:${p.name}`, p);
914
- }
1492
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
915
1493
  for (const mutation of arrayMutations) {
916
- const { arrayName, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
1494
+ const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
1495
+ const scopePath = mutationScopePath ?? [];
917
1496
  // REG-117: For nested mutations (obj.arr.push), resolve target node
918
- // First try direct lookup, then fallback to base object
919
1497
  let targetNodeId = null;
920
1498
  let nestedProperty;
921
1499
  if (isNested && baseObjectName) {
922
1500
  // Skip 'this.items.push' - 'this' is not a variable node
923
1501
  if (baseObjectName === 'this')
924
1502
  continue;
925
- // Nested mutation: try base object lookup
926
- const baseVar = varLookup.get(`${file}:${baseObjectName}`);
927
- const baseParam = !baseVar ? paramLookup.get(`${file}:${baseObjectName}`) : null;
1503
+ // Nested mutation: try base object lookup with scope chain (REG-309)
1504
+ const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
1505
+ const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
928
1506
  targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
929
1507
  nestedProperty = propertyName;
930
1508
  }
931
1509
  else {
932
- // Direct mutation: arr.push()
933
- const arrayVar = varLookup.get(`${file}:${arrayName}`);
934
- const arrayParam = !arrayVar ? paramLookup.get(`${file}:${arrayName}`) : null;
1510
+ // Direct mutation: arr.push() (REG-309)
1511
+ const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
1512
+ const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
935
1513
  targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
936
1514
  }
937
1515
  if (!targetNodeId)
@@ -939,9 +1517,9 @@ export class GraphBuilder {
939
1517
  // Create FLOWS_INTO edges for each inserted value
940
1518
  for (const arg of insertedValues) {
941
1519
  if (arg.valueType === 'VARIABLE' && arg.valueName) {
942
- // O(1) lookup instead of O(n) find
943
- const sourceVar = varLookup.get(`${file}:${arg.valueName}`);
944
- const sourceParam = !sourceVar ? paramLookup.get(`${file}:${arg.valueName}`) : null;
1520
+ // Scope-aware lookup for source variable (REG-309)
1521
+ const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
1522
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
945
1523
  const sourceNodeId = sourceVar?.id ?? sourceParam?.id;
946
1524
  if (sourceNodeId) {
947
1525
  const edgeData = {
@@ -975,15 +1553,17 @@ export class GraphBuilder {
975
1553
  */
976
1554
  bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations) {
977
1555
  for (const mutation of objectMutations) {
978
- const { objectName, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
1556
+ const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
1557
+ const scopePath = mutationScopePath ?? [];
979
1558
  // Find the target node (object variable, parameter, or class for 'this')
980
1559
  let objectNodeId = null;
981
1560
  let effectiveMutationType = mutationType;
982
1561
  if (objectName !== 'this') {
983
- // Regular object - find variable or parameter
984
- const objectVar = variableDeclarations.find(v => v.name === objectName && v.file === file);
985
- const objectParam = !objectVar ? parameters.find(p => p.name === objectName && p.file === file) : null;
986
- objectNodeId = objectVar?.id ?? objectParam?.id ?? null;
1562
+ // Regular object - find variable, parameter, or function using scope chain (REG-309)
1563
+ const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
1564
+ const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
1565
+ const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
1566
+ objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
987
1567
  if (!objectNodeId)
988
1568
  continue;
989
1569
  }
@@ -1003,9 +1583,9 @@ export class GraphBuilder {
1003
1583
  }
1004
1584
  // Create FLOWS_INTO edge for VARIABLE value type
1005
1585
  if (value.valueType === 'VARIABLE' && value.valueName) {
1006
- // Find the source: can be variable, parameter, or function (arrow functions assigned to const)
1007
- const sourceVar = variableDeclarations.find(v => v.name === value.valueName && v.file === file);
1008
- const sourceParam = !sourceVar ? parameters.find(p => p.name === value.valueName && p.file === file) : null;
1586
+ // Find the source: can be variable, parameter, or function using scope chain (REG-309)
1587
+ const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
1588
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
1009
1589
  const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
1010
1590
  const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
1011
1591
  if (sourceNodeId && objectNodeId) {
@@ -1029,6 +1609,577 @@ export class GraphBuilder {
1029
1609
  // For literals, object literals, etc. - we just track variable -> object flows for now
1030
1610
  }
1031
1611
  }
1612
+ /**
1613
+ * Resolve variable by name using scope chain lookup (REG-309).
1614
+ * Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
1615
+ *
1616
+ * @param name - Variable name
1617
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
1618
+ * @param file - File path
1619
+ * @param variables - All variable declarations
1620
+ * @returns Variable declaration or null if not found
1621
+ */
1622
+ resolveVariableInScope(name, scopePath, file, variables) {
1623
+ // Try current scope, then parent, then grandparent, etc.
1624
+ for (let i = scopePath.length; i >= 0; i--) {
1625
+ const searchScopePath = scopePath.slice(0, i);
1626
+ const matchingVar = variables.find(v => {
1627
+ if (v.name !== name || v.file !== file)
1628
+ return false;
1629
+ // Variable ID IS the semantic ID (when scopeTracker was available during analysis)
1630
+ // Format: file->scope1->scope2->TYPE->name
1631
+ // Legacy format: VARIABLE#name#file#line:column:counter
1632
+ // Try parsing as semantic ID
1633
+ const parsed = parseSemanticId(v.id);
1634
+ // REG-329: Check for both VARIABLE and CONSTANT (const declarations)
1635
+ if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
1636
+ // FIXED (REG-309): Handle module-level scope matching
1637
+ // Empty search scope [] should match semantic ID scope ['global']
1638
+ if (searchScopePath.length === 0) {
1639
+ return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
1640
+ }
1641
+ // Non-empty scope: exact match
1642
+ return this.scopePathsMatch(parsed.scopePath, searchScopePath);
1643
+ }
1644
+ // Legacy ID - assume module-level if no semantic ID
1645
+ return searchScopePath.length === 0;
1646
+ });
1647
+ if (matchingVar)
1648
+ return matchingVar;
1649
+ }
1650
+ return null;
1651
+ }
1652
+ /**
1653
+ * Resolve parameter by name using scope chain lookup (REG-309).
1654
+ * Same semantics as resolveVariableInScope but for parameters.
1655
+ *
1656
+ * @param name - Parameter name
1657
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
1658
+ * @param file - File path
1659
+ * @param parameters - All parameter declarations
1660
+ * @returns Parameter declaration or null if not found
1661
+ */
1662
+ resolveParameterInScope(name, scopePath, file, parameters) {
1663
+ // Parameters have semanticId field populated (unlike variables which use id field)
1664
+ return parameters.find(p => {
1665
+ if (p.name !== name || p.file !== file)
1666
+ return false;
1667
+ if (p.semanticId) {
1668
+ const parsed = parseSemanticId(p.semanticId);
1669
+ if (parsed && parsed.type === 'PARAMETER') {
1670
+ // Check if parameter's scope matches any scope in the chain
1671
+ for (let i = scopePath.length; i >= 0; i--) {
1672
+ const searchScopePath = scopePath.slice(0, i);
1673
+ // FIXED (REG-309): Handle module-level scope matching for parameters
1674
+ if (searchScopePath.length === 0) {
1675
+ if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
1676
+ return true;
1677
+ }
1678
+ }
1679
+ else {
1680
+ if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
1681
+ return true;
1682
+ }
1683
+ }
1684
+ }
1685
+ }
1686
+ }
1687
+ return false;
1688
+ }) ?? null;
1689
+ }
1690
+ /**
1691
+ * Check if two scope paths match (REG-309).
1692
+ * Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
1693
+ */
1694
+ scopePathsMatch(a, b) {
1695
+ if (a.length !== b.length)
1696
+ return false;
1697
+ return a.every((item, idx) => item === b[idx]);
1698
+ }
1699
+ /**
1700
+ * Buffer FLOWS_INTO edges for variable reassignments.
1701
+ * Handles: x = y, x += y (when x is already declared, not initialization)
1702
+ *
1703
+ * Edge patterns:
1704
+ * - Simple assignment (=): source --FLOWS_INTO--> variable
1705
+ * - Compound operators (+=, -=, etc.):
1706
+ * - source --FLOWS_INTO--> variable (write new value)
1707
+ * - variable --READS_FROM--> variable (self-loop: reads current value before write)
1708
+ *
1709
+ * REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
1710
+ *
1711
+ * REG-290: Complete implementation with inline node creation (no continue statements).
1712
+ */
1713
+ bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters) {
1714
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
1715
+ // Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
1716
+ for (const reassignment of variableReassignments) {
1717
+ const { variableName, mutationScopePath, valueType, valueName, valueId, callLine, callColumn, operator, literalValue, expressionType, expressionMetadata, file, line, column } = reassignment;
1718
+ // Find target variable node using scope chain resolution (REG-309)
1719
+ const scopePath = mutationScopePath ?? [];
1720
+ const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
1721
+ const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
1722
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
1723
+ if (!targetNodeId) {
1724
+ // Variable not found - could be external reference
1725
+ continue;
1726
+ }
1727
+ // Resolve source node based on value type
1728
+ let sourceNodeId = null;
1729
+ // LITERAL: Create node inline (NO CONTINUE STATEMENT)
1730
+ if (valueType === 'LITERAL' && valueId) {
1731
+ // Create LITERAL node
1732
+ this._bufferNode({
1733
+ type: 'LITERAL',
1734
+ id: valueId,
1735
+ value: literalValue,
1736
+ file,
1737
+ line,
1738
+ column
1739
+ });
1740
+ sourceNodeId = valueId;
1741
+ }
1742
+ // VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
1743
+ else if (valueType === 'VARIABLE' && valueName) {
1744
+ const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
1745
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
1746
+ sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
1747
+ }
1748
+ // CALL_SITE: Look up existing call node
1749
+ else if (valueType === 'CALL_SITE' && callLine && callColumn) {
1750
+ const callSite = callSites.find(cs => cs.line === callLine && cs.column === callColumn && cs.file === file);
1751
+ sourceNodeId = callSite?.id ?? null;
1752
+ }
1753
+ // METHOD_CALL: Look up existing method call node
1754
+ else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
1755
+ const methodCall = methodCalls.find(mc => mc.line === callLine && mc.column === callColumn && mc.file === file);
1756
+ sourceNodeId = methodCall?.id ?? null;
1757
+ }
1758
+ // EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
1759
+ else if (valueType === 'EXPRESSION' && valueId && expressionType) {
1760
+ // Create EXPRESSION node using NodeFactory
1761
+ const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType, file, line, column, {
1762
+ id: valueId, // ID from JSASTAnalyzer
1763
+ object: expressionMetadata?.object,
1764
+ property: expressionMetadata?.property,
1765
+ computed: expressionMetadata?.computed,
1766
+ computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
1767
+ operator: expressionMetadata?.operator
1768
+ });
1769
+ this._bufferNode(expressionNode);
1770
+ sourceNodeId = valueId;
1771
+ }
1772
+ // Create edges if source found
1773
+ if (sourceNodeId && targetNodeId) {
1774
+ // For compound operators (operator !== '='), LHS reads its own current value
1775
+ // Create READS_FROM self-loop (Linus requirement)
1776
+ if (operator !== '=') {
1777
+ this._bufferEdge({
1778
+ type: 'READS_FROM',
1779
+ src: targetNodeId, // Variable reads from...
1780
+ dst: targetNodeId // ...itself (self-loop)
1781
+ });
1782
+ }
1783
+ // RHS flows into LHS (write side)
1784
+ this._bufferEdge({
1785
+ type: 'FLOWS_INTO',
1786
+ src: sourceNodeId,
1787
+ dst: targetNodeId
1788
+ });
1789
+ }
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Buffer RETURNS edges connecting return expressions to their containing functions.
1794
+ *
1795
+ * Edge direction: returnExpression --RETURNS--> function
1796
+ *
1797
+ * This enables tracing data flow through function calls:
1798
+ * - Query: "What does formatDate return?"
1799
+ * - Answer: Follow RETURNS edges from function to see all possible return values
1800
+ */
1801
+ bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters) {
1802
+ for (const ret of returnStatements) {
1803
+ const { parentFunctionId, returnValueType, file } = ret;
1804
+ // Skip if no value returned (bare return;)
1805
+ if (returnValueType === 'NONE') {
1806
+ continue;
1807
+ }
1808
+ let sourceNodeId = null;
1809
+ switch (returnValueType) {
1810
+ case 'LITERAL':
1811
+ // Direct reference to literal node
1812
+ sourceNodeId = ret.returnValueId ?? null;
1813
+ break;
1814
+ case 'VARIABLE': {
1815
+ // Find variable declaration by name in same file
1816
+ const varName = ret.returnValueName;
1817
+ if (varName) {
1818
+ const sourceVar = variableDeclarations.find(v => v.name === varName && v.file === file);
1819
+ if (sourceVar) {
1820
+ sourceNodeId = sourceVar.id;
1821
+ }
1822
+ else {
1823
+ // Check parameters
1824
+ const sourceParam = parameters.find(p => p.name === varName && p.file === file);
1825
+ if (sourceParam) {
1826
+ sourceNodeId = sourceParam.id;
1827
+ }
1828
+ }
1829
+ }
1830
+ break;
1831
+ }
1832
+ case 'CALL_SITE': {
1833
+ // Find call site by coordinates
1834
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
1835
+ if (returnValueLine && returnValueColumn) {
1836
+ const callSite = callSites.find(cs => cs.line === returnValueLine &&
1837
+ cs.column === returnValueColumn &&
1838
+ (returnValueCallName ? cs.name === returnValueCallName : true));
1839
+ if (callSite) {
1840
+ sourceNodeId = callSite.id;
1841
+ }
1842
+ }
1843
+ break;
1844
+ }
1845
+ case 'METHOD_CALL': {
1846
+ // Find method call by coordinates and method name
1847
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
1848
+ if (returnValueLine && returnValueColumn) {
1849
+ const methodCall = methodCalls.find(mc => mc.line === returnValueLine &&
1850
+ mc.column === returnValueColumn &&
1851
+ mc.file === file &&
1852
+ (returnValueCallName ? mc.method === returnValueCallName : true));
1853
+ if (methodCall) {
1854
+ sourceNodeId = methodCall.id;
1855
+ }
1856
+ }
1857
+ break;
1858
+ }
1859
+ case 'EXPRESSION': {
1860
+ // REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
1861
+ const { expressionType, returnValueId, returnValueLine, returnValueColumn, operator, object, property, computed, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, expressionSourceNames, unaryArgSourceName } = ret;
1862
+ // Skip if no expression ID was generated
1863
+ if (!returnValueId) {
1864
+ break;
1865
+ }
1866
+ // Create EXPRESSION node using NodeFactory
1867
+ const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', file, returnValueLine || ret.line, returnValueColumn || ret.column, {
1868
+ id: returnValueId,
1869
+ object,
1870
+ property,
1871
+ computed,
1872
+ operator
1873
+ });
1874
+ this._bufferNode(expressionNode);
1875
+ sourceNodeId = returnValueId;
1876
+ // Buffer DERIVES_FROM edges based on expression type
1877
+ // Helper function to find source variable or parameter
1878
+ const findSource = (name) => {
1879
+ const variable = variableDeclarations.find(v => v.name === name && v.file === file);
1880
+ if (variable)
1881
+ return variable.id;
1882
+ const param = parameters.find(p => p.name === name && p.file === file);
1883
+ if (param)
1884
+ return param.id;
1885
+ return null;
1886
+ };
1887
+ // MemberExpression: derives from the object
1888
+ if (expressionType === 'MemberExpression' && objectSourceName) {
1889
+ const sourceId = findSource(objectSourceName);
1890
+ if (sourceId) {
1891
+ this._bufferEdge({
1892
+ type: 'DERIVES_FROM',
1893
+ src: returnValueId,
1894
+ dst: sourceId
1895
+ });
1896
+ }
1897
+ }
1898
+ // BinaryExpression / LogicalExpression: derives from left and right operands
1899
+ if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
1900
+ if (leftSourceName) {
1901
+ const sourceId = findSource(leftSourceName);
1902
+ if (sourceId) {
1903
+ this._bufferEdge({
1904
+ type: 'DERIVES_FROM',
1905
+ src: returnValueId,
1906
+ dst: sourceId
1907
+ });
1908
+ }
1909
+ }
1910
+ if (rightSourceName) {
1911
+ const sourceId = findSource(rightSourceName);
1912
+ if (sourceId) {
1913
+ this._bufferEdge({
1914
+ type: 'DERIVES_FROM',
1915
+ src: returnValueId,
1916
+ dst: sourceId
1917
+ });
1918
+ }
1919
+ }
1920
+ }
1921
+ // ConditionalExpression: derives from consequent and alternate
1922
+ if (expressionType === 'ConditionalExpression') {
1923
+ if (consequentSourceName) {
1924
+ const sourceId = findSource(consequentSourceName);
1925
+ if (sourceId) {
1926
+ this._bufferEdge({
1927
+ type: 'DERIVES_FROM',
1928
+ src: returnValueId,
1929
+ dst: sourceId
1930
+ });
1931
+ }
1932
+ }
1933
+ if (alternateSourceName) {
1934
+ const sourceId = findSource(alternateSourceName);
1935
+ if (sourceId) {
1936
+ this._bufferEdge({
1937
+ type: 'DERIVES_FROM',
1938
+ src: returnValueId,
1939
+ dst: sourceId
1940
+ });
1941
+ }
1942
+ }
1943
+ }
1944
+ // UnaryExpression: derives from the argument
1945
+ if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
1946
+ const sourceId = findSource(unaryArgSourceName);
1947
+ if (sourceId) {
1948
+ this._bufferEdge({
1949
+ type: 'DERIVES_FROM',
1950
+ src: returnValueId,
1951
+ dst: sourceId
1952
+ });
1953
+ }
1954
+ }
1955
+ // TemplateLiteral: derives from all embedded expressions
1956
+ if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
1957
+ for (const sourceName of expressionSourceNames) {
1958
+ const sourceId = findSource(sourceName);
1959
+ if (sourceId) {
1960
+ this._bufferEdge({
1961
+ type: 'DERIVES_FROM',
1962
+ src: returnValueId,
1963
+ dst: sourceId
1964
+ });
1965
+ }
1966
+ }
1967
+ }
1968
+ break;
1969
+ }
1970
+ }
1971
+ // Create RETURNS edge if we found a source node
1972
+ if (sourceNodeId && parentFunctionId) {
1973
+ this._bufferEdge({
1974
+ type: 'RETURNS',
1975
+ src: sourceNodeId,
1976
+ dst: parentFunctionId
1977
+ });
1978
+ }
1979
+ }
1980
+ }
1981
+ /**
1982
+ * Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
1983
+ *
1984
+ * Handles two target types:
1985
+ * - IDENTIFIER: Simple variable (i++, --count)
1986
+ * - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
1987
+ *
1988
+ * Creates:
1989
+ * - UPDATE_EXPRESSION node with operator and target metadata
1990
+ * - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
1991
+ * - READS_FROM self-loop: target -> target (reads current value before update)
1992
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
1993
+ *
1994
+ * REG-288: Initial implementation for IDENTIFIER targets
1995
+ * REG-312: Extended for MEMBER_EXPRESSION targets
1996
+ */
1997
+ bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations) {
1998
+ // Build lookup caches: O(n) instead of O(n*m)
1999
+ const varLookup = new Map();
2000
+ for (const v of variableDeclarations) {
2001
+ varLookup.set(`${v.file}:${v.name}`, v);
2002
+ }
2003
+ const paramLookup = new Map();
2004
+ for (const p of parameters) {
2005
+ paramLookup.set(`${p.file}:${p.name}`, p);
2006
+ }
2007
+ for (const update of updateExpressions) {
2008
+ if (update.targetType === 'IDENTIFIER') {
2009
+ // REG-288: Simple identifier (i++, --count)
2010
+ this.bufferIdentifierUpdate(update, varLookup, paramLookup);
2011
+ }
2012
+ else if (update.targetType === 'MEMBER_EXPRESSION') {
2013
+ // REG-312: Member expression (obj.prop++, arr[i]++)
2014
+ this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
2015
+ }
2016
+ }
2017
+ }
2018
+ /**
2019
+ * Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
2020
+ * REG-288: Original implementation extracted for clarity
2021
+ */
2022
+ bufferIdentifierUpdate(update, varLookup, paramLookup) {
2023
+ const { variableName, operator, prefix, file, line, column, parentScopeId } = update;
2024
+ if (!variableName)
2025
+ return;
2026
+ // Find target variable node
2027
+ const targetVar = varLookup.get(`${file}:${variableName}`);
2028
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
2029
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
2030
+ if (!targetNodeId) {
2031
+ // Variable not found - could be module-level or external reference
2032
+ return;
2033
+ }
2034
+ // Create UPDATE_EXPRESSION node
2035
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2036
+ this._bufferNode({
2037
+ type: 'UPDATE_EXPRESSION',
2038
+ id: updateId,
2039
+ name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
2040
+ targetType: 'IDENTIFIER',
2041
+ operator,
2042
+ prefix,
2043
+ variableName,
2044
+ file,
2045
+ line,
2046
+ column
2047
+ });
2048
+ // Create READS_FROM self-loop
2049
+ this._bufferEdge({
2050
+ type: 'READS_FROM',
2051
+ src: targetNodeId,
2052
+ dst: targetNodeId
2053
+ });
2054
+ // Create MODIFIES edge
2055
+ this._bufferEdge({
2056
+ type: 'MODIFIES',
2057
+ src: updateId,
2058
+ dst: targetNodeId
2059
+ });
2060
+ // Create CONTAINS edge
2061
+ if (parentScopeId) {
2062
+ this._bufferEdge({
2063
+ type: 'CONTAINS',
2064
+ src: parentScopeId,
2065
+ dst: updateId
2066
+ });
2067
+ }
2068
+ }
2069
+ /**
2070
+ * Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
2071
+ * REG-312: New implementation for member expression targets
2072
+ *
2073
+ * Creates:
2074
+ * - UPDATE_EXPRESSION node with member expression metadata
2075
+ * - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
2076
+ * - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
2077
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
2078
+ */
2079
+ bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations) {
2080
+ const { objectName, propertyName, mutationType, computedPropertyVar, enclosingClassName, operator, prefix, file, line, column, parentScopeId } = update;
2081
+ if (!objectName || !propertyName)
2082
+ return;
2083
+ // Find target object node
2084
+ let objectNodeId = null;
2085
+ if (objectName !== 'this') {
2086
+ // Regular object: obj.prop++, arr[i]++
2087
+ const targetVar = varLookup.get(`${file}:${objectName}`);
2088
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
2089
+ objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
2090
+ }
2091
+ else {
2092
+ // this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
2093
+ if (!enclosingClassName)
2094
+ return;
2095
+ const fileBasename = basename(file);
2096
+ const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
2097
+ objectNodeId = classDecl?.id ?? null;
2098
+ }
2099
+ if (!objectNodeId) {
2100
+ // Object not found - external reference or scope issue
2101
+ return;
2102
+ }
2103
+ // Create UPDATE_EXPRESSION node
2104
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2105
+ // Display name: "obj.prop++" or "this.count++" or "arr[i]++"
2106
+ const displayName = (() => {
2107
+ const opStr = prefix ? operator : '';
2108
+ const postOpStr = prefix ? '' : operator;
2109
+ if (objectName === 'this') {
2110
+ return `${opStr}this.${propertyName}${postOpStr}`;
2111
+ }
2112
+ if (mutationType === 'computed') {
2113
+ const computedPart = computedPropertyVar || '?';
2114
+ return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
2115
+ }
2116
+ return `${opStr}${objectName}.${propertyName}${postOpStr}`;
2117
+ })();
2118
+ this._bufferNode({
2119
+ type: 'UPDATE_EXPRESSION',
2120
+ id: updateId,
2121
+ name: displayName,
2122
+ targetType: 'MEMBER_EXPRESSION',
2123
+ operator,
2124
+ prefix,
2125
+ objectName,
2126
+ propertyName,
2127
+ mutationType,
2128
+ computedPropertyVar,
2129
+ enclosingClassName,
2130
+ file,
2131
+ line,
2132
+ column
2133
+ });
2134
+ // Create READS_FROM self-loop (object reads from itself)
2135
+ this._bufferEdge({
2136
+ type: 'READS_FROM',
2137
+ src: objectNodeId,
2138
+ dst: objectNodeId
2139
+ });
2140
+ // Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
2141
+ this._bufferEdge({
2142
+ type: 'MODIFIES',
2143
+ src: updateId,
2144
+ dst: objectNodeId
2145
+ });
2146
+ // Create CONTAINS edge
2147
+ if (parentScopeId) {
2148
+ this._bufferEdge({
2149
+ type: 'CONTAINS',
2150
+ src: parentScopeId,
2151
+ dst: updateId
2152
+ });
2153
+ }
2154
+ }
2155
+ /**
2156
+ * Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
2157
+ *
2158
+ * Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
2159
+ * This enables traceValues to follow Promise data flow:
2160
+ *
2161
+ * Example:
2162
+ * ```
2163
+ * const result = new Promise((resolve) => {
2164
+ * resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
2165
+ * });
2166
+ * ```
2167
+ *
2168
+ * The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
2169
+ * data flows FROM resolve(value) TO the Promise result.
2170
+ */
2171
+ bufferPromiseResolutionEdges(promiseResolutions) {
2172
+ for (const resolution of promiseResolutions) {
2173
+ this._bufferEdge({
2174
+ type: 'RESOLVES_TO',
2175
+ src: resolution.callId,
2176
+ dst: resolution.constructorCallId,
2177
+ metadata: {
2178
+ isReject: resolution.isReject
2179
+ }
2180
+ });
2181
+ }
2182
+ }
1032
2183
  /**
1033
2184
  * Buffer OBJECT_LITERAL nodes to the graph.
1034
2185
  * These are object literals passed as function arguments or nested in other literals.
@@ -1065,6 +2216,46 @@ export class GraphBuilder {
1065
2216
  });
1066
2217
  }
1067
2218
  }
2219
+ /**
2220
+ * Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
2221
+ * Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
2222
+ *
2223
+ * REG-329: Adds scope-aware variable resolution for VARIABLE property values.
2224
+ * Uses the same resolveVariableInScope infrastructure as mutation handlers.
2225
+ */
2226
+ bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters) {
2227
+ for (const prop of objectProperties) {
2228
+ // REG-329: Handle VARIABLE value types with scope resolution
2229
+ if (prop.valueType === 'VARIABLE' && prop.valueName) {
2230
+ const scopePath = prop.valueScopePath ?? [];
2231
+ const file = prop.file;
2232
+ // Resolve variable using scope chain
2233
+ const resolvedVar = this.resolveVariableInScope(prop.valueName, scopePath, file, variableDeclarations);
2234
+ const resolvedParam = !resolvedVar
2235
+ ? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
2236
+ : null;
2237
+ const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
2238
+ if (resolvedNodeId) {
2239
+ this._bufferEdge({
2240
+ type: 'HAS_PROPERTY',
2241
+ src: prop.objectId,
2242
+ dst: resolvedNodeId,
2243
+ propertyName: prop.propertyName
2244
+ });
2245
+ }
2246
+ continue;
2247
+ }
2248
+ // Existing logic for non-VARIABLE types
2249
+ if (prop.valueNodeId) {
2250
+ this._bufferEdge({
2251
+ type: 'HAS_PROPERTY',
2252
+ src: prop.objectId,
2253
+ dst: prop.valueNodeId,
2254
+ propertyName: prop.propertyName
2255
+ });
2256
+ }
2257
+ }
2258
+ }
1068
2259
  /**
1069
2260
  * Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
1070
2261
  */