@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
@@ -2,7 +2,14 @@
2
2
  * GraphBuilder - создание узлов и рёбер графа из собранных AST данных
3
3
  * OPTIMIZED: Uses batched writes to reduce FFI overhead
4
4
  */
5
- import { dirname, resolve } from 'path';
5
+ import { basename } from 'path';
6
+ import { ImportNode } from '../../../core/nodes/ImportNode.js';
7
+ import { InterfaceNode } from '../../../core/nodes/InterfaceNode.js';
8
+ import { EnumNode } from '../../../core/nodes/EnumNode.js';
9
+ import { DecoratorNode } from '../../../core/nodes/DecoratorNode.js';
10
+ import { NetworkRequestNode } from '../../../core/nodes/NetworkRequestNode.js';
11
+ import { NodeFactory } from '../../../core/NodeFactory.js';
12
+ import { computeSemanticId, parseSemanticId } from '../../../core/SemanticId.js';
6
13
  export class GraphBuilder {
7
14
  // Track singleton nodes to avoid duplicates (net:stdio, net:request, etc.)
8
15
  _createdSingletons = new Set();
@@ -51,9 +58,29 @@ export class GraphBuilder {
51
58
  * Создаёт ноды и рёбра в графе (BATCHED VERSION)
52
59
  */
53
60
  async build(module, graph, projectPath, data) {
54
- const { functions, parameters = [], scopes, variableDeclarations, callSites, methodCalls = [], eventListeners = [], classInstantiations = [], classDeclarations = [], methodCallbacks = [], callArguments = [], imports = [], exports = [], httpRequests = [], literals = [], variableAssignments = [],
61
+ const { functions, parameters = [], scopes,
62
+ // Branching
63
+ branches = [], cases = [],
64
+ // Control flow (loops)
65
+ loops = [],
66
+ // Control flow (try/catch/finally) - Phase 4
67
+ tryBlocks = [], catchBlocks = [], finallyBlocks = [], variableDeclarations, callSites, methodCalls = [], eventListeners = [], classInstantiations = [], constructorCalls = [], classDeclarations = [], methodCallbacks = [], callArguments = [], imports = [], exports = [], httpRequests = [], literals = [], variableAssignments = [],
55
68
  // TypeScript-specific collections
56
- interfaces = [], typeAliases = [], enums = [], decorators = [] } = data;
69
+ interfaces = [], typeAliases = [], enums = [], decorators = [],
70
+ // Array mutation tracking for FLOWS_INTO edges
71
+ arrayMutations = [],
72
+ // Object mutation tracking for FLOWS_INTO edges
73
+ objectMutations = [],
74
+ // Variable reassignment tracking for FLOWS_INTO edges (REG-290)
75
+ variableReassignments = [],
76
+ // Return statement tracking for RETURNS edges
77
+ returnStatements = [],
78
+ // Update expression tracking for MODIFIES edges (REG-288, REG-312)
79
+ updateExpressions = [],
80
+ // Promise resolution tracking for RESOLVES_TO edges (REG-334)
81
+ promiseResolutions = [],
82
+ // Object/Array literal tracking
83
+ objectLiterals = [], objectProperties = [], arrayLiterals = [] } = data;
57
84
  // Reset buffers for this build
58
85
  this._nodeBuffer = [];
59
86
  this._edgeBuffer = [];
@@ -67,10 +94,40 @@ export class GraphBuilder {
67
94
  const { parentFunctionId, parentScopeId, capturesFrom, modifies, ...scopeData } = scope;
68
95
  this._bufferNode(scopeData);
69
96
  }
70
- // 3. Buffer variables
97
+ // 2.5. Buffer BRANCH nodes
98
+ // Note: parentScopeId is kept on node for query support (REG-275 test requirement)
99
+ for (const branch of branches) {
100
+ const { discriminantExpressionId, discriminantExpressionType, discriminantLine, discriminantColumn, ...branchData } = branch;
101
+ this._bufferNode(branchData);
102
+ }
103
+ // 2.6. Buffer CASE nodes
104
+ for (const caseInfo of cases) {
105
+ const { parentBranchId, ...caseData } = caseInfo;
106
+ this._bufferNode(caseData);
107
+ }
108
+ // 2.7. Buffer LOOP nodes
109
+ for (const loop of loops) {
110
+ // Exclude metadata used for edge creation (not stored on node)
111
+ const { iteratesOverName, iteratesOverLine, iteratesOverColumn, conditionExpressionId, conditionExpressionType, conditionLine, conditionColumn, ...loopData } = loop;
112
+ this._bufferNode(loopData);
113
+ }
114
+ // 2.8. Buffer TRY_BLOCK nodes (Phase 4)
115
+ for (const tryBlock of tryBlocks) {
116
+ this._bufferNode(tryBlock);
117
+ }
118
+ // 2.9. Buffer CATCH_BLOCK nodes (Phase 4)
119
+ for (const catchBlock of catchBlocks) {
120
+ const { parentTryBlockId, ...catchData } = catchBlock;
121
+ this._bufferNode(catchData);
122
+ }
123
+ // 2.10. Buffer FINALLY_BLOCK nodes (Phase 4)
124
+ for (const finallyBlock of finallyBlocks) {
125
+ const { parentTryBlockId, ...finallyData } = finallyBlock;
126
+ this._bufferNode(finallyData);
127
+ }
128
+ // 3. Buffer variables (keep parentScopeId on node for queries)
71
129
  for (const varDecl of variableDeclarations) {
72
- const { parentScopeId, ...varData } = varDecl;
73
- this._bufferNode(varData);
130
+ this._bufferNode(varDecl);
74
131
  }
75
132
  // 3.5. Buffer PARAMETER nodes and HAS_PARAMETER edges
76
133
  for (const param of parameters) {
@@ -86,21 +143,49 @@ export class GraphBuilder {
86
143
  });
87
144
  }
88
145
  }
89
- // 4. Buffer CALL_SITE
146
+ // 4. Buffer CALL_SITE (keep parentScopeId on node for queries)
90
147
  for (const callSite of callSites) {
91
- const { parentScopeId, targetFunctionName, ...callData } = callSite;
148
+ const { targetFunctionName, ...callData } = callSite;
92
149
  this._bufferNode(callData);
93
150
  }
151
+ // 4.5 Buffer CONSTRUCTOR_CALL nodes
152
+ for (const constructorCall of constructorCalls) {
153
+ this._bufferNode({
154
+ id: constructorCall.id,
155
+ type: constructorCall.type,
156
+ name: `new ${constructorCall.className}()`,
157
+ className: constructorCall.className,
158
+ isBuiltin: constructorCall.isBuiltin,
159
+ file: constructorCall.file,
160
+ line: constructorCall.line,
161
+ column: constructorCall.column
162
+ });
163
+ }
94
164
  // 5. Buffer edges for functions
95
165
  this.bufferFunctionEdges(module, functions);
96
166
  // 6. Buffer edges for SCOPE
97
167
  this.bufferScopeEdges(scopes, variableDeclarations);
168
+ // 6.3. Buffer edges for LOOP (HAS_BODY, ITERATES_OVER, CONTAINS)
169
+ this.bufferLoopEdges(loops, scopes, variableDeclarations, parameters);
170
+ // 6.35. Buffer HAS_CONDITION edges for LOOP (REG-280)
171
+ this.bufferLoopConditionEdges(loops, callSites);
172
+ // 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
173
+ this.bufferLoopConditionExpressions(loops);
174
+ // 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
175
+ // Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
176
+ this.bufferBranchEdges(branches, callSites, scopes);
177
+ // 6.6. Buffer edges for CASE
178
+ this.bufferCaseEdges(cases);
179
+ // 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
180
+ this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
181
+ // 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
182
+ this.bufferDiscriminantExpressions(branches, callSites);
98
183
  // 7. Buffer edges for variables
99
184
  this.bufferVariableEdges(variableDeclarations);
100
185
  // 8. Buffer edges for CALL_SITE
101
186
  this.bufferCallSiteEdges(callSites, functions);
102
- // 9. Buffer METHOD_CALL nodes and CONTAINS edges
103
- this.bufferMethodCalls(methodCalls);
187
+ // 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
188
+ this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
104
189
  // 10. Buffer net:stdio and WRITES_TO edges for console.log/error
105
190
  this.bufferStdioNodes(methodCalls);
106
191
  // 11. Buffer CLASS nodes for class declarations and CONTAINS edges
@@ -119,6 +204,13 @@ export class GraphBuilder {
119
204
  this.bufferHttpRequests(httpRequests, functions);
120
205
  // 18. Buffer LITERAL nodes
121
206
  this.bufferLiterals(literals);
207
+ // 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
208
+ this.bufferObjectLiteralNodes(objectLiterals);
209
+ // 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
210
+ this.bufferArrayLiteralNodes(arrayLiterals);
211
+ // 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
212
+ // REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
213
+ this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
122
214
  // 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
123
215
  this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
124
216
  // 20. Buffer PASSES_ARGUMENT edges (CALL -> argument)
@@ -133,14 +225,25 @@ export class GraphBuilder {
133
225
  this.bufferDecoratorNodes(decorators);
134
226
  // 25. Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
135
227
  this.bufferImplementsEdges(classDeclarations, interfaces);
228
+ // 26. Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
229
+ this.bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters);
230
+ // 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
231
+ // REG-152: Now includes classDeclarations for this.prop = value patterns
232
+ this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
233
+ // 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
234
+ this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
235
+ // 29. Buffer RETURNS edges for return statements
236
+ this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
237
+ // 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
238
+ this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
239
+ // 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
240
+ this.bufferPromiseResolutionEdges(promiseResolutions);
136
241
  // FLUSH: Write all nodes first, then edges in single batch calls
137
242
  const nodesCreated = await this._flushNodes(graph);
138
243
  const edgesCreated = await this._flushEdges(graph);
139
- // Handle async operations that need graph queries (IMPORTS_FROM edges)
140
- const importExportEdges = await this.createImportExportEdges(module, imports, exports, graph, projectPath);
141
244
  // Handle async operations for ASSIGNED_FROM with CLASS lookups
142
245
  const classAssignmentEdges = await this.createClassAssignmentEdges(variableAssignments, graph);
143
- return { nodes: nodesCreated, edges: edgesCreated + importExportEdges + classAssignmentEdges };
246
+ return { nodes: nodesCreated, edges: edgesCreated + classAssignmentEdges };
144
247
  }
145
248
  // ============= BUFFERED METHODS (synchronous, no awaits) =============
146
249
  bufferFunctionEdges(module, functions) {
@@ -206,6 +309,371 @@ export class GraphBuilder {
206
309
  }
207
310
  }
208
311
  }
312
+ /**
313
+ * Buffer LOOP edges (CONTAINS, HAS_BODY, ITERATES_OVER)
314
+ *
315
+ * Creates edges for:
316
+ * - Parent -> CONTAINS -> LOOP
317
+ * - LOOP -> HAS_BODY -> body SCOPE
318
+ * - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
319
+ *
320
+ * Scope-aware variable lookup for ITERATES_OVER:
321
+ * For for-of/for-in, finds the iterated variable preferring:
322
+ * 1. Variables declared before the loop on same or earlier line (closest first)
323
+ * 2. Parameters (function arguments)
324
+ */
325
+ bufferLoopEdges(loops, scopes, variableDeclarations, parameters) {
326
+ for (const loop of loops) {
327
+ // Parent -> CONTAINS -> LOOP
328
+ if (loop.parentScopeId) {
329
+ this._bufferEdge({
330
+ type: 'CONTAINS',
331
+ src: loop.parentScopeId,
332
+ dst: loop.id
333
+ });
334
+ }
335
+ // LOOP -> HAS_BODY -> body SCOPE
336
+ // Find the body scope by matching parentScopeId to loop.id
337
+ const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
338
+ if (bodyScope) {
339
+ this._bufferEdge({
340
+ type: 'HAS_BODY',
341
+ src: loop.id,
342
+ dst: bodyScope.id
343
+ });
344
+ }
345
+ // LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
346
+ if (loop.iteratesOverName && (loop.loopType === 'for-in' || loop.loopType === 'for-of')) {
347
+ // For MemberExpression iterables (obj.items), extract base object
348
+ const iterableName = loop.iteratesOverName.includes('.')
349
+ ? loop.iteratesOverName.split('.')[0]
350
+ : loop.iteratesOverName;
351
+ // Scope-aware lookup: prefer parameters over variables
352
+ // Parameters are function-local and shadow outer variables
353
+ const param = parameters.find(p => p.name === iterableName && p.file === loop.file);
354
+ // Determine iteration type: for-in iterates keys, for-of iterates values
355
+ const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
356
+ if (param) {
357
+ // Parameter found - most local binding
358
+ this._bufferEdge({
359
+ type: 'ITERATES_OVER',
360
+ src: loop.id,
361
+ dst: param.id,
362
+ metadata: { iterates }
363
+ });
364
+ }
365
+ else {
366
+ // Find variable by name and line proximity (scope-aware heuristic)
367
+ // Prefer variables declared before the loop in the same file
368
+ const candidateVars = variableDeclarations.filter(v => v.name === iterableName &&
369
+ v.file === loop.file &&
370
+ (v.line ?? 0) <= loop.line // Declared before or on loop line
371
+ );
372
+ // Sort by line descending to find closest declaration
373
+ candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
374
+ if (candidateVars.length > 0) {
375
+ this._bufferEdge({
376
+ type: 'ITERATES_OVER',
377
+ src: loop.id,
378
+ dst: candidateVars[0].id,
379
+ metadata: { iterates }
380
+ });
381
+ }
382
+ }
383
+ }
384
+ // REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
385
+ if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
386
+ // Find the variable declared in the init on this line
387
+ const initVar = variableDeclarations.find(v => v.name === loop.initVariableName &&
388
+ v.file === loop.file &&
389
+ v.line === loop.initLine);
390
+ if (initVar) {
391
+ this._bufferEdge({
392
+ type: 'HAS_INIT',
393
+ src: loop.id,
394
+ dst: initVar.id
395
+ });
396
+ }
397
+ }
398
+ // REG-282: LOOP -> HAS_CONDITION -> EXPRESSION (i < 10 or condition for while/do-while)
399
+ if (loop.testExpressionId && loop.testExpressionType) {
400
+ // Create EXPRESSION node for the test
401
+ this._bufferNode({
402
+ id: loop.testExpressionId,
403
+ type: 'EXPRESSION',
404
+ name: loop.testExpressionType,
405
+ file: loop.file,
406
+ line: loop.testLine,
407
+ column: loop.testColumn,
408
+ expressionType: loop.testExpressionType
409
+ });
410
+ this._bufferEdge({
411
+ type: 'HAS_CONDITION',
412
+ src: loop.id,
413
+ dst: loop.testExpressionId
414
+ });
415
+ }
416
+ // REG-282: LOOP (for) -> HAS_UPDATE -> EXPRESSION (i++)
417
+ if (loop.loopType === 'for' && loop.updateExpressionId && loop.updateExpressionType) {
418
+ // Create EXPRESSION node for the update
419
+ this._bufferNode({
420
+ id: loop.updateExpressionId,
421
+ type: 'EXPRESSION',
422
+ name: loop.updateExpressionType,
423
+ file: loop.file,
424
+ line: loop.updateLine,
425
+ column: loop.updateColumn,
426
+ expressionType: loop.updateExpressionType
427
+ });
428
+ this._bufferEdge({
429
+ type: 'HAS_UPDATE',
430
+ src: loop.id,
431
+ dst: loop.updateExpressionId
432
+ });
433
+ }
434
+ }
435
+ }
436
+ /**
437
+ * Buffer HAS_CONDITION edges from LOOP to condition EXPRESSION/CALL nodes.
438
+ * Also creates EXPRESSION nodes for non-CallExpression conditions.
439
+ *
440
+ * REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
441
+ * condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
442
+ *
443
+ * For CallExpression conditions, links to existing CALL_SITE node by coordinates.
444
+ */
445
+ bufferLoopConditionEdges(loops, callSites) {
446
+ for (const loop of loops) {
447
+ // Skip for-in/for-of loops - they don't have test expressions
448
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
449
+ continue;
450
+ }
451
+ // Skip if no condition (e.g., infinite for loop: for(;;))
452
+ if (!loop.conditionExpressionId) {
453
+ continue;
454
+ }
455
+ // LOOP -> HAS_CONDITION -> EXPRESSION/CALL
456
+ let targetId = loop.conditionExpressionId;
457
+ // For CallExpression conditions, look up the actual CALL_SITE by coordinates
458
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
459
+ if (loop.conditionExpressionType === 'CallExpression' && loop.conditionLine && loop.conditionColumn !== undefined) {
460
+ const callSite = callSites.find(cs => cs.file === loop.file &&
461
+ cs.line === loop.conditionLine &&
462
+ cs.column === loop.conditionColumn);
463
+ if (callSite) {
464
+ targetId = callSite.id;
465
+ }
466
+ }
467
+ this._bufferEdge({
468
+ type: 'HAS_CONDITION',
469
+ src: loop.id,
470
+ dst: targetId
471
+ });
472
+ }
473
+ }
474
+ /**
475
+ * Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
476
+ * Similar to bufferDiscriminantExpressions but for loops.
477
+ *
478
+ * REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
479
+ * CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
480
+ */
481
+ bufferLoopConditionExpressions(loops) {
482
+ for (const loop of loops) {
483
+ // Skip for-in/for-of loops - they don't have test expressions
484
+ if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
485
+ continue;
486
+ }
487
+ if (loop.conditionExpressionId && loop.conditionExpressionType) {
488
+ // Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
489
+ if (loop.conditionExpressionType === 'CallExpression') {
490
+ continue;
491
+ }
492
+ // Only create if it looks like an EXPRESSION ID
493
+ if (loop.conditionExpressionId.includes(':EXPRESSION:')) {
494
+ this._bufferNode({
495
+ id: loop.conditionExpressionId,
496
+ type: 'EXPRESSION',
497
+ name: loop.conditionExpressionType,
498
+ file: loop.file,
499
+ line: loop.conditionLine,
500
+ column: loop.conditionColumn,
501
+ expressionType: loop.conditionExpressionType
502
+ });
503
+ }
504
+ }
505
+ }
506
+ }
507
+ /**
508
+ * Buffer BRANCH edges (CONTAINS, HAS_CONDITION, HAS_CONSEQUENT, HAS_ALTERNATE)
509
+ *
510
+ * REG-275: For CallExpression discriminants (switch(getType())), looks up the
511
+ * actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
512
+ *
513
+ * Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
514
+ * pointing to the if-body and else-body SCOPEs.
515
+ */
516
+ bufferBranchEdges(branches, callSites, scopes) {
517
+ for (const branch of branches) {
518
+ // Parent SCOPE -> CONTAINS -> BRANCH
519
+ if (branch.parentScopeId) {
520
+ this._bufferEdge({
521
+ type: 'CONTAINS',
522
+ src: branch.parentScopeId,
523
+ dst: branch.id
524
+ });
525
+ }
526
+ // BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
527
+ if (branch.discriminantExpressionId) {
528
+ let targetId = branch.discriminantExpressionId;
529
+ // For CallExpression discriminants, look up the actual CALL_SITE by coordinates
530
+ // because CALL_SITE uses semantic IDs that don't match the generated ID
531
+ if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
532
+ const callSite = callSites.find(cs => cs.file === branch.file &&
533
+ cs.line === branch.discriminantLine &&
534
+ cs.column === branch.discriminantColumn);
535
+ if (callSite) {
536
+ targetId = callSite.id;
537
+ }
538
+ }
539
+ this._bufferEdge({
540
+ type: 'HAS_CONDITION',
541
+ src: branch.id,
542
+ dst: targetId
543
+ });
544
+ }
545
+ // Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
546
+ if (branch.branchType === 'if') {
547
+ // Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
548
+ const consequentScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'if_statement');
549
+ if (consequentScope) {
550
+ this._bufferEdge({
551
+ type: 'HAS_CONSEQUENT',
552
+ src: branch.id,
553
+ dst: consequentScope.id
554
+ });
555
+ }
556
+ // Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
557
+ const alternateScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'else_statement');
558
+ if (alternateScope) {
559
+ this._bufferEdge({
560
+ type: 'HAS_ALTERNATE',
561
+ src: branch.id,
562
+ dst: alternateScope.id
563
+ });
564
+ }
565
+ // For else-if chains: if this branch is the alternate of another branch
566
+ // This is handled differently - see below
567
+ }
568
+ // REG-287: For ternary branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges to expressions
569
+ if (branch.branchType === 'ternary') {
570
+ if (branch.consequentExpressionId) {
571
+ this._bufferEdge({
572
+ type: 'HAS_CONSEQUENT',
573
+ src: branch.id,
574
+ dst: branch.consequentExpressionId
575
+ });
576
+ }
577
+ if (branch.alternateExpressionId) {
578
+ this._bufferEdge({
579
+ type: 'HAS_ALTERNATE',
580
+ src: branch.id,
581
+ dst: branch.alternateExpressionId
582
+ });
583
+ }
584
+ }
585
+ // Phase 3: For else-if chains, create HAS_ALTERNATE from parent branch to this branch
586
+ if (branch.isAlternateOfBranchId) {
587
+ this._bufferEdge({
588
+ type: 'HAS_ALTERNATE',
589
+ src: branch.isAlternateOfBranchId,
590
+ dst: branch.id
591
+ });
592
+ }
593
+ }
594
+ }
595
+ /**
596
+ * Buffer CASE edges (HAS_CASE, HAS_DEFAULT)
597
+ */
598
+ bufferCaseEdges(cases) {
599
+ for (const caseInfo of cases) {
600
+ // BRANCH -> HAS_CASE or HAS_DEFAULT -> CASE
601
+ const edgeType = caseInfo.isDefault ? 'HAS_DEFAULT' : 'HAS_CASE';
602
+ this._bufferEdge({
603
+ type: edgeType,
604
+ src: caseInfo.parentBranchId,
605
+ dst: caseInfo.id
606
+ });
607
+ }
608
+ }
609
+ /**
610
+ * Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK nodes (Phase 4)
611
+ *
612
+ * Creates edges for:
613
+ * - Parent -> CONTAINS -> TRY_BLOCK
614
+ * - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
615
+ * - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
616
+ */
617
+ bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks) {
618
+ // Buffer TRY_BLOCK edges
619
+ for (const tryBlock of tryBlocks) {
620
+ // Parent -> CONTAINS -> TRY_BLOCK
621
+ if (tryBlock.parentScopeId) {
622
+ this._bufferEdge({
623
+ type: 'CONTAINS',
624
+ src: tryBlock.parentScopeId,
625
+ dst: tryBlock.id
626
+ });
627
+ }
628
+ }
629
+ // Buffer CATCH_BLOCK edges (HAS_CATCH from TRY_BLOCK)
630
+ for (const catchBlock of catchBlocks) {
631
+ // TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
632
+ this._bufferEdge({
633
+ type: 'HAS_CATCH',
634
+ src: catchBlock.parentTryBlockId,
635
+ dst: catchBlock.id
636
+ });
637
+ }
638
+ // Buffer FINALLY_BLOCK edges (HAS_FINALLY from TRY_BLOCK)
639
+ for (const finallyBlock of finallyBlocks) {
640
+ // TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
641
+ this._bufferEdge({
642
+ type: 'HAS_FINALLY',
643
+ src: finallyBlock.parentTryBlockId,
644
+ dst: finallyBlock.id
645
+ });
646
+ }
647
+ }
648
+ /**
649
+ * Buffer EXPRESSION nodes for switch discriminants
650
+ * Uses stored metadata directly instead of parsing from ID (Linus improvement)
651
+ *
652
+ * REG-275: For CallExpression discriminants, we don't create nodes here since
653
+ * bufferBranchEdges links to the existing CALL_SITE node by coordinates.
654
+ */
655
+ bufferDiscriminantExpressions(branches, callSites) {
656
+ for (const branch of branches) {
657
+ if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
658
+ // Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
659
+ if (branch.discriminantExpressionType === 'CallExpression') {
660
+ continue;
661
+ }
662
+ // Only create if it looks like an EXPRESSION ID
663
+ if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
664
+ this._bufferNode({
665
+ id: branch.discriminantExpressionId,
666
+ type: 'EXPRESSION',
667
+ name: branch.discriminantExpressionType,
668
+ file: branch.file,
669
+ line: branch.discriminantLine,
670
+ column: branch.discriminantColumn,
671
+ expressionType: branch.discriminantExpressionType
672
+ });
673
+ }
674
+ }
675
+ }
676
+ }
209
677
  bufferVariableEdges(variableDeclarations) {
210
678
  for (const varDecl of variableDeclarations) {
211
679
  const { parentScopeId, ...varData } = varDecl;
@@ -237,39 +705,61 @@ export class GraphBuilder {
237
705
  }
238
706
  }
239
707
  }
240
- bufferMethodCalls(methodCalls) {
708
+ bufferMethodCalls(methodCalls, variableDeclarations, parameters) {
241
709
  for (const methodCall of methodCalls) {
242
- const { parentScopeId, ...methodData } = methodCall;
243
- // Buffer METHOD_CALL node
244
- this._bufferNode(methodData);
710
+ // Keep parentScopeId on node for queries
711
+ this._bufferNode(methodCall);
245
712
  // SCOPE -> CONTAINS -> METHOD_CALL
246
713
  this._bufferEdge({
247
714
  type: 'CONTAINS',
248
- src: parentScopeId,
249
- dst: methodData.id
715
+ src: methodCall.parentScopeId,
716
+ dst: methodCall.id
250
717
  });
718
+ // REG-262: Create USES edge from METHOD_CALL to receiver variable
719
+ // Skip 'this' - it's not a variable node
720
+ if (methodCall.object && methodCall.object !== 'this') {
721
+ // Handle nested member expressions: obj.nested.method() -> use base 'obj'
722
+ const receiverName = methodCall.object.includes('.')
723
+ ? methodCall.object.split('.')[0]
724
+ : methodCall.object;
725
+ // Find receiver variable in current file
726
+ const receiverVar = variableDeclarations.find(v => v.name === receiverName && v.file === methodCall.file);
727
+ if (receiverVar) {
728
+ this._bufferEdge({
729
+ type: 'USES',
730
+ src: methodCall.id,
731
+ dst: receiverVar.id
732
+ });
733
+ }
734
+ else {
735
+ // Check parameters (function arguments)
736
+ const receiverParam = parameters.find(p => p.name === receiverName && p.file === methodCall.file);
737
+ if (receiverParam) {
738
+ this._bufferEdge({
739
+ type: 'USES',
740
+ src: methodCall.id,
741
+ dst: receiverParam.id
742
+ });
743
+ }
744
+ }
745
+ }
251
746
  }
252
747
  }
253
748
  bufferStdioNodes(methodCalls) {
254
749
  const consoleIOMethods = methodCalls.filter(mc => (mc.object === 'console' && (mc.method === 'log' || mc.method === 'error')));
255
750
  if (consoleIOMethods.length > 0) {
256
- const stdioId = 'net:stdio#__stdio__';
751
+ const stdioNode = NodeFactory.createExternalStdio();
257
752
  // Buffer net:stdio node only once (singleton)
258
- if (!this._createdSingletons.has(stdioId)) {
259
- this._bufferNode({
260
- id: stdioId,
261
- type: 'net:stdio',
262
- name: '__stdio__',
263
- description: 'Standard input/output stream'
264
- });
265
- this._createdSingletons.add(stdioId);
753
+ if (!this._createdSingletons.has(stdioNode.id)) {
754
+ this._bufferNode(stdioNode);
755
+ this._createdSingletons.add(stdioNode.id);
266
756
  }
267
757
  // Buffer WRITES_TO edges for console.log/error
268
758
  for (const methodCall of consoleIOMethods) {
269
759
  this._bufferEdge({
270
760
  type: 'WRITES_TO',
271
761
  src: methodCall.id,
272
- dst: stdioId
762
+ dst: stdioNode.id
273
763
  });
274
764
  }
275
765
  }
@@ -295,9 +785,13 @@ export class GraphBuilder {
295
785
  dst: methodId
296
786
  });
297
787
  }
298
- // If superClass, buffer DERIVES_FROM edge
788
+ // If superClass, buffer DERIVES_FROM edge with computed ID
299
789
  if (superClass) {
300
- const superClassId = `CLASS#${superClass}#${file}`;
790
+ // Compute superclass ID using semantic ID format
791
+ // Assume superclass is in same file at global scope (most common case)
792
+ // When superclass is in different file, edge will be dangling until that file analyzed
793
+ const globalContext = { file, scopePath: [] };
794
+ const superClassId = computeSemanticId('CLASS', superClass, globalContext);
301
795
  this._bufferEdge({
302
796
  type: 'DERIVES_FROM',
303
797
  src: id,
@@ -308,9 +802,11 @@ export class GraphBuilder {
308
802
  }
309
803
  bufferClassNodes(module, classInstantiations, classDeclarations) {
310
804
  // Create lookup map: className → declaration ID
805
+ // Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
806
+ const moduleBasename = basename(module.file);
311
807
  const declarationMap = new Map();
312
808
  for (const decl of classDeclarations) {
313
- if (decl.file === module.file) {
809
+ if (decl.file === moduleBasename) {
314
810
  declarationMap.set(decl.name, decl.id);
315
811
  }
316
812
  }
@@ -318,16 +814,12 @@ export class GraphBuilder {
318
814
  const { variableId, className, line } = instantiation;
319
815
  let classId = declarationMap.get(className);
320
816
  if (!classId) {
321
- // External class - buffer CLASS node
322
- classId = `${module.file}:CLASS:${className}:${line}`;
323
- this._bufferNode({
324
- id: classId,
325
- type: 'CLASS',
326
- name: className,
327
- file: module.file,
328
- line,
329
- isInstantiationRef: true
330
- });
817
+ // External class - compute semantic ID
818
+ // Use basename to match CLASS node format (scopeTracker uses basename)
819
+ // When class is in different file, edge will be dangling until that file analyzed
820
+ const globalContext = { file: moduleBasename, scopePath: [] };
821
+ classId = computeSemanticId('CLASS', className, globalContext);
822
+ // NO node creation - node will exist when class file analyzed
331
823
  }
332
824
  // Buffer INSTANCE_OF edge
333
825
  this._bufferEdge({
@@ -352,119 +844,136 @@ export class GraphBuilder {
352
844
  }
353
845
  bufferImportNodes(module, imports) {
354
846
  for (const imp of imports) {
355
- const { source, specifiers, line } = imp;
356
- for (const spec of specifiers) {
357
- const importType = spec.imported === 'default' ? 'default' :
358
- spec.imported === '*' ? 'namespace' : 'named';
359
- const importId = `${module.file}:IMPORT:${source}:${spec.local}:${line}`;
360
- this._bufferNode({
361
- id: importId,
362
- type: 'IMPORT',
363
- source: source,
364
- importType: importType,
365
- imported: spec.imported,
366
- local: spec.local,
367
- file: module.file,
368
- line: line
847
+ const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
848
+ // REG-273: Handle side-effect-only imports (no specifiers)
849
+ if (specifiers.length === 0) {
850
+ // Side-effect import: import './polyfill.js'
851
+ const importNode = ImportNode.create(source, // name = source (no local binding)
852
+ module.file, // file
853
+ line, // line (stored as field, not in ID)
854
+ column || 0, // column
855
+ source, // source module
856
+ {
857
+ imported: '*', // no specific export
858
+ local: source, // source becomes local
859
+ sideEffect: true // mark as side-effect import
369
860
  });
861
+ this._bufferNode(importNode);
370
862
  // MODULE -> CONTAINS -> IMPORT
371
863
  this._bufferEdge({
372
864
  type: 'CONTAINS',
373
865
  src: module.id,
374
- dst: importId
866
+ dst: importNode.id
375
867
  });
376
868
  // Create EXTERNAL_MODULE node for external modules
377
869
  const isRelative = source.startsWith('./') || source.startsWith('../');
378
870
  if (!isRelative) {
379
- const externalModuleId = `EXTERNAL_MODULE:${source}`;
380
- this._bufferNode({
381
- id: externalModuleId,
382
- type: 'EXTERNAL_MODULE',
383
- name: source,
384
- file: module.file,
385
- line: line
386
- });
871
+ const externalModule = NodeFactory.createExternalModule(source);
872
+ // Avoid duplicate EXTERNAL_MODULE nodes
873
+ if (!this._createdSingletons.has(externalModule.id)) {
874
+ this._bufferNode(externalModule);
875
+ this._createdSingletons.add(externalModule.id);
876
+ }
387
877
  this._bufferEdge({
388
878
  type: 'IMPORTS',
389
879
  src: module.id,
390
- dst: externalModuleId
880
+ dst: externalModule.id
391
881
  });
392
882
  }
393
883
  }
884
+ else {
885
+ // Regular imports with specifiers
886
+ for (const spec of specifiers) {
887
+ // Use ImportNode factory for proper semantic IDs and field population
888
+ const importNode = ImportNode.create(spec.local, // name = local binding
889
+ module.file, // file
890
+ line, // line (stored as field, not in ID)
891
+ column || 0, // column
892
+ source, // source module
893
+ {
894
+ imported: spec.imported,
895
+ local: spec.local,
896
+ sideEffect: false, // regular imports are not side-effects
897
+ // importType is auto-detected from imported field
898
+ // Dynamic import fields
899
+ isDynamic,
900
+ isResolvable,
901
+ dynamicPath
902
+ });
903
+ this._bufferNode(importNode);
904
+ // MODULE -> CONTAINS -> IMPORT
905
+ this._bufferEdge({
906
+ type: 'CONTAINS',
907
+ src: module.id,
908
+ dst: importNode.id
909
+ });
910
+ // Create EXTERNAL_MODULE node for external modules
911
+ const isRelative = source.startsWith('./') || source.startsWith('../');
912
+ if (!isRelative) {
913
+ const externalModule = NodeFactory.createExternalModule(source);
914
+ // Avoid duplicate EXTERNAL_MODULE nodes
915
+ if (!this._createdSingletons.has(externalModule.id)) {
916
+ this._bufferNode(externalModule);
917
+ this._createdSingletons.add(externalModule.id);
918
+ }
919
+ this._bufferEdge({
920
+ type: 'IMPORTS',
921
+ src: module.id,
922
+ dst: externalModule.id
923
+ });
924
+ }
925
+ }
926
+ }
394
927
  }
395
928
  }
396
929
  bufferExportNodes(module, exports) {
397
930
  for (const exp of exports) {
398
931
  const { type, line, name, specifiers, source } = exp;
399
932
  if (type === 'default') {
400
- const exportId = `${module.file}:EXPORT:default:${line}`;
401
- this._bufferNode({
402
- id: exportId,
403
- type: 'EXPORT',
404
- exportType: 'default',
405
- name: 'default',
406
- file: module.file,
407
- line: line
408
- });
933
+ const exportNode = NodeFactory.createExport('default', module.file, line, 0, { default: true, exportType: 'default' });
934
+ this._bufferNode(exportNode);
409
935
  this._bufferEdge({
410
936
  type: 'CONTAINS',
411
937
  src: module.id,
412
- dst: exportId
938
+ dst: exportNode.id
413
939
  });
414
940
  }
415
941
  else if (type === 'named') {
416
942
  if (specifiers) {
417
943
  for (const spec of specifiers) {
418
- const exportId = `${module.file}:EXPORT:${spec.exported}:${line}`;
419
- this._bufferNode({
420
- id: exportId,
421
- type: 'EXPORT',
422
- exportType: 'named',
423
- name: spec.exported,
944
+ const exportNode = NodeFactory.createExport(spec.exported, module.file, line, 0, {
424
945
  local: spec.local,
425
- file: module.file,
426
- line: line,
427
- source: source
946
+ source: source,
947
+ exportType: 'named'
428
948
  });
949
+ this._bufferNode(exportNode);
429
950
  this._bufferEdge({
430
951
  type: 'CONTAINS',
431
952
  src: module.id,
432
- dst: exportId
953
+ dst: exportNode.id
433
954
  });
434
955
  }
435
956
  }
436
957
  else if (name) {
437
- const exportId = `${module.file}:EXPORT:${name}:${line}`;
438
- this._bufferNode({
439
- id: exportId,
440
- type: 'EXPORT',
441
- exportType: 'named',
442
- name: name,
443
- file: module.file,
444
- line: line
445
- });
958
+ const exportNode = NodeFactory.createExport(name, module.file, line, 0, { exportType: 'named' });
959
+ this._bufferNode(exportNode);
446
960
  this._bufferEdge({
447
961
  type: 'CONTAINS',
448
962
  src: module.id,
449
- dst: exportId
963
+ dst: exportNode.id
450
964
  });
451
965
  }
452
966
  }
453
967
  else if (type === 'all') {
454
- const exportId = `${module.file}:EXPORT:*:${line}`;
455
- this._bufferNode({
456
- id: exportId,
457
- type: 'EXPORT',
458
- exportType: 'all',
459
- name: '*',
460
- file: module.file,
461
- line: line,
462
- source: source
968
+ const exportNode = NodeFactory.createExport('*', module.file, line, 0, {
969
+ source: source,
970
+ exportType: 'all'
463
971
  });
972
+ this._bufferNode(exportNode);
464
973
  this._bufferEdge({
465
974
  type: 'CONTAINS',
466
975
  src: module.id,
467
- dst: exportId
976
+ dst: exportNode.id
468
977
  });
469
978
  }
470
979
  }
@@ -493,14 +1002,11 @@ export class GraphBuilder {
493
1002
  }
494
1003
  bufferHttpRequests(httpRequests, functions) {
495
1004
  if (httpRequests.length > 0) {
496
- const networkId = 'net:request#__network__';
497
- if (!this._createdSingletons.has(networkId)) {
498
- this._bufferNode({
499
- id: networkId,
500
- type: 'net:request',
501
- name: '__network__'
502
- });
503
- this._createdSingletons.add(networkId);
1005
+ // Create net:request singleton using factory
1006
+ const networkNode = NetworkRequestNode.create();
1007
+ if (!this._createdSingletons.has(networkNode.id)) {
1008
+ this._bufferNode(networkNode);
1009
+ this._createdSingletons.add(networkNode.id);
504
1010
  }
505
1011
  for (const request of httpRequests) {
506
1012
  const { parentScopeId, ...requestData } = request;
@@ -508,7 +1014,7 @@ export class GraphBuilder {
508
1014
  this._bufferEdge({
509
1015
  type: 'CALLS',
510
1016
  src: request.id,
511
- dst: networkId
1017
+ dst: networkNode.id
512
1018
  });
513
1019
  if (parentScopeId) {
514
1020
  const scopeParts = parentScopeId.split(':');
@@ -536,11 +1042,26 @@ export class GraphBuilder {
536
1042
  }
537
1043
  bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters) {
538
1044
  for (const assignment of variableAssignments) {
539
- const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, className } = assignment;
1045
+ const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, column, className } = assignment;
540
1046
  // Skip CLASS sourceType - handled async in createClassAssignmentEdges
541
1047
  if (sourceType === 'CLASS') {
542
1048
  continue;
543
1049
  }
1050
+ // CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
1051
+ // Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
1052
+ if (sourceType === 'CONSTRUCTOR_CALL' && className) {
1053
+ const constructorLine = line ?? 0;
1054
+ const constructorColumn = column ?? 0;
1055
+ const constructorFile = assignment.file ?? '';
1056
+ // Generate ID matching the one created in NewExpression visitor
1057
+ const constructorCallId = NodeFactory.generateConstructorCallId(className, constructorFile, constructorLine, constructorColumn);
1058
+ this._bufferEdge({
1059
+ type: 'ASSIGNED_FROM',
1060
+ src: variableId,
1061
+ dst: constructorCallId
1062
+ });
1063
+ continue;
1064
+ }
544
1065
  // Direct LITERAL assignment
545
1066
  if (sourceId && sourceType !== 'EXPRESSION') {
546
1067
  this._bufferEdge({
@@ -582,8 +1103,10 @@ export class GraphBuilder {
582
1103
  }
583
1104
  // VARIABLE by name
584
1105
  else if (sourceType === 'VARIABLE' && sourceName) {
585
- const varIdParts = variableId.split('#');
586
- const varFile = varIdParts.length >= 3 ? varIdParts[2] : null;
1106
+ // Find the current variable's file by looking it up in variableDeclarations
1107
+ // (semantic IDs don't have predictable file positions like old hash-based IDs)
1108
+ const currentVar = variableDeclarations.find(v => v.id === variableId);
1109
+ const varFile = currentVar?.file ?? null;
587
1110
  const sourceVariable = variableDeclarations.find(v => v.name === sourceName && v.file === varFile);
588
1111
  if (sourceVariable) {
589
1112
  this._bufferEdge({
@@ -614,35 +1137,25 @@ export class GraphBuilder {
614
1137
  });
615
1138
  }
616
1139
  }
617
- // EXPRESSION node creation
1140
+ // EXPRESSION node creation using NodeFactory
618
1141
  else if (sourceType === 'EXPRESSION' && sourceId) {
619
- const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine } = assignment;
620
- const expressionNode = {
621
- id: sourceId,
622
- type: 'EXPRESSION',
623
- expressionType,
624
- file: exprFile,
625
- line: exprLine
626
- };
627
- if (expressionType === 'MemberExpression') {
628
- expressionNode.object = object;
629
- expressionNode.property = property;
630
- expressionNode.computed = computed;
631
- if (computedPropertyVar) {
632
- expressionNode.computedPropertyVar = computedPropertyVar;
633
- }
634
- expressionNode.name = `${object}.${property}`;
635
- }
636
- else if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
637
- expressionNode.operator = operator;
638
- expressionNode.name = `<${expressionType}>`;
639
- }
640
- else if (expressionType === 'ConditionalExpression') {
641
- expressionNode.name = '<ternary>';
642
- }
643
- else if (expressionType === 'TemplateLiteral') {
644
- expressionNode.name = '<template>';
645
- }
1142
+ const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine, column: exprColumn,
1143
+ // Destructuring support (REG-201)
1144
+ path, baseName, propertyPath, arrayIndex } = assignment;
1145
+ // Create node from upstream metadata using factory
1146
+ const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', exprFile || '', exprLine || 0, exprColumn || 0, {
1147
+ id: sourceId, // ID from JSASTAnalyzer
1148
+ object,
1149
+ property,
1150
+ computed,
1151
+ computedPropertyVar: computedPropertyVar ?? undefined,
1152
+ operator,
1153
+ // Destructuring support (REG-201)
1154
+ path,
1155
+ baseName,
1156
+ propertyPath,
1157
+ arrayIndex
1158
+ });
646
1159
  this._bufferNode(expressionNode);
647
1160
  this._bufferEdge({
648
1161
  type: 'ASSIGNED_FROM',
@@ -662,6 +1175,41 @@ export class GraphBuilder {
662
1175
  });
663
1176
  }
664
1177
  }
1178
+ // Call-based source lookup (REG-223)
1179
+ else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
1180
+ const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
1181
+ // Try CALL_SITE first (direct function calls)
1182
+ const callSite = callSites.find(cs => cs.line === callSourceLine &&
1183
+ cs.column === callSourceColumn &&
1184
+ (callSourceName ? cs.name === callSourceName : true));
1185
+ if (callSite) {
1186
+ this._bufferEdge({
1187
+ type: 'DERIVES_FROM',
1188
+ src: sourceId,
1189
+ dst: callSite.id
1190
+ });
1191
+ }
1192
+ // Fall back to methodCalls (arr.map(), obj.getConfig())
1193
+ else {
1194
+ const methodCall = methodCalls.find(mc => mc.line === callSourceLine &&
1195
+ mc.column === callSourceColumn &&
1196
+ (callSourceName ? mc.name === callSourceName : true));
1197
+ if (methodCall) {
1198
+ this._bufferEdge({
1199
+ type: 'DERIVES_FROM',
1200
+ src: sourceId,
1201
+ dst: methodCall.id
1202
+ });
1203
+ }
1204
+ // Log warning when lookup fails (per Linus review - no silent failures)
1205
+ else {
1206
+ console.warn(`[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
1207
+ `at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
1208
+ `Expected CALL_SITE or methodCall for "${callSourceName}". ` +
1209
+ `This indicates a coordinate mismatch or missing call node.`);
1210
+ }
1211
+ }
1212
+ }
665
1213
  if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
666
1214
  if (leftSourceName) {
667
1215
  const leftVar = variableDeclarations.find(v => v.name === leftSourceName && (!varFile || v.file === varFile));
@@ -770,6 +1318,12 @@ export class GraphBuilder {
770
1318
  targetNodeId = nestedCall.id;
771
1319
  }
772
1320
  }
1321
+ else if (targetType === 'LITERAL' ||
1322
+ targetType === 'OBJECT_LITERAL' ||
1323
+ targetType === 'ARRAY_LITERAL') {
1324
+ // targetId is already set by CallExpressionVisitor
1325
+ targetNodeId = targetId;
1326
+ }
773
1327
  if (targetNodeId) {
774
1328
  const edgeData = {
775
1329
  type: 'PASSES_ARGUMENT',
@@ -787,53 +1341,50 @@ export class GraphBuilder {
787
1341
  // ============= TypeScript-specific buffer methods =============
788
1342
  /**
789
1343
  * Buffer INTERFACE nodes and EXTENDS edges
1344
+ *
1345
+ * Uses two-pass approach:
1346
+ * 1. First pass: create all interface nodes, store in Map
1347
+ * 2. Second pass: create EXTENDS edges using stored node IDs
790
1348
  */
791
1349
  bufferInterfaceNodes(module, interfaces) {
1350
+ // First pass: create all interface nodes and store them
1351
+ const interfaceNodes = new Map();
792
1352
  for (const iface of interfaces) {
793
- // Buffer INTERFACE node
794
- this._bufferNode({
795
- id: iface.id,
796
- type: 'INTERFACE',
797
- name: iface.name,
798
- file: iface.file,
799
- line: iface.line,
800
- column: iface.column,
801
- properties: iface.properties,
802
- extends: iface.extends
1353
+ const interfaceNode = InterfaceNode.create(iface.name, iface.file, iface.line, iface.column || 0, {
1354
+ extends: iface.extends,
1355
+ properties: iface.properties
803
1356
  });
1357
+ interfaceNodes.set(iface.name, interfaceNode);
1358
+ this._bufferNode(interfaceNode);
804
1359
  // MODULE -> CONTAINS -> INTERFACE
805
1360
  this._bufferEdge({
806
1361
  type: 'CONTAINS',
807
1362
  src: module.id,
808
- dst: iface.id
1363
+ dst: interfaceNode.id
809
1364
  });
810
- // INTERFACE -> EXTENDS -> INTERFACE (for each parent interface)
1365
+ }
1366
+ // Second pass: create EXTENDS edges
1367
+ for (const iface of interfaces) {
811
1368
  if (iface.extends && iface.extends.length > 0) {
1369
+ const srcNode = interfaceNodes.get(iface.name);
812
1370
  for (const parentName of iface.extends) {
813
- // Try to find the parent interface in the same file
814
- const parentInterface = interfaces.find(i => i.name === parentName);
815
- if (parentInterface) {
1371
+ const parentNode = interfaceNodes.get(parentName);
1372
+ if (parentNode) {
1373
+ // Same-file interface
816
1374
  this._bufferEdge({
817
1375
  type: 'EXTENDS',
818
- src: iface.id,
819
- dst: parentInterface.id
1376
+ src: srcNode.id,
1377
+ dst: parentNode.id
820
1378
  });
821
1379
  }
822
1380
  else {
823
1381
  // External interface - create a reference node
824
- const externalId = `INTERFACE#${parentName}#${iface.file}#external`;
825
- this._bufferNode({
826
- id: externalId,
827
- type: 'INTERFACE',
828
- name: parentName,
829
- file: iface.file,
830
- line: iface.line,
831
- isExternal: true
832
- });
1382
+ const externalInterface = NodeFactory.createInterface(parentName, iface.file, iface.line, 0, { isExternal: true });
1383
+ this._bufferNode(externalInterface);
833
1384
  this._bufferEdge({
834
1385
  type: 'EXTENDS',
835
- src: iface.id,
836
- dst: externalId
1386
+ src: srcNode.id,
1387
+ dst: externalInterface.id
837
1388
  });
838
1389
  }
839
1390
  }
@@ -845,45 +1396,35 @@ export class GraphBuilder {
845
1396
  */
846
1397
  bufferTypeAliasNodes(module, typeAliases) {
847
1398
  for (const typeAlias of typeAliases) {
848
- // Buffer TYPE node
849
- this._bufferNode({
850
- id: typeAlias.id,
851
- type: 'TYPE',
852
- name: typeAlias.name,
853
- file: typeAlias.file,
854
- line: typeAlias.line,
855
- column: typeAlias.column,
856
- aliasOf: typeAlias.aliasOf
857
- });
1399
+ // Create TYPE node using factory
1400
+ const typeNode = NodeFactory.createType(typeAlias.name, typeAlias.file, typeAlias.line, typeAlias.column || 0, { aliasOf: typeAlias.aliasOf });
1401
+ this._bufferNode(typeNode);
858
1402
  // MODULE -> CONTAINS -> TYPE
859
1403
  this._bufferEdge({
860
1404
  type: 'CONTAINS',
861
1405
  src: module.id,
862
- dst: typeAlias.id
1406
+ dst: typeNode.id
863
1407
  });
864
1408
  }
865
1409
  }
866
1410
  /**
867
1411
  * Buffer ENUM nodes
1412
+ * Uses EnumNode.create() to ensure consistent ID format (colon separator)
868
1413
  */
869
1414
  bufferEnumNodes(module, enums) {
870
1415
  for (const enumDecl of enums) {
871
- // Buffer ENUM node
872
- this._bufferNode({
873
- id: enumDecl.id,
874
- type: 'ENUM',
875
- name: enumDecl.name,
876
- file: enumDecl.file,
877
- line: enumDecl.line,
878
- column: enumDecl.column,
879
- isConst: enumDecl.isConst,
880
- members: enumDecl.members
1416
+ // Use EnumNode.create() to generate proper ID (colon format)
1417
+ // Do NOT use enumDecl.id which has legacy # format from TypeScriptVisitor
1418
+ const enumNode = EnumNode.create(enumDecl.name, enumDecl.file, enumDecl.line, enumDecl.column || 0, {
1419
+ isConst: enumDecl.isConst || false,
1420
+ members: enumDecl.members || []
881
1421
  });
1422
+ this._bufferNode(enumNode);
882
1423
  // MODULE -> CONTAINS -> ENUM
883
1424
  this._bufferEdge({
884
1425
  type: 'CONTAINS',
885
1426
  src: module.id,
886
- dst: enumDecl.id
1427
+ dst: enumNode.id // Use factory-generated ID (colon format)
887
1428
  });
888
1429
  }
889
1430
  }
@@ -892,22 +1433,15 @@ export class GraphBuilder {
892
1433
  */
893
1434
  bufferDecoratorNodes(decorators) {
894
1435
  for (const decorator of decorators) {
895
- // Buffer DECORATOR node
896
- this._bufferNode({
897
- id: decorator.id,
898
- type: 'DECORATOR',
899
- name: decorator.name,
900
- file: decorator.file,
901
- line: decorator.line,
902
- column: decorator.column,
903
- arguments: decorator.arguments,
904
- targetType: decorator.targetType
905
- });
1436
+ // Create DECORATOR node using factory (generates colon-format ID)
1437
+ const decoratorNode = DecoratorNode.create(decorator.name, decorator.file, decorator.line, decorator.column || 0, decorator.targetId, // Now included in the node!
1438
+ decorator.targetType, { arguments: decorator.arguments });
1439
+ this._bufferNode(decoratorNode);
906
1440
  // TARGET -> DECORATED_BY -> DECORATOR
907
1441
  this._bufferEdge({
908
1442
  type: 'DECORATED_BY',
909
1443
  src: decorator.targetId,
910
- dst: decorator.id
1444
+ dst: decoratorNode.id // Use factory-generated ID (colon format)
911
1445
  });
912
1446
  }
913
1447
  }
@@ -921,27 +1455,23 @@ export class GraphBuilder {
921
1455
  // Try to find the interface in the same file
922
1456
  const iface = interfaces.find(i => i.name === ifaceName);
923
1457
  if (iface) {
1458
+ // Compute interface ID using same formula as InterfaceNode.create()
1459
+ // Format: {file}:INTERFACE:{name}:{line}
1460
+ const interfaceId = `${iface.file}:INTERFACE:${iface.name}:${iface.line}`;
924
1461
  this._bufferEdge({
925
1462
  type: 'IMPLEMENTS',
926
1463
  src: classDecl.id,
927
- dst: iface.id
1464
+ dst: interfaceId
928
1465
  });
929
1466
  }
930
1467
  else {
931
1468
  // External interface - create a reference node
932
- const externalId = `INTERFACE#${ifaceName}#${classDecl.file}#external`;
933
- this._bufferNode({
934
- id: externalId,
935
- type: 'INTERFACE',
936
- name: ifaceName,
937
- file: classDecl.file,
938
- line: classDecl.line,
939
- isExternal: true
940
- });
1469
+ const externalInterface = NodeFactory.createInterface(ifaceName, classDecl.file, classDecl.line, 0, { isExternal: true });
1470
+ this._bufferNode(externalInterface);
941
1471
  this._bufferEdge({
942
1472
  type: 'IMPLEMENTS',
943
1473
  src: classDecl.id,
944
- dst: externalId
1474
+ dst: externalInterface.id
945
1475
  });
946
1476
  }
947
1477
  }
@@ -949,127 +1479,808 @@ export class GraphBuilder {
949
1479
  }
950
1480
  }
951
1481
  /**
952
- * Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
1482
+ * Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
1483
+ * Creates edges from inserted values to the array variable
1484
+ *
1485
+ * REG-117: Now handles nested mutations like obj.arr.push(item):
1486
+ * - For nested mutations, falls back to base object if array property not found
1487
+ * - Adds nestedProperty metadata for tracking
1488
+ *
1489
+ * OPTIMIZED: Uses Map-based lookup cache for O(1) variable lookups instead of O(n) find()
953
1490
  */
954
- async createClassAssignmentEdges(variableAssignments, graph) {
955
- let edgesCreated = 0;
956
- for (const assignment of variableAssignments) {
957
- const { variableId, sourceType, className } = assignment;
958
- if (sourceType === 'CLASS' && className) {
959
- const parts = variableId.split('#');
960
- const file = parts.length >= 3 ? parts[2] : null;
961
- let classNode = null;
962
- for await (const node of graph.queryNodes({ type: 'CLASS' })) {
963
- if (node.name === className && (!file || node.file === file)) {
964
- classNode = node;
965
- break;
1491
+ bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters) {
1492
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
1493
+ for (const mutation of arrayMutations) {
1494
+ const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
1495
+ const scopePath = mutationScopePath ?? [];
1496
+ // REG-117: For nested mutations (obj.arr.push), resolve target node
1497
+ let targetNodeId = null;
1498
+ let nestedProperty;
1499
+ if (isNested && baseObjectName) {
1500
+ // Skip 'this.items.push' - 'this' is not a variable node
1501
+ if (baseObjectName === 'this')
1502
+ continue;
1503
+ // Nested mutation: try base object lookup with scope chain (REG-309)
1504
+ const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
1505
+ const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
1506
+ targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
1507
+ nestedProperty = propertyName;
1508
+ }
1509
+ else {
1510
+ // Direct mutation: arr.push() (REG-309)
1511
+ const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
1512
+ const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
1513
+ targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
1514
+ }
1515
+ if (!targetNodeId)
1516
+ continue;
1517
+ // Create FLOWS_INTO edges for each inserted value
1518
+ for (const arg of insertedValues) {
1519
+ if (arg.valueType === 'VARIABLE' && arg.valueName) {
1520
+ // Scope-aware lookup for source variable (REG-309)
1521
+ const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
1522
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
1523
+ const sourceNodeId = sourceVar?.id ?? sourceParam?.id;
1524
+ if (sourceNodeId) {
1525
+ const edgeData = {
1526
+ type: 'FLOWS_INTO',
1527
+ src: sourceNodeId,
1528
+ dst: targetNodeId,
1529
+ mutationMethod,
1530
+ argIndex: arg.argIndex
1531
+ };
1532
+ if (arg.isSpread) {
1533
+ edgeData.isSpread = true;
1534
+ }
1535
+ // REG-117: Add nested property metadata
1536
+ if (nestedProperty) {
1537
+ edgeData.nestedProperty = nestedProperty;
1538
+ }
1539
+ this._bufferEdge(edgeData);
966
1540
  }
967
1541
  }
968
- if (classNode) {
969
- await graph.addEdge({
970
- type: 'ASSIGNED_FROM',
971
- src: variableId,
972
- dst: classNode.id
973
- });
974
- edgesCreated++;
975
- }
1542
+ // For literals, object literals, etc. - we could create edges from LITERAL nodes
1543
+ // but for now we just track variable -> array flows
976
1544
  }
977
1545
  }
978
- return edgesCreated;
979
1546
  }
980
1547
  /**
981
- * Create IMPORTS_FROM edges linking imports to their target exports
1548
+ * Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
1549
+ * Creates edges from source values to the object variable being mutated.
1550
+ *
1551
+ * REG-152: For 'this.prop = value' patterns inside classes, creates edges
1552
+ * to the CLASS node with mutationType: 'this_property'.
982
1553
  */
983
- async createImportExportEdges(module, imports, _exports, graph, _projectPath) {
984
- let edgesCreated = 0;
985
- for (const imp of imports) {
986
- const { source, specifiers, line } = imp;
987
- // Только для относительных импортов
988
- const isRelative = source.startsWith('./') || source.startsWith('../');
989
- if (!isRelative) {
990
- continue;
1554
+ bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations) {
1555
+ for (const mutation of objectMutations) {
1556
+ const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
1557
+ const scopePath = mutationScopePath ?? [];
1558
+ // Find the target node (object variable, parameter, or class for 'this')
1559
+ let objectNodeId = null;
1560
+ let effectiveMutationType = mutationType;
1561
+ if (objectName !== 'this') {
1562
+ // Regular object - find variable, parameter, or function using scope chain (REG-309)
1563
+ const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
1564
+ const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
1565
+ const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
1566
+ objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
1567
+ if (!objectNodeId)
1568
+ continue;
991
1569
  }
992
- // Резолвим целевой модуль
993
- const currentDir = dirname(module.file);
994
- let targetPath = resolve(currentDir, source);
995
- // Пытаемся найти файл с расширениями .js, .ts, .jsx, .tsx
996
- const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '/index.js', '/index.ts'];
997
- let targetModule = null;
998
- // Ищем MODULE ноду по file атрибуту (не по ID, т.к. формат ID изменился)
999
- for (const ext of extensions) {
1000
- const testPath = targetPath + ext;
1001
- // Ищем MODULE с этим file path
1002
- for await (const node of graph.queryNodes({ type: 'MODULE' })) {
1003
- if (node.file === testPath) {
1004
- targetModule = node;
1005
- targetPath = testPath;
1006
- break;
1570
+ else {
1571
+ // REG-152: 'this' mutations - find the CLASS node
1572
+ if (!enclosingClassName)
1573
+ continue; // Skip if no class context (e.g., standalone function)
1574
+ // Compare using basename since classes use scopeTracker.file (basename)
1575
+ // but mutations use module.file (full path)
1576
+ const fileBasename = basename(file);
1577
+ const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
1578
+ objectNodeId = classDecl?.id ?? null;
1579
+ if (!objectNodeId)
1580
+ continue; // Skip if class not found
1581
+ // Use special mutation type to distinguish from regular property mutations
1582
+ effectiveMutationType = 'this_property';
1583
+ }
1584
+ // Create FLOWS_INTO edge for VARIABLE value type
1585
+ if (value.valueType === 'VARIABLE' && value.valueName) {
1586
+ // Find the source: can be variable, parameter, or function using scope chain (REG-309)
1587
+ const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
1588
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
1589
+ const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
1590
+ const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
1591
+ if (sourceNodeId && objectNodeId) {
1592
+ const edgeData = {
1593
+ type: 'FLOWS_INTO',
1594
+ src: sourceNodeId,
1595
+ dst: objectNodeId,
1596
+ mutationType: effectiveMutationType,
1597
+ propertyName,
1598
+ computedPropertyVar // For enrichment phase resolution
1599
+ };
1600
+ if (value.argIndex !== undefined) {
1601
+ edgeData.argIndex = value.argIndex;
1007
1602
  }
1603
+ if (value.isSpread) {
1604
+ edgeData.isSpread = true;
1605
+ }
1606
+ this._bufferEdge(edgeData);
1008
1607
  }
1009
- if (targetModule)
1010
- break;
1011
1608
  }
1012
- if (!targetModule) {
1013
- // Целевой модуль не найден в графе
1609
+ // For literals, object literals, etc. - we just track variable -> object flows for now
1610
+ }
1611
+ }
1612
+ /**
1613
+ * Resolve variable by name using scope chain lookup (REG-309).
1614
+ * Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
1615
+ *
1616
+ * @param name - Variable name
1617
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
1618
+ * @param file - File path
1619
+ * @param variables - All variable declarations
1620
+ * @returns Variable declaration or null if not found
1621
+ */
1622
+ resolveVariableInScope(name, scopePath, file, variables) {
1623
+ // Try current scope, then parent, then grandparent, etc.
1624
+ for (let i = scopePath.length; i >= 0; i--) {
1625
+ const searchScopePath = scopePath.slice(0, i);
1626
+ const matchingVar = variables.find(v => {
1627
+ if (v.name !== name || v.file !== file)
1628
+ return false;
1629
+ // Variable ID IS the semantic ID (when scopeTracker was available during analysis)
1630
+ // Format: file->scope1->scope2->TYPE->name
1631
+ // Legacy format: VARIABLE#name#file#line:column:counter
1632
+ // Try parsing as semantic ID
1633
+ const parsed = parseSemanticId(v.id);
1634
+ // REG-329: Check for both VARIABLE and CONSTANT (const declarations)
1635
+ if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
1636
+ // FIXED (REG-309): Handle module-level scope matching
1637
+ // Empty search scope [] should match semantic ID scope ['global']
1638
+ if (searchScopePath.length === 0) {
1639
+ return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
1640
+ }
1641
+ // Non-empty scope: exact match
1642
+ return this.scopePathsMatch(parsed.scopePath, searchScopePath);
1643
+ }
1644
+ // Legacy ID - assume module-level if no semantic ID
1645
+ return searchScopePath.length === 0;
1646
+ });
1647
+ if (matchingVar)
1648
+ return matchingVar;
1649
+ }
1650
+ return null;
1651
+ }
1652
+ /**
1653
+ * Resolve parameter by name using scope chain lookup (REG-309).
1654
+ * Same semantics as resolveVariableInScope but for parameters.
1655
+ *
1656
+ * @param name - Parameter name
1657
+ * @param scopePath - Scope path where reference occurs (from ScopeTracker)
1658
+ * @param file - File path
1659
+ * @param parameters - All parameter declarations
1660
+ * @returns Parameter declaration or null if not found
1661
+ */
1662
+ resolveParameterInScope(name, scopePath, file, parameters) {
1663
+ // Parameters have semanticId field populated (unlike variables which use id field)
1664
+ return parameters.find(p => {
1665
+ if (p.name !== name || p.file !== file)
1666
+ return false;
1667
+ if (p.semanticId) {
1668
+ const parsed = parseSemanticId(p.semanticId);
1669
+ if (parsed && parsed.type === 'PARAMETER') {
1670
+ // Check if parameter's scope matches any scope in the chain
1671
+ for (let i = scopePath.length; i >= 0; i--) {
1672
+ const searchScopePath = scopePath.slice(0, i);
1673
+ // FIXED (REG-309): Handle module-level scope matching for parameters
1674
+ if (searchScopePath.length === 0) {
1675
+ if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
1676
+ return true;
1677
+ }
1678
+ }
1679
+ else {
1680
+ if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
1681
+ return true;
1682
+ }
1683
+ }
1684
+ }
1685
+ }
1686
+ }
1687
+ return false;
1688
+ }) ?? null;
1689
+ }
1690
+ /**
1691
+ * Check if two scope paths match (REG-309).
1692
+ * Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
1693
+ */
1694
+ scopePathsMatch(a, b) {
1695
+ if (a.length !== b.length)
1696
+ return false;
1697
+ return a.every((item, idx) => item === b[idx]);
1698
+ }
1699
+ /**
1700
+ * Buffer FLOWS_INTO edges for variable reassignments.
1701
+ * Handles: x = y, x += y (when x is already declared, not initialization)
1702
+ *
1703
+ * Edge patterns:
1704
+ * - Simple assignment (=): source --FLOWS_INTO--> variable
1705
+ * - Compound operators (+=, -=, etc.):
1706
+ * - source --FLOWS_INTO--> variable (write new value)
1707
+ * - variable --READS_FROM--> variable (self-loop: reads current value before write)
1708
+ *
1709
+ * REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
1710
+ *
1711
+ * REG-290: Complete implementation with inline node creation (no continue statements).
1712
+ */
1713
+ bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters) {
1714
+ // Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
1715
+ // Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
1716
+ for (const reassignment of variableReassignments) {
1717
+ const { variableName, mutationScopePath, valueType, valueName, valueId, callLine, callColumn, operator, literalValue, expressionType, expressionMetadata, file, line, column } = reassignment;
1718
+ // Find target variable node using scope chain resolution (REG-309)
1719
+ const scopePath = mutationScopePath ?? [];
1720
+ const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
1721
+ const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
1722
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
1723
+ if (!targetNodeId) {
1724
+ // Variable not found - could be external reference
1014
1725
  continue;
1015
1726
  }
1016
- // Создаём IMPORTS edge от MODULE к MODULE (для совместимости с тестами)
1017
- await graph.addEdge({
1018
- type: 'IMPORTS',
1019
- src: module.id,
1020
- dst: targetModule.id
1021
- });
1022
- edgesCreated++;
1023
- // Для каждого импортированного идентификатора создаём ребро к соответствующему EXPORT
1024
- for (const spec of specifiers) {
1025
- const importId = `${module.file}:IMPORT:${source}:${spec.local}:${line}`;
1026
- const importType = spec.imported === 'default' ? 'default' :
1027
- spec.imported === '*' ? 'namespace' : 'named';
1028
- if (importType === 'namespace') {
1029
- // import * as foo - связываем со всем модулем
1030
- await graph.addEdge({
1031
- type: 'IMPORTS_FROM',
1032
- src: importId,
1033
- dst: targetModule.id
1727
+ // Resolve source node based on value type
1728
+ let sourceNodeId = null;
1729
+ // LITERAL: Create node inline (NO CONTINUE STATEMENT)
1730
+ if (valueType === 'LITERAL' && valueId) {
1731
+ // Create LITERAL node
1732
+ this._bufferNode({
1733
+ type: 'LITERAL',
1734
+ id: valueId,
1735
+ value: literalValue,
1736
+ file,
1737
+ line,
1738
+ column
1739
+ });
1740
+ sourceNodeId = valueId;
1741
+ }
1742
+ // VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
1743
+ else if (valueType === 'VARIABLE' && valueName) {
1744
+ const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
1745
+ const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
1746
+ sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
1747
+ }
1748
+ // CALL_SITE: Look up existing call node
1749
+ else if (valueType === 'CALL_SITE' && callLine && callColumn) {
1750
+ const callSite = callSites.find(cs => cs.line === callLine && cs.column === callColumn && cs.file === file);
1751
+ sourceNodeId = callSite?.id ?? null;
1752
+ }
1753
+ // METHOD_CALL: Look up existing method call node
1754
+ else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
1755
+ const methodCall = methodCalls.find(mc => mc.line === callLine && mc.column === callColumn && mc.file === file);
1756
+ sourceNodeId = methodCall?.id ?? null;
1757
+ }
1758
+ // EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
1759
+ else if (valueType === 'EXPRESSION' && valueId && expressionType) {
1760
+ // Create EXPRESSION node using NodeFactory
1761
+ const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType, file, line, column, {
1762
+ id: valueId, // ID from JSASTAnalyzer
1763
+ object: expressionMetadata?.object,
1764
+ property: expressionMetadata?.property,
1765
+ computed: expressionMetadata?.computed,
1766
+ computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
1767
+ operator: expressionMetadata?.operator
1768
+ });
1769
+ this._bufferNode(expressionNode);
1770
+ sourceNodeId = valueId;
1771
+ }
1772
+ // Create edges if source found
1773
+ if (sourceNodeId && targetNodeId) {
1774
+ // For compound operators (operator !== '='), LHS reads its own current value
1775
+ // Create READS_FROM self-loop (Linus requirement)
1776
+ if (operator !== '=') {
1777
+ this._bufferEdge({
1778
+ type: 'READS_FROM',
1779
+ src: targetNodeId, // Variable reads from...
1780
+ dst: targetNodeId // ...itself (self-loop)
1034
1781
  });
1035
- edgesCreated++;
1036
1782
  }
1037
- else if (importType === 'default') {
1038
- // Находим EXPORT default в целевом модуле
1039
- const targetExports = [];
1040
- for await (const node of graph.queryNodes({ type: 'EXPORT' })) {
1041
- const exportNode = node;
1042
- if (exportNode.file === targetPath && exportNode.exportType === 'default') {
1043
- targetExports.push(exportNode);
1783
+ // RHS flows into LHS (write side)
1784
+ this._bufferEdge({
1785
+ type: 'FLOWS_INTO',
1786
+ src: sourceNodeId,
1787
+ dst: targetNodeId
1788
+ });
1789
+ }
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Buffer RETURNS edges connecting return expressions to their containing functions.
1794
+ *
1795
+ * Edge direction: returnExpression --RETURNS--> function
1796
+ *
1797
+ * This enables tracing data flow through function calls:
1798
+ * - Query: "What does formatDate return?"
1799
+ * - Answer: Follow RETURNS edges from function to see all possible return values
1800
+ */
1801
+ bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters) {
1802
+ for (const ret of returnStatements) {
1803
+ const { parentFunctionId, returnValueType, file } = ret;
1804
+ // Skip if no value returned (bare return;)
1805
+ if (returnValueType === 'NONE') {
1806
+ continue;
1807
+ }
1808
+ let sourceNodeId = null;
1809
+ switch (returnValueType) {
1810
+ case 'LITERAL':
1811
+ // Direct reference to literal node
1812
+ sourceNodeId = ret.returnValueId ?? null;
1813
+ break;
1814
+ case 'VARIABLE': {
1815
+ // Find variable declaration by name in same file
1816
+ const varName = ret.returnValueName;
1817
+ if (varName) {
1818
+ const sourceVar = variableDeclarations.find(v => v.name === varName && v.file === file);
1819
+ if (sourceVar) {
1820
+ sourceNodeId = sourceVar.id;
1821
+ }
1822
+ else {
1823
+ // Check parameters
1824
+ const sourceParam = parameters.find(p => p.name === varName && p.file === file);
1825
+ if (sourceParam) {
1826
+ sourceNodeId = sourceParam.id;
1827
+ }
1044
1828
  }
1045
1829
  }
1046
- if (targetExports.length > 0) {
1047
- await graph.addEdge({
1048
- type: 'IMPORTS_FROM',
1049
- src: importId,
1050
- dst: targetExports[0].id
1051
- });
1052
- edgesCreated++;
1830
+ break;
1831
+ }
1832
+ case 'CALL_SITE': {
1833
+ // Find call site by coordinates
1834
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
1835
+ if (returnValueLine && returnValueColumn) {
1836
+ const callSite = callSites.find(cs => cs.line === returnValueLine &&
1837
+ cs.column === returnValueColumn &&
1838
+ (returnValueCallName ? cs.name === returnValueCallName : true));
1839
+ if (callSite) {
1840
+ sourceNodeId = callSite.id;
1841
+ }
1053
1842
  }
1843
+ break;
1054
1844
  }
1055
- else {
1056
- // Named import - находим соответствующий named export
1057
- const targetExports = [];
1058
- for await (const node of graph.queryNodes({ type: 'EXPORT' })) {
1059
- const exportNode = node;
1060
- if (exportNode.file === targetPath && exportNode.exportType === 'named' && exportNode.name === spec.imported) {
1061
- targetExports.push(exportNode);
1845
+ case 'METHOD_CALL': {
1846
+ // Find method call by coordinates and method name
1847
+ const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
1848
+ if (returnValueLine && returnValueColumn) {
1849
+ const methodCall = methodCalls.find(mc => mc.line === returnValueLine &&
1850
+ mc.column === returnValueColumn &&
1851
+ mc.file === file &&
1852
+ (returnValueCallName ? mc.method === returnValueCallName : true));
1853
+ if (methodCall) {
1854
+ sourceNodeId = methodCall.id;
1062
1855
  }
1063
1856
  }
1064
- if (targetExports.length > 0) {
1065
- await graph.addEdge({
1066
- type: 'IMPORTS_FROM',
1067
- src: importId,
1068
- dst: targetExports[0].id
1069
- });
1070
- edgesCreated++;
1857
+ break;
1858
+ }
1859
+ case 'EXPRESSION': {
1860
+ // REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
1861
+ const { expressionType, returnValueId, returnValueLine, returnValueColumn, operator, object, property, computed, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, expressionSourceNames, unaryArgSourceName } = ret;
1862
+ // Skip if no expression ID was generated
1863
+ if (!returnValueId) {
1864
+ break;
1865
+ }
1866
+ // Create EXPRESSION node using NodeFactory
1867
+ const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', file, returnValueLine || ret.line, returnValueColumn || ret.column, {
1868
+ id: returnValueId,
1869
+ object,
1870
+ property,
1871
+ computed,
1872
+ operator
1873
+ });
1874
+ this._bufferNode(expressionNode);
1875
+ sourceNodeId = returnValueId;
1876
+ // Buffer DERIVES_FROM edges based on expression type
1877
+ // Helper function to find source variable or parameter
1878
+ const findSource = (name) => {
1879
+ const variable = variableDeclarations.find(v => v.name === name && v.file === file);
1880
+ if (variable)
1881
+ return variable.id;
1882
+ const param = parameters.find(p => p.name === name && p.file === file);
1883
+ if (param)
1884
+ return param.id;
1885
+ return null;
1886
+ };
1887
+ // MemberExpression: derives from the object
1888
+ if (expressionType === 'MemberExpression' && objectSourceName) {
1889
+ const sourceId = findSource(objectSourceName);
1890
+ if (sourceId) {
1891
+ this._bufferEdge({
1892
+ type: 'DERIVES_FROM',
1893
+ src: returnValueId,
1894
+ dst: sourceId
1895
+ });
1896
+ }
1897
+ }
1898
+ // BinaryExpression / LogicalExpression: derives from left and right operands
1899
+ if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
1900
+ if (leftSourceName) {
1901
+ const sourceId = findSource(leftSourceName);
1902
+ if (sourceId) {
1903
+ this._bufferEdge({
1904
+ type: 'DERIVES_FROM',
1905
+ src: returnValueId,
1906
+ dst: sourceId
1907
+ });
1908
+ }
1909
+ }
1910
+ if (rightSourceName) {
1911
+ const sourceId = findSource(rightSourceName);
1912
+ if (sourceId) {
1913
+ this._bufferEdge({
1914
+ type: 'DERIVES_FROM',
1915
+ src: returnValueId,
1916
+ dst: sourceId
1917
+ });
1918
+ }
1919
+ }
1920
+ }
1921
+ // ConditionalExpression: derives from consequent and alternate
1922
+ if (expressionType === 'ConditionalExpression') {
1923
+ if (consequentSourceName) {
1924
+ const sourceId = findSource(consequentSourceName);
1925
+ if (sourceId) {
1926
+ this._bufferEdge({
1927
+ type: 'DERIVES_FROM',
1928
+ src: returnValueId,
1929
+ dst: sourceId
1930
+ });
1931
+ }
1932
+ }
1933
+ if (alternateSourceName) {
1934
+ const sourceId = findSource(alternateSourceName);
1935
+ if (sourceId) {
1936
+ this._bufferEdge({
1937
+ type: 'DERIVES_FROM',
1938
+ src: returnValueId,
1939
+ dst: sourceId
1940
+ });
1941
+ }
1942
+ }
1943
+ }
1944
+ // UnaryExpression: derives from the argument
1945
+ if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
1946
+ const sourceId = findSource(unaryArgSourceName);
1947
+ if (sourceId) {
1948
+ this._bufferEdge({
1949
+ type: 'DERIVES_FROM',
1950
+ src: returnValueId,
1951
+ dst: sourceId
1952
+ });
1953
+ }
1954
+ }
1955
+ // TemplateLiteral: derives from all embedded expressions
1956
+ if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
1957
+ for (const sourceName of expressionSourceNames) {
1958
+ const sourceId = findSource(sourceName);
1959
+ if (sourceId) {
1960
+ this._bufferEdge({
1961
+ type: 'DERIVES_FROM',
1962
+ src: returnValueId,
1963
+ dst: sourceId
1964
+ });
1965
+ }
1966
+ }
1967
+ }
1968
+ break;
1969
+ }
1970
+ }
1971
+ // Create RETURNS edge if we found a source node
1972
+ if (sourceNodeId && parentFunctionId) {
1973
+ this._bufferEdge({
1974
+ type: 'RETURNS',
1975
+ src: sourceNodeId,
1976
+ dst: parentFunctionId
1977
+ });
1978
+ }
1979
+ }
1980
+ }
1981
+ /**
1982
+ * Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
1983
+ *
1984
+ * Handles two target types:
1985
+ * - IDENTIFIER: Simple variable (i++, --count)
1986
+ * - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
1987
+ *
1988
+ * Creates:
1989
+ * - UPDATE_EXPRESSION node with operator and target metadata
1990
+ * - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
1991
+ * - READS_FROM self-loop: target -> target (reads current value before update)
1992
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
1993
+ *
1994
+ * REG-288: Initial implementation for IDENTIFIER targets
1995
+ * REG-312: Extended for MEMBER_EXPRESSION targets
1996
+ */
1997
+ bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations) {
1998
+ // Build lookup caches: O(n) instead of O(n*m)
1999
+ const varLookup = new Map();
2000
+ for (const v of variableDeclarations) {
2001
+ varLookup.set(`${v.file}:${v.name}`, v);
2002
+ }
2003
+ const paramLookup = new Map();
2004
+ for (const p of parameters) {
2005
+ paramLookup.set(`${p.file}:${p.name}`, p);
2006
+ }
2007
+ for (const update of updateExpressions) {
2008
+ if (update.targetType === 'IDENTIFIER') {
2009
+ // REG-288: Simple identifier (i++, --count)
2010
+ this.bufferIdentifierUpdate(update, varLookup, paramLookup);
2011
+ }
2012
+ else if (update.targetType === 'MEMBER_EXPRESSION') {
2013
+ // REG-312: Member expression (obj.prop++, arr[i]++)
2014
+ this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
2015
+ }
2016
+ }
2017
+ }
2018
+ /**
2019
+ * Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
2020
+ * REG-288: Original implementation extracted for clarity
2021
+ */
2022
+ bufferIdentifierUpdate(update, varLookup, paramLookup) {
2023
+ const { variableName, operator, prefix, file, line, column, parentScopeId } = update;
2024
+ if (!variableName)
2025
+ return;
2026
+ // Find target variable node
2027
+ const targetVar = varLookup.get(`${file}:${variableName}`);
2028
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
2029
+ const targetNodeId = targetVar?.id ?? targetParam?.id;
2030
+ if (!targetNodeId) {
2031
+ // Variable not found - could be module-level or external reference
2032
+ return;
2033
+ }
2034
+ // Create UPDATE_EXPRESSION node
2035
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2036
+ this._bufferNode({
2037
+ type: 'UPDATE_EXPRESSION',
2038
+ id: updateId,
2039
+ name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
2040
+ targetType: 'IDENTIFIER',
2041
+ operator,
2042
+ prefix,
2043
+ variableName,
2044
+ file,
2045
+ line,
2046
+ column
2047
+ });
2048
+ // Create READS_FROM self-loop
2049
+ this._bufferEdge({
2050
+ type: 'READS_FROM',
2051
+ src: targetNodeId,
2052
+ dst: targetNodeId
2053
+ });
2054
+ // Create MODIFIES edge
2055
+ this._bufferEdge({
2056
+ type: 'MODIFIES',
2057
+ src: updateId,
2058
+ dst: targetNodeId
2059
+ });
2060
+ // Create CONTAINS edge
2061
+ if (parentScopeId) {
2062
+ this._bufferEdge({
2063
+ type: 'CONTAINS',
2064
+ src: parentScopeId,
2065
+ dst: updateId
2066
+ });
2067
+ }
2068
+ }
2069
+ /**
2070
+ * Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
2071
+ * REG-312: New implementation for member expression targets
2072
+ *
2073
+ * Creates:
2074
+ * - UPDATE_EXPRESSION node with member expression metadata
2075
+ * - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
2076
+ * - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
2077
+ * - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
2078
+ */
2079
+ bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations) {
2080
+ const { objectName, propertyName, mutationType, computedPropertyVar, enclosingClassName, operator, prefix, file, line, column, parentScopeId } = update;
2081
+ if (!objectName || !propertyName)
2082
+ return;
2083
+ // Find target object node
2084
+ let objectNodeId = null;
2085
+ if (objectName !== 'this') {
2086
+ // Regular object: obj.prop++, arr[i]++
2087
+ const targetVar = varLookup.get(`${file}:${objectName}`);
2088
+ const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
2089
+ objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
2090
+ }
2091
+ else {
2092
+ // this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
2093
+ if (!enclosingClassName)
2094
+ return;
2095
+ const fileBasename = basename(file);
2096
+ const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
2097
+ objectNodeId = classDecl?.id ?? null;
2098
+ }
2099
+ if (!objectNodeId) {
2100
+ // Object not found - external reference or scope issue
2101
+ return;
2102
+ }
2103
+ // Create UPDATE_EXPRESSION node
2104
+ const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
2105
+ // Display name: "obj.prop++" or "this.count++" or "arr[i]++"
2106
+ const displayName = (() => {
2107
+ const opStr = prefix ? operator : '';
2108
+ const postOpStr = prefix ? '' : operator;
2109
+ if (objectName === 'this') {
2110
+ return `${opStr}this.${propertyName}${postOpStr}`;
2111
+ }
2112
+ if (mutationType === 'computed') {
2113
+ const computedPart = computedPropertyVar || '?';
2114
+ return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
2115
+ }
2116
+ return `${opStr}${objectName}.${propertyName}${postOpStr}`;
2117
+ })();
2118
+ this._bufferNode({
2119
+ type: 'UPDATE_EXPRESSION',
2120
+ id: updateId,
2121
+ name: displayName,
2122
+ targetType: 'MEMBER_EXPRESSION',
2123
+ operator,
2124
+ prefix,
2125
+ objectName,
2126
+ propertyName,
2127
+ mutationType,
2128
+ computedPropertyVar,
2129
+ enclosingClassName,
2130
+ file,
2131
+ line,
2132
+ column
2133
+ });
2134
+ // Create READS_FROM self-loop (object reads from itself)
2135
+ this._bufferEdge({
2136
+ type: 'READS_FROM',
2137
+ src: objectNodeId,
2138
+ dst: objectNodeId
2139
+ });
2140
+ // Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
2141
+ this._bufferEdge({
2142
+ type: 'MODIFIES',
2143
+ src: updateId,
2144
+ dst: objectNodeId
2145
+ });
2146
+ // Create CONTAINS edge
2147
+ if (parentScopeId) {
2148
+ this._bufferEdge({
2149
+ type: 'CONTAINS',
2150
+ src: parentScopeId,
2151
+ dst: updateId
2152
+ });
2153
+ }
2154
+ }
2155
+ /**
2156
+ * Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
2157
+ *
2158
+ * Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
2159
+ * This enables traceValues to follow Promise data flow:
2160
+ *
2161
+ * Example:
2162
+ * ```
2163
+ * const result = new Promise((resolve) => {
2164
+ * resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
2165
+ * });
2166
+ * ```
2167
+ *
2168
+ * The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
2169
+ * data flows FROM resolve(value) TO the Promise result.
2170
+ */
2171
+ bufferPromiseResolutionEdges(promiseResolutions) {
2172
+ for (const resolution of promiseResolutions) {
2173
+ this._bufferEdge({
2174
+ type: 'RESOLVES_TO',
2175
+ src: resolution.callId,
2176
+ dst: resolution.constructorCallId,
2177
+ metadata: {
2178
+ isReject: resolution.isReject
2179
+ }
2180
+ });
2181
+ }
2182
+ }
2183
+ /**
2184
+ * Buffer OBJECT_LITERAL nodes to the graph.
2185
+ * These are object literals passed as function arguments or nested in other literals.
2186
+ */
2187
+ bufferObjectLiteralNodes(objectLiterals) {
2188
+ for (const obj of objectLiterals) {
2189
+ this._bufferNode({
2190
+ id: obj.id,
2191
+ type: obj.type,
2192
+ name: '<object>',
2193
+ file: obj.file,
2194
+ line: obj.line,
2195
+ column: obj.column,
2196
+ parentCallId: obj.parentCallId,
2197
+ argIndex: obj.argIndex
2198
+ });
2199
+ }
2200
+ }
2201
+ /**
2202
+ * Buffer ARRAY_LITERAL nodes to the graph.
2203
+ * These are array literals passed as function arguments or nested in other literals.
2204
+ */
2205
+ bufferArrayLiteralNodes(arrayLiterals) {
2206
+ for (const arr of arrayLiterals) {
2207
+ this._bufferNode({
2208
+ id: arr.id,
2209
+ type: arr.type,
2210
+ name: '<array>',
2211
+ file: arr.file,
2212
+ line: arr.line,
2213
+ column: arr.column,
2214
+ parentCallId: arr.parentCallId,
2215
+ argIndex: arr.argIndex
2216
+ });
2217
+ }
2218
+ }
2219
+ /**
2220
+ * Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
2221
+ * Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
2222
+ *
2223
+ * REG-329: Adds scope-aware variable resolution for VARIABLE property values.
2224
+ * Uses the same resolveVariableInScope infrastructure as mutation handlers.
2225
+ */
2226
+ bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters) {
2227
+ for (const prop of objectProperties) {
2228
+ // REG-329: Handle VARIABLE value types with scope resolution
2229
+ if (prop.valueType === 'VARIABLE' && prop.valueName) {
2230
+ const scopePath = prop.valueScopePath ?? [];
2231
+ const file = prop.file;
2232
+ // Resolve variable using scope chain
2233
+ const resolvedVar = this.resolveVariableInScope(prop.valueName, scopePath, file, variableDeclarations);
2234
+ const resolvedParam = !resolvedVar
2235
+ ? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
2236
+ : null;
2237
+ const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
2238
+ if (resolvedNodeId) {
2239
+ this._bufferEdge({
2240
+ type: 'HAS_PROPERTY',
2241
+ src: prop.objectId,
2242
+ dst: resolvedNodeId,
2243
+ propertyName: prop.propertyName
2244
+ });
2245
+ }
2246
+ continue;
2247
+ }
2248
+ // Existing logic for non-VARIABLE types
2249
+ if (prop.valueNodeId) {
2250
+ this._bufferEdge({
2251
+ type: 'HAS_PROPERTY',
2252
+ src: prop.objectId,
2253
+ dst: prop.valueNodeId,
2254
+ propertyName: prop.propertyName
2255
+ });
2256
+ }
2257
+ }
2258
+ }
2259
+ /**
2260
+ * Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
2261
+ */
2262
+ async createClassAssignmentEdges(variableAssignments, graph) {
2263
+ let edgesCreated = 0;
2264
+ for (const assignment of variableAssignments) {
2265
+ const { variableId, sourceType, className } = assignment;
2266
+ if (sourceType === 'CLASS' && className) {
2267
+ const parts = variableId.split('#');
2268
+ const file = parts.length >= 3 ? parts[2] : null;
2269
+ let classNode = null;
2270
+ for await (const node of graph.queryNodes({ type: 'CLASS' })) {
2271
+ if (node.name === className && (!file || node.file === file)) {
2272
+ classNode = node;
2273
+ break;
1071
2274
  }
1072
2275
  }
2276
+ if (classNode) {
2277
+ await graph.addEdge({
2278
+ type: 'ASSIGNED_FROM',
2279
+ src: variableId,
2280
+ dst: classNode.id
2281
+ });
2282
+ edgesCreated++;
2283
+ }
1073
2284
  }
1074
2285
  }
1075
2286
  return edgesCreated;