@grafema/core 0.1.0-alpha.5 → 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 (530) hide show
  1. package/README.md +0 -1
  2. package/dist/Orchestrator.d.ts +31 -2
  3. package/dist/Orchestrator.d.ts.map +1 -1
  4. package/dist/Orchestrator.js +222 -27
  5. package/dist/config/ConfigLoader.d.ts +90 -0
  6. package/dist/config/ConfigLoader.d.ts.map +1 -0
  7. package/dist/config/ConfigLoader.js +249 -0
  8. package/dist/config/index.d.ts +6 -0
  9. package/dist/config/index.d.ts.map +1 -0
  10. package/dist/config/index.js +4 -0
  11. package/dist/core/ASTWorker.d.ts +11 -36
  12. package/dist/core/ASTWorker.d.ts.map +1 -1
  13. package/dist/core/ASTWorker.js +93 -99
  14. package/dist/core/CoverageAnalyzer.d.ts +65 -0
  15. package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
  16. package/dist/core/CoverageAnalyzer.js +198 -0
  17. package/dist/core/FileExplainer.d.ts +101 -0
  18. package/dist/core/FileExplainer.d.ts.map +1 -0
  19. package/dist/core/FileExplainer.js +139 -0
  20. package/dist/core/FileNodeManager.d.ts +40 -0
  21. package/dist/core/FileNodeManager.d.ts.map +1 -0
  22. package/dist/core/FileNodeManager.js +84 -0
  23. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  24. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  25. package/dist/core/GraphFreshnessChecker.js +101 -0
  26. package/dist/core/HashUtils.d.ts +24 -0
  27. package/dist/core/HashUtils.d.ts.map +1 -0
  28. package/dist/core/HashUtils.js +45 -0
  29. package/dist/core/IncrementalReanalyzer.d.ts +36 -0
  30. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  31. package/dist/core/IncrementalReanalyzer.js +132 -0
  32. package/dist/core/NodeFactory.d.ts +266 -19
  33. package/dist/core/NodeFactory.d.ts.map +1 -1
  34. package/dist/core/NodeFactory.js +256 -21
  35. package/dist/core/ScopeTracker.d.ts +84 -0
  36. package/dist/core/ScopeTracker.d.ts.map +1 -0
  37. package/dist/core/ScopeTracker.js +116 -0
  38. package/dist/core/SemanticId.d.ts +90 -0
  39. package/dist/core/SemanticId.d.ts.map +1 -0
  40. package/dist/core/SemanticId.js +115 -0
  41. package/dist/core/VersionManager.d.ts.map +1 -1
  42. package/dist/core/VersionManager.js +3 -2
  43. package/dist/core/nodes/ArgumentExpressionNode.d.ts +43 -0
  44. package/dist/core/nodes/ArgumentExpressionNode.d.ts.map +1 -0
  45. package/dist/core/nodes/ArgumentExpressionNode.js +60 -0
  46. package/dist/core/nodes/ArrayLiteralNode.d.ts +27 -0
  47. package/dist/core/nodes/ArrayLiteralNode.d.ts.map +1 -0
  48. package/dist/core/nodes/ArrayLiteralNode.js +43 -0
  49. package/dist/core/nodes/BranchNode.d.ts +41 -0
  50. package/dist/core/nodes/BranchNode.d.ts.map +1 -0
  51. package/dist/core/nodes/BranchNode.js +82 -0
  52. package/dist/core/nodes/CallSiteNode.d.ts +30 -2
  53. package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
  54. package/dist/core/nodes/CallSiteNode.js +54 -4
  55. package/dist/core/nodes/CaseNode.d.ts +43 -0
  56. package/dist/core/nodes/CaseNode.d.ts.map +1 -0
  57. package/dist/core/nodes/CaseNode.js +81 -0
  58. package/dist/core/nodes/ClassNode.d.ts +34 -2
  59. package/dist/core/nodes/ClassNode.d.ts.map +1 -1
  60. package/dist/core/nodes/ClassNode.js +52 -4
  61. package/dist/core/nodes/ConstantNode.d.ts +2 -2
  62. package/dist/core/nodes/ConstantNode.d.ts.map +1 -1
  63. package/dist/core/nodes/ConstantNode.js +6 -4
  64. package/dist/core/nodes/ConstructorCallNode.d.ts +51 -0
  65. package/dist/core/nodes/ConstructorCallNode.d.ts.map +1 -0
  66. package/dist/core/nodes/ConstructorCallNode.js +171 -0
  67. package/dist/core/nodes/DatabaseQueryNode.d.ts +3 -2
  68. package/dist/core/nodes/DatabaseQueryNode.d.ts.map +1 -1
  69. package/dist/core/nodes/DatabaseQueryNode.js +5 -2
  70. package/dist/core/nodes/DecoratorNode.d.ts +42 -0
  71. package/dist/core/nodes/DecoratorNode.d.ts.map +1 -0
  72. package/dist/core/nodes/DecoratorNode.js +64 -0
  73. package/dist/core/nodes/EnumNode.d.ts +42 -0
  74. package/dist/core/nodes/EnumNode.d.ts.map +1 -0
  75. package/dist/core/nodes/EnumNode.js +56 -0
  76. package/dist/core/nodes/EventListenerNode.d.ts +4 -4
  77. package/dist/core/nodes/EventListenerNode.d.ts.map +1 -1
  78. package/dist/core/nodes/EventListenerNode.js +7 -4
  79. package/dist/core/nodes/ExportNode.d.ts +38 -2
  80. package/dist/core/nodes/ExportNode.d.ts.map +1 -1
  81. package/dist/core/nodes/ExportNode.js +54 -4
  82. package/dist/core/nodes/ExpressionNode.d.ts +97 -0
  83. package/dist/core/nodes/ExpressionNode.d.ts.map +1 -0
  84. package/dist/core/nodes/ExpressionNode.js +180 -0
  85. package/dist/core/nodes/ExternalModuleNode.d.ts +32 -0
  86. package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -0
  87. package/dist/core/nodes/ExternalModuleNode.js +49 -0
  88. package/dist/core/nodes/ExternalStdioNode.d.ts +13 -6
  89. package/dist/core/nodes/ExternalStdioNode.d.ts.map +1 -1
  90. package/dist/core/nodes/ExternalStdioNode.js +15 -8
  91. package/dist/core/nodes/FunctionNode.d.ts +36 -0
  92. package/dist/core/nodes/FunctionNode.d.ts.map +1 -1
  93. package/dist/core/nodes/FunctionNode.js +80 -1
  94. package/dist/core/nodes/HttpRequestNode.d.ts +4 -4
  95. package/dist/core/nodes/HttpRequestNode.d.ts.map +1 -1
  96. package/dist/core/nodes/HttpRequestNode.js +7 -4
  97. package/dist/core/nodes/ImportNode.d.ts +28 -6
  98. package/dist/core/nodes/ImportNode.d.ts.map +1 -1
  99. package/dist/core/nodes/ImportNode.js +43 -8
  100. package/dist/core/nodes/InterfaceNode.d.ts +46 -0
  101. package/dist/core/nodes/InterfaceNode.d.ts.map +1 -0
  102. package/dist/core/nodes/InterfaceNode.js +57 -0
  103. package/dist/core/nodes/IssueNode.d.ts +73 -0
  104. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  105. package/dist/core/nodes/IssueNode.js +129 -0
  106. package/dist/core/nodes/LiteralNode.d.ts +2 -2
  107. package/dist/core/nodes/LiteralNode.d.ts.map +1 -1
  108. package/dist/core/nodes/LiteralNode.js +6 -4
  109. package/dist/core/nodes/MethodCallNode.d.ts +32 -2
  110. package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
  111. package/dist/core/nodes/MethodCallNode.js +57 -4
  112. package/dist/core/nodes/MethodNode.d.ts +34 -2
  113. package/dist/core/nodes/MethodNode.d.ts.map +1 -1
  114. package/dist/core/nodes/MethodNode.js +55 -3
  115. package/dist/core/nodes/ModuleNode.d.ts +31 -0
  116. package/dist/core/nodes/ModuleNode.d.ts.map +1 -1
  117. package/dist/core/nodes/ModuleNode.js +37 -0
  118. package/dist/core/nodes/NetworkRequestNode.d.ts +54 -0
  119. package/dist/core/nodes/NetworkRequestNode.d.ts.map +1 -0
  120. package/dist/core/nodes/NetworkRequestNode.js +65 -0
  121. package/dist/core/nodes/ObjectLiteralNode.d.ts +27 -0
  122. package/dist/core/nodes/ObjectLiteralNode.d.ts.map +1 -0
  123. package/dist/core/nodes/ObjectLiteralNode.js +43 -0
  124. package/dist/core/nodes/ParameterNode.d.ts +2 -2
  125. package/dist/core/nodes/ParameterNode.d.ts.map +1 -1
  126. package/dist/core/nodes/ParameterNode.js +5 -3
  127. package/dist/core/nodes/ScopeNode.d.ts +31 -0
  128. package/dist/core/nodes/ScopeNode.d.ts.map +1 -1
  129. package/dist/core/nodes/ScopeNode.js +49 -0
  130. package/dist/core/nodes/TypeNode.d.ts +36 -0
  131. package/dist/core/nodes/TypeNode.d.ts.map +1 -0
  132. package/dist/core/nodes/TypeNode.js +55 -0
  133. package/dist/core/nodes/VariableDeclarationNode.d.ts +29 -2
  134. package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
  135. package/dist/core/nodes/VariableDeclarationNode.js +48 -4
  136. package/dist/core/nodes/index.d.ts +15 -1
  137. package/dist/core/nodes/index.d.ts.map +1 -1
  138. package/dist/core/nodes/index.js +17 -0
  139. package/dist/data/builtins/BuiltinRegistry.d.ts +78 -0
  140. package/dist/data/builtins/BuiltinRegistry.d.ts.map +1 -0
  141. package/dist/data/builtins/BuiltinRegistry.js +110 -0
  142. package/dist/data/builtins/definitions.d.ts +28 -0
  143. package/dist/data/builtins/definitions.d.ts.map +1 -0
  144. package/dist/data/builtins/definitions.js +250 -0
  145. package/dist/data/builtins/index.d.ts +10 -0
  146. package/dist/data/builtins/index.d.ts.map +1 -0
  147. package/dist/data/builtins/index.js +8 -0
  148. package/dist/data/builtins/jsGlobals.d.ts +18 -0
  149. package/dist/data/builtins/jsGlobals.d.ts.map +1 -0
  150. package/dist/data/builtins/jsGlobals.js +26 -0
  151. package/dist/data/builtins/types.d.ts +34 -0
  152. package/dist/data/builtins/types.d.ts.map +1 -0
  153. package/dist/data/builtins/types.js +7 -0
  154. package/dist/data/globals/definitions.d.ts +27 -0
  155. package/dist/data/globals/definitions.d.ts.map +1 -0
  156. package/dist/data/globals/definitions.js +117 -0
  157. package/dist/data/globals/index.d.ts +36 -0
  158. package/dist/data/globals/index.d.ts.map +1 -0
  159. package/dist/data/globals/index.js +52 -0
  160. package/dist/diagnostics/DiagnosticCollector.d.ts +98 -0
  161. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  162. package/dist/diagnostics/DiagnosticCollector.js +129 -0
  163. package/dist/diagnostics/DiagnosticReporter.d.ts +100 -0
  164. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  165. package/dist/diagnostics/DiagnosticReporter.js +247 -0
  166. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  167. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  168. package/dist/diagnostics/DiagnosticWriter.js +43 -0
  169. package/dist/diagnostics/index.d.ts +14 -0
  170. package/dist/diagnostics/index.d.ts.map +1 -0
  171. package/dist/diagnostics/index.js +11 -0
  172. package/dist/errors/GrafemaError.d.ts +161 -0
  173. package/dist/errors/GrafemaError.d.ts.map +1 -0
  174. package/dist/errors/GrafemaError.js +181 -0
  175. package/dist/index.d.ts +73 -1
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +70 -1
  178. package/dist/logging/Logger.d.ts +48 -0
  179. package/dist/logging/Logger.d.ts.map +1 -0
  180. package/dist/logging/Logger.js +134 -0
  181. package/dist/plugins/Plugin.d.ts +5 -1
  182. package/dist/plugins/Plugin.d.ts.map +1 -1
  183. package/dist/plugins/Plugin.js +33 -0
  184. package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
  185. package/dist/plugins/analysis/DatabaseAnalyzer.js +14 -6
  186. package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
  187. package/dist/plugins/analysis/ExpressAnalyzer.js +29 -19
  188. package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts +148 -0
  189. package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts.map +1 -0
  190. package/dist/plugins/analysis/ExpressResponseAnalyzer.js +495 -0
  191. package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
  192. package/dist/plugins/analysis/ExpressRouteAnalyzer.js +71 -29
  193. package/dist/plugins/analysis/FetchAnalyzer.d.ts +41 -0
  194. package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
  195. package/dist/plugins/analysis/FetchAnalyzer.js +187 -19
  196. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts +6 -3
  197. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts.map +1 -1
  198. package/dist/plugins/analysis/IncrementalAnalysisPlugin.js +76 -80
  199. package/dist/plugins/analysis/JSASTAnalyzer.d.ts +313 -19
  200. package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
  201. package/dist/plugins/analysis/JSASTAnalyzer.js +3430 -503
  202. package/dist/plugins/analysis/ReactAnalyzer.d.ts.map +1 -1
  203. package/dist/plugins/analysis/ReactAnalyzer.js +56 -57
  204. package/dist/plugins/analysis/RustAnalyzer.d.ts.map +1 -1
  205. package/dist/plugins/analysis/RustAnalyzer.js +16 -11
  206. package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
  207. package/dist/plugins/analysis/SQLiteAnalyzer.js +11 -7
  208. package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts.map +1 -1
  209. package/dist/plugins/analysis/ServiceLayerAnalyzer.js +21 -9
  210. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts +9 -0
  211. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
  212. package/dist/plugins/analysis/SocketIOAnalyzer.js +117 -21
  213. package/dist/plugins/analysis/SystemDbAnalyzer.d.ts.map +1 -1
  214. package/dist/plugins/analysis/SystemDbAnalyzer.js +15 -5
  215. package/dist/plugins/analysis/ast/GraphBuilder.d.ts +207 -4
  216. package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
  217. package/dist/plugins/analysis/ast/GraphBuilder.js +1527 -316
  218. package/dist/plugins/analysis/ast/IdGenerator.d.ts +105 -0
  219. package/dist/plugins/analysis/ast/IdGenerator.d.ts.map +1 -0
  220. package/dist/plugins/analysis/ast/IdGenerator.js +116 -0
  221. package/dist/plugins/analysis/ast/types.d.ts +470 -5
  222. package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
  223. package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts +33 -0
  224. package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts.map +1 -0
  225. package/dist/plugins/analysis/ast/utils/createParameterNodes.js +89 -0
  226. package/dist/plugins/analysis/ast/utils/index.d.ts +6 -0
  227. package/dist/plugins/analysis/ast/utils/index.d.ts.map +1 -0
  228. package/dist/plugins/analysis/ast/utils/index.js +5 -0
  229. package/dist/plugins/analysis/ast/utils/location.d.ts +87 -0
  230. package/dist/plugins/analysis/ast/utils/location.d.ts.map +1 -0
  231. package/dist/plugins/analysis/ast/utils/location.js +78 -0
  232. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +14 -5
  233. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -1
  234. package/dist/plugins/analysis/ast/visitors/ASTVisitor.js +6 -5
  235. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +100 -9
  236. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
  237. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +674 -125
  238. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts +4 -1
  239. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts.map +1 -1
  240. package/dist/plugins/analysis/ast/visitors/ClassVisitor.js +72 -32
  241. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +14 -1
  242. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
  243. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +190 -63
  244. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts +4 -0
  245. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
  246. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +112 -8
  247. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts +12 -1
  248. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts.map +1 -1
  249. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js +36 -14
  250. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +20 -2
  251. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
  252. package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +243 -45
  253. package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts.map +1 -1
  254. package/dist/plugins/discovery/MonorepoServiceDiscovery.js +3 -2
  255. package/dist/plugins/discovery/SimpleProjectDiscovery.d.ts.map +1 -1
  256. package/dist/plugins/discovery/SimpleProjectDiscovery.js +5 -1
  257. package/dist/plugins/discovery/WorkspaceDiscovery.d.ts +22 -0
  258. package/dist/plugins/discovery/WorkspaceDiscovery.d.ts.map +1 -0
  259. package/dist/plugins/discovery/WorkspaceDiscovery.js +141 -0
  260. package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts +46 -0
  261. package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts.map +1 -0
  262. package/dist/plugins/discovery/resolveSourceEntrypoint.js +86 -0
  263. package/dist/plugins/discovery/workspaces/detector.d.ts +21 -0
  264. package/dist/plugins/discovery/workspaces/detector.d.ts.map +1 -0
  265. package/dist/plugins/discovery/workspaces/detector.js +49 -0
  266. package/dist/plugins/discovery/workspaces/globResolver.d.ts +35 -0
  267. package/dist/plugins/discovery/workspaces/globResolver.d.ts.map +1 -0
  268. package/dist/plugins/discovery/workspaces/globResolver.js +184 -0
  269. package/dist/plugins/discovery/workspaces/index.d.ts +9 -0
  270. package/dist/plugins/discovery/workspaces/index.d.ts.map +1 -0
  271. package/dist/plugins/discovery/workspaces/index.js +8 -0
  272. package/dist/plugins/discovery/workspaces/parsers.d.ts +38 -0
  273. package/dist/plugins/discovery/workspaces/parsers.d.ts.map +1 -0
  274. package/dist/plugins/discovery/workspaces/parsers.js +80 -0
  275. package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
  276. package/dist/plugins/enrichment/AliasTracker.js +29 -8
  277. package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts +32 -0
  278. package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts.map +1 -0
  279. package/dist/plugins/enrichment/ArgumentParameterLinker.js +175 -0
  280. package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts +51 -0
  281. package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts.map +1 -0
  282. package/dist/plugins/enrichment/ClosureCaptureEnricher.js +205 -0
  283. package/dist/plugins/enrichment/ExternalCallResolver.d.ts +42 -0
  284. package/dist/plugins/enrichment/ExternalCallResolver.d.ts.map +1 -0
  285. package/dist/plugins/enrichment/ExternalCallResolver.js +213 -0
  286. package/dist/plugins/enrichment/FunctionCallResolver.d.ts +58 -0
  287. package/dist/plugins/enrichment/FunctionCallResolver.d.ts.map +1 -0
  288. package/dist/plugins/enrichment/FunctionCallResolver.js +340 -0
  289. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts +16 -3
  290. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
  291. package/dist/plugins/enrichment/HTTPConnectionEnricher.js +78 -27
  292. package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -1
  293. package/dist/plugins/enrichment/ImportExportLinker.js +23 -6
  294. package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
  295. package/dist/plugins/enrichment/MethodCallResolver.js +33 -13
  296. package/dist/plugins/enrichment/MountPointResolver.d.ts +14 -12
  297. package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
  298. package/dist/plugins/enrichment/MountPointResolver.js +173 -147
  299. package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts +44 -0
  300. package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts.map +1 -0
  301. package/dist/plugins/enrichment/NodejsBuiltinsResolver.js +271 -0
  302. package/dist/plugins/enrichment/PrefixEvaluator.d.ts.map +1 -1
  303. package/dist/plugins/enrichment/PrefixEvaluator.js +16 -7
  304. package/dist/plugins/enrichment/RustFFIEnricher.d.ts.map +1 -1
  305. package/dist/plugins/enrichment/RustFFIEnricher.js +6 -5
  306. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +22 -27
  307. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
  308. package/dist/plugins/enrichment/ValueDomainAnalyzer.js +185 -143
  309. package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts.map +1 -1
  310. package/dist/plugins/indexing/IncrementalModuleIndexer.js +23 -14
  311. package/dist/plugins/indexing/JSModuleIndexer.d.ts +15 -0
  312. package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
  313. package/dist/plugins/indexing/JSModuleIndexer.js +121 -31
  314. package/dist/plugins/indexing/RustModuleIndexer.d.ts +1 -1
  315. package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -1
  316. package/dist/plugins/indexing/RustModuleIndexer.js +8 -7
  317. package/dist/plugins/indexing/ServiceDetector.d.ts +10 -0
  318. package/dist/plugins/indexing/ServiceDetector.d.ts.map +1 -1
  319. package/dist/plugins/indexing/ServiceDetector.js +28 -15
  320. package/dist/plugins/validation/BrokenImportValidator.d.ts +31 -0
  321. package/dist/plugins/validation/BrokenImportValidator.d.ts.map +1 -0
  322. package/dist/plugins/validation/BrokenImportValidator.js +249 -0
  323. package/dist/plugins/validation/CallResolverValidator.d.ts +21 -10
  324. package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -1
  325. package/dist/plugins/validation/CallResolverValidator.js +103 -77
  326. package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
  327. package/dist/plugins/validation/DataFlowValidator.js +62 -49
  328. package/dist/plugins/validation/EvalBanValidator.d.ts.map +1 -1
  329. package/dist/plugins/validation/EvalBanValidator.js +17 -16
  330. package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -1
  331. package/dist/plugins/validation/GraphConnectivityValidator.js +44 -24
  332. package/dist/plugins/validation/NodeCreationValidator.d.ts +85 -0
  333. package/dist/plugins/validation/NodeCreationValidator.d.ts.map +1 -0
  334. package/dist/plugins/validation/NodeCreationValidator.js +415 -0
  335. package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -1
  336. package/dist/plugins/validation/SQLInjectionValidator.js +61 -19
  337. package/dist/plugins/validation/ShadowingDetector.d.ts.map +1 -1
  338. package/dist/plugins/validation/ShadowingDetector.js +6 -5
  339. package/dist/plugins/validation/TypeScriptDeadCodeValidator.d.ts.map +1 -1
  340. package/dist/plugins/validation/TypeScriptDeadCodeValidator.js +12 -11
  341. package/dist/plugins/vcs/GitPlugin.d.ts.map +1 -1
  342. package/dist/plugins/vcs/GitPlugin.js +10 -12
  343. package/dist/plugins/vcs/VCSPlugin.d.ts +3 -2
  344. package/dist/plugins/vcs/VCSPlugin.d.ts.map +1 -1
  345. package/dist/plugins/vcs/VCSPlugin.js +5 -5
  346. package/dist/queries/findCallsInFunction.d.ts +52 -0
  347. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  348. package/dist/queries/findCallsInFunction.js +135 -0
  349. package/dist/queries/findContainingFunction.d.ts +45 -0
  350. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  351. package/dist/queries/findContainingFunction.js +54 -0
  352. package/dist/queries/index.d.ts +14 -0
  353. package/dist/queries/index.d.ts.map +1 -0
  354. package/dist/queries/index.js +11 -0
  355. package/dist/queries/traceValues.d.ts +70 -0
  356. package/dist/queries/traceValues.d.ts.map +1 -0
  357. package/dist/queries/traceValues.js +299 -0
  358. package/dist/queries/types.d.ts +163 -0
  359. package/dist/queries/types.d.ts.map +1 -0
  360. package/dist/queries/types.js +9 -0
  361. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  362. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  363. package/dist/schema/GraphSchemaExtractor.js +124 -0
  364. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  365. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  366. package/dist/schema/InterfaceSchemaExtractor.js +112 -0
  367. package/dist/schema/index.d.ts +5 -0
  368. package/dist/schema/index.d.ts.map +1 -0
  369. package/dist/schema/index.js +2 -0
  370. package/dist/storage/backends/RFDBServerBackend.d.ts +21 -34
  371. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
  372. package/dist/storage/backends/RFDBServerBackend.js +72 -62
  373. package/dist/storage/backends/typeValidation.d.ts.map +1 -1
  374. package/dist/storage/backends/typeValidation.js +1 -0
  375. package/dist/validation/PathValidator.d.ts +1 -2
  376. package/dist/validation/PathValidator.d.ts.map +1 -1
  377. package/package.json +3 -3
  378. package/src/Orchestrator.ts +272 -27
  379. package/src/config/ConfigLoader.ts +354 -0
  380. package/src/config/index.ts +5 -0
  381. package/src/core/ASTWorker.ts +143 -139
  382. package/src/core/CoverageAnalyzer.ts +243 -0
  383. package/src/core/FileExplainer.ts +179 -0
  384. package/src/core/FileNodeManager.ts +100 -0
  385. package/src/core/GraphFreshnessChecker.ts +143 -0
  386. package/src/core/HashUtils.ts +48 -0
  387. package/src/core/IncrementalReanalyzer.ts +192 -0
  388. package/src/core/NodeFactory.ts +470 -23
  389. package/src/core/ScopeTracker.ts +154 -0
  390. package/src/core/SemanticId.ts +192 -0
  391. package/src/core/VersionManager.ts +3 -2
  392. package/src/core/nodes/ArgumentExpressionNode.ts +89 -0
  393. package/src/core/nodes/ArrayLiteralNode.ts +66 -0
  394. package/src/core/nodes/BranchNode.ts +113 -0
  395. package/src/core/nodes/CallSiteNode.ts +64 -4
  396. package/src/core/nodes/CaseNode.ts +123 -0
  397. package/src/core/nodes/ClassNode.ts +67 -4
  398. package/src/core/nodes/ConstantNode.ts +5 -4
  399. package/src/core/nodes/ConstructorCallNode.ts +217 -0
  400. package/src/core/nodes/DatabaseQueryNode.ts +5 -1
  401. package/src/core/nodes/DecoratorNode.ts +92 -0
  402. package/src/core/nodes/EnumNode.ts +87 -0
  403. package/src/core/nodes/EventListenerNode.ts +7 -4
  404. package/src/core/nodes/ExportNode.ts +74 -4
  405. package/src/core/nodes/ExpressionNode.ts +232 -0
  406. package/src/core/nodes/ExternalModuleNode.ts +65 -0
  407. package/src/core/nodes/ExternalStdioNode.ts +17 -9
  408. package/src/core/nodes/FunctionNode.ts +101 -1
  409. package/src/core/nodes/HttpRequestNode.ts +7 -4
  410. package/src/core/nodes/ImportNode.ts +62 -13
  411. package/src/core/nodes/InterfaceNode.ts +92 -0
  412. package/src/core/nodes/IssueNode.ts +177 -0
  413. package/src/core/nodes/LiteralNode.ts +5 -4
  414. package/src/core/nodes/MethodCallNode.ts +70 -4
  415. package/src/core/nodes/MethodNode.ts +68 -3
  416. package/src/core/nodes/ModuleNode.ts +50 -0
  417. package/src/core/nodes/NetworkRequestNode.ts +77 -0
  418. package/src/core/nodes/ObjectLiteralNode.ts +66 -0
  419. package/src/core/nodes/ParameterNode.ts +4 -3
  420. package/src/core/nodes/ScopeNode.ts +65 -0
  421. package/src/core/nodes/TypeNode.ts +79 -0
  422. package/src/core/nodes/VariableDeclarationNode.ts +58 -4
  423. package/src/core/nodes/index.ts +21 -1
  424. package/src/data/builtins/BuiltinRegistry.ts +124 -0
  425. package/src/data/builtins/definitions.ts +267 -0
  426. package/src/data/builtins/index.ts +10 -0
  427. package/src/data/builtins/jsGlobals.ts +28 -0
  428. package/src/data/builtins/types.ts +36 -0
  429. package/src/data/globals/definitions.ts +156 -0
  430. package/src/data/globals/index.ts +66 -0
  431. package/src/diagnostics/DiagnosticCollector.ts +163 -0
  432. package/src/diagnostics/DiagnosticReporter.ts +324 -0
  433. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  434. package/src/diagnostics/index.ts +16 -0
  435. package/src/errors/GrafemaError.ts +239 -0
  436. package/src/index.ts +193 -1
  437. package/src/logging/Logger.ts +152 -0
  438. package/src/plugins/Plugin.ts +42 -0
  439. package/src/plugins/analysis/DatabaseAnalyzer.ts +16 -8
  440. package/src/plugins/analysis/ExpressAnalyzer.ts +33 -19
  441. package/src/plugins/analysis/ExpressResponseAnalyzer.ts +636 -0
  442. package/src/plugins/analysis/ExpressRouteAnalyzer.ts +76 -36
  443. package/src/plugins/analysis/FetchAnalyzer.ts +232 -21
  444. package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +84 -101
  445. package/src/plugins/analysis/JSASTAnalyzer.ts +4265 -587
  446. package/src/plugins/analysis/ReactAnalyzer.ts +57 -57
  447. package/src/plugins/analysis/RustAnalyzer.ts +16 -11
  448. package/src/plugins/analysis/SQLiteAnalyzer.ts +13 -7
  449. package/src/plugins/analysis/ServiceLayerAnalyzer.ts +22 -16
  450. package/src/plugins/analysis/SocketIOAnalyzer.ts +151 -28
  451. package/src/plugins/analysis/SystemDbAnalyzer.ts +16 -11
  452. package/src/plugins/analysis/ast/GraphBuilder.ts +1947 -327
  453. package/src/plugins/analysis/ast/IdGenerator.ts +177 -0
  454. package/src/plugins/analysis/ast/types.ts +596 -6
  455. package/src/plugins/analysis/ast/utils/createParameterNodes.ts +104 -0
  456. package/src/plugins/analysis/ast/utils/index.ts +12 -0
  457. package/src/plugins/analysis/ast/utils/location.ts +103 -0
  458. package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +19 -8
  459. package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +924 -83
  460. package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +97 -44
  461. package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +234 -93
  462. package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +124 -9
  463. package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +41 -14
  464. package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +294 -49
  465. package/src/plugins/discovery/MonorepoServiceDiscovery.ts +3 -2
  466. package/src/plugins/discovery/SimpleProjectDiscovery.ts +6 -1
  467. package/src/plugins/discovery/WorkspaceDiscovery.ts +184 -0
  468. package/src/plugins/discovery/resolveSourceEntrypoint.ts +103 -0
  469. package/src/plugins/discovery/workspaces/detector.ts +63 -0
  470. package/src/plugins/discovery/workspaces/globResolver.ts +229 -0
  471. package/src/plugins/discovery/workspaces/index.ts +23 -0
  472. package/src/plugins/discovery/workspaces/parsers.ts +99 -0
  473. package/src/plugins/enrichment/AliasTracker.ts +35 -8
  474. package/src/plugins/enrichment/ArgumentParameterLinker.ts +240 -0
  475. package/src/plugins/enrichment/ClosureCaptureEnricher.ts +267 -0
  476. package/src/plugins/enrichment/ExternalCallResolver.ts +262 -0
  477. package/src/plugins/enrichment/FunctionCallResolver.ts +456 -0
  478. package/src/plugins/enrichment/HTTPConnectionEnricher.ts +84 -27
  479. package/src/plugins/enrichment/ImportExportLinker.ts +24 -6
  480. package/src/plugins/enrichment/MethodCallResolver.ts +39 -13
  481. package/src/plugins/enrichment/MountPointResolver.ts +208 -195
  482. package/src/plugins/enrichment/NodejsBuiltinsResolver.ts +365 -0
  483. package/src/plugins/enrichment/PrefixEvaluator.ts +16 -7
  484. package/src/plugins/enrichment/RustFFIEnricher.ts +6 -5
  485. package/src/plugins/enrichment/ValueDomainAnalyzer.ts +209 -189
  486. package/src/plugins/indexing/IncrementalModuleIndexer.ts +23 -14
  487. package/src/plugins/indexing/JSModuleIndexer.ts +140 -34
  488. package/src/plugins/indexing/RustModuleIndexer.ts +8 -7
  489. package/src/plugins/validation/BrokenImportValidator.ts +325 -0
  490. package/src/plugins/validation/CallResolverValidator.ts +131 -110
  491. package/src/plugins/validation/DataFlowValidator.ts +88 -67
  492. package/src/plugins/validation/EvalBanValidator.ts +17 -16
  493. package/src/plugins/validation/GraphConnectivityValidator.ts +58 -24
  494. package/src/plugins/validation/NodeCreationValidator.ts +554 -0
  495. package/src/plugins/validation/SQLInjectionValidator.ts +63 -20
  496. package/src/plugins/validation/ShadowingDetector.ts +6 -5
  497. package/src/plugins/validation/TypeScriptDeadCodeValidator.ts +12 -11
  498. package/src/plugins/vcs/GitPlugin.ts +40 -12
  499. package/src/plugins/vcs/VCSPlugin.ts +7 -5
  500. package/src/queries/README.md +46 -0
  501. package/src/queries/findCallsInFunction.ts +206 -0
  502. package/src/queries/findContainingFunction.ts +83 -0
  503. package/src/queries/index.ts +23 -0
  504. package/src/queries/traceValues.ts +398 -0
  505. package/src/queries/types.ts +187 -0
  506. package/src/schema/GraphSchemaExtractor.ts +177 -0
  507. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  508. package/src/schema/index.ts +5 -0
  509. package/src/storage/backends/RFDBServerBackend.ts +100 -98
  510. package/src/storage/backends/typeValidation.ts +1 -0
  511. package/src/validation/PathValidator.ts +1 -1
  512. package/dist/core/AnalysisWorker.d.ts +0 -14
  513. package/dist/core/AnalysisWorker.d.ts.map +0 -1
  514. package/dist/core/AnalysisWorker.js +0 -307
  515. package/dist/core/ParallelAnalyzer.d.ts +0 -120
  516. package/dist/core/ParallelAnalyzer.d.ts.map +0 -1
  517. package/dist/core/ParallelAnalyzer.js +0 -331
  518. package/dist/core/QueueWorker.d.ts +0 -12
  519. package/dist/core/QueueWorker.d.ts.map +0 -1
  520. package/dist/core/QueueWorker.js +0 -567
  521. package/dist/core/RFDBClient.d.ts +0 -179
  522. package/dist/core/RFDBClient.d.ts.map +0 -1
  523. package/dist/core/RFDBClient.js +0 -429
  524. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts +0 -19
  525. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts.map +0 -1
  526. package/dist/plugins/discovery/ZonServiceDiscovery.js +0 -204
  527. package/src/core/AnalysisWorker.ts +0 -410
  528. package/src/core/ParallelAnalyzer.ts +0 -476
  529. package/src/core/QueueWorker.ts +0 -780
  530. package/src/plugins/indexing/ServiceDetector.ts +0 -230
@@ -3,18 +3,29 @@
3
3
  * OPTIMIZED: Uses batched writes to reduce FFI overhead
4
4
  */
5
5
 
6
- import { dirname, resolve } from 'path';
6
+ import { dirname, resolve, basename } from 'path';
7
7
  import type { GraphBackend } from '@grafema/types';
8
+ import { ImportNode } from '../../../core/nodes/ImportNode.js';
9
+ import { InterfaceNode, type InterfaceNodeRecord } from '../../../core/nodes/InterfaceNode.js';
10
+ import { EnumNode, type EnumNodeRecord } from '../../../core/nodes/EnumNode.js';
11
+ import { DecoratorNode } from '../../../core/nodes/DecoratorNode.js';
12
+ import { NetworkRequestNode } from '../../../core/nodes/NetworkRequestNode.js';
13
+ import { NodeFactory } from '../../../core/NodeFactory.js';
14
+ import { computeSemanticId, parseSemanticId } from '../../../core/SemanticId.js';
8
15
  import type {
9
16
  ModuleNode,
10
17
  FunctionInfo,
11
18
  ParameterInfo,
12
19
  ScopeInfo,
20
+ BranchInfo,
21
+ CaseInfo,
22
+ LoopInfo,
13
23
  VariableDeclarationInfo,
14
24
  CallSiteInfo,
15
25
  MethodCallInfo,
16
26
  EventListenerInfo,
17
27
  ClassInstantiationInfo,
28
+ ConstructorCallInfo,
18
29
  ClassDeclarationInfo,
19
30
  MethodCallbackInfo,
20
31
  CallArgumentInfo,
@@ -27,6 +38,18 @@ import type {
27
38
  TypeAliasInfo,
28
39
  EnumDeclarationInfo,
29
40
  DecoratorInfo,
41
+ ArrayMutationInfo,
42
+ ObjectMutationInfo,
43
+ VariableReassignmentInfo,
44
+ ReturnStatementInfo,
45
+ ObjectLiteralInfo,
46
+ ObjectPropertyInfo,
47
+ ArrayLiteralInfo,
48
+ TryBlockInfo,
49
+ CatchBlockInfo,
50
+ FinallyBlockInfo,
51
+ UpdateExpressionInfo,
52
+ PromiseResolutionInfo,
30
53
  ASTCollections,
31
54
  GraphNode,
32
55
  GraphEdge,
@@ -91,11 +114,21 @@ export class GraphBuilder {
91
114
  functions,
92
115
  parameters = [],
93
116
  scopes,
117
+ // Branching
118
+ branches = [],
119
+ cases = [],
120
+ // Control flow (loops)
121
+ loops = [],
122
+ // Control flow (try/catch/finally) - Phase 4
123
+ tryBlocks = [],
124
+ catchBlocks = [],
125
+ finallyBlocks = [],
94
126
  variableDeclarations,
95
127
  callSites,
96
128
  methodCalls = [],
97
129
  eventListeners = [],
98
130
  classInstantiations = [],
131
+ constructorCalls = [],
99
132
  classDeclarations = [],
100
133
  methodCallbacks = [],
101
134
  callArguments = [],
@@ -108,7 +141,23 @@ export class GraphBuilder {
108
141
  interfaces = [],
109
142
  typeAliases = [],
110
143
  enums = [],
111
- decorators = []
144
+ decorators = [],
145
+ // Array mutation tracking for FLOWS_INTO edges
146
+ arrayMutations = [],
147
+ // Object mutation tracking for FLOWS_INTO edges
148
+ objectMutations = [],
149
+ // Variable reassignment tracking for FLOWS_INTO edges (REG-290)
150
+ variableReassignments = [],
151
+ // Return statement tracking for RETURNS edges
152
+ returnStatements = [],
153
+ // Update expression tracking for MODIFIES edges (REG-288, REG-312)
154
+ updateExpressions = [],
155
+ // Promise resolution tracking for RESOLVES_TO edges (REG-334)
156
+ promiseResolutions = [],
157
+ // Object/Array literal tracking
158
+ objectLiterals = [],
159
+ objectProperties = [],
160
+ arrayLiterals = []
112
161
  } = data;
113
162
 
114
163
  // Reset buffers for this build
@@ -127,10 +176,50 @@ export class GraphBuilder {
127
176
  this._bufferNode(scopeData as GraphNode);
128
177
  }
129
178
 
130
- // 3. Buffer variables
179
+ // 2.5. Buffer BRANCH nodes
180
+ // Note: parentScopeId is kept on node for query support (REG-275 test requirement)
181
+ for (const branch of branches) {
182
+ const { discriminantExpressionId, discriminantExpressionType, discriminantLine, discriminantColumn, ...branchData } = branch;
183
+ this._bufferNode(branchData as GraphNode);
184
+ }
185
+
186
+ // 2.6. Buffer CASE nodes
187
+ for (const caseInfo of cases) {
188
+ const { parentBranchId, ...caseData } = caseInfo;
189
+ this._bufferNode(caseData as GraphNode);
190
+ }
191
+
192
+ // 2.7. Buffer LOOP nodes
193
+ for (const loop of loops) {
194
+ // Exclude metadata used for edge creation (not stored on node)
195
+ const {
196
+ iteratesOverName, iteratesOverLine, iteratesOverColumn,
197
+ conditionExpressionId, conditionExpressionType, conditionLine, conditionColumn,
198
+ ...loopData
199
+ } = loop;
200
+ this._bufferNode(loopData as GraphNode);
201
+ }
202
+
203
+ // 2.8. Buffer TRY_BLOCK nodes (Phase 4)
204
+ for (const tryBlock of tryBlocks) {
205
+ this._bufferNode(tryBlock as GraphNode);
206
+ }
207
+
208
+ // 2.9. Buffer CATCH_BLOCK nodes (Phase 4)
209
+ for (const catchBlock of catchBlocks) {
210
+ const { parentTryBlockId, ...catchData } = catchBlock;
211
+ this._bufferNode(catchData as GraphNode);
212
+ }
213
+
214
+ // 2.10. Buffer FINALLY_BLOCK nodes (Phase 4)
215
+ for (const finallyBlock of finallyBlocks) {
216
+ const { parentTryBlockId, ...finallyData } = finallyBlock;
217
+ this._bufferNode(finallyData as GraphNode);
218
+ }
219
+
220
+ // 3. Buffer variables (keep parentScopeId on node for queries)
131
221
  for (const varDecl of variableDeclarations) {
132
- const { parentScopeId, ...varData } = varDecl;
133
- this._bufferNode(varData as GraphNode);
222
+ this._bufferNode(varDecl as unknown as GraphNode);
134
223
  }
135
224
 
136
225
  // 3.5. Buffer PARAMETER nodes and HAS_PARAMETER edges
@@ -149,26 +238,62 @@ export class GraphBuilder {
149
238
  }
150
239
  }
151
240
 
152
- // 4. Buffer CALL_SITE
241
+ // 4. Buffer CALL_SITE (keep parentScopeId on node for queries)
153
242
  for (const callSite of callSites) {
154
- const { parentScopeId, targetFunctionName, ...callData } = callSite;
243
+ const { targetFunctionName, ...callData } = callSite;
155
244
  this._bufferNode(callData as GraphNode);
156
245
  }
157
246
 
247
+ // 4.5 Buffer CONSTRUCTOR_CALL nodes
248
+ for (const constructorCall of constructorCalls) {
249
+ this._bufferNode({
250
+ id: constructorCall.id,
251
+ type: constructorCall.type,
252
+ name: `new ${constructorCall.className}()`,
253
+ className: constructorCall.className,
254
+ isBuiltin: constructorCall.isBuiltin,
255
+ file: constructorCall.file,
256
+ line: constructorCall.line,
257
+ column: constructorCall.column
258
+ } as GraphNode);
259
+ }
260
+
158
261
  // 5. Buffer edges for functions
159
262
  this.bufferFunctionEdges(module, functions);
160
263
 
161
264
  // 6. Buffer edges for SCOPE
162
265
  this.bufferScopeEdges(scopes, variableDeclarations);
163
266
 
267
+ // 6.3. Buffer edges for LOOP (HAS_BODY, ITERATES_OVER, CONTAINS)
268
+ this.bufferLoopEdges(loops, scopes, variableDeclarations, parameters);
269
+
270
+ // 6.35. Buffer HAS_CONDITION edges for LOOP (REG-280)
271
+ this.bufferLoopConditionEdges(loops, callSites);
272
+
273
+ // 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
274
+ this.bufferLoopConditionExpressions(loops);
275
+
276
+ // 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
277
+ // Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
278
+ this.bufferBranchEdges(branches, callSites, scopes);
279
+
280
+ // 6.6. Buffer edges for CASE
281
+ this.bufferCaseEdges(cases);
282
+
283
+ // 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
284
+ this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
285
+
286
+ // 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
287
+ this.bufferDiscriminantExpressions(branches, callSites);
288
+
164
289
  // 7. Buffer edges for variables
165
290
  this.bufferVariableEdges(variableDeclarations);
166
291
 
167
292
  // 8. Buffer edges for CALL_SITE
168
293
  this.bufferCallSiteEdges(callSites, functions);
169
294
 
170
- // 9. Buffer METHOD_CALL nodes and CONTAINS edges
171
- this.bufferMethodCalls(methodCalls);
295
+ // 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
296
+ this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
172
297
 
173
298
  // 10. Buffer net:stdio and WRITES_TO edges for console.log/error
174
299
  this.bufferStdioNodes(methodCalls);
@@ -197,6 +322,16 @@ export class GraphBuilder {
197
322
  // 18. Buffer LITERAL nodes
198
323
  this.bufferLiterals(literals);
199
324
 
325
+ // 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
326
+ this.bufferObjectLiteralNodes(objectLiterals);
327
+
328
+ // 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
329
+ this.bufferArrayLiteralNodes(arrayLiterals);
330
+
331
+ // 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
332
+ // REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
333
+ this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
334
+
200
335
  // 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
201
336
  this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
202
337
 
@@ -218,17 +353,33 @@ export class GraphBuilder {
218
353
  // 25. Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
219
354
  this.bufferImplementsEdges(classDeclarations, interfaces);
220
355
 
356
+ // 26. Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
357
+ this.bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters);
358
+
359
+ // 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
360
+ // REG-152: Now includes classDeclarations for this.prop = value patterns
361
+ this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
362
+
363
+ // 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
364
+ this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
365
+
366
+ // 29. Buffer RETURNS edges for return statements
367
+ this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
368
+
369
+ // 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
370
+ this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
371
+
372
+ // 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
373
+ this.bufferPromiseResolutionEdges(promiseResolutions);
374
+
221
375
  // FLUSH: Write all nodes first, then edges in single batch calls
222
376
  const nodesCreated = await this._flushNodes(graph);
223
377
  const edgesCreated = await this._flushEdges(graph);
224
378
 
225
- // Handle async operations that need graph queries (IMPORTS_FROM edges)
226
- const importExportEdges = await this.createImportExportEdges(module, imports, exports, graph, projectPath);
227
-
228
379
  // Handle async operations for ASSIGNED_FROM with CLASS lookups
229
380
  const classAssignmentEdges = await this.createClassAssignmentEdges(variableAssignments, graph);
230
381
 
231
- return { nodes: nodesCreated, edges: edgesCreated + importExportEdges + classAssignmentEdges };
382
+ return { nodes: nodesCreated, edges: edgesCreated + classAssignmentEdges };
232
383
  }
233
384
 
234
385
  // ============= BUFFERED METHODS (synchronous, no awaits) =============
@@ -302,6 +453,428 @@ export class GraphBuilder {
302
453
  }
303
454
  }
304
455
 
456
+ /**
457
+ * Buffer LOOP edges (CONTAINS, HAS_BODY, ITERATES_OVER)
458
+ *
459
+ * Creates edges for:
460
+ * - Parent -> CONTAINS -> LOOP
461
+ * - LOOP -> HAS_BODY -> body SCOPE
462
+ * - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
463
+ *
464
+ * Scope-aware variable lookup for ITERATES_OVER:
465
+ * For for-of/for-in, finds the iterated variable preferring:
466
+ * 1. Variables declared before the loop on same or earlier line (closest first)
467
+ * 2. Parameters (function arguments)
468
+ */
469
+ private bufferLoopEdges(
470
+ loops: LoopInfo[],
471
+ scopes: ScopeInfo[],
472
+ variableDeclarations: VariableDeclarationInfo[],
473
+ parameters: ParameterInfo[]
474
+ ): void {
475
+ for (const loop of loops) {
476
+ // Parent -> CONTAINS -> LOOP
477
+ if (loop.parentScopeId) {
478
+ this._bufferEdge({
479
+ type: 'CONTAINS',
480
+ src: loop.parentScopeId,
481
+ dst: loop.id
482
+ });
483
+ }
484
+
485
+ // LOOP -> HAS_BODY -> body SCOPE
486
+ // Find the body scope by matching parentScopeId to loop.id
487
+ const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
488
+ if (bodyScope) {
489
+ this._bufferEdge({
490
+ type: 'HAS_BODY',
491
+ src: loop.id,
492
+ dst: bodyScope.id
493
+ });
494
+ }
495
+
496
+ // LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
497
+ if (loop.iteratesOverName && (loop.loopType === 'for-in' || loop.loopType === 'for-of')) {
498
+ // For MemberExpression iterables (obj.items), extract base object
499
+ const iterableName = loop.iteratesOverName.includes('.')
500
+ ? loop.iteratesOverName.split('.')[0]
501
+ : loop.iteratesOverName;
502
+
503
+ // Scope-aware lookup: prefer parameters over variables
504
+ // Parameters are function-local and shadow outer variables
505
+ const param = parameters.find(p =>
506
+ p.name === iterableName && p.file === loop.file
507
+ );
508
+
509
+ // Determine iteration type: for-in iterates keys, for-of iterates values
510
+ const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
511
+
512
+ if (param) {
513
+ // Parameter found - most local binding
514
+ this._bufferEdge({
515
+ type: 'ITERATES_OVER',
516
+ src: loop.id,
517
+ dst: param.id,
518
+ metadata: { iterates }
519
+ });
520
+ } else {
521
+ // Find variable by name and line proximity (scope-aware heuristic)
522
+ // Prefer variables declared before the loop in the same file
523
+ const candidateVars = variableDeclarations.filter(v =>
524
+ v.name === iterableName &&
525
+ v.file === loop.file &&
526
+ (v.line ?? 0) <= loop.line // Declared before or on loop line
527
+ );
528
+
529
+ // Sort by line descending to find closest declaration
530
+ candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
531
+
532
+ if (candidateVars.length > 0) {
533
+ this._bufferEdge({
534
+ type: 'ITERATES_OVER',
535
+ src: loop.id,
536
+ dst: candidateVars[0].id,
537
+ metadata: { iterates }
538
+ });
539
+ }
540
+ }
541
+ }
542
+
543
+ // REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
544
+ if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
545
+ // Find the variable declared in the init on this line
546
+ const initVar = variableDeclarations.find(v =>
547
+ v.name === loop.initVariableName &&
548
+ v.file === loop.file &&
549
+ v.line === loop.initLine
550
+ );
551
+ if (initVar) {
552
+ this._bufferEdge({
553
+ type: 'HAS_INIT',
554
+ src: loop.id,
555
+ dst: initVar.id
556
+ });
557
+ }
558
+ }
559
+
560
+ // REG-282: LOOP -> HAS_CONDITION -> EXPRESSION (i < 10 or condition for while/do-while)
561
+ if (loop.testExpressionId && loop.testExpressionType) {
562
+ // Create EXPRESSION node for the test
563
+ this._bufferNode({
564
+ id: loop.testExpressionId,
565
+ type: 'EXPRESSION',
566
+ name: loop.testExpressionType,
567
+ file: loop.file,
568
+ line: loop.testLine,
569
+ column: loop.testColumn,
570
+ expressionType: loop.testExpressionType
571
+ });
572
+
573
+ this._bufferEdge({
574
+ type: 'HAS_CONDITION',
575
+ src: loop.id,
576
+ dst: loop.testExpressionId
577
+ });
578
+ }
579
+
580
+ // REG-282: LOOP (for) -> HAS_UPDATE -> EXPRESSION (i++)
581
+ if (loop.loopType === 'for' && loop.updateExpressionId && loop.updateExpressionType) {
582
+ // Create EXPRESSION node for the update
583
+ this._bufferNode({
584
+ id: loop.updateExpressionId,
585
+ type: 'EXPRESSION',
586
+ name: loop.updateExpressionType,
587
+ file: loop.file,
588
+ line: loop.updateLine,
589
+ column: loop.updateColumn,
590
+ expressionType: loop.updateExpressionType
591
+ });
592
+
593
+ this._bufferEdge({
594
+ type: 'HAS_UPDATE',
595
+ src: loop.id,
596
+ dst: loop.updateExpressionId
597
+ });
598
+ }
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Buffer HAS_CONDITION edges from LOOP to condition EXPRESSION/CALL nodes.
604
+ * Also creates EXPRESSION nodes for non-CallExpression conditions.
605
+ *
606
+ * REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
607
+ * condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
608
+ *
609
+ * For CallExpression conditions, links to existing CALL_SITE node by coordinates.
610
+ */
611
+ private bufferLoopConditionEdges(loops: LoopInfo[], callSites: CallSiteInfo[]): void {
612
+ for (const loop of loops) {
613
+ // Skip for-in/for-of loops - they don't have test expressions
614
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
615
+ continue;
616
+ }
617
+
618
+ // Skip if no condition (e.g., infinite for loop: for(;;))
619
+ if (!loop.conditionExpressionId) {
620
+ continue;
621
+ }
622
+
623
+ // LOOP -> HAS_CONDITION -> EXPRESSION/CALL
624
+ let targetId = loop.conditionExpressionId;
625
+
626
+ // For CallExpression conditions, look up the actual CALL_SITE by coordinates
627
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
628
+ if (loop.conditionExpressionType === 'CallExpression' && loop.conditionLine && loop.conditionColumn !== undefined) {
629
+ const callSite = callSites.find(cs =>
630
+ cs.file === loop.file &&
631
+ cs.line === loop.conditionLine &&
632
+ cs.column === loop.conditionColumn
633
+ );
634
+ if (callSite) {
635
+ targetId = callSite.id;
636
+ }
637
+ }
638
+
639
+ this._bufferEdge({
640
+ type: 'HAS_CONDITION',
641
+ src: loop.id,
642
+ dst: targetId
643
+ });
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
649
+ * Similar to bufferDiscriminantExpressions but for loops.
650
+ *
651
+ * REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
652
+ * CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
653
+ */
654
+ private bufferLoopConditionExpressions(loops: LoopInfo[]): void {
655
+ for (const loop of loops) {
656
+ // Skip for-in/for-of loops - they don't have test expressions
657
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
658
+ continue;
659
+ }
660
+
661
+ if (loop.conditionExpressionId && loop.conditionExpressionType) {
662
+ // Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
663
+ if (loop.conditionExpressionType === 'CallExpression') {
664
+ continue;
665
+ }
666
+
667
+ // Only create if it looks like an EXPRESSION ID
668
+ if (loop.conditionExpressionId.includes(':EXPRESSION:')) {
669
+ this._bufferNode({
670
+ id: loop.conditionExpressionId,
671
+ type: 'EXPRESSION',
672
+ name: loop.conditionExpressionType,
673
+ file: loop.file,
674
+ line: loop.conditionLine,
675
+ column: loop.conditionColumn,
676
+ expressionType: loop.conditionExpressionType
677
+ });
678
+ }
679
+ }
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Buffer BRANCH edges (CONTAINS, HAS_CONDITION, HAS_CONSEQUENT, HAS_ALTERNATE)
685
+ *
686
+ * REG-275: For CallExpression discriminants (switch(getType())), looks up the
687
+ * actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
688
+ *
689
+ * Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
690
+ * pointing to the if-body and else-body SCOPEs.
691
+ */
692
+ private bufferBranchEdges(branches: BranchInfo[], callSites: CallSiteInfo[], scopes: ScopeInfo[]): void {
693
+ for (const branch of branches) {
694
+ // Parent SCOPE -> CONTAINS -> BRANCH
695
+ if (branch.parentScopeId) {
696
+ this._bufferEdge({
697
+ type: 'CONTAINS',
698
+ src: branch.parentScopeId,
699
+ dst: branch.id
700
+ });
701
+ }
702
+
703
+ // BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
704
+ if (branch.discriminantExpressionId) {
705
+ let targetId = branch.discriminantExpressionId;
706
+
707
+ // For CallExpression discriminants, look up the actual CALL_SITE by coordinates
708
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
709
+ if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
710
+ const callSite = callSites.find(cs =>
711
+ cs.file === branch.file &&
712
+ cs.line === branch.discriminantLine &&
713
+ cs.column === branch.discriminantColumn
714
+ );
715
+ if (callSite) {
716
+ targetId = callSite.id;
717
+ }
718
+ }
719
+
720
+ this._bufferEdge({
721
+ type: 'HAS_CONDITION',
722
+ src: branch.id,
723
+ dst: targetId
724
+ });
725
+ }
726
+
727
+ // Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
728
+ if (branch.branchType === 'if') {
729
+ // Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
730
+ const consequentScope = scopes.find(s =>
731
+ s.parentScopeId === branch.id && s.scopeType === 'if_statement'
732
+ );
733
+ if (consequentScope) {
734
+ this._bufferEdge({
735
+ type: 'HAS_CONSEQUENT',
736
+ src: branch.id,
737
+ dst: consequentScope.id
738
+ });
739
+ }
740
+
741
+ // Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
742
+ const alternateScope = scopes.find(s =>
743
+ s.parentScopeId === branch.id && s.scopeType === 'else_statement'
744
+ );
745
+ if (alternateScope) {
746
+ this._bufferEdge({
747
+ type: 'HAS_ALTERNATE',
748
+ src: branch.id,
749
+ dst: alternateScope.id
750
+ });
751
+ }
752
+
753
+ // For else-if chains: if this branch is the alternate of another branch
754
+ // This is handled differently - see below
755
+ }
756
+
757
+ // REG-287: For ternary branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges to expressions
758
+ if (branch.branchType === 'ternary') {
759
+ if (branch.consequentExpressionId) {
760
+ this._bufferEdge({
761
+ type: 'HAS_CONSEQUENT',
762
+ src: branch.id,
763
+ dst: branch.consequentExpressionId
764
+ });
765
+ }
766
+ if (branch.alternateExpressionId) {
767
+ this._bufferEdge({
768
+ type: 'HAS_ALTERNATE',
769
+ src: branch.id,
770
+ dst: branch.alternateExpressionId
771
+ });
772
+ }
773
+ }
774
+
775
+ // Phase 3: For else-if chains, create HAS_ALTERNATE from parent branch to this branch
776
+ if (branch.isAlternateOfBranchId) {
777
+ this._bufferEdge({
778
+ type: 'HAS_ALTERNATE',
779
+ src: branch.isAlternateOfBranchId,
780
+ dst: branch.id
781
+ });
782
+ }
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Buffer CASE edges (HAS_CASE, HAS_DEFAULT)
788
+ */
789
+ private bufferCaseEdges(cases: CaseInfo[]): void {
790
+ for (const caseInfo of cases) {
791
+ // BRANCH -> HAS_CASE or HAS_DEFAULT -> CASE
792
+ const edgeType = caseInfo.isDefault ? 'HAS_DEFAULT' : 'HAS_CASE';
793
+ this._bufferEdge({
794
+ type: edgeType,
795
+ src: caseInfo.parentBranchId,
796
+ dst: caseInfo.id
797
+ });
798
+ }
799
+ }
800
+
801
+ /**
802
+ * Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK nodes (Phase 4)
803
+ *
804
+ * Creates edges for:
805
+ * - Parent -> CONTAINS -> TRY_BLOCK
806
+ * - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
807
+ * - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
808
+ */
809
+ private bufferTryCatchFinallyEdges(
810
+ tryBlocks: TryBlockInfo[],
811
+ catchBlocks: CatchBlockInfo[],
812
+ finallyBlocks: FinallyBlockInfo[]
813
+ ): void {
814
+ // Buffer TRY_BLOCK edges
815
+ for (const tryBlock of tryBlocks) {
816
+ // Parent -> CONTAINS -> TRY_BLOCK
817
+ if (tryBlock.parentScopeId) {
818
+ this._bufferEdge({
819
+ type: 'CONTAINS',
820
+ src: tryBlock.parentScopeId,
821
+ dst: tryBlock.id
822
+ });
823
+ }
824
+ }
825
+
826
+ // Buffer CATCH_BLOCK edges (HAS_CATCH from TRY_BLOCK)
827
+ for (const catchBlock of catchBlocks) {
828
+ // TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
829
+ this._bufferEdge({
830
+ type: 'HAS_CATCH',
831
+ src: catchBlock.parentTryBlockId,
832
+ dst: catchBlock.id
833
+ });
834
+ }
835
+
836
+ // Buffer FINALLY_BLOCK edges (HAS_FINALLY from TRY_BLOCK)
837
+ for (const finallyBlock of finallyBlocks) {
838
+ // TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
839
+ this._bufferEdge({
840
+ type: 'HAS_FINALLY',
841
+ src: finallyBlock.parentTryBlockId,
842
+ dst: finallyBlock.id
843
+ });
844
+ }
845
+ }
846
+
847
+ /**
848
+ * Buffer EXPRESSION nodes for switch discriminants
849
+ * Uses stored metadata directly instead of parsing from ID (Linus improvement)
850
+ *
851
+ * REG-275: For CallExpression discriminants, we don't create nodes here since
852
+ * bufferBranchEdges links to the existing CALL_SITE node by coordinates.
853
+ */
854
+ private bufferDiscriminantExpressions(branches: BranchInfo[], callSites: CallSiteInfo[]): void {
855
+ for (const branch of branches) {
856
+ if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
857
+ // Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
858
+ if (branch.discriminantExpressionType === 'CallExpression') {
859
+ continue;
860
+ }
861
+
862
+ // Only create if it looks like an EXPRESSION ID
863
+ if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
864
+ this._bufferNode({
865
+ id: branch.discriminantExpressionId,
866
+ type: 'EXPRESSION',
867
+ name: branch.discriminantExpressionType,
868
+ file: branch.file,
869
+ line: branch.discriminantLine,
870
+ column: branch.discriminantColumn,
871
+ expressionType: branch.discriminantExpressionType
872
+ });
873
+ }
874
+ }
875
+ }
876
+ }
877
+
305
878
  private bufferVariableEdges(variableDeclarations: VariableDeclarationInfo[]): void {
306
879
  for (const varDecl of variableDeclarations) {
307
880
  const { parentScopeId, ...varData } = varDecl;
@@ -338,19 +911,56 @@ export class GraphBuilder {
338
911
  }
339
912
  }
340
913
 
341
- private bufferMethodCalls(methodCalls: MethodCallInfo[]): void {
914
+ private bufferMethodCalls(
915
+ methodCalls: MethodCallInfo[],
916
+ variableDeclarations: VariableDeclarationInfo[],
917
+ parameters: ParameterInfo[]
918
+ ): void {
342
919
  for (const methodCall of methodCalls) {
343
- const { parentScopeId, ...methodData } = methodCall;
344
-
345
- // Buffer METHOD_CALL node
346
- this._bufferNode(methodData as GraphNode);
920
+ // Keep parentScopeId on node for queries
921
+ this._bufferNode(methodCall as unknown as GraphNode);
347
922
 
348
923
  // SCOPE -> CONTAINS -> METHOD_CALL
349
924
  this._bufferEdge({
350
925
  type: 'CONTAINS',
351
- src: parentScopeId as string,
352
- dst: methodData.id
926
+ src: methodCall.parentScopeId as string,
927
+ dst: methodCall.id
353
928
  });
929
+
930
+ // REG-262: Create USES edge from METHOD_CALL to receiver variable
931
+ // Skip 'this' - it's not a variable node
932
+ if (methodCall.object && methodCall.object !== 'this') {
933
+ // Handle nested member expressions: obj.nested.method() -> use base 'obj'
934
+ const receiverName = methodCall.object.includes('.')
935
+ ? methodCall.object.split('.')[0]
936
+ : methodCall.object;
937
+
938
+ // Find receiver variable in current file
939
+ const receiverVar = variableDeclarations.find(v =>
940
+ v.name === receiverName && v.file === methodCall.file
941
+ );
942
+
943
+ if (receiverVar) {
944
+ this._bufferEdge({
945
+ type: 'USES',
946
+ src: methodCall.id,
947
+ dst: receiverVar.id
948
+ });
949
+ } else {
950
+ // Check parameters (function arguments)
951
+ const receiverParam = parameters.find(p =>
952
+ p.name === receiverName && p.file === methodCall.file
953
+ );
954
+
955
+ if (receiverParam) {
956
+ this._bufferEdge({
957
+ type: 'USES',
958
+ src: methodCall.id,
959
+ dst: receiverParam.id
960
+ });
961
+ }
962
+ }
963
+ }
354
964
  }
355
965
  }
356
966
 
@@ -360,16 +970,12 @@ export class GraphBuilder {
360
970
  );
361
971
 
362
972
  if (consoleIOMethods.length > 0) {
363
- const stdioId = 'net:stdio#__stdio__';
973
+ const stdioNode = NodeFactory.createExternalStdio();
974
+
364
975
  // Buffer net:stdio node only once (singleton)
365
- if (!this._createdSingletons.has(stdioId)) {
366
- this._bufferNode({
367
- id: stdioId,
368
- type: 'net:stdio',
369
- name: '__stdio__',
370
- description: 'Standard input/output stream'
371
- });
372
- this._createdSingletons.add(stdioId);
976
+ if (!this._createdSingletons.has(stdioNode.id)) {
977
+ this._bufferNode(stdioNode as unknown as GraphNode);
978
+ this._createdSingletons.add(stdioNode.id);
373
979
  }
374
980
 
375
981
  // Buffer WRITES_TO edges for console.log/error
@@ -377,7 +983,7 @@ export class GraphBuilder {
377
983
  this._bufferEdge({
378
984
  type: 'WRITES_TO',
379
985
  src: methodCall.id,
380
- dst: stdioId
986
+ dst: stdioNode.id
381
987
  });
382
988
  }
383
989
  }
@@ -407,9 +1013,14 @@ export class GraphBuilder {
407
1013
  });
408
1014
  }
409
1015
 
410
- // If superClass, buffer DERIVES_FROM edge
1016
+ // If superClass, buffer DERIVES_FROM edge with computed ID
411
1017
  if (superClass) {
412
- const superClassId = `CLASS#${superClass}#${file}`;
1018
+ // Compute superclass ID using semantic ID format
1019
+ // Assume superclass is in same file at global scope (most common case)
1020
+ // When superclass is in different file, edge will be dangling until that file analyzed
1021
+ const globalContext = { file, scopePath: [] as string[] };
1022
+ const superClassId = computeSemanticId('CLASS', superClass, globalContext);
1023
+
413
1024
  this._bufferEdge({
414
1025
  type: 'DERIVES_FROM',
415
1026
  src: id,
@@ -421,9 +1032,11 @@ export class GraphBuilder {
421
1032
 
422
1033
  private bufferClassNodes(module: ModuleNode, classInstantiations: ClassInstantiationInfo[], classDeclarations: ClassDeclarationInfo[]): void {
423
1034
  // Create lookup map: className → declaration ID
1035
+ // Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
1036
+ const moduleBasename = basename(module.file);
424
1037
  const declarationMap = new Map<string, string>();
425
1038
  for (const decl of classDeclarations) {
426
- if (decl.file === module.file) {
1039
+ if (decl.file === moduleBasename) {
427
1040
  declarationMap.set(decl.name, decl.id);
428
1041
  }
429
1042
  }
@@ -434,16 +1047,13 @@ export class GraphBuilder {
434
1047
  let classId = declarationMap.get(className);
435
1048
 
436
1049
  if (!classId) {
437
- // External class - buffer CLASS node
438
- classId = `${module.file}:CLASS:${className}:${line}`;
439
- this._bufferNode({
440
- id: classId,
441
- type: 'CLASS',
442
- name: className,
443
- file: module.file,
444
- line,
445
- isInstantiationRef: true
446
- });
1050
+ // External class - compute semantic ID
1051
+ // Use basename to match CLASS node format (scopeTracker uses basename)
1052
+ // When class is in different file, edge will be dangling until that file analyzed
1053
+ const globalContext = { file: moduleBasename, scopePath: [] as string[] };
1054
+ classId = computeSemanticId('CLASS', className, globalContext);
1055
+
1056
+ // NO node creation - node will exist when class file analyzed
447
1057
  }
448
1058
 
449
1059
  // Buffer INSTANCE_OF edge
@@ -475,50 +1085,98 @@ export class GraphBuilder {
475
1085
 
476
1086
  private bufferImportNodes(module: ModuleNode, imports: ImportInfo[]): void {
477
1087
  for (const imp of imports) {
478
- const { source, specifiers, line } = imp;
479
-
480
- for (const spec of specifiers) {
481
- const importType = spec.imported === 'default' ? 'default' :
482
- spec.imported === '*' ? 'namespace' : 'named';
483
-
484
- const importId = `${module.file}:IMPORT:${source}:${spec.local}:${line}`;
1088
+ const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
1089
+
1090
+ // REG-273: Handle side-effect-only imports (no specifiers)
1091
+ if (specifiers.length === 0) {
1092
+ // Side-effect import: import './polyfill.js'
1093
+ const importNode = ImportNode.create(
1094
+ source, // name = source (no local binding)
1095
+ module.file, // file
1096
+ line, // line (stored as field, not in ID)
1097
+ column || 0, // column
1098
+ source, // source module
1099
+ {
1100
+ imported: '*', // no specific export
1101
+ local: source, // source becomes local
1102
+ sideEffect: true // mark as side-effect import
1103
+ }
1104
+ );
485
1105
 
486
- this._bufferNode({
487
- id: importId,
488
- type: 'IMPORT',
489
- source: source,
490
- importType: importType,
491
- imported: spec.imported,
492
- local: spec.local,
493
- file: module.file,
494
- line: line
495
- });
1106
+ this._bufferNode(importNode as unknown as GraphNode);
496
1107
 
497
1108
  // MODULE -> CONTAINS -> IMPORT
498
1109
  this._bufferEdge({
499
1110
  type: 'CONTAINS',
500
1111
  src: module.id,
501
- dst: importId
1112
+ dst: importNode.id
502
1113
  });
503
1114
 
504
1115
  // Create EXTERNAL_MODULE node for external modules
505
1116
  const isRelative = source.startsWith('./') || source.startsWith('../');
506
1117
  if (!isRelative) {
507
- const externalModuleId = `EXTERNAL_MODULE:${source}`;
1118
+ const externalModule = NodeFactory.createExternalModule(source);
508
1119
 
509
- this._bufferNode({
510
- id: externalModuleId,
511
- type: 'EXTERNAL_MODULE',
512
- name: source,
513
- file: module.file,
514
- line: line
515
- });
1120
+ // Avoid duplicate EXTERNAL_MODULE nodes
1121
+ if (!this._createdSingletons.has(externalModule.id)) {
1122
+ this._bufferNode(externalModule as unknown as GraphNode);
1123
+ this._createdSingletons.add(externalModule.id);
1124
+ }
516
1125
 
517
1126
  this._bufferEdge({
518
1127
  type: 'IMPORTS',
519
1128
  src: module.id,
520
- dst: externalModuleId
1129
+ dst: externalModule.id
1130
+ });
1131
+ }
1132
+ } else {
1133
+ // Regular imports with specifiers
1134
+ for (const spec of specifiers) {
1135
+ // Use ImportNode factory for proper semantic IDs and field population
1136
+ const importNode = ImportNode.create(
1137
+ spec.local, // name = local binding
1138
+ module.file, // file
1139
+ line, // line (stored as field, not in ID)
1140
+ column || 0, // column
1141
+ source, // source module
1142
+ {
1143
+ imported: spec.imported,
1144
+ local: spec.local,
1145
+ sideEffect: false, // regular imports are not side-effects
1146
+ // importType is auto-detected from imported field
1147
+ // Dynamic import fields
1148
+ isDynamic,
1149
+ isResolvable,
1150
+ dynamicPath
1151
+ }
1152
+ );
1153
+
1154
+ this._bufferNode(importNode as unknown as GraphNode);
1155
+
1156
+ // MODULE -> CONTAINS -> IMPORT
1157
+ this._bufferEdge({
1158
+ type: 'CONTAINS',
1159
+ src: module.id,
1160
+ dst: importNode.id
521
1161
  });
1162
+
1163
+ // Create EXTERNAL_MODULE node for external modules
1164
+ const isRelative = source.startsWith('./') || source.startsWith('../');
1165
+ if (!isRelative) {
1166
+ const externalModule = NodeFactory.createExternalModule(source);
1167
+
1168
+ // Avoid duplicate EXTERNAL_MODULE nodes
1169
+ if (!this._createdSingletons.has(externalModule.id)) {
1170
+ this._bufferNode(externalModule as unknown as GraphNode);
1171
+ this._createdSingletons.add(externalModule.id);
1172
+ }
1173
+
1174
+ this._bufferEdge({
1175
+ type: 'IMPORTS',
1176
+ src: module.id,
1177
+ dst: externalModule.id
1178
+ });
1179
+ }
522
1180
  }
523
1181
  }
524
1182
  }
@@ -529,79 +1187,79 @@ export class GraphBuilder {
529
1187
  const { type, line, name, specifiers, source } = exp;
530
1188
 
531
1189
  if (type === 'default') {
532
- const exportId = `${module.file}:EXPORT:default:${line}`;
533
-
534
- this._bufferNode({
535
- id: exportId,
536
- type: 'EXPORT',
537
- exportType: 'default',
538
- name: 'default',
539
- file: module.file,
540
- line: line
541
- });
1190
+ const exportNode = NodeFactory.createExport(
1191
+ 'default',
1192
+ module.file,
1193
+ line,
1194
+ 0,
1195
+ { default: true, exportType: 'default' }
1196
+ );
1197
+
1198
+ this._bufferNode(exportNode as unknown as GraphNode);
542
1199
 
543
1200
  this._bufferEdge({
544
1201
  type: 'CONTAINS',
545
1202
  src: module.id,
546
- dst: exportId
1203
+ dst: exportNode.id
547
1204
  });
548
1205
  } else if (type === 'named') {
549
1206
  if (specifiers) {
550
1207
  for (const spec of specifiers) {
551
- const exportId = `${module.file}:EXPORT:${spec.exported}:${line}`;
1208
+ const exportNode = NodeFactory.createExport(
1209
+ spec.exported,
1210
+ module.file,
1211
+ line,
1212
+ 0,
1213
+ {
1214
+ local: spec.local,
1215
+ source: source,
1216
+ exportType: 'named'
1217
+ }
1218
+ );
552
1219
 
553
- this._bufferNode({
554
- id: exportId,
555
- type: 'EXPORT',
556
- exportType: 'named',
557
- name: spec.exported,
558
- local: spec.local,
559
- file: module.file,
560
- line: line,
561
- source: source
562
- });
1220
+ this._bufferNode(exportNode as unknown as GraphNode);
563
1221
 
564
1222
  this._bufferEdge({
565
1223
  type: 'CONTAINS',
566
1224
  src: module.id,
567
- dst: exportId
1225
+ dst: exportNode.id
568
1226
  });
569
1227
  }
570
1228
  } else if (name) {
571
- const exportId = `${module.file}:EXPORT:${name}:${line}`;
1229
+ const exportNode = NodeFactory.createExport(
1230
+ name,
1231
+ module.file,
1232
+ line,
1233
+ 0,
1234
+ { exportType: 'named' }
1235
+ );
572
1236
 
573
- this._bufferNode({
574
- id: exportId,
575
- type: 'EXPORT',
576
- exportType: 'named',
577
- name: name,
578
- file: module.file,
579
- line: line
580
- });
1237
+ this._bufferNode(exportNode as unknown as GraphNode);
581
1238
 
582
1239
  this._bufferEdge({
583
1240
  type: 'CONTAINS',
584
1241
  src: module.id,
585
- dst: exportId
1242
+ dst: exportNode.id
586
1243
  });
587
1244
  }
588
1245
  } else if (type === 'all') {
589
- const exportId = `${module.file}:EXPORT:*:${line}`;
1246
+ const exportNode = NodeFactory.createExport(
1247
+ '*',
1248
+ module.file,
1249
+ line,
1250
+ 0,
1251
+ {
1252
+ source: source,
1253
+ exportType: 'all'
1254
+ }
1255
+ );
590
1256
 
591
- this._bufferNode({
592
- id: exportId,
593
- type: 'EXPORT',
594
- exportType: 'all',
595
- name: '*',
596
- file: module.file,
597
- line: line,
598
- source: source
599
- });
1257
+ this._bufferNode(exportNode as unknown as GraphNode);
600
1258
 
601
1259
  this._bufferEdge({
602
1260
  type: 'CONTAINS',
603
1261
  src: module.id,
604
- dst: exportId
1262
+ dst: exportNode.id
605
1263
  });
606
1264
  }
607
1265
  }
@@ -638,15 +1296,12 @@ export class GraphBuilder {
638
1296
 
639
1297
  private bufferHttpRequests(httpRequests: HttpRequestInfo[], functions: FunctionInfo[]): void {
640
1298
  if (httpRequests.length > 0) {
641
- const networkId = 'net:request#__network__';
1299
+ // Create net:request singleton using factory
1300
+ const networkNode = NetworkRequestNode.create();
642
1301
 
643
- if (!this._createdSingletons.has(networkId)) {
644
- this._bufferNode({
645
- id: networkId,
646
- type: 'net:request',
647
- name: '__network__'
648
- });
649
- this._createdSingletons.add(networkId);
1302
+ if (!this._createdSingletons.has(networkNode.id)) {
1303
+ this._bufferNode(networkNode as unknown as GraphNode);
1304
+ this._createdSingletons.add(networkNode.id);
650
1305
  }
651
1306
 
652
1307
  for (const request of httpRequests) {
@@ -657,7 +1312,7 @@ export class GraphBuilder {
657
1312
  this._bufferEdge({
658
1313
  type: 'CALLS',
659
1314
  src: request.id,
660
- dst: networkId
1315
+ dst: networkNode.id
661
1316
  });
662
1317
 
663
1318
  if (parentScopeId) {
@@ -710,6 +1365,7 @@ export class GraphBuilder {
710
1365
  sourceFile,
711
1366
  functionName,
712
1367
  line,
1368
+ column,
713
1369
  className
714
1370
  } = assignment;
715
1371
 
@@ -718,6 +1374,29 @@ export class GraphBuilder {
718
1374
  continue;
719
1375
  }
720
1376
 
1377
+ // CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
1378
+ // Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
1379
+ if (sourceType === 'CONSTRUCTOR_CALL' && className) {
1380
+ const constructorLine = line ?? 0;
1381
+ const constructorColumn = column ?? 0;
1382
+ const constructorFile = assignment.file ?? '';
1383
+
1384
+ // Generate ID matching the one created in NewExpression visitor
1385
+ const constructorCallId = NodeFactory.generateConstructorCallId(
1386
+ className,
1387
+ constructorFile,
1388
+ constructorLine,
1389
+ constructorColumn
1390
+ );
1391
+
1392
+ this._bufferEdge({
1393
+ type: 'ASSIGNED_FROM',
1394
+ src: variableId,
1395
+ dst: constructorCallId
1396
+ });
1397
+ continue;
1398
+ }
1399
+
721
1400
  // Direct LITERAL assignment
722
1401
  if (sourceId && sourceType !== 'EXPRESSION') {
723
1402
  this._bufferEdge({
@@ -766,8 +1445,10 @@ export class GraphBuilder {
766
1445
  }
767
1446
  // VARIABLE by name
768
1447
  else if (sourceType === 'VARIABLE' && sourceName) {
769
- const varIdParts = variableId.split('#');
770
- const varFile = varIdParts.length >= 3 ? varIdParts[2] : null;
1448
+ // Find the current variable's file by looking it up in variableDeclarations
1449
+ // (semantic IDs don't have predictable file positions like old hash-based IDs)
1450
+ const currentVar = variableDeclarations.find(v => v.id === variableId);
1451
+ const varFile = currentVar?.file ?? null;
771
1452
  const sourceVariable = variableDeclarations.find(v =>
772
1453
  v.name === sourceName && v.file === varFile
773
1454
  );
@@ -806,7 +1487,7 @@ export class GraphBuilder {
806
1487
  });
807
1488
  }
808
1489
  }
809
- // EXPRESSION node creation
1490
+ // EXPRESSION node creation using NodeFactory
810
1491
  else if (sourceType === 'EXPRESSION' && sourceId) {
811
1492
  const {
812
1493
  expressionType,
@@ -821,33 +1502,35 @@ export class GraphBuilder {
821
1502
  consequentSourceName,
822
1503
  alternateSourceName,
823
1504
  file: exprFile,
824
- line: exprLine
1505
+ line: exprLine,
1506
+ column: exprColumn,
1507
+ // Destructuring support (REG-201)
1508
+ path,
1509
+ baseName,
1510
+ propertyPath,
1511
+ arrayIndex
825
1512
  } = assignment;
826
1513
 
827
- const expressionNode: GraphNode = {
828
- id: sourceId,
829
- type: 'EXPRESSION',
830
- expressionType,
831
- file: exprFile,
832
- line: exprLine
833
- };
834
-
835
- if (expressionType === 'MemberExpression') {
836
- expressionNode.object = object;
837
- expressionNode.property = property;
838
- expressionNode.computed = computed;
839
- if (computedPropertyVar) {
840
- expressionNode.computedPropertyVar = computedPropertyVar;
1514
+ // Create node from upstream metadata using factory
1515
+ const expressionNode = NodeFactory.createExpressionFromMetadata(
1516
+ expressionType || 'Unknown',
1517
+ exprFile || '',
1518
+ exprLine || 0,
1519
+ exprColumn || 0,
1520
+ {
1521
+ id: sourceId, // ID from JSASTAnalyzer
1522
+ object,
1523
+ property,
1524
+ computed,
1525
+ computedPropertyVar: computedPropertyVar ?? undefined,
1526
+ operator,
1527
+ // Destructuring support (REG-201)
1528
+ path,
1529
+ baseName,
1530
+ propertyPath,
1531
+ arrayIndex
841
1532
  }
842
- expressionNode.name = `${object}.${property}`;
843
- } else if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
844
- expressionNode.operator = operator;
845
- expressionNode.name = `<${expressionType}>`;
846
- } else if (expressionType === 'ConditionalExpression') {
847
- expressionNode.name = '<ternary>';
848
- } else if (expressionType === 'TemplateLiteral') {
849
- expressionNode.name = '<template>';
850
- }
1533
+ );
851
1534
 
852
1535
  this._bufferNode(expressionNode);
853
1536
 
@@ -873,6 +1556,50 @@ export class GraphBuilder {
873
1556
  });
874
1557
  }
875
1558
  }
1559
+ // Call-based source lookup (REG-223)
1560
+ else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
1561
+ const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
1562
+
1563
+ // Try CALL_SITE first (direct function calls)
1564
+ const callSite = callSites.find(cs =>
1565
+ cs.line === callSourceLine &&
1566
+ cs.column === callSourceColumn &&
1567
+ (callSourceName ? cs.name === callSourceName : true)
1568
+ );
1569
+
1570
+ if (callSite) {
1571
+ this._bufferEdge({
1572
+ type: 'DERIVES_FROM',
1573
+ src: sourceId,
1574
+ dst: callSite.id
1575
+ });
1576
+ }
1577
+ // Fall back to methodCalls (arr.map(), obj.getConfig())
1578
+ else {
1579
+ const methodCall = methodCalls.find(mc =>
1580
+ mc.line === callSourceLine &&
1581
+ mc.column === callSourceColumn &&
1582
+ (callSourceName ? mc.name === callSourceName : true)
1583
+ );
1584
+
1585
+ if (methodCall) {
1586
+ this._bufferEdge({
1587
+ type: 'DERIVES_FROM',
1588
+ src: sourceId,
1589
+ dst: methodCall.id
1590
+ });
1591
+ }
1592
+ // Log warning when lookup fails (per Linus review - no silent failures)
1593
+ else {
1594
+ console.warn(
1595
+ `[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
1596
+ `at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
1597
+ `Expected CALL_SITE or methodCall for "${callSourceName}". ` +
1598
+ `This indicates a coordinate mismatch or missing call node.`
1599
+ );
1600
+ }
1601
+ }
1602
+ }
876
1603
 
877
1604
  if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
878
1605
  if (leftSourceName) {
@@ -1029,6 +1756,12 @@ export class GraphBuilder {
1029
1756
  targetNodeId = nestedCall.id;
1030
1757
  }
1031
1758
  }
1759
+ else if (targetType === 'LITERAL' ||
1760
+ targetType === 'OBJECT_LITERAL' ||
1761
+ targetType === 'ARRAY_LITERAL') {
1762
+ // targetId is already set by CallExpressionVisitor
1763
+ targetNodeId = targetId;
1764
+ }
1032
1765
 
1033
1766
  if (targetNodeId) {
1034
1767
  const edgeData: GraphEdge = {
@@ -1051,54 +1784,66 @@ export class GraphBuilder {
1051
1784
 
1052
1785
  /**
1053
1786
  * Buffer INTERFACE nodes and EXTENDS edges
1787
+ *
1788
+ * Uses two-pass approach:
1789
+ * 1. First pass: create all interface nodes, store in Map
1790
+ * 2. Second pass: create EXTENDS edges using stored node IDs
1054
1791
  */
1055
1792
  private bufferInterfaceNodes(module: ModuleNode, interfaces: InterfaceDeclarationInfo[]): void {
1793
+ // First pass: create all interface nodes and store them
1794
+ const interfaceNodes = new Map<string, InterfaceNodeRecord>();
1795
+
1056
1796
  for (const iface of interfaces) {
1057
- // Buffer INTERFACE node
1058
- this._bufferNode({
1059
- id: iface.id,
1060
- type: 'INTERFACE',
1061
- name: iface.name,
1062
- file: iface.file,
1063
- line: iface.line,
1064
- column: iface.column,
1065
- properties: iface.properties,
1066
- extends: iface.extends
1067
- });
1797
+ const interfaceNode = InterfaceNode.create(
1798
+ iface.name,
1799
+ iface.file,
1800
+ iface.line,
1801
+ iface.column || 0,
1802
+ {
1803
+ extends: iface.extends,
1804
+ properties: iface.properties
1805
+ }
1806
+ );
1807
+ interfaceNodes.set(iface.name, interfaceNode);
1808
+ this._bufferNode(interfaceNode as unknown as GraphNode);
1068
1809
 
1069
1810
  // MODULE -> CONTAINS -> INTERFACE
1070
1811
  this._bufferEdge({
1071
1812
  type: 'CONTAINS',
1072
1813
  src: module.id,
1073
- dst: iface.id
1814
+ dst: interfaceNode.id
1074
1815
  });
1816
+ }
1075
1817
 
1076
- // INTERFACE -> EXTENDS -> INTERFACE (for each parent interface)
1818
+ // Second pass: create EXTENDS edges
1819
+ for (const iface of interfaces) {
1077
1820
  if (iface.extends && iface.extends.length > 0) {
1821
+ const srcNode = interfaceNodes.get(iface.name)!;
1822
+
1078
1823
  for (const parentName of iface.extends) {
1079
- // Try to find the parent interface in the same file
1080
- const parentInterface = interfaces.find(i => i.name === parentName);
1081
- if (parentInterface) {
1824
+ const parentNode = interfaceNodes.get(parentName);
1825
+
1826
+ if (parentNode) {
1827
+ // Same-file interface
1082
1828
  this._bufferEdge({
1083
1829
  type: 'EXTENDS',
1084
- src: iface.id,
1085
- dst: parentInterface.id
1830
+ src: srcNode.id,
1831
+ dst: parentNode.id
1086
1832
  });
1087
1833
  } else {
1088
1834
  // External interface - create a reference node
1089
- const externalId = `INTERFACE#${parentName}#${iface.file}#external`;
1090
- this._bufferNode({
1091
- id: externalId,
1092
- type: 'INTERFACE',
1093
- name: parentName,
1094
- file: iface.file,
1095
- line: iface.line,
1096
- isExternal: true
1097
- });
1835
+ const externalInterface = NodeFactory.createInterface(
1836
+ parentName,
1837
+ iface.file,
1838
+ iface.line,
1839
+ 0,
1840
+ { isExternal: true }
1841
+ );
1842
+ this._bufferNode(externalInterface as unknown as GraphNode);
1098
1843
  this._bufferEdge({
1099
1844
  type: 'EXTENDS',
1100
- src: iface.id,
1101
- dst: externalId
1845
+ src: srcNode.id,
1846
+ dst: externalInterface.id
1102
1847
  });
1103
1848
  }
1104
1849
  }
@@ -1111,48 +1856,51 @@ export class GraphBuilder {
1111
1856
  */
1112
1857
  private bufferTypeAliasNodes(module: ModuleNode, typeAliases: TypeAliasInfo[]): void {
1113
1858
  for (const typeAlias of typeAliases) {
1114
- // Buffer TYPE node
1115
- this._bufferNode({
1116
- id: typeAlias.id,
1117
- type: 'TYPE',
1118
- name: typeAlias.name,
1119
- file: typeAlias.file,
1120
- line: typeAlias.line,
1121
- column: typeAlias.column,
1122
- aliasOf: typeAlias.aliasOf
1123
- });
1859
+ // Create TYPE node using factory
1860
+ const typeNode = NodeFactory.createType(
1861
+ typeAlias.name,
1862
+ typeAlias.file,
1863
+ typeAlias.line,
1864
+ typeAlias.column || 0,
1865
+ { aliasOf: typeAlias.aliasOf }
1866
+ );
1867
+ this._bufferNode(typeNode as unknown as GraphNode);
1124
1868
 
1125
1869
  // MODULE -> CONTAINS -> TYPE
1126
1870
  this._bufferEdge({
1127
1871
  type: 'CONTAINS',
1128
1872
  src: module.id,
1129
- dst: typeAlias.id
1873
+ dst: typeNode.id
1130
1874
  });
1131
1875
  }
1132
1876
  }
1133
1877
 
1134
1878
  /**
1135
1879
  * Buffer ENUM nodes
1880
+ * Uses EnumNode.create() to ensure consistent ID format (colon separator)
1136
1881
  */
1137
1882
  private bufferEnumNodes(module: ModuleNode, enums: EnumDeclarationInfo[]): void {
1138
1883
  for (const enumDecl of enums) {
1139
- // Buffer ENUM node
1140
- this._bufferNode({
1141
- id: enumDecl.id,
1142
- type: 'ENUM',
1143
- name: enumDecl.name,
1144
- file: enumDecl.file,
1145
- line: enumDecl.line,
1146
- column: enumDecl.column,
1147
- isConst: enumDecl.isConst,
1148
- members: enumDecl.members
1149
- });
1884
+ // Use EnumNode.create() to generate proper ID (colon format)
1885
+ // Do NOT use enumDecl.id which has legacy # format from TypeScriptVisitor
1886
+ const enumNode = EnumNode.create(
1887
+ enumDecl.name,
1888
+ enumDecl.file,
1889
+ enumDecl.line,
1890
+ enumDecl.column || 0,
1891
+ {
1892
+ isConst: enumDecl.isConst || false,
1893
+ members: enumDecl.members || []
1894
+ }
1895
+ );
1896
+
1897
+ this._bufferNode(enumNode as unknown as GraphNode);
1150
1898
 
1151
1899
  // MODULE -> CONTAINS -> ENUM
1152
1900
  this._bufferEdge({
1153
1901
  type: 'CONTAINS',
1154
1902
  src: module.id,
1155
- dst: enumDecl.id
1903
+ dst: enumNode.id // Use factory-generated ID (colon format)
1156
1904
  });
1157
1905
  }
1158
1906
  }
@@ -1162,23 +1910,24 @@ export class GraphBuilder {
1162
1910
  */
1163
1911
  private bufferDecoratorNodes(decorators: DecoratorInfo[]): void {
1164
1912
  for (const decorator of decorators) {
1165
- // Buffer DECORATOR node
1166
- this._bufferNode({
1167
- id: decorator.id,
1168
- type: 'DECORATOR',
1169
- name: decorator.name,
1170
- file: decorator.file,
1171
- line: decorator.line,
1172
- column: decorator.column,
1173
- arguments: decorator.arguments,
1174
- targetType: decorator.targetType
1175
- });
1913
+ // Create DECORATOR node using factory (generates colon-format ID)
1914
+ const decoratorNode = DecoratorNode.create(
1915
+ decorator.name,
1916
+ decorator.file,
1917
+ decorator.line,
1918
+ decorator.column || 0,
1919
+ decorator.targetId, // Now included in the node!
1920
+ decorator.targetType,
1921
+ { arguments: decorator.arguments }
1922
+ );
1923
+
1924
+ this._bufferNode(decoratorNode as unknown as GraphNode);
1176
1925
 
1177
1926
  // TARGET -> DECORATED_BY -> DECORATOR
1178
1927
  this._bufferEdge({
1179
1928
  type: 'DECORATED_BY',
1180
1929
  src: decorator.targetId,
1181
- dst: decorator.id
1930
+ dst: decoratorNode.id // Use factory-generated ID (colon format)
1182
1931
  });
1183
1932
  }
1184
1933
  }
@@ -1193,26 +1942,28 @@ export class GraphBuilder {
1193
1942
  // Try to find the interface in the same file
1194
1943
  const iface = interfaces.find(i => i.name === ifaceName);
1195
1944
  if (iface) {
1945
+ // Compute interface ID using same formula as InterfaceNode.create()
1946
+ // Format: {file}:INTERFACE:{name}:{line}
1947
+ const interfaceId = `${iface.file}:INTERFACE:${iface.name}:${iface.line}`;
1196
1948
  this._bufferEdge({
1197
1949
  type: 'IMPLEMENTS',
1198
1950
  src: classDecl.id,
1199
- dst: iface.id
1951
+ dst: interfaceId
1200
1952
  });
1201
1953
  } else {
1202
1954
  // External interface - create a reference node
1203
- const externalId = `INTERFACE#${ifaceName}#${classDecl.file}#external`;
1204
- this._bufferNode({
1205
- id: externalId,
1206
- type: 'INTERFACE',
1207
- name: ifaceName,
1208
- file: classDecl.file,
1209
- line: classDecl.line,
1210
- isExternal: true
1211
- });
1955
+ const externalInterface = NodeFactory.createInterface(
1956
+ ifaceName,
1957
+ classDecl.file,
1958
+ classDecl.line,
1959
+ 0,
1960
+ { isExternal: true }
1961
+ );
1962
+ this._bufferNode(externalInterface as unknown as GraphNode);
1212
1963
  this._bufferEdge({
1213
1964
  type: 'IMPLEMENTS',
1214
1965
  src: classDecl.id,
1215
- dst: externalId
1966
+ dst: externalInterface.id
1216
1967
  });
1217
1968
  }
1218
1969
  }
@@ -1221,148 +1972,1017 @@ export class GraphBuilder {
1221
1972
  }
1222
1973
 
1223
1974
  /**
1224
- * Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
1975
+ * Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
1976
+ * Creates edges from inserted values to the array variable
1977
+ *
1978
+ * REG-117: Now handles nested mutations like obj.arr.push(item):
1979
+ * - For nested mutations, falls back to base object if array property not found
1980
+ * - Adds nestedProperty metadata for tracking
1981
+ *
1982
+ * OPTIMIZED: Uses Map-based lookup cache for O(1) variable lookups instead of O(n) find()
1225
1983
  */
1226
- private async createClassAssignmentEdges(variableAssignments: VariableAssignmentInfo[], graph: GraphBackend): Promise<number> {
1227
- let edgesCreated = 0;
1984
+ private bufferArrayMutationEdges(
1985
+ arrayMutations: ArrayMutationInfo[],
1986
+ variableDeclarations: VariableDeclarationInfo[],
1987
+ parameters: ParameterInfo[]
1988
+ ): void {
1989
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
1228
1990
 
1229
- for (const assignment of variableAssignments) {
1230
- const { variableId, sourceType, className } = assignment;
1991
+ for (const mutation of arrayMutations) {
1992
+ const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
1231
1993
 
1232
- if (sourceType === 'CLASS' && className) {
1233
- const parts = variableId.split('#');
1234
- const file = parts.length >= 3 ? parts[2] : null;
1994
+ const scopePath = mutationScopePath ?? [];
1235
1995
 
1236
- let classNode: { id: string; name: string; file?: string } | null = null;
1237
- for await (const node of graph.queryNodes({ type: 'CLASS' })) {
1238
- if (node.name === className && (!file || node.file === file)) {
1239
- classNode = node as { id: string; name: string; file?: string };
1240
- break;
1996
+ // REG-117: For nested mutations (obj.arr.push), resolve target node
1997
+ let targetNodeId: string | null = null;
1998
+ let nestedProperty: string | undefined;
1999
+
2000
+ if (isNested && baseObjectName) {
2001
+ // Skip 'this.items.push' - 'this' is not a variable node
2002
+ if (baseObjectName === 'this') continue;
2003
+
2004
+ // Nested mutation: try base object lookup with scope chain (REG-309)
2005
+ const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
2006
+ const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
2007
+ targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
2008
+ nestedProperty = propertyName;
2009
+ } else {
2010
+ // Direct mutation: arr.push() (REG-309)
2011
+ const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
2012
+ const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
2013
+ targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
2014
+ }
2015
+
2016
+ if (!targetNodeId) continue;
2017
+
2018
+ // Create FLOWS_INTO edges for each inserted value
2019
+ for (const arg of insertedValues) {
2020
+ if (arg.valueType === 'VARIABLE' && arg.valueName) {
2021
+ // Scope-aware lookup for source variable (REG-309)
2022
+ const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
2023
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
2024
+ const sourceNodeId = sourceVar?.id ?? sourceParam?.id;
2025
+
2026
+ if (sourceNodeId) {
2027
+ const edgeData: GraphEdge = {
2028
+ type: 'FLOWS_INTO',
2029
+ src: sourceNodeId,
2030
+ dst: targetNodeId,
2031
+ mutationMethod,
2032
+ argIndex: arg.argIndex
2033
+ };
2034
+ if (arg.isSpread) {
2035
+ edgeData.isSpread = true;
2036
+ }
2037
+ // REG-117: Add nested property metadata
2038
+ if (nestedProperty) {
2039
+ edgeData.nestedProperty = nestedProperty;
2040
+ }
2041
+ this._bufferEdge(edgeData);
1241
2042
  }
1242
2043
  }
2044
+ // For literals, object literals, etc. - we could create edges from LITERAL nodes
2045
+ // but for now we just track variable -> array flows
2046
+ }
2047
+ }
2048
+ }
1243
2049
 
1244
- if (classNode) {
1245
- await graph.addEdge({
1246
- type: 'ASSIGNED_FROM',
1247
- src: variableId,
1248
- dst: classNode.id
1249
- });
1250
- edgesCreated++;
2050
+ /**
2051
+ * Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
2052
+ * Creates edges from source values to the object variable being mutated.
2053
+ *
2054
+ * REG-152: For 'this.prop = value' patterns inside classes, creates edges
2055
+ * to the CLASS node with mutationType: 'this_property'.
2056
+ */
2057
+ private bufferObjectMutationEdges(
2058
+ objectMutations: ObjectMutationInfo[],
2059
+ variableDeclarations: VariableDeclarationInfo[],
2060
+ parameters: ParameterInfo[],
2061
+ functions: FunctionInfo[],
2062
+ classDeclarations: ClassDeclarationInfo[]
2063
+ ): void {
2064
+ for (const mutation of objectMutations) {
2065
+ const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
2066
+
2067
+ const scopePath = mutationScopePath ?? [];
2068
+
2069
+ // Find the target node (object variable, parameter, or class for 'this')
2070
+ let objectNodeId: string | null = null;
2071
+ let effectiveMutationType: 'property' | 'computed' | 'assign' | 'spread' | 'this_property' = mutationType;
2072
+
2073
+ if (objectName !== 'this') {
2074
+ // Regular object - find variable, parameter, or function using scope chain (REG-309)
2075
+ const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
2076
+ const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
2077
+ const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
2078
+ objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
2079
+ if (!objectNodeId) continue;
2080
+ } else {
2081
+ // REG-152: 'this' mutations - find the CLASS node
2082
+ if (!enclosingClassName) continue; // Skip if no class context (e.g., standalone function)
2083
+
2084
+ // Compare using basename since classes use scopeTracker.file (basename)
2085
+ // but mutations use module.file (full path)
2086
+ const fileBasename = basename(file);
2087
+ const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
2088
+ objectNodeId = classDecl?.id ?? null;
2089
+
2090
+ if (!objectNodeId) continue; // Skip if class not found
2091
+
2092
+ // Use special mutation type to distinguish from regular property mutations
2093
+ effectiveMutationType = 'this_property';
2094
+ }
2095
+
2096
+ // Create FLOWS_INTO edge for VARIABLE value type
2097
+ if (value.valueType === 'VARIABLE' && value.valueName) {
2098
+ // Find the source: can be variable, parameter, or function using scope chain (REG-309)
2099
+ const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
2100
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
2101
+ const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
2102
+ const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
2103
+
2104
+ if (sourceNodeId && objectNodeId) {
2105
+ const edgeData: GraphEdge = {
2106
+ type: 'FLOWS_INTO',
2107
+ src: sourceNodeId,
2108
+ dst: objectNodeId,
2109
+ mutationType: effectiveMutationType,
2110
+ propertyName,
2111
+ computedPropertyVar // For enrichment phase resolution
2112
+ };
2113
+ if (value.argIndex !== undefined) {
2114
+ edgeData.argIndex = value.argIndex;
2115
+ }
2116
+ if (value.isSpread) {
2117
+ edgeData.isSpread = true;
2118
+ }
2119
+ this._bufferEdge(edgeData);
1251
2120
  }
1252
2121
  }
2122
+ // For literals, object literals, etc. - we just track variable -> object flows for now
1253
2123
  }
2124
+ }
1254
2125
 
1255
- return edgesCreated;
2126
+ /**
2127
+ * Resolve variable by name using scope chain lookup (REG-309).
2128
+ * Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
2129
+ *
2130
+ * @param name - Variable name
2131
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
2132
+ * @param file - File path
2133
+ * @param variables - All variable declarations
2134
+ * @returns Variable declaration or null if not found
2135
+ */
2136
+ private resolveVariableInScope(
2137
+ name: string,
2138
+ scopePath: string[],
2139
+ file: string,
2140
+ variables: VariableDeclarationInfo[]
2141
+ ): VariableDeclarationInfo | null {
2142
+ // Try current scope, then parent, then grandparent, etc.
2143
+ for (let i = scopePath.length; i >= 0; i--) {
2144
+ const searchScopePath = scopePath.slice(0, i);
2145
+
2146
+ const matchingVar = variables.find(v => {
2147
+ if (v.name !== name || v.file !== file) return false;
2148
+
2149
+ // Variable ID IS the semantic ID (when scopeTracker was available during analysis)
2150
+ // Format: file->scope1->scope2->TYPE->name
2151
+ // Legacy format: VARIABLE#name#file#line:column:counter
2152
+
2153
+ // Try parsing as semantic ID
2154
+ const parsed = parseSemanticId(v.id);
2155
+ // REG-329: Check for both VARIABLE and CONSTANT (const declarations)
2156
+ if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
2157
+ // FIXED (REG-309): Handle module-level scope matching
2158
+ // Empty search scope [] should match semantic ID scope ['global']
2159
+ if (searchScopePath.length === 0) {
2160
+ return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
2161
+ }
2162
+ // Non-empty scope: exact match
2163
+ return this.scopePathsMatch(parsed.scopePath, searchScopePath);
2164
+ }
2165
+
2166
+ // Legacy ID - assume module-level if no semantic ID
2167
+ return searchScopePath.length === 0;
2168
+ });
2169
+
2170
+ if (matchingVar) return matchingVar;
2171
+ }
2172
+
2173
+ return null;
1256
2174
  }
1257
2175
 
1258
2176
  /**
1259
- * Create IMPORTS_FROM edges linking imports to their target exports
2177
+ * Resolve parameter by name using scope chain lookup (REG-309).
2178
+ * Same semantics as resolveVariableInScope but for parameters.
2179
+ *
2180
+ * @param name - Parameter name
2181
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
2182
+ * @param file - File path
2183
+ * @param parameters - All parameter declarations
2184
+ * @returns Parameter declaration or null if not found
1260
2185
  */
1261
- private async createImportExportEdges(
1262
- module: ModuleNode,
1263
- imports: ImportInfo[],
1264
- _exports: ExportInfo[],
1265
- graph: GraphBackend,
1266
- _projectPath: string
1267
- ): Promise<number> {
1268
- let edgesCreated = 0;
2186
+ private resolveParameterInScope(
2187
+ name: string,
2188
+ scopePath: string[],
2189
+ file: string,
2190
+ parameters: ParameterInfo[]
2191
+ ): ParameterInfo | null {
2192
+ // Parameters have semanticId field populated (unlike variables which use id field)
2193
+ return parameters.find(p => {
2194
+ if (p.name !== name || p.file !== file) return false;
2195
+
2196
+ if (p.semanticId) {
2197
+ const parsed = parseSemanticId(p.semanticId);
2198
+ if (parsed && parsed.type === 'PARAMETER') {
2199
+ // Check if parameter's scope matches any scope in the chain
2200
+ for (let i = scopePath.length; i >= 0; i--) {
2201
+ const searchScopePath = scopePath.slice(0, i);
2202
+
2203
+ // FIXED (REG-309): Handle module-level scope matching for parameters
2204
+ if (searchScopePath.length === 0) {
2205
+ if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
2206
+ return true;
2207
+ }
2208
+ } else {
2209
+ if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
2210
+ return true;
2211
+ }
2212
+ }
2213
+ }
2214
+ }
2215
+ }
2216
+ return false;
2217
+ }) ?? null;
2218
+ }
1269
2219
 
1270
- for (const imp of imports) {
1271
- const { source, specifiers, line } = imp;
2220
+ /**
2221
+ * Check if two scope paths match (REG-309).
2222
+ * Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
2223
+ */
2224
+ private scopePathsMatch(a: string[], b: string[]): boolean {
2225
+ if (a.length !== b.length) return false;
2226
+ return a.every((item, idx) => item === b[idx]);
2227
+ }
1272
2228
 
1273
- // Только для относительных импортов
1274
- const isRelative = source.startsWith('./') || source.startsWith('../');
1275
- if (!isRelative) {
2229
+ /**
2230
+ * Buffer FLOWS_INTO edges for variable reassignments.
2231
+ * Handles: x = y, x += y (when x is already declared, not initialization)
2232
+ *
2233
+ * Edge patterns:
2234
+ * - Simple assignment (=): source --FLOWS_INTO--> variable
2235
+ * - Compound operators (+=, -=, etc.):
2236
+ * - source --FLOWS_INTO--> variable (write new value)
2237
+ * - variable --READS_FROM--> variable (self-loop: reads current value before write)
2238
+ *
2239
+ * REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
2240
+ *
2241
+ * REG-290: Complete implementation with inline node creation (no continue statements).
2242
+ */
2243
+ private bufferVariableReassignmentEdges(
2244
+ variableReassignments: VariableReassignmentInfo[],
2245
+ variableDeclarations: VariableDeclarationInfo[],
2246
+ callSites: CallSiteInfo[],
2247
+ methodCalls: MethodCallInfo[],
2248
+ parameters: ParameterInfo[]
2249
+ ): void {
2250
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
2251
+ // Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
2252
+
2253
+ for (const reassignment of variableReassignments) {
2254
+ const {
2255
+ variableName,
2256
+ mutationScopePath,
2257
+ valueType,
2258
+ valueName,
2259
+ valueId,
2260
+ callLine,
2261
+ callColumn,
2262
+ operator,
2263
+ literalValue,
2264
+ expressionType,
2265
+ expressionMetadata,
2266
+ file,
2267
+ line,
2268
+ column
2269
+ } = reassignment;
2270
+
2271
+ // Find target variable node using scope chain resolution (REG-309)
2272
+ const scopePath = mutationScopePath ?? [];
2273
+ const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
2274
+ const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
2275
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
2276
+
2277
+ if (!targetNodeId) {
2278
+ // Variable not found - could be external reference
1276
2279
  continue;
1277
2280
  }
1278
2281
 
1279
- // Резолвим целевой модуль
1280
- const currentDir = dirname(module.file);
1281
- let targetPath = resolve(currentDir, source);
2282
+ // Resolve source node based on value type
2283
+ let sourceNodeId: string | null = null;
1282
2284
 
1283
- // Пытаемся найти файл с расширениями .js, .ts, .jsx, .tsx
1284
- const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '/index.js', '/index.ts'];
1285
- let targetModule: { id: string; file: string } | null = null;
2285
+ // LITERAL: Create node inline (NO CONTINUE STATEMENT)
2286
+ if (valueType === 'LITERAL' && valueId) {
2287
+ // Create LITERAL node
2288
+ this._bufferNode({
2289
+ type: 'LITERAL',
2290
+ id: valueId,
2291
+ value: literalValue,
2292
+ file,
2293
+ line,
2294
+ column
2295
+ });
2296
+ sourceNodeId = valueId;
2297
+ }
2298
+ // VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
2299
+ else if (valueType === 'VARIABLE' && valueName) {
2300
+ const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
2301
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
2302
+ sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
2303
+ }
2304
+ // CALL_SITE: Look up existing call node
2305
+ else if (valueType === 'CALL_SITE' && callLine && callColumn) {
2306
+ const callSite = callSites.find(cs =>
2307
+ cs.line === callLine && cs.column === callColumn && cs.file === file
2308
+ );
2309
+ sourceNodeId = callSite?.id ?? null;
2310
+ }
2311
+ // METHOD_CALL: Look up existing method call node
2312
+ else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
2313
+ const methodCall = methodCalls.find(mc =>
2314
+ mc.line === callLine && mc.column === callColumn && mc.file === file
2315
+ );
2316
+ sourceNodeId = methodCall?.id ?? null;
2317
+ }
2318
+ // EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
2319
+ else if (valueType === 'EXPRESSION' && valueId && expressionType) {
2320
+ // Create EXPRESSION node using NodeFactory
2321
+ const expressionNode = NodeFactory.createExpressionFromMetadata(
2322
+ expressionType,
2323
+ file,
2324
+ line,
2325
+ column,
2326
+ {
2327
+ id: valueId, // ID from JSASTAnalyzer
2328
+ object: expressionMetadata?.object,
2329
+ property: expressionMetadata?.property,
2330
+ computed: expressionMetadata?.computed,
2331
+ computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
2332
+ operator: expressionMetadata?.operator
2333
+ }
2334
+ );
1286
2335
 
1287
- // Ищем MODULE ноду по file атрибуту (не по ID, т.к. формат ID изменился)
1288
- for (const ext of extensions) {
1289
- const testPath = targetPath + ext;
2336
+ this._bufferNode(expressionNode);
2337
+ sourceNodeId = valueId;
2338
+ }
1290
2339
 
1291
- // Ищем MODULE с этим file path
1292
- for await (const node of graph.queryNodes({ type: 'MODULE' })) {
1293
- if (node.file === testPath) {
1294
- targetModule = node as { id: string; file: string };
1295
- targetPath = testPath;
1296
- break;
1297
- }
2340
+ // Create edges if source found
2341
+ if (sourceNodeId && targetNodeId) {
2342
+ // For compound operators (operator !== '='), LHS reads its own current value
2343
+ // Create READS_FROM self-loop (Linus requirement)
2344
+ if (operator !== '=') {
2345
+ this._bufferEdge({
2346
+ type: 'READS_FROM',
2347
+ src: targetNodeId, // Variable reads from...
2348
+ dst: targetNodeId // ...itself (self-loop)
2349
+ });
1298
2350
  }
1299
- if (targetModule) break;
2351
+
2352
+ // RHS flows into LHS (write side)
2353
+ this._bufferEdge({
2354
+ type: 'FLOWS_INTO',
2355
+ src: sourceNodeId,
2356
+ dst: targetNodeId
2357
+ });
1300
2358
  }
2359
+ }
2360
+ }
2361
+
2362
+ /**
2363
+ * Buffer RETURNS edges connecting return expressions to their containing functions.
2364
+ *
2365
+ * Edge direction: returnExpression --RETURNS--> function
2366
+ *
2367
+ * This enables tracing data flow through function calls:
2368
+ * - Query: "What does formatDate return?"
2369
+ * - Answer: Follow RETURNS edges from function to see all possible return values
2370
+ */
2371
+ private bufferReturnEdges(
2372
+ returnStatements: ReturnStatementInfo[],
2373
+ callSites: CallSiteInfo[],
2374
+ methodCalls: MethodCallInfo[],
2375
+ variableDeclarations: VariableDeclarationInfo[],
2376
+ parameters: ParameterInfo[]
2377
+ ): void {
2378
+ for (const ret of returnStatements) {
2379
+ const { parentFunctionId, returnValueType, file } = ret;
1301
2380
 
1302
- if (!targetModule) {
1303
- // Целевой модуль не найден в графе
2381
+ // Skip if no value returned (bare return;)
2382
+ if (returnValueType === 'NONE') {
1304
2383
  continue;
1305
2384
  }
1306
2385
 
1307
- // Создаём IMPORTS edge от MODULE к MODULE (для совместимости с тестами)
1308
- await graph.addEdge({
1309
- type: 'IMPORTS',
1310
- src: module.id,
1311
- dst: targetModule.id
1312
- });
1313
- edgesCreated++;
2386
+ let sourceNodeId: string | null = null;
1314
2387
 
1315
- // Для каждого импортированного идентификатора создаём ребро к соответствующему EXPORT
1316
- for (const spec of specifiers) {
1317
- const importId = `${module.file}:IMPORT:${source}:${spec.local}:${line}`;
1318
- const importType = spec.imported === 'default' ? 'default' :
1319
- spec.imported === '*' ? 'namespace' : 'named';
2388
+ switch (returnValueType) {
2389
+ case 'LITERAL':
2390
+ // Direct reference to literal node
2391
+ sourceNodeId = ret.returnValueId ?? null;
2392
+ break;
1320
2393
 
1321
- if (importType === 'namespace') {
1322
- // import * as foo - связываем со всем модулем
1323
- await graph.addEdge({
1324
- type: 'IMPORTS_FROM',
1325
- src: importId,
1326
- dst: targetModule.id
1327
- });
1328
- edgesCreated++;
1329
- } else if (importType === 'default') {
1330
- // Находим EXPORT default в целевом модуле
1331
- const targetExports: { id: string }[] = [];
1332
- for await (const node of graph.queryNodes({ type: 'EXPORT' })) {
1333
- const exportNode = node as { id: string; file?: string; exportType?: string };
1334
- if (exportNode.file === targetPath && exportNode.exportType === 'default') {
1335
- targetExports.push(exportNode);
2394
+ case 'VARIABLE': {
2395
+ // Find variable declaration by name in same file
2396
+ const varName = ret.returnValueName;
2397
+ if (varName) {
2398
+ const sourceVar = variableDeclarations.find(v =>
2399
+ v.name === varName && v.file === file
2400
+ );
2401
+ if (sourceVar) {
2402
+ sourceNodeId = sourceVar.id;
2403
+ } else {
2404
+ // Check parameters
2405
+ const sourceParam = parameters.find(p =>
2406
+ p.name === varName && p.file === file
2407
+ );
2408
+ if (sourceParam) {
2409
+ sourceNodeId = sourceParam.id;
2410
+ }
1336
2411
  }
1337
2412
  }
2413
+ break;
2414
+ }
1338
2415
 
1339
- if (targetExports.length > 0) {
1340
- await graph.addEdge({
1341
- type: 'IMPORTS_FROM',
1342
- src: importId,
1343
- dst: targetExports[0].id
1344
- });
1345
- edgesCreated++;
2416
+ case 'CALL_SITE': {
2417
+ // Find call site by coordinates
2418
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
2419
+ if (returnValueLine && returnValueColumn) {
2420
+ const callSite = callSites.find(cs =>
2421
+ cs.line === returnValueLine &&
2422
+ cs.column === returnValueColumn &&
2423
+ (returnValueCallName ? cs.name === returnValueCallName : true)
2424
+ );
2425
+ if (callSite) {
2426
+ sourceNodeId = callSite.id;
2427
+ }
1346
2428
  }
1347
- } else {
1348
- // Named import - находим соответствующий named export
1349
- const targetExports: { id: string }[] = [];
1350
- for await (const node of graph.queryNodes({ type: 'EXPORT' })) {
1351
- const exportNode = node as { id: string; file?: string; exportType?: string; name?: string };
1352
- if (exportNode.file === targetPath && exportNode.exportType === 'named' && exportNode.name === spec.imported) {
1353
- targetExports.push(exportNode);
2429
+ break;
2430
+ }
2431
+
2432
+ case 'METHOD_CALL': {
2433
+ // Find method call by coordinates and method name
2434
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
2435
+ if (returnValueLine && returnValueColumn) {
2436
+ const methodCall = methodCalls.find(mc =>
2437
+ mc.line === returnValueLine &&
2438
+ mc.column === returnValueColumn &&
2439
+ mc.file === file &&
2440
+ (returnValueCallName ? mc.method === returnValueCallName : true)
2441
+ );
2442
+ if (methodCall) {
2443
+ sourceNodeId = methodCall.id;
1354
2444
  }
1355
2445
  }
2446
+ break;
2447
+ }
1356
2448
 
1357
- if (targetExports.length > 0) {
1358
- await graph.addEdge({
1359
- type: 'IMPORTS_FROM',
1360
- src: importId,
1361
- dst: targetExports[0].id
1362
- });
1363
- edgesCreated++;
2449
+ case 'EXPRESSION': {
2450
+ // REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
2451
+ const {
2452
+ expressionType,
2453
+ returnValueId,
2454
+ returnValueLine,
2455
+ returnValueColumn,
2456
+ operator,
2457
+ object,
2458
+ property,
2459
+ computed,
2460
+ objectSourceName,
2461
+ leftSourceName,
2462
+ rightSourceName,
2463
+ consequentSourceName,
2464
+ alternateSourceName,
2465
+ expressionSourceNames,
2466
+ unaryArgSourceName
2467
+ } = ret;
2468
+
2469
+ // Skip if no expression ID was generated
2470
+ if (!returnValueId) {
2471
+ break;
2472
+ }
2473
+
2474
+ // Create EXPRESSION node using NodeFactory
2475
+ const expressionNode = NodeFactory.createExpressionFromMetadata(
2476
+ expressionType || 'Unknown',
2477
+ file,
2478
+ returnValueLine || ret.line,
2479
+ returnValueColumn || ret.column,
2480
+ {
2481
+ id: returnValueId,
2482
+ object,
2483
+ property,
2484
+ computed,
2485
+ operator
2486
+ }
2487
+ );
2488
+
2489
+ this._bufferNode(expressionNode);
2490
+ sourceNodeId = returnValueId;
2491
+
2492
+ // Buffer DERIVES_FROM edges based on expression type
2493
+ // Helper function to find source variable or parameter
2494
+ const findSource = (name: string): string | null => {
2495
+ const variable = variableDeclarations.find(v =>
2496
+ v.name === name && v.file === file
2497
+ );
2498
+ if (variable) return variable.id;
2499
+
2500
+ const param = parameters.find(p =>
2501
+ p.name === name && p.file === file
2502
+ );
2503
+ if (param) return param.id;
2504
+
2505
+ return null;
2506
+ };
2507
+
2508
+ // MemberExpression: derives from the object
2509
+ if (expressionType === 'MemberExpression' && objectSourceName) {
2510
+ const sourceId = findSource(objectSourceName);
2511
+ if (sourceId) {
2512
+ this._bufferEdge({
2513
+ type: 'DERIVES_FROM',
2514
+ src: returnValueId,
2515
+ dst: sourceId
2516
+ });
2517
+ }
2518
+ }
2519
+
2520
+ // BinaryExpression / LogicalExpression: derives from left and right operands
2521
+ if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
2522
+ if (leftSourceName) {
2523
+ const sourceId = findSource(leftSourceName);
2524
+ if (sourceId) {
2525
+ this._bufferEdge({
2526
+ type: 'DERIVES_FROM',
2527
+ src: returnValueId,
2528
+ dst: sourceId
2529
+ });
2530
+ }
2531
+ }
2532
+ if (rightSourceName) {
2533
+ const sourceId = findSource(rightSourceName);
2534
+ if (sourceId) {
2535
+ this._bufferEdge({
2536
+ type: 'DERIVES_FROM',
2537
+ src: returnValueId,
2538
+ dst: sourceId
2539
+ });
2540
+ }
2541
+ }
2542
+ }
2543
+
2544
+ // ConditionalExpression: derives from consequent and alternate
2545
+ if (expressionType === 'ConditionalExpression') {
2546
+ if (consequentSourceName) {
2547
+ const sourceId = findSource(consequentSourceName);
2548
+ if (sourceId) {
2549
+ this._bufferEdge({
2550
+ type: 'DERIVES_FROM',
2551
+ src: returnValueId,
2552
+ dst: sourceId
2553
+ });
2554
+ }
2555
+ }
2556
+ if (alternateSourceName) {
2557
+ const sourceId = findSource(alternateSourceName);
2558
+ if (sourceId) {
2559
+ this._bufferEdge({
2560
+ type: 'DERIVES_FROM',
2561
+ src: returnValueId,
2562
+ dst: sourceId
2563
+ });
2564
+ }
2565
+ }
2566
+ }
2567
+
2568
+ // UnaryExpression: derives from the argument
2569
+ if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
2570
+ const sourceId = findSource(unaryArgSourceName);
2571
+ if (sourceId) {
2572
+ this._bufferEdge({
2573
+ type: 'DERIVES_FROM',
2574
+ src: returnValueId,
2575
+ dst: sourceId
2576
+ });
2577
+ }
2578
+ }
2579
+
2580
+ // TemplateLiteral: derives from all embedded expressions
2581
+ if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
2582
+ for (const sourceName of expressionSourceNames) {
2583
+ const sourceId = findSource(sourceName);
2584
+ if (sourceId) {
2585
+ this._bufferEdge({
2586
+ type: 'DERIVES_FROM',
2587
+ src: returnValueId,
2588
+ dst: sourceId
2589
+ });
2590
+ }
2591
+ }
2592
+ }
2593
+
2594
+ break;
2595
+ }
2596
+ }
2597
+
2598
+ // Create RETURNS edge if we found a source node
2599
+ if (sourceNodeId && parentFunctionId) {
2600
+ this._bufferEdge({
2601
+ type: 'RETURNS',
2602
+ src: sourceNodeId,
2603
+ dst: parentFunctionId
2604
+ });
2605
+ }
2606
+ }
2607
+ }
2608
+
2609
+ /**
2610
+ * Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
2611
+ *
2612
+ * Handles two target types:
2613
+ * - IDENTIFIER: Simple variable (i++, --count)
2614
+ * - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
2615
+ *
2616
+ * Creates:
2617
+ * - UPDATE_EXPRESSION node with operator and target metadata
2618
+ * - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
2619
+ * - READS_FROM self-loop: target -> target (reads current value before update)
2620
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
2621
+ *
2622
+ * REG-288: Initial implementation for IDENTIFIER targets
2623
+ * REG-312: Extended for MEMBER_EXPRESSION targets
2624
+ */
2625
+ private bufferUpdateExpressionEdges(
2626
+ updateExpressions: UpdateExpressionInfo[],
2627
+ variableDeclarations: VariableDeclarationInfo[],
2628
+ parameters: ParameterInfo[],
2629
+ classDeclarations: ClassDeclarationInfo[]
2630
+ ): void {
2631
+ // Build lookup caches: O(n) instead of O(n*m)
2632
+ const varLookup = new Map<string, VariableDeclarationInfo>();
2633
+ for (const v of variableDeclarations) {
2634
+ varLookup.set(`${v.file}:${v.name}`, v);
2635
+ }
2636
+
2637
+ const paramLookup = new Map<string, ParameterInfo>();
2638
+ for (const p of parameters) {
2639
+ paramLookup.set(`${p.file}:${p.name}`, p);
2640
+ }
2641
+
2642
+ for (const update of updateExpressions) {
2643
+ if (update.targetType === 'IDENTIFIER') {
2644
+ // REG-288: Simple identifier (i++, --count)
2645
+ this.bufferIdentifierUpdate(update, varLookup, paramLookup);
2646
+ } else if (update.targetType === 'MEMBER_EXPRESSION') {
2647
+ // REG-312: Member expression (obj.prop++, arr[i]++)
2648
+ this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
2649
+ }
2650
+ }
2651
+ }
2652
+
2653
+ /**
2654
+ * Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
2655
+ * REG-288: Original implementation extracted for clarity
2656
+ */
2657
+ private bufferIdentifierUpdate(
2658
+ update: UpdateExpressionInfo,
2659
+ varLookup: Map<string, VariableDeclarationInfo>,
2660
+ paramLookup: Map<string, ParameterInfo>
2661
+ ): void {
2662
+ const {
2663
+ variableName,
2664
+ operator,
2665
+ prefix,
2666
+ file,
2667
+ line,
2668
+ column,
2669
+ parentScopeId
2670
+ } = update;
2671
+
2672
+ if (!variableName) return;
2673
+
2674
+ // Find target variable node
2675
+ const targetVar = varLookup.get(`${file}:${variableName}`);
2676
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
2677
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
2678
+
2679
+ if (!targetNodeId) {
2680
+ // Variable not found - could be module-level or external reference
2681
+ return;
2682
+ }
2683
+
2684
+ // Create UPDATE_EXPRESSION node
2685
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2686
+
2687
+ this._bufferNode({
2688
+ type: 'UPDATE_EXPRESSION',
2689
+ id: updateId,
2690
+ name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
2691
+ targetType: 'IDENTIFIER',
2692
+ operator,
2693
+ prefix,
2694
+ variableName,
2695
+ file,
2696
+ line,
2697
+ column
2698
+ } as GraphNode);
2699
+
2700
+ // Create READS_FROM self-loop
2701
+ this._bufferEdge({
2702
+ type: 'READS_FROM',
2703
+ src: targetNodeId,
2704
+ dst: targetNodeId
2705
+ });
2706
+
2707
+ // Create MODIFIES edge
2708
+ this._bufferEdge({
2709
+ type: 'MODIFIES',
2710
+ src: updateId,
2711
+ dst: targetNodeId
2712
+ });
2713
+
2714
+ // Create CONTAINS edge
2715
+ if (parentScopeId) {
2716
+ this._bufferEdge({
2717
+ type: 'CONTAINS',
2718
+ src: parentScopeId,
2719
+ dst: updateId
2720
+ });
2721
+ }
2722
+ }
2723
+
2724
+ /**
2725
+ * Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
2726
+ * REG-312: New implementation for member expression targets
2727
+ *
2728
+ * Creates:
2729
+ * - UPDATE_EXPRESSION node with member expression metadata
2730
+ * - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
2731
+ * - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
2732
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
2733
+ */
2734
+ private bufferMemberExpressionUpdate(
2735
+ update: UpdateExpressionInfo,
2736
+ varLookup: Map<string, VariableDeclarationInfo>,
2737
+ paramLookup: Map<string, ParameterInfo>,
2738
+ classDeclarations: ClassDeclarationInfo[]
2739
+ ): void {
2740
+ const {
2741
+ objectName,
2742
+ propertyName,
2743
+ mutationType,
2744
+ computedPropertyVar,
2745
+ enclosingClassName,
2746
+ operator,
2747
+ prefix,
2748
+ file,
2749
+ line,
2750
+ column,
2751
+ parentScopeId
2752
+ } = update;
2753
+
2754
+ if (!objectName || !propertyName) return;
2755
+
2756
+ // Find target object node
2757
+ let objectNodeId: string | null = null;
2758
+
2759
+ if (objectName !== 'this') {
2760
+ // Regular object: obj.prop++, arr[i]++
2761
+ const targetVar = varLookup.get(`${file}:${objectName}`);
2762
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
2763
+ objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
2764
+ } else {
2765
+ // this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
2766
+ if (!enclosingClassName) return;
2767
+
2768
+ const fileBasename = basename(file);
2769
+ const classDecl = classDeclarations.find(c =>
2770
+ c.name === enclosingClassName && c.file === fileBasename
2771
+ );
2772
+ objectNodeId = classDecl?.id ?? null;
2773
+ }
2774
+
2775
+ if (!objectNodeId) {
2776
+ // Object not found - external reference or scope issue
2777
+ return;
2778
+ }
2779
+
2780
+ // Create UPDATE_EXPRESSION node
2781
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2782
+
2783
+ // Display name: "obj.prop++" or "this.count++" or "arr[i]++"
2784
+ const displayName = (() => {
2785
+ const opStr = prefix ? operator : '';
2786
+ const postOpStr = prefix ? '' : operator;
2787
+
2788
+ if (objectName === 'this') {
2789
+ return `${opStr}this.${propertyName}${postOpStr}`;
2790
+ }
2791
+ if (mutationType === 'computed') {
2792
+ const computedPart = computedPropertyVar || '?';
2793
+ return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
2794
+ }
2795
+ return `${opStr}${objectName}.${propertyName}${postOpStr}`;
2796
+ })();
2797
+
2798
+ this._bufferNode({
2799
+ type: 'UPDATE_EXPRESSION',
2800
+ id: updateId,
2801
+ name: displayName,
2802
+ targetType: 'MEMBER_EXPRESSION',
2803
+ operator,
2804
+ prefix,
2805
+ objectName,
2806
+ propertyName,
2807
+ mutationType,
2808
+ computedPropertyVar,
2809
+ enclosingClassName,
2810
+ file,
2811
+ line,
2812
+ column
2813
+ } as GraphNode);
2814
+
2815
+ // Create READS_FROM self-loop (object reads from itself)
2816
+ this._bufferEdge({
2817
+ type: 'READS_FROM',
2818
+ src: objectNodeId,
2819
+ dst: objectNodeId
2820
+ });
2821
+
2822
+ // Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
2823
+ this._bufferEdge({
2824
+ type: 'MODIFIES',
2825
+ src: updateId,
2826
+ dst: objectNodeId
2827
+ });
2828
+
2829
+ // Create CONTAINS edge
2830
+ if (parentScopeId) {
2831
+ this._bufferEdge({
2832
+ type: 'CONTAINS',
2833
+ src: parentScopeId,
2834
+ dst: updateId
2835
+ });
2836
+ }
2837
+ }
2838
+
2839
+ /**
2840
+ * Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
2841
+ *
2842
+ * Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
2843
+ * This enables traceValues to follow Promise data flow:
2844
+ *
2845
+ * Example:
2846
+ * ```
2847
+ * const result = new Promise((resolve) => {
2848
+ * resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
2849
+ * });
2850
+ * ```
2851
+ *
2852
+ * The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
2853
+ * data flows FROM resolve(value) TO the Promise result.
2854
+ */
2855
+ private bufferPromiseResolutionEdges(promiseResolutions: PromiseResolutionInfo[]): void {
2856
+ for (const resolution of promiseResolutions) {
2857
+ this._bufferEdge({
2858
+ type: 'RESOLVES_TO',
2859
+ src: resolution.callId,
2860
+ dst: resolution.constructorCallId,
2861
+ metadata: {
2862
+ isReject: resolution.isReject
2863
+ }
2864
+ });
2865
+ }
2866
+ }
2867
+
2868
+ /**
2869
+ * Buffer OBJECT_LITERAL nodes to the graph.
2870
+ * These are object literals passed as function arguments or nested in other literals.
2871
+ */
2872
+ private bufferObjectLiteralNodes(objectLiterals: ObjectLiteralInfo[]): void {
2873
+ for (const obj of objectLiterals) {
2874
+ this._bufferNode({
2875
+ id: obj.id,
2876
+ type: obj.type,
2877
+ name: '<object>',
2878
+ file: obj.file,
2879
+ line: obj.line,
2880
+ column: obj.column,
2881
+ parentCallId: obj.parentCallId,
2882
+ argIndex: obj.argIndex
2883
+ } as GraphNode);
2884
+ }
2885
+ }
2886
+
2887
+ /**
2888
+ * Buffer ARRAY_LITERAL nodes to the graph.
2889
+ * These are array literals passed as function arguments or nested in other literals.
2890
+ */
2891
+ private bufferArrayLiteralNodes(arrayLiterals: ArrayLiteralInfo[]): void {
2892
+ for (const arr of arrayLiterals) {
2893
+ this._bufferNode({
2894
+ id: arr.id,
2895
+ type: arr.type,
2896
+ name: '<array>',
2897
+ file: arr.file,
2898
+ line: arr.line,
2899
+ column: arr.column,
2900
+ parentCallId: arr.parentCallId,
2901
+ argIndex: arr.argIndex
2902
+ } as GraphNode);
2903
+ }
2904
+ }
2905
+
2906
+ /**
2907
+ * Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
2908
+ * Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
2909
+ *
2910
+ * REG-329: Adds scope-aware variable resolution for VARIABLE property values.
2911
+ * Uses the same resolveVariableInScope infrastructure as mutation handlers.
2912
+ */
2913
+ private bufferObjectPropertyEdges(
2914
+ objectProperties: ObjectPropertyInfo[],
2915
+ variableDeclarations: VariableDeclarationInfo[],
2916
+ parameters: ParameterInfo[]
2917
+ ): void {
2918
+ for (const prop of objectProperties) {
2919
+ // REG-329: Handle VARIABLE value types with scope resolution
2920
+ if (prop.valueType === 'VARIABLE' && prop.valueName) {
2921
+ const scopePath = prop.valueScopePath ?? [];
2922
+ const file = prop.file;
2923
+
2924
+ // Resolve variable using scope chain
2925
+ const resolvedVar = this.resolveVariableInScope(
2926
+ prop.valueName, scopePath, file, variableDeclarations
2927
+ );
2928
+ const resolvedParam = !resolvedVar
2929
+ ? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
2930
+ : null;
2931
+
2932
+ const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
2933
+
2934
+ if (resolvedNodeId) {
2935
+ this._bufferEdge({
2936
+ type: 'HAS_PROPERTY',
2937
+ src: prop.objectId,
2938
+ dst: resolvedNodeId,
2939
+ propertyName: prop.propertyName
2940
+ });
2941
+ }
2942
+ continue;
2943
+ }
2944
+
2945
+ // Existing logic for non-VARIABLE types
2946
+ if (prop.valueNodeId) {
2947
+ this._bufferEdge({
2948
+ type: 'HAS_PROPERTY',
2949
+ src: prop.objectId,
2950
+ dst: prop.valueNodeId,
2951
+ propertyName: prop.propertyName
2952
+ });
2953
+ }
2954
+ }
2955
+ }
2956
+
2957
+ /**
2958
+ * Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
2959
+ */
2960
+ private async createClassAssignmentEdges(variableAssignments: VariableAssignmentInfo[], graph: GraphBackend): Promise<number> {
2961
+ let edgesCreated = 0;
2962
+
2963
+ for (const assignment of variableAssignments) {
2964
+ const { variableId, sourceType, className } = assignment;
2965
+
2966
+ if (sourceType === 'CLASS' && className) {
2967
+ const parts = variableId.split('#');
2968
+ const file = parts.length >= 3 ? parts[2] : null;
2969
+
2970
+ let classNode: { id: string; name: string; file?: string } | null = null;
2971
+ for await (const node of graph.queryNodes({ type: 'CLASS' })) {
2972
+ if (node.name === className && (!file || node.file === file)) {
2973
+ classNode = node as { id: string; name: string; file?: string };
2974
+ break;
1364
2975
  }
1365
2976
  }
2977
+
2978
+ if (classNode) {
2979
+ await graph.addEdge({
2980
+ type: 'ASSIGNED_FROM',
2981
+ src: variableId,
2982
+ dst: classNode.id
2983
+ });
2984
+ edgesCreated++;
2985
+ }
1366
2986
  }
1367
2987
  }
1368
2988