@grafema/core 0.1.0-alpha.5 → 0.1.1-alpha

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 (391) hide show
  1. package/README.md +0 -1
  2. package/dist/Orchestrator.d.ts +24 -2
  3. package/dist/Orchestrator.d.ts.map +1 -1
  4. package/dist/Orchestrator.js +197 -24
  5. package/dist/config/ConfigLoader.d.ts +72 -0
  6. package/dist/config/ConfigLoader.d.ts.map +1 -0
  7. package/dist/config/ConfigLoader.js +187 -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/FileNodeManager.d.ts +40 -0
  18. package/dist/core/FileNodeManager.d.ts.map +1 -0
  19. package/dist/core/FileNodeManager.js +84 -0
  20. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  21. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  22. package/dist/core/GraphFreshnessChecker.js +101 -0
  23. package/dist/core/HashUtils.d.ts +24 -0
  24. package/dist/core/HashUtils.d.ts.map +1 -0
  25. package/dist/core/HashUtils.js +45 -0
  26. package/dist/core/IncrementalReanalyzer.d.ts +36 -0
  27. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  28. package/dist/core/IncrementalReanalyzer.js +132 -0
  29. package/dist/core/NodeFactory.d.ts +225 -17
  30. package/dist/core/NodeFactory.d.ts.map +1 -1
  31. package/dist/core/NodeFactory.js +208 -18
  32. package/dist/core/ScopeTracker.d.ts +84 -0
  33. package/dist/core/ScopeTracker.d.ts.map +1 -0
  34. package/dist/core/ScopeTracker.js +116 -0
  35. package/dist/core/SemanticId.d.ts +90 -0
  36. package/dist/core/SemanticId.d.ts.map +1 -0
  37. package/dist/core/SemanticId.js +115 -0
  38. package/dist/core/VersionManager.d.ts.map +1 -1
  39. package/dist/core/VersionManager.js +3 -2
  40. package/dist/core/nodes/ArgumentExpressionNode.d.ts +43 -0
  41. package/dist/core/nodes/ArgumentExpressionNode.d.ts.map +1 -0
  42. package/dist/core/nodes/ArgumentExpressionNode.js +60 -0
  43. package/dist/core/nodes/ArrayLiteralNode.d.ts +27 -0
  44. package/dist/core/nodes/ArrayLiteralNode.d.ts.map +1 -0
  45. package/dist/core/nodes/ArrayLiteralNode.js +41 -0
  46. package/dist/core/nodes/CallSiteNode.d.ts +28 -0
  47. package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
  48. package/dist/core/nodes/CallSiteNode.js +46 -0
  49. package/dist/core/nodes/ClassNode.d.ts +33 -1
  50. package/dist/core/nodes/ClassNode.d.ts.map +1 -1
  51. package/dist/core/nodes/ClassNode.js +46 -2
  52. package/dist/core/nodes/DecoratorNode.d.ts +42 -0
  53. package/dist/core/nodes/DecoratorNode.d.ts.map +1 -0
  54. package/dist/core/nodes/DecoratorNode.js +62 -0
  55. package/dist/core/nodes/EnumNode.d.ts +42 -0
  56. package/dist/core/nodes/EnumNode.d.ts.map +1 -0
  57. package/dist/core/nodes/EnumNode.js +54 -0
  58. package/dist/core/nodes/ExportNode.d.ts +37 -1
  59. package/dist/core/nodes/ExportNode.d.ts.map +1 -1
  60. package/dist/core/nodes/ExportNode.js +48 -2
  61. package/dist/core/nodes/ExpressionNode.d.ts +97 -0
  62. package/dist/core/nodes/ExpressionNode.d.ts.map +1 -0
  63. package/dist/core/nodes/ExpressionNode.js +178 -0
  64. package/dist/core/nodes/ExternalModuleNode.d.ts +28 -0
  65. package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -0
  66. package/dist/core/nodes/ExternalModuleNode.js +41 -0
  67. package/dist/core/nodes/ExternalStdioNode.d.ts +13 -6
  68. package/dist/core/nodes/ExternalStdioNode.d.ts.map +1 -1
  69. package/dist/core/nodes/ExternalStdioNode.js +15 -8
  70. package/dist/core/nodes/FunctionNode.d.ts +36 -0
  71. package/dist/core/nodes/FunctionNode.d.ts.map +1 -1
  72. package/dist/core/nodes/FunctionNode.js +80 -1
  73. package/dist/core/nodes/ImportNode.d.ts +19 -5
  74. package/dist/core/nodes/ImportNode.d.ts.map +1 -1
  75. package/dist/core/nodes/ImportNode.js +23 -5
  76. package/dist/core/nodes/InterfaceNode.d.ts +46 -0
  77. package/dist/core/nodes/InterfaceNode.d.ts.map +1 -0
  78. package/dist/core/nodes/InterfaceNode.js +55 -0
  79. package/dist/core/nodes/IssueNode.d.ts +73 -0
  80. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  81. package/dist/core/nodes/IssueNode.js +129 -0
  82. package/dist/core/nodes/MethodCallNode.d.ts +30 -0
  83. package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
  84. package/dist/core/nodes/MethodCallNode.js +49 -0
  85. package/dist/core/nodes/MethodNode.d.ts +32 -0
  86. package/dist/core/nodes/MethodNode.d.ts.map +1 -1
  87. package/dist/core/nodes/MethodNode.js +48 -0
  88. package/dist/core/nodes/ModuleNode.d.ts +31 -0
  89. package/dist/core/nodes/ModuleNode.d.ts.map +1 -1
  90. package/dist/core/nodes/ModuleNode.js +37 -0
  91. package/dist/core/nodes/NetworkRequestNode.d.ts +54 -0
  92. package/dist/core/nodes/NetworkRequestNode.d.ts.map +1 -0
  93. package/dist/core/nodes/NetworkRequestNode.js +65 -0
  94. package/dist/core/nodes/ObjectLiteralNode.d.ts +27 -0
  95. package/dist/core/nodes/ObjectLiteralNode.d.ts.map +1 -0
  96. package/dist/core/nodes/ObjectLiteralNode.js +41 -0
  97. package/dist/core/nodes/ScopeNode.d.ts +31 -0
  98. package/dist/core/nodes/ScopeNode.d.ts.map +1 -1
  99. package/dist/core/nodes/ScopeNode.js +49 -0
  100. package/dist/core/nodes/TypeNode.d.ts +36 -0
  101. package/dist/core/nodes/TypeNode.d.ts.map +1 -0
  102. package/dist/core/nodes/TypeNode.js +53 -0
  103. package/dist/core/nodes/VariableDeclarationNode.d.ts +27 -0
  104. package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
  105. package/dist/core/nodes/VariableDeclarationNode.js +40 -0
  106. package/dist/core/nodes/index.d.ts +12 -1
  107. package/dist/core/nodes/index.d.ts.map +1 -1
  108. package/dist/core/nodes/index.js +14 -0
  109. package/dist/diagnostics/DiagnosticCollector.d.ts +98 -0
  110. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  111. package/dist/diagnostics/DiagnosticCollector.js +129 -0
  112. package/dist/diagnostics/DiagnosticReporter.d.ts +77 -0
  113. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  114. package/dist/diagnostics/DiagnosticReporter.js +159 -0
  115. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  116. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  117. package/dist/diagnostics/DiagnosticWriter.js +43 -0
  118. package/dist/diagnostics/index.d.ts +14 -0
  119. package/dist/diagnostics/index.d.ts.map +1 -0
  120. package/dist/diagnostics/index.js +11 -0
  121. package/dist/errors/GrafemaError.d.ts +118 -0
  122. package/dist/errors/GrafemaError.d.ts.map +1 -0
  123. package/dist/errors/GrafemaError.js +131 -0
  124. package/dist/index.d.ts +57 -1
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +54 -1
  127. package/dist/logging/Logger.d.ts +48 -0
  128. package/dist/logging/Logger.d.ts.map +1 -0
  129. package/dist/logging/Logger.js +134 -0
  130. package/dist/plugins/Plugin.d.ts +5 -1
  131. package/dist/plugins/Plugin.d.ts.map +1 -1
  132. package/dist/plugins/Plugin.js +33 -0
  133. package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
  134. package/dist/plugins/analysis/DatabaseAnalyzer.js +13 -6
  135. package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
  136. package/dist/plugins/analysis/ExpressAnalyzer.js +27 -19
  137. package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
  138. package/dist/plugins/analysis/ExpressRouteAnalyzer.js +21 -14
  139. package/dist/plugins/analysis/FetchAnalyzer.d.ts +1 -0
  140. package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
  141. package/dist/plugins/analysis/FetchAnalyzer.js +34 -14
  142. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts +6 -3
  143. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts.map +1 -1
  144. package/dist/plugins/analysis/IncrementalAnalysisPlugin.js +76 -80
  145. package/dist/plugins/analysis/JSASTAnalyzer.d.ts +180 -17
  146. package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
  147. package/dist/plugins/analysis/JSASTAnalyzer.js +1171 -471
  148. package/dist/plugins/analysis/ReactAnalyzer.d.ts.map +1 -1
  149. package/dist/plugins/analysis/ReactAnalyzer.js +56 -57
  150. package/dist/plugins/analysis/RustAnalyzer.d.ts.map +1 -1
  151. package/dist/plugins/analysis/RustAnalyzer.js +15 -10
  152. package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
  153. package/dist/plugins/analysis/SQLiteAnalyzer.js +9 -7
  154. package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts.map +1 -1
  155. package/dist/plugins/analysis/ServiceLayerAnalyzer.js +21 -9
  156. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
  157. package/dist/plugins/analysis/SocketIOAnalyzer.js +27 -15
  158. package/dist/plugins/analysis/SystemDbAnalyzer.d.ts.map +1 -1
  159. package/dist/plugins/analysis/SystemDbAnalyzer.js +15 -5
  160. package/dist/plugins/analysis/ast/GraphBuilder.d.ts +34 -4
  161. package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
  162. package/dist/plugins/analysis/ast/GraphBuilder.js +318 -298
  163. package/dist/plugins/analysis/ast/IdGenerator.d.ts +105 -0
  164. package/dist/plugins/analysis/ast/IdGenerator.d.ts.map +1 -0
  165. package/dist/plugins/analysis/ast/IdGenerator.js +116 -0
  166. package/dist/plugins/analysis/ast/types.d.ts +176 -5
  167. package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
  168. package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts +33 -0
  169. package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts.map +1 -0
  170. package/dist/plugins/analysis/ast/utils/createParameterNodes.js +89 -0
  171. package/dist/plugins/analysis/ast/utils/index.d.ts +6 -0
  172. package/dist/plugins/analysis/ast/utils/index.d.ts.map +1 -0
  173. package/dist/plugins/analysis/ast/utils/index.js +5 -0
  174. package/dist/plugins/analysis/ast/utils/location.d.ts +87 -0
  175. package/dist/plugins/analysis/ast/utils/location.d.ts.map +1 -0
  176. package/dist/plugins/analysis/ast/utils/location.js +78 -0
  177. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +9 -4
  178. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -1
  179. package/dist/plugins/analysis/ast/visitors/ASTVisitor.js +6 -5
  180. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +99 -9
  181. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
  182. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +663 -125
  183. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts +4 -1
  184. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts.map +1 -1
  185. package/dist/plugins/analysis/ast/visitors/ClassVisitor.js +72 -32
  186. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +4 -1
  187. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
  188. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +128 -63
  189. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
  190. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +11 -8
  191. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts +12 -1
  192. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts.map +1 -1
  193. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js +36 -14
  194. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +4 -1
  195. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
  196. package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +17 -13
  197. package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts.map +1 -1
  198. package/dist/plugins/discovery/MonorepoServiceDiscovery.js +3 -2
  199. package/dist/plugins/discovery/SimpleProjectDiscovery.d.ts.map +1 -1
  200. package/dist/plugins/discovery/SimpleProjectDiscovery.js +5 -1
  201. package/dist/plugins/discovery/WorkspaceDiscovery.d.ts +22 -0
  202. package/dist/plugins/discovery/WorkspaceDiscovery.d.ts.map +1 -0
  203. package/dist/plugins/discovery/WorkspaceDiscovery.js +136 -0
  204. package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts +46 -0
  205. package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts.map +1 -0
  206. package/dist/plugins/discovery/resolveSourceEntrypoint.js +86 -0
  207. package/dist/plugins/discovery/workspaces/detector.d.ts +21 -0
  208. package/dist/plugins/discovery/workspaces/detector.d.ts.map +1 -0
  209. package/dist/plugins/discovery/workspaces/detector.js +49 -0
  210. package/dist/plugins/discovery/workspaces/globResolver.d.ts +35 -0
  211. package/dist/plugins/discovery/workspaces/globResolver.d.ts.map +1 -0
  212. package/dist/plugins/discovery/workspaces/globResolver.js +184 -0
  213. package/dist/plugins/discovery/workspaces/index.d.ts +9 -0
  214. package/dist/plugins/discovery/workspaces/index.d.ts.map +1 -0
  215. package/dist/plugins/discovery/workspaces/index.js +8 -0
  216. package/dist/plugins/discovery/workspaces/parsers.d.ts +38 -0
  217. package/dist/plugins/discovery/workspaces/parsers.d.ts.map +1 -0
  218. package/dist/plugins/discovery/workspaces/parsers.js +80 -0
  219. package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
  220. package/dist/plugins/enrichment/AliasTracker.js +14 -8
  221. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
  222. package/dist/plugins/enrichment/HTTPConnectionEnricher.js +14 -7
  223. package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -1
  224. package/dist/plugins/enrichment/ImportExportLinker.js +23 -6
  225. package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
  226. package/dist/plugins/enrichment/MethodCallResolver.js +18 -12
  227. package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
  228. package/dist/plugins/enrichment/MountPointResolver.js +8 -3
  229. package/dist/plugins/enrichment/PrefixEvaluator.d.ts.map +1 -1
  230. package/dist/plugins/enrichment/PrefixEvaluator.js +16 -7
  231. package/dist/plugins/enrichment/RustFFIEnricher.d.ts.map +1 -1
  232. package/dist/plugins/enrichment/RustFFIEnricher.js +6 -5
  233. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +17 -0
  234. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
  235. package/dist/plugins/enrichment/ValueDomainAnalyzer.js +129 -10
  236. package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts.map +1 -1
  237. package/dist/plugins/indexing/IncrementalModuleIndexer.js +23 -14
  238. package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
  239. package/dist/plugins/indexing/JSModuleIndexer.js +63 -31
  240. package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -1
  241. package/dist/plugins/indexing/RustModuleIndexer.js +5 -4
  242. package/dist/plugins/indexing/ServiceDetector.d.ts +10 -0
  243. package/dist/plugins/indexing/ServiceDetector.d.ts.map +1 -1
  244. package/dist/plugins/indexing/ServiceDetector.js +28 -15
  245. package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -1
  246. package/dist/plugins/validation/CallResolverValidator.js +8 -7
  247. package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
  248. package/dist/plugins/validation/DataFlowValidator.js +17 -12
  249. package/dist/plugins/validation/EvalBanValidator.d.ts.map +1 -1
  250. package/dist/plugins/validation/EvalBanValidator.js +17 -16
  251. package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -1
  252. package/dist/plugins/validation/GraphConnectivityValidator.js +19 -23
  253. package/dist/plugins/validation/NodeCreationValidator.d.ts +85 -0
  254. package/dist/plugins/validation/NodeCreationValidator.d.ts.map +1 -0
  255. package/dist/plugins/validation/NodeCreationValidator.js +415 -0
  256. package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -1
  257. package/dist/plugins/validation/SQLInjectionValidator.js +59 -16
  258. package/dist/plugins/validation/ShadowingDetector.d.ts.map +1 -1
  259. package/dist/plugins/validation/ShadowingDetector.js +6 -5
  260. package/dist/plugins/validation/TypeScriptDeadCodeValidator.d.ts.map +1 -1
  261. package/dist/plugins/validation/TypeScriptDeadCodeValidator.js +12 -11
  262. package/dist/plugins/vcs/GitPlugin.d.ts.map +1 -1
  263. package/dist/plugins/vcs/GitPlugin.js +10 -12
  264. package/dist/plugins/vcs/VCSPlugin.d.ts +3 -2
  265. package/dist/plugins/vcs/VCSPlugin.d.ts.map +1 -1
  266. package/dist/plugins/vcs/VCSPlugin.js +5 -5
  267. package/dist/storage/backends/RFDBServerBackend.d.ts +10 -17
  268. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
  269. package/dist/storage/backends/RFDBServerBackend.js +31 -10
  270. package/dist/validation/PathValidator.d.ts +1 -2
  271. package/dist/validation/PathValidator.d.ts.map +1 -1
  272. package/package.json +3 -3
  273. package/src/Orchestrator.ts +237 -24
  274. package/src/config/ConfigLoader.ts +263 -0
  275. package/src/config/index.ts +5 -0
  276. package/src/core/ASTWorker.ts +143 -139
  277. package/src/core/CoverageAnalyzer.ts +243 -0
  278. package/src/core/FileNodeManager.ts +100 -0
  279. package/src/core/GraphFreshnessChecker.ts +143 -0
  280. package/src/core/HashUtils.ts +48 -0
  281. package/src/core/IncrementalReanalyzer.ts +192 -0
  282. package/src/core/NodeFactory.ts +401 -18
  283. package/src/core/ScopeTracker.ts +154 -0
  284. package/src/core/SemanticId.ts +192 -0
  285. package/src/core/VersionManager.ts +3 -2
  286. package/src/core/nodes/ArgumentExpressionNode.ts +89 -0
  287. package/src/core/nodes/ArrayLiteralNode.ts +65 -0
  288. package/src/core/nodes/CallSiteNode.ts +58 -0
  289. package/src/core/nodes/ClassNode.ts +63 -2
  290. package/src/core/nodes/DecoratorNode.ts +91 -0
  291. package/src/core/nodes/EnumNode.ts +86 -0
  292. package/src/core/nodes/ExportNode.ts +70 -2
  293. package/src/core/nodes/ExpressionNode.ts +231 -0
  294. package/src/core/nodes/ExternalModuleNode.ts +56 -0
  295. package/src/core/nodes/ExternalStdioNode.ts +17 -9
  296. package/src/core/nodes/FunctionNode.ts +101 -1
  297. package/src/core/nodes/ImportNode.ts +32 -10
  298. package/src/core/nodes/InterfaceNode.ts +91 -0
  299. package/src/core/nodes/IssueNode.ts +177 -0
  300. package/src/core/nodes/MethodCallNode.ts +64 -0
  301. package/src/core/nodes/MethodNode.ts +63 -0
  302. package/src/core/nodes/ModuleNode.ts +50 -0
  303. package/src/core/nodes/NetworkRequestNode.ts +77 -0
  304. package/src/core/nodes/ObjectLiteralNode.ts +65 -0
  305. package/src/core/nodes/ScopeNode.ts +65 -0
  306. package/src/core/nodes/TypeNode.ts +78 -0
  307. package/src/core/nodes/VariableDeclarationNode.ts +52 -0
  308. package/src/core/nodes/index.ts +18 -1
  309. package/src/diagnostics/DiagnosticCollector.ts +163 -0
  310. package/src/diagnostics/DiagnosticReporter.ts +204 -0
  311. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  312. package/src/diagnostics/index.ts +16 -0
  313. package/src/errors/GrafemaError.ts +174 -0
  314. package/src/index.ts +148 -1
  315. package/src/logging/Logger.ts +152 -0
  316. package/src/plugins/Plugin.ts +42 -0
  317. package/src/plugins/analysis/DatabaseAnalyzer.ts +14 -8
  318. package/src/plugins/analysis/ExpressAnalyzer.ts +29 -19
  319. package/src/plugins/analysis/ExpressRouteAnalyzer.ts +22 -21
  320. package/src/plugins/analysis/FetchAnalyzer.ts +39 -16
  321. package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +84 -101
  322. package/src/plugins/analysis/JSASTAnalyzer.ts +1483 -503
  323. package/src/plugins/analysis/ReactAnalyzer.ts +57 -57
  324. package/src/plugins/analysis/RustAnalyzer.ts +15 -10
  325. package/src/plugins/analysis/SQLiteAnalyzer.ts +10 -7
  326. package/src/plugins/analysis/ServiceLayerAnalyzer.ts +22 -16
  327. package/src/plugins/analysis/SocketIOAnalyzer.ts +31 -22
  328. package/src/plugins/analysis/SystemDbAnalyzer.ts +16 -11
  329. package/src/plugins/analysis/ast/GraphBuilder.ts +439 -327
  330. package/src/plugins/analysis/ast/IdGenerator.ts +177 -0
  331. package/src/plugins/analysis/ast/types.ts +209 -6
  332. package/src/plugins/analysis/ast/utils/createParameterNodes.ts +104 -0
  333. package/src/plugins/analysis/ast/utils/index.ts +12 -0
  334. package/src/plugins/analysis/ast/utils/location.ts +103 -0
  335. package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +11 -8
  336. package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +909 -83
  337. package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +97 -44
  338. package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +159 -93
  339. package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +12 -8
  340. package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +41 -14
  341. package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +37 -17
  342. package/src/plugins/discovery/MonorepoServiceDiscovery.ts +3 -2
  343. package/src/plugins/discovery/SimpleProjectDiscovery.ts +6 -1
  344. package/src/plugins/discovery/WorkspaceDiscovery.ts +177 -0
  345. package/src/plugins/discovery/resolveSourceEntrypoint.ts +103 -0
  346. package/src/plugins/discovery/workspaces/detector.ts +63 -0
  347. package/src/plugins/discovery/workspaces/globResolver.ts +229 -0
  348. package/src/plugins/discovery/workspaces/index.ts +23 -0
  349. package/src/plugins/discovery/workspaces/parsers.ts +99 -0
  350. package/src/plugins/enrichment/AliasTracker.ts +14 -8
  351. package/src/plugins/enrichment/HTTPConnectionEnricher.ts +14 -7
  352. package/src/plugins/enrichment/ImportExportLinker.ts +24 -6
  353. package/src/plugins/enrichment/MethodCallResolver.ts +18 -12
  354. package/src/plugins/enrichment/MountPointResolver.ts +8 -3
  355. package/src/plugins/enrichment/PrefixEvaluator.ts +16 -7
  356. package/src/plugins/enrichment/RustFFIEnricher.ts +6 -5
  357. package/src/plugins/enrichment/ValueDomainAnalyzer.ts +149 -12
  358. package/src/plugins/indexing/IncrementalModuleIndexer.ts +23 -14
  359. package/src/plugins/indexing/JSModuleIndexer.ts +74 -34
  360. package/src/plugins/indexing/RustModuleIndexer.ts +5 -4
  361. package/src/plugins/validation/CallResolverValidator.ts +8 -7
  362. package/src/plugins/validation/DataFlowValidator.ts +16 -12
  363. package/src/plugins/validation/EvalBanValidator.ts +17 -16
  364. package/src/plugins/validation/GraphConnectivityValidator.ts +19 -23
  365. package/src/plugins/validation/NodeCreationValidator.ts +554 -0
  366. package/src/plugins/validation/SQLInjectionValidator.ts +61 -15
  367. package/src/plugins/validation/ShadowingDetector.ts +6 -5
  368. package/src/plugins/validation/TypeScriptDeadCodeValidator.ts +12 -11
  369. package/src/plugins/vcs/GitPlugin.ts +40 -12
  370. package/src/plugins/vcs/VCSPlugin.ts +7 -5
  371. package/src/storage/backends/RFDBServerBackend.ts +43 -29
  372. package/src/validation/PathValidator.ts +1 -1
  373. package/dist/core/AnalysisWorker.d.ts +0 -14
  374. package/dist/core/AnalysisWorker.d.ts.map +0 -1
  375. package/dist/core/AnalysisWorker.js +0 -307
  376. package/dist/core/ParallelAnalyzer.d.ts +0 -120
  377. package/dist/core/ParallelAnalyzer.d.ts.map +0 -1
  378. package/dist/core/ParallelAnalyzer.js +0 -331
  379. package/dist/core/QueueWorker.d.ts +0 -12
  380. package/dist/core/QueueWorker.d.ts.map +0 -1
  381. package/dist/core/QueueWorker.js +0 -567
  382. package/dist/core/RFDBClient.d.ts +0 -179
  383. package/dist/core/RFDBClient.d.ts.map +0 -1
  384. package/dist/core/RFDBClient.js +0 -429
  385. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts +0 -19
  386. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts.map +0 -1
  387. package/dist/plugins/discovery/ZonServiceDiscovery.js +0 -204
  388. package/src/core/AnalysisWorker.ts +0 -410
  389. package/src/core/ParallelAnalyzer.ts +0 -476
  390. package/src/core/QueueWorker.ts +0 -780
  391. package/src/plugins/indexing/ServiceDetector.ts +0 -230
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { readFileSync } from 'fs';
6
6
  import { createHash } from 'crypto';
7
+ import { basename } from 'path';
7
8
  import { parse } from '@babel/parser';
8
9
  import traverseModule from '@babel/traverse';
9
10
  import * as t from '@babel/types';
@@ -25,19 +26,22 @@ import { ImportExportVisitor, VariableVisitor, FunctionVisitor, ClassVisitor, Ca
25
26
  import { Task } from '../../core/Task.js';
26
27
  import { PriorityQueue } from '../../core/PriorityQueue.js';
27
28
  import { WorkerPool } from '../../core/WorkerPool.js';
29
+ import { ASTWorkerPool } from '../../core/ASTWorkerPool.js';
28
30
  import { ConditionParser } from './ast/ConditionParser.js';
31
+ import { getLine, getColumn } from './ast/utils/location.js';
29
32
  import { Profiler } from '../../core/Profiler.js';
33
+ import { ScopeTracker } from '../../core/ScopeTracker.js';
34
+ import { computeSemanticId } from '../../core/SemanticId.js';
35
+ import { ExpressionNode } from '../../core/nodes/ExpressionNode.js';
30
36
  export class JSASTAnalyzer extends Plugin {
31
37
  graphBuilder;
32
38
  analyzedModules;
33
39
  profiler;
34
- _cacheCleared;
35
40
  constructor() {
36
41
  super();
37
42
  this.graphBuilder = new GraphBuilder();
38
43
  this.analyzedModules = new Set();
39
44
  this.profiler = new Profiler('JSASTAnalyzer');
40
- this._cacheCleared = false;
41
45
  }
42
46
  get metadata() {
43
47
  return {
@@ -110,12 +114,12 @@ export class JSASTAnalyzer extends Plugin {
110
114
  return true;
111
115
  }
112
116
  async execute(context) {
117
+ const logger = this.log(context);
113
118
  try {
114
119
  const { manifest, graph, forceAnalysis = false } = context;
115
120
  const projectPath = manifest?.projectPath ?? '';
116
- if (forceAnalysis && !this._cacheCleared) {
121
+ if (forceAnalysis) {
117
122
  this.analyzedModules.clear();
118
- this._cacheCleared = true;
119
123
  }
120
124
  const allModules = await this.getModuleNodes(graph);
121
125
  const modulesToAnalyze = [];
@@ -132,11 +136,15 @@ export class JSASTAnalyzer extends Plugin {
132
136
  skippedCount++;
133
137
  }
134
138
  }
135
- console.log(`[JSASTAnalyzer] Starting parallel analysis of ${modulesToAnalyze.length} modules (${skippedCount} cached)...`);
139
+ logger.info('Starting module analysis', { toAnalyze: modulesToAnalyze.length, cached: skippedCount });
136
140
  if (modulesToAnalyze.length === 0) {
137
- console.log(`[JSASTAnalyzer] All modules are up-to-date, skipping analysis`);
141
+ logger.info('All modules up-to-date, skipping analysis');
138
142
  return createSuccessResult({ nodes: 0, edges: 0 });
139
143
  }
144
+ // Use ASTWorkerPool for true parallel parsing with worker_threads if enabled
145
+ if (context.parallelParsing) {
146
+ return await this.executeParallel(modulesToAnalyze, graph, projectPath, context);
147
+ }
140
148
  const queue = new PriorityQueue();
141
149
  const pool = new WorkerPool(context.workerCount || 10);
142
150
  pool.registerHandler('ANALYZE_MODULE', async (task) => {
@@ -171,7 +179,7 @@ export class JSASTAnalyzer extends Plugin {
171
179
  pool.on('worker:task:completed', () => {
172
180
  completed++;
173
181
  if (completed % 10 === 0 || completed === modulesToAnalyze.length) {
174
- console.log(`[JSASTAnalyzer] Progress: ${completed}/${modulesToAnalyze.length}`);
182
+ logger.debug('Analysis progress', { completed, total: modulesToAnalyze.length });
175
183
  }
176
184
  });
177
185
  await pool.processQueue(queue);
@@ -193,17 +201,86 @@ export class JSASTAnalyzer extends Plugin {
193
201
  edgesCreated += task.result.edges;
194
202
  }
195
203
  }
196
- console.log(`[JSASTAnalyzer] Analyzed ${modulesToAnalyze.length} modules, created ${nodesCreated} nodes`);
197
- console.log(`[JSASTAnalyzer] Stats:`, stats);
204
+ logger.info('Analysis complete', { modulesAnalyzed: modulesToAnalyze.length, nodesCreated });
205
+ logger.debug('Worker stats', { ...stats });
198
206
  this.profiler.printSummary();
199
207
  return createSuccessResult({ nodes: nodesCreated, edges: edgesCreated }, { modulesAnalyzed: modulesToAnalyze.length, workerStats: stats });
200
208
  }
201
209
  catch (error) {
202
- console.error(`[JSASTAnalyzer] Error:`, error);
210
+ logger.error('Analysis failed', { error: error instanceof Error ? error.message : String(error) });
203
211
  const err = error instanceof Error ? error : new Error(String(error));
204
212
  return createErrorResult(err);
205
213
  }
206
214
  }
215
+ /**
216
+ * Execute parallel analysis using ASTWorkerPool (worker_threads).
217
+ *
218
+ * This method uses actual OS threads for true parallel CPU-intensive parsing.
219
+ * Workers generate semantic IDs using ScopeTracker, matching sequential behavior.
220
+ *
221
+ * @param modules - Modules to analyze
222
+ * @param graph - Graph backend for writing results
223
+ * @param projectPath - Project root path
224
+ * @param context - Analysis context with options
225
+ * @returns Plugin result with node/edge counts
226
+ */
227
+ async executeParallel(modules, graph, projectPath, context) {
228
+ const logger = this.log(context);
229
+ const workerCount = context.workerCount || 4;
230
+ const pool = new ASTWorkerPool(workerCount);
231
+ logger.debug('Starting parallel parsing', { workerCount });
232
+ try {
233
+ await pool.init();
234
+ // Convert ModuleNode to ASTModuleInfo format
235
+ const moduleInfos = modules.map(m => ({
236
+ id: m.id,
237
+ file: m.file,
238
+ name: m.name
239
+ }));
240
+ // Parse all modules in parallel using worker threads
241
+ const results = await pool.parseModules(moduleInfos);
242
+ let nodesCreated = 0;
243
+ let edgesCreated = 0;
244
+ let errors = 0;
245
+ // Process results - collections already have semantic IDs from workers
246
+ for (const result of results) {
247
+ if (result.error) {
248
+ logger.warn('Parse error', { file: result.module.file, error: result.error.message });
249
+ errors++;
250
+ continue;
251
+ }
252
+ if (result.collections) {
253
+ // Find original module for metadata
254
+ const module = modules.find(m => m.id === result.module.id);
255
+ if (!module)
256
+ continue;
257
+ // Pass collections directly to GraphBuilder - IDs already semantic
258
+ // Cast is safe because ASTWorker.ASTCollections is structurally compatible
259
+ // with ast/types.ASTCollections (METHOD extends FUNCTION semantically)
260
+ const buildResult = await this.graphBuilder.build(module, graph, projectPath, result.collections);
261
+ if (typeof buildResult === 'object' && buildResult !== null) {
262
+ nodesCreated += buildResult.nodes || 0;
263
+ edgesCreated += buildResult.edges || 0;
264
+ }
265
+ }
266
+ // Report progress
267
+ if (context.onProgress) {
268
+ context.onProgress({
269
+ phase: 'analysis',
270
+ currentPlugin: 'JSASTAnalyzer',
271
+ message: `Processed ${result.module.file.replace(projectPath, '')}`,
272
+ totalFiles: modules.length,
273
+ processedFiles: results.indexOf(result) + 1
274
+ });
275
+ }
276
+ }
277
+ logger.info('Parallel parsing complete', { nodesCreated, edgesCreated, errors });
278
+ return createSuccessResult({ nodes: nodesCreated, edges: edgesCreated }, { modulesAnalyzed: modules.length - errors, parallelParsing: true });
279
+ }
280
+ finally {
281
+ await pool.terminate();
282
+ }
283
+ }
207
284
  /**
208
285
  * Extract variable names from destructuring patterns
209
286
  * Uses t.isX() type guards to avoid casts
@@ -313,8 +390,8 @@ export class JSASTAnalyzer extends Plugin {
313
390
  sourceId: null,
314
391
  sourceType: 'CALL_SITE',
315
392
  callName: initExpression.callee.name,
316
- callLine: initExpression.loc.start.line,
317
- callColumn: initExpression.loc.start.column
393
+ callLine: getLine(initExpression),
394
+ callColumn: getColumn(initExpression)
318
395
  });
319
396
  return;
320
397
  }
@@ -324,7 +401,7 @@ export class JSASTAnalyzer extends Plugin {
324
401
  const objectName = callee.object.type === 'Identifier' ? callee.object.name : (callee.object.type === 'ThisExpression' ? 'this' : 'unknown');
325
402
  const methodName = callee.property.type === 'Identifier' ? callee.property.name : 'unknown';
326
403
  const fullName = `${objectName}.${methodName}`;
327
- const methodCallId = `CALL#${fullName}#${module.file}#${initExpression.loc.start.line}:${initExpression.loc.start.column}:inline`;
404
+ const methodCallId = `CALL#${fullName}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:inline`;
328
405
  const existing = variableAssignments.find(a => a.sourceId === methodCallId);
329
406
  if (!existing) {
330
407
  const extractedArgs = [];
@@ -332,15 +409,15 @@ export class JSASTAnalyzer extends Plugin {
332
409
  if (arg.type !== 'SpreadElement') {
333
410
  const argLiteralValue = ExpressionEvaluator.extractLiteralValue(arg);
334
411
  if (argLiteralValue !== null) {
335
- const literalId = `LITERAL#arg${index}#${module.file}#${initExpression.loc.start.line}:${initExpression.loc.start.column}:${literalCounterRef.value++}`;
412
+ const literalId = `LITERAL#arg${index}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:${literalCounterRef.value++}`;
336
413
  literals.push({
337
414
  id: literalId,
338
415
  type: 'LITERAL',
339
416
  value: argLiteralValue,
340
417
  valueType: typeof argLiteralValue,
341
418
  file: module.file,
342
- line: arg.loc?.start.line || initExpression.loc.start.line,
343
- column: arg.loc?.start.column || initExpression.loc.start.column,
419
+ line: arg.loc?.start.line || getLine(initExpression),
420
+ column: arg.loc?.start.column || getColumn(initExpression),
344
421
  parentCallId: methodCallId,
345
422
  argIndex: index
346
423
  });
@@ -359,8 +436,8 @@ export class JSASTAnalyzer extends Plugin {
359
436
  method: methodName,
360
437
  file: module.file,
361
438
  arguments: extractedArgs,
362
- line: initExpression.loc.start.line,
363
- column: initExpression.loc.start.column
439
+ line: getLine(initExpression),
440
+ column: getColumn(initExpression)
364
441
  });
365
442
  }
366
443
  variableAssignments.push({
@@ -414,7 +491,8 @@ export class JSASTAnalyzer extends Plugin {
414
491
  const computedPropertyVar = initExpression.computed && initExpression.property.type === 'Identifier'
415
492
  ? initExpression.property.name
416
493
  : null;
417
- const expressionId = `EXPRESSION#${objectName}.${propertyName}#${module.file}#${line}:${initExpression.start}`;
494
+ const column = initExpression.start ?? 0;
495
+ const expressionId = ExpressionNode.generateId('MemberExpression', module.file, line, column);
418
496
  variableAssignments.push({
419
497
  variableId,
420
498
  sourceType: 'EXPRESSION',
@@ -426,13 +504,15 @@ export class JSASTAnalyzer extends Plugin {
426
504
  computedPropertyVar,
427
505
  objectSourceName: initExpression.object.type === 'Identifier' ? initExpression.object.name : null,
428
506
  file: module.file,
429
- line: line
507
+ line: line,
508
+ column: column
430
509
  });
431
510
  return;
432
511
  }
433
512
  // 8. BinaryExpression
434
513
  if (initExpression.type === 'BinaryExpression') {
435
- const expressionId = `EXPRESSION#binary#${module.file}#${line}:${initExpression.start}`;
514
+ const column = initExpression.start ?? 0;
515
+ const expressionId = ExpressionNode.generateId('BinaryExpression', module.file, line, column);
436
516
  variableAssignments.push({
437
517
  variableId,
438
518
  sourceType: 'EXPRESSION',
@@ -442,13 +522,15 @@ export class JSASTAnalyzer extends Plugin {
442
522
  leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
443
523
  rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
444
524
  file: module.file,
445
- line: line
525
+ line: line,
526
+ column: column
446
527
  });
447
528
  return;
448
529
  }
449
530
  // 9. ConditionalExpression
450
531
  if (initExpression.type === 'ConditionalExpression') {
451
- const expressionId = `EXPRESSION#conditional#${module.file}#${line}:${initExpression.start}`;
532
+ const column = initExpression.start ?? 0;
533
+ const expressionId = ExpressionNode.generateId('ConditionalExpression', module.file, line, column);
452
534
  variableAssignments.push({
453
535
  variableId,
454
536
  sourceType: 'EXPRESSION',
@@ -457,7 +539,8 @@ export class JSASTAnalyzer extends Plugin {
457
539
  consequentSourceName: initExpression.consequent.type === 'Identifier' ? initExpression.consequent.name : null,
458
540
  alternateSourceName: initExpression.alternate.type === 'Identifier' ? initExpression.alternate.name : null,
459
541
  file: module.file,
460
- line: line
542
+ line: line,
543
+ column: column
461
544
  });
462
545
  this.trackVariableAssignment(initExpression.consequent, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
463
546
  this.trackVariableAssignment(initExpression.alternate, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
@@ -465,7 +548,8 @@ export class JSASTAnalyzer extends Plugin {
465
548
  }
466
549
  // 10. LogicalExpression
467
550
  if (initExpression.type === 'LogicalExpression') {
468
- const expressionId = `EXPRESSION#logical#${module.file}#${line}:${initExpression.start}`;
551
+ const column = initExpression.start ?? 0;
552
+ const expressionId = ExpressionNode.generateId('LogicalExpression', module.file, line, column);
469
553
  variableAssignments.push({
470
554
  variableId,
471
555
  sourceType: 'EXPRESSION',
@@ -475,7 +559,8 @@ export class JSASTAnalyzer extends Plugin {
475
559
  leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
476
560
  rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
477
561
  file: module.file,
478
- line: line
562
+ line: line,
563
+ column: column
479
564
  });
480
565
  this.trackVariableAssignment(initExpression.left, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
481
566
  this.trackVariableAssignment(initExpression.right, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
@@ -483,7 +568,8 @@ export class JSASTAnalyzer extends Plugin {
483
568
  }
484
569
  // 11. TemplateLiteral
485
570
  if (initExpression.type === 'TemplateLiteral' && initExpression.expressions.length > 0) {
486
- const expressionId = `EXPRESSION#template#${module.file}#${line}:${initExpression.start}`;
571
+ const column = initExpression.start ?? 0;
572
+ const expressionId = ExpressionNode.generateId('TemplateLiteral', module.file, line, column);
487
573
  const expressionSourceNames = initExpression.expressions
488
574
  .filter((expr) => expr.type === 'Identifier')
489
575
  .map(expr => expr.name);
@@ -494,7 +580,8 @@ export class JSASTAnalyzer extends Plugin {
494
580
  expressionType: 'TemplateLiteral',
495
581
  expressionSourceNames,
496
582
  file: module.file,
497
- line: line
583
+ line: line,
584
+ column: column
498
585
  });
499
586
  for (const expr of initExpression.expressions) {
500
587
  // Filter out TSType nodes (only in TypeScript code)
@@ -531,6 +618,9 @@ export class JSASTAnalyzer extends Plugin {
531
618
  plugins: ['jsx', 'typescript']
532
619
  });
533
620
  this.profiler.end('babel_parse');
621
+ // Create ScopeTracker for semantic ID generation
622
+ // Use basename for shorter, more readable semantic IDs
623
+ const scopeTracker = new ScopeTracker(basename(module.file));
534
624
  const functions = [];
535
625
  const parameters = [];
536
626
  const scopes = [];
@@ -552,6 +642,15 @@ export class JSASTAnalyzer extends Plugin {
552
642
  const typeAliases = [];
553
643
  const enums = [];
554
644
  const decorators = [];
645
+ // Object/Array literal tracking for data flow
646
+ const objectLiterals = [];
647
+ const objectProperties = [];
648
+ const arrayLiterals = [];
649
+ const arrayElements = [];
650
+ // Array mutation tracking for FLOWS_INTO edges
651
+ const arrayMutations = [];
652
+ // Object mutation tracking for FLOWS_INTO edges
653
+ const objectMutations = [];
555
654
  const ifScopeCounterRef = { value: 0 };
556
655
  const scopeCounterRef = { value: 0 };
557
656
  const varDeclCounterRef = { value: 0 };
@@ -560,6 +659,8 @@ export class JSASTAnalyzer extends Plugin {
560
659
  const httpRequestCounterRef = { value: 0 };
561
660
  const literalCounterRef = { value: 0 };
562
661
  const anonymousFunctionCounterRef = { value: 0 };
662
+ const objectLiteralCounterRef = { value: 0 };
663
+ const arrayLiteralCounterRef = { value: 0 };
563
664
  const processedNodes = {
564
665
  functions: new Set(),
565
666
  classes: new Set(),
@@ -579,34 +680,40 @@ export class JSASTAnalyzer extends Plugin {
579
680
  this.profiler.end('traverse_imports');
580
681
  // Variables
581
682
  this.profiler.start('traverse_variables');
582
- const variableVisitor = new VariableVisitor(module, { variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef }, this.extractVariableNamesFromPattern.bind(this), this.trackVariableAssignment.bind(this));
683
+ const variableVisitor = new VariableVisitor(module, { variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef }, this.extractVariableNamesFromPattern.bind(this), this.trackVariableAssignment.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
684
+ );
583
685
  traverse(ast, variableVisitor.getHandlers());
584
686
  this.profiler.end('traverse_variables');
585
- // Module-level scope context for consistent anonymous naming
586
- const moduleScopeCtx = {
587
- semanticPath: module.name,
588
- siblingCounters: new Map()
589
- };
590
687
  const allCollections = {
591
688
  functions, parameters, scopes, variableDeclarations, callSites, methodCalls,
592
689
  eventListeners, methodCallbacks, callArguments, classInstantiations, classDeclarations,
593
690
  httpRequests, literals, variableAssignments,
594
691
  // TypeScript-specific collections
595
692
  interfaces, typeAliases, enums, decorators,
693
+ // Object/Array literal tracking
694
+ objectLiterals, objectProperties, arrayLiterals, arrayElements,
695
+ // Array mutation tracking
696
+ arrayMutations,
697
+ // Object mutation tracking
698
+ objectMutations,
699
+ objectLiteralCounterRef, arrayLiteralCounterRef,
596
700
  ifScopeCounterRef, scopeCounterRef, varDeclCounterRef,
597
701
  callSiteCounterRef, functionCounterRef, httpRequestCounterRef,
598
702
  literalCounterRef, anonymousFunctionCounterRef, processedNodes,
599
- imports, exports, moduleScopeCtx, code,
703
+ imports, exports, code,
600
704
  // VisitorCollections compatibility
601
705
  classes: classDeclarations,
602
706
  methods: [],
603
707
  variables: variableDeclarations,
604
708
  sideEffects: [],
605
- variableCounterRef: varDeclCounterRef
709
+ variableCounterRef: varDeclCounterRef,
710
+ // ScopeTracker for semantic ID generation
711
+ scopeTracker
606
712
  };
607
713
  // Functions
608
714
  this.profiler.start('traverse_functions');
609
- const functionVisitor = new FunctionVisitor(module, allCollections, this.analyzeFunctionBody.bind(this));
715
+ const functionVisitor = new FunctionVisitor(module, allCollections, this.analyzeFunctionBody.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
716
+ );
610
717
  traverse(ast, functionVisitor.getHandlers());
611
718
  this.profiler.end('traverse_functions');
612
719
  // AssignmentExpression (module-level function assignments)
@@ -631,20 +738,20 @@ export class JSASTAnalyzer extends Plugin {
631
738
  functionName = assignNode.left.name;
632
739
  }
633
740
  const funcNode = assignNode.right;
634
- const functionId = `FUNCTION#${functionName}#${module.file}#${assignNode.loc.start.line}:${assignNode.loc.start.column}`;
741
+ // Use semantic ID as primary ID (matching FunctionVisitor pattern)
742
+ const functionId = computeSemanticId('FUNCTION', functionName, scopeTracker.getContext());
635
743
  functions.push({
636
744
  id: functionId,
637
- stableId: functionId,
638
745
  type: 'FUNCTION',
639
746
  name: functionName,
640
747
  file: module.file,
641
- line: assignNode.loc.start.line,
642
- column: assignNode.loc.start.column,
748
+ line: getLine(assignNode),
749
+ column: getColumn(assignNode),
643
750
  async: funcNode.async || false,
644
751
  generator: funcNode.type === 'FunctionExpression' ? funcNode.generator : false,
645
752
  isAssignment: true
646
753
  });
647
- const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${assignNode.loc.start.line}`;
754
+ const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${getLine(assignNode)}`;
648
755
  scopes.push({
649
756
  id: funcBodyScopeId,
650
757
  type: 'SCOPE',
@@ -653,28 +760,31 @@ export class JSASTAnalyzer extends Plugin {
653
760
  semanticId: `${functionName}:function_body[0]`,
654
761
  conditional: false,
655
762
  file: module.file,
656
- line: assignNode.loc.start.line,
763
+ line: getLine(assignNode),
657
764
  parentFunctionId: functionId
658
765
  });
659
766
  const funcPath = assignPath.get('right');
660
- // Create scope context for analyzing the function body
661
- const funcScopeCtx = {
662
- semanticPath: functionName,
663
- siblingCounters: new Map()
664
- };
665
- this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections, funcScopeCtx);
767
+ // Enter function scope for semantic ID generation and analyze
768
+ scopeTracker.enterScope(functionName, 'function');
769
+ this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections);
770
+ scopeTracker.exitScope();
666
771
  }
772
+ // Check for indexed array assignment at module level: arr[i] = value
773
+ this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
774
+ // Check for object property assignment at module level: obj.prop = value
775
+ this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
667
776
  }
668
777
  });
669
778
  this.profiler.end('traverse_assignments');
670
779
  // Classes
671
780
  this.profiler.start('traverse_classes');
672
- const classVisitor = new ClassVisitor(module, allCollections, this.analyzeFunctionBody.bind(this));
781
+ const classVisitor = new ClassVisitor(module, allCollections, this.analyzeFunctionBody.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
782
+ );
673
783
  traverse(ast, classVisitor.getHandlers());
674
784
  this.profiler.end('traverse_classes');
675
785
  // TypeScript-specific constructs (interfaces, type aliases, enums)
676
786
  this.profiler.start('traverse_typescript');
677
- const typescriptVisitor = new TypeScriptVisitor(module, allCollections);
787
+ const typescriptVisitor = new TypeScriptVisitor(module, allCollections, scopeTracker);
678
788
  traverse(ast, typescriptVisitor.getHandlers());
679
789
  this.profiler.end('traverse_typescript');
680
790
  // Module-level callbacks
@@ -686,22 +796,22 @@ export class JSASTAnalyzer extends Plugin {
686
796
  if (functionParent)
687
797
  return;
688
798
  if (funcPath.parent && funcPath.parent.type === 'CallExpression') {
689
- const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(moduleScopeCtx);
690
- const functionId = `FUNCTION#${funcName}#${module.file}#${funcNode.loc.start.line}:${funcNode.loc.start.column}`;
799
+ const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(scopeTracker);
800
+ // Use semantic ID as primary ID (matching FunctionVisitor pattern)
801
+ const functionId = computeSemanticId('FUNCTION', funcName, scopeTracker.getContext());
691
802
  functions.push({
692
803
  id: functionId,
693
- stableId: functionId,
694
804
  type: 'FUNCTION',
695
805
  name: funcName,
696
806
  file: module.file,
697
- line: funcNode.loc.start.line,
698
- column: funcNode.loc.start.column,
807
+ line: getLine(funcNode),
808
+ column: getColumn(funcNode),
699
809
  async: funcNode.async || false,
700
810
  generator: funcNode.generator || false,
701
811
  isCallback: true,
702
812
  parentScopeId: module.id
703
813
  });
704
- const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${funcNode.loc.start.line}`;
814
+ const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(funcNode)}`;
705
815
  scopes.push({
706
816
  id: callbackScopeId,
707
817
  type: 'SCOPE',
@@ -710,15 +820,13 @@ export class JSASTAnalyzer extends Plugin {
710
820
  semanticId: `${funcName}:callback_body[0]`,
711
821
  conditional: false,
712
822
  file: module.file,
713
- line: funcNode.loc.start.line,
823
+ line: getLine(funcNode),
714
824
  parentFunctionId: functionId
715
825
  });
716
- // Create scope context for analyzing the callback body
717
- const callbackScopeCtx = {
718
- semanticPath: funcName,
719
- siblingCounters: new Map()
720
- };
721
- this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections, callbackScopeCtx);
826
+ // Enter callback scope for semantic ID generation and analyze
827
+ scopeTracker.enterScope(funcName, 'callback');
828
+ this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections);
829
+ scopeTracker.exitScope();
722
830
  funcPath.skip();
723
831
  }
724
832
  }
@@ -726,7 +834,7 @@ export class JSASTAnalyzer extends Plugin {
726
834
  this.profiler.end('traverse_callbacks');
727
835
  // Call expressions
728
836
  this.profiler.start('traverse_calls');
729
- const callExpressionVisitor = new CallExpressionVisitor(module, allCollections);
837
+ const callExpressionVisitor = new CallExpressionVisitor(module, allCollections, scopeTracker);
730
838
  traverse(ast, callExpressionVisitor.getHandlers());
731
839
  this.profiler.end('traverse_calls');
732
840
  // Module-level IfStatements
@@ -739,37 +847,37 @@ export class JSASTAnalyzer extends Plugin {
739
847
  const ifNode = ifPath.node;
740
848
  const condition = code.substring(ifNode.test.start, ifNode.test.end) || 'condition';
741
849
  const counterId = ifScopeCounterRef.value++;
742
- const ifScopeId = `SCOPE#if#${module.file}#${ifNode.loc.start.line}:${ifNode.loc.start.column}:${counterId}`;
850
+ const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
743
851
  const constraints = ConditionParser.parse(ifNode.test);
744
- const ifSemanticId = this.generateSemanticId('if_statement', moduleScopeCtx);
852
+ const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
745
853
  scopes.push({
746
854
  id: ifScopeId,
747
855
  type: 'SCOPE',
748
856
  scopeType: 'if_statement',
749
- name: `if:${ifNode.loc.start.line}:${ifNode.loc.start.column}:${counterId}`,
857
+ name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
750
858
  semanticId: ifSemanticId,
751
859
  conditional: true,
752
860
  condition,
753
861
  constraints: constraints.length > 0 ? constraints : undefined,
754
862
  file: module.file,
755
- line: ifNode.loc.start.line,
863
+ line: getLine(ifNode),
756
864
  parentScopeId: module.id
757
865
  });
758
866
  if (ifNode.alternate && ifNode.alternate.type !== 'IfStatement') {
759
867
  const elseCounterId = ifScopeCounterRef.value++;
760
- const elseScopeId = `SCOPE#else#${module.file}#${ifNode.alternate.loc.start.line}:${ifNode.alternate.loc.start.column}:${elseCounterId}`;
868
+ const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
761
869
  const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
762
- const elseSemanticId = this.generateSemanticId('else_statement', moduleScopeCtx);
870
+ const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
763
871
  scopes.push({
764
872
  id: elseScopeId,
765
873
  type: 'SCOPE',
766
874
  scopeType: 'else_statement',
767
- name: `else:${ifNode.alternate.loc.start.line}:${ifNode.alternate.loc.start.column}:${elseCounterId}`,
875
+ name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
768
876
  semanticId: elseSemanticId,
769
877
  conditional: true,
770
878
  constraints: negatedConstraints,
771
879
  file: module.file,
772
- line: ifNode.alternate.loc.start.line,
880
+ line: getLine(ifNode.alternate),
773
881
  parentScopeId: module.id
774
882
  });
775
883
  }
@@ -799,55 +907,428 @@ export class JSASTAnalyzer extends Plugin {
799
907
  interfaces,
800
908
  typeAliases,
801
909
  enums,
802
- decorators
910
+ decorators,
911
+ // Array mutation tracking
912
+ arrayMutations,
913
+ // Object mutation tracking
914
+ objectMutations,
915
+ // Object/Array literal tracking - use allCollections refs as visitors may have created new arrays
916
+ objectLiterals: allCollections.objectLiterals || objectLiterals,
917
+ arrayLiterals: allCollections.arrayLiterals || arrayLiterals
803
918
  });
804
919
  this.profiler.end('graph_build');
805
920
  nodesCreated = result.nodes;
806
921
  edgesCreated = result.edges;
807
922
  }
808
- catch (error) {
809
- const err = error instanceof Error ? error : new Error(String(error));
810
- console.error(`[JSASTAnalyzer] Error analyzing ${module.file}:`, err.message);
811
- console.error(err.stack);
923
+ catch {
924
+ // Error analyzing module - silently skip, caller handles the result
812
925
  }
813
926
  return { nodes: nodesCreated, edges: edgesCreated };
814
927
  }
815
928
  /**
816
- * Helper to generate semantic ID for a scope and update counters
929
+ * Helper to generate semantic ID for a scope using ScopeTracker.
930
+ * Format: "scopePath:scopeType[index]" e.g. "MyClass->myMethod:if_statement[0]"
817
931
  */
818
- generateSemanticId(scopeType, scopeCtx) {
819
- if (!scopeCtx)
932
+ generateSemanticId(scopeType, scopeTracker) {
933
+ if (!scopeTracker)
820
934
  return undefined;
821
- const siblingIndex = scopeCtx.siblingCounters.get(scopeType) || 0;
822
- scopeCtx.siblingCounters.set(scopeType, siblingIndex + 1);
823
- return `${scopeCtx.semanticPath}:${scopeType}[${siblingIndex}]`;
935
+ const scopePath = scopeTracker.getScopePath();
936
+ const siblingIndex = scopeTracker.getItemCounter(`semanticId:${scopeType}`);
937
+ return `${scopePath}:${scopeType}[${siblingIndex}]`;
824
938
  }
825
939
  /**
826
- * Helper to create child scope context from a semantic ID
940
+ * Generate a unique anonymous function name within the current scope.
941
+ * Uses ScopeTracker.getSiblingIndex() for stable naming.
827
942
  */
828
- createChildScopeContext(semanticId) {
829
- if (!semanticId)
830
- return undefined;
943
+ generateAnonymousName(scopeTracker) {
944
+ if (!scopeTracker)
945
+ return 'anonymous';
946
+ const index = scopeTracker.getSiblingIndex('anonymous');
947
+ return `anonymous[${index}]`;
948
+ }
949
+ /**
950
+ * Factory method to create loop scope handlers.
951
+ * All loop statements (for, for-in, for-of, while, do-while) follow the same pattern:
952
+ * 1. Create scope with SCOPE#<scopeType>#file#line:counter
953
+ * 2. Generate semantic ID
954
+ * 3. Push to scopes array
955
+ * 4. Enter/exit scope tracker
956
+ *
957
+ * @param trackerScopeType - Scope type for ScopeTracker (e.g., 'for', 'for-in', 'while')
958
+ * @param scopeType - Scope type for the graph node (e.g., 'for-loop', 'for-in-loop')
959
+ * @param parentScopeId - Parent scope ID for the scope node
960
+ * @param module - Module context
961
+ * @param scopes - Collection to push scope nodes to
962
+ * @param scopeCounterRef - Counter for unique scope IDs
963
+ * @param scopeTracker - Tracker for semantic ID generation
964
+ */
965
+ /**
966
+ * Handles VariableDeclaration nodes within function bodies.
967
+ *
968
+ * Extracts variable names from patterns (including destructuring), determines
969
+ * if the variable should be CONSTANT or VARIABLE, generates semantic or legacy IDs,
970
+ * and tracks class instantiations and variable assignments.
971
+ *
972
+ * @param varPath - The NodePath for the VariableDeclaration
973
+ * @param parentScopeId - Parent scope ID for the variable
974
+ * @param module - Module context with file info
975
+ * @param variableDeclarations - Collection to push variable declarations to
976
+ * @param classInstantiations - Collection to push class instantiations to
977
+ * @param literals - Collection for literal tracking
978
+ * @param variableAssignments - Collection for variable assignment tracking
979
+ * @param varDeclCounterRef - Counter for unique variable declaration IDs
980
+ * @param literalCounterRef - Counter for unique literal IDs
981
+ * @param scopeTracker - Tracker for semantic ID generation
982
+ * @param parentScopeVariables - Set to track variables for closure analysis
983
+ */
984
+ handleVariableDeclaration(varPath, parentScopeId, module, variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker, parentScopeVariables) {
985
+ const varNode = varPath.node;
986
+ const isConst = varNode.kind === 'const';
987
+ varNode.declarations.forEach(declarator => {
988
+ const variables = this.extractVariableNamesFromPattern(declarator.id);
989
+ variables.forEach(varInfo => {
990
+ const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
991
+ const isLiteral = literalValue !== null;
992
+ const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
993
+ const shouldBeConstant = isConst && (isLiteral || isNewExpression);
994
+ const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
995
+ // Generate semantic ID (primary) or legacy ID (fallback)
996
+ const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
997
+ const varId = scopeTracker
998
+ ? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
999
+ : legacyId;
1000
+ parentScopeVariables.add({
1001
+ name: varInfo.name,
1002
+ id: varId,
1003
+ scopeId: parentScopeId
1004
+ });
1005
+ if (shouldBeConstant) {
1006
+ const constantData = {
1007
+ id: varId,
1008
+ type: 'CONSTANT',
1009
+ name: varInfo.name,
1010
+ file: module.file,
1011
+ line: varInfo.loc.start.line,
1012
+ parentScopeId
1013
+ };
1014
+ if (isLiteral) {
1015
+ constantData.value = literalValue;
1016
+ }
1017
+ variableDeclarations.push(constantData);
1018
+ const init = declarator.init;
1019
+ if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
1020
+ const className = init.callee.name;
1021
+ classInstantiations.push({
1022
+ variableId: varId,
1023
+ variableName: varInfo.name,
1024
+ className: className,
1025
+ line: varInfo.loc.start.line,
1026
+ parentScopeId
1027
+ });
1028
+ }
1029
+ }
1030
+ else {
1031
+ variableDeclarations.push({
1032
+ id: varId,
1033
+ type: 'VARIABLE',
1034
+ name: varInfo.name,
1035
+ file: module.file,
1036
+ line: varInfo.loc.start.line,
1037
+ parentScopeId
1038
+ });
1039
+ }
1040
+ if (declarator.init) {
1041
+ this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
1042
+ }
1043
+ });
1044
+ });
1045
+ }
1046
+ createLoopScopeHandler(trackerScopeType, scopeType, parentScopeId, module, scopes, scopeCounterRef, scopeTracker) {
831
1047
  return {
832
- semanticPath: semanticId,
833
- siblingCounters: new Map()
1048
+ enter: (path) => {
1049
+ const node = path.node;
1050
+ const scopeId = `SCOPE#${scopeType}#${module.file}#${getLine(node)}:${scopeCounterRef.value++}`;
1051
+ const semanticId = this.generateSemanticId(scopeType, scopeTracker);
1052
+ scopes.push({
1053
+ id: scopeId,
1054
+ type: 'SCOPE',
1055
+ scopeType,
1056
+ semanticId,
1057
+ file: module.file,
1058
+ line: getLine(node),
1059
+ parentScopeId
1060
+ });
1061
+ // Enter scope for semantic ID generation
1062
+ if (scopeTracker) {
1063
+ scopeTracker.enterCountedScope(trackerScopeType);
1064
+ }
1065
+ },
1066
+ exit: () => {
1067
+ // Exit scope
1068
+ if (scopeTracker) {
1069
+ scopeTracker.exitScope();
1070
+ }
1071
+ }
834
1072
  };
835
1073
  }
836
1074
  /**
837
- * Generate a unique anonymous function name within the current scope
838
- * Uses scopeCtx.siblingCounters to ensure stability across JS/TS versions
1075
+ * Process VariableDeclarations within a try/catch/finally block.
1076
+ * This is a simplified version that doesn't track parentScopeVariables or class instantiations.
1077
+ *
1078
+ * @param blockPath - The NodePath for the block to process
1079
+ * @param blockScopeId - The scope ID for variables in this block
1080
+ * @param module - Module context
1081
+ * @param variableDeclarations - Collection to push variable declarations to
1082
+ * @param literals - Collection for literal tracking
1083
+ * @param variableAssignments - Collection for variable assignment tracking
1084
+ * @param varDeclCounterRef - Counter for unique variable declaration IDs
1085
+ * @param literalCounterRef - Counter for unique literal IDs
1086
+ * @param scopeTracker - Tracker for semantic ID generation
839
1087
  */
840
- generateAnonymousName(scopeCtx) {
841
- if (!scopeCtx)
842
- return 'anonymous';
843
- const index = scopeCtx.siblingCounters.get('anonymous') || 0;
844
- scopeCtx.siblingCounters.set('anonymous', index + 1);
845
- return `anonymous[${index}]`;
1088
+ processBlockVariables(blockPath, blockScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker) {
1089
+ blockPath.traverse({
1090
+ VariableDeclaration: (varPath) => {
1091
+ const varNode = varPath.node;
1092
+ const isConst = varNode.kind === 'const';
1093
+ varNode.declarations.forEach(declarator => {
1094
+ const variables = this.extractVariableNamesFromPattern(declarator.id);
1095
+ variables.forEach(varInfo => {
1096
+ const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
1097
+ const isLiteral = literalValue !== null;
1098
+ const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
1099
+ const shouldBeConstant = isConst && (isLiteral || isNewExpression);
1100
+ const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
1101
+ const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
1102
+ const varId = scopeTracker
1103
+ ? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
1104
+ : legacyId;
1105
+ variableDeclarations.push({
1106
+ id: varId,
1107
+ type: nodeType,
1108
+ name: varInfo.name,
1109
+ file: module.file,
1110
+ line: varInfo.loc.start.line,
1111
+ parentScopeId: blockScopeId
1112
+ });
1113
+ if (declarator.init) {
1114
+ this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
1115
+ }
1116
+ });
1117
+ });
1118
+ }
1119
+ });
1120
+ }
1121
+ /**
1122
+ * Handles TryStatement nodes within function bodies.
1123
+ * Creates try, catch (with optional error parameter), and finally scopes,
1124
+ * and processes variable declarations within each block.
1125
+ *
1126
+ * @param tryPath - The NodePath for the TryStatement
1127
+ * @param parentScopeId - Parent scope ID for the scope nodes
1128
+ * @param module - Module context
1129
+ * @param scopes - Collection to push scope nodes to
1130
+ * @param variableDeclarations - Collection to push variable declarations to
1131
+ * @param literals - Collection for literal tracking
1132
+ * @param variableAssignments - Collection for variable assignment tracking
1133
+ * @param scopeCounterRef - Counter for unique scope IDs
1134
+ * @param varDeclCounterRef - Counter for unique variable declaration IDs
1135
+ * @param literalCounterRef - Counter for unique literal IDs
1136
+ * @param scopeTracker - Tracker for semantic ID generation
1137
+ */
1138
+ handleTryStatement(tryPath, parentScopeId, module, scopes, variableDeclarations, literals, variableAssignments, scopeCounterRef, varDeclCounterRef, literalCounterRef, scopeTracker) {
1139
+ const tryNode = tryPath.node;
1140
+ // Create and process try block
1141
+ const tryScopeId = `SCOPE#try-block#${module.file}#${getLine(tryNode)}:${scopeCounterRef.value++}`;
1142
+ const trySemanticId = this.generateSemanticId('try-block', scopeTracker);
1143
+ scopes.push({
1144
+ id: tryScopeId,
1145
+ type: 'SCOPE',
1146
+ scopeType: 'try-block',
1147
+ semanticId: trySemanticId,
1148
+ file: module.file,
1149
+ line: getLine(tryNode),
1150
+ parentScopeId
1151
+ });
1152
+ if (scopeTracker) {
1153
+ scopeTracker.enterCountedScope('try');
1154
+ }
1155
+ this.processBlockVariables(tryPath.get('block'), tryScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
1156
+ if (scopeTracker) {
1157
+ scopeTracker.exitScope();
1158
+ }
1159
+ // Create and process catch block if present
1160
+ if (tryNode.handler) {
1161
+ const catchBlock = tryNode.handler;
1162
+ const catchScopeId = `SCOPE#catch-block#${module.file}#${getLine(catchBlock)}:${scopeCounterRef.value++}`;
1163
+ const catchSemanticId = this.generateSemanticId('catch-block', scopeTracker);
1164
+ scopes.push({
1165
+ id: catchScopeId,
1166
+ type: 'SCOPE',
1167
+ scopeType: 'catch-block',
1168
+ semanticId: catchSemanticId,
1169
+ file: module.file,
1170
+ line: getLine(catchBlock),
1171
+ parentScopeId
1172
+ });
1173
+ if (scopeTracker) {
1174
+ scopeTracker.enterCountedScope('catch');
1175
+ }
1176
+ // Handle catch parameter (e.g., catch (e))
1177
+ if (catchBlock.param) {
1178
+ const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
1179
+ errorVarInfo.forEach(varInfo => {
1180
+ const legacyId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
1181
+ const varId = scopeTracker
1182
+ ? computeSemanticId('VARIABLE', varInfo.name, scopeTracker.getContext())
1183
+ : legacyId;
1184
+ variableDeclarations.push({
1185
+ id: varId,
1186
+ type: 'VARIABLE',
1187
+ name: varInfo.name,
1188
+ file: module.file,
1189
+ line: varInfo.loc.start.line,
1190
+ parentScopeId: catchScopeId
1191
+ });
1192
+ });
1193
+ }
1194
+ this.processBlockVariables(tryPath.get('handler.body'), catchScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
1195
+ if (scopeTracker) {
1196
+ scopeTracker.exitScope();
1197
+ }
1198
+ }
1199
+ // Create and process finally block if present
1200
+ if (tryNode.finalizer) {
1201
+ const finallyScopeId = `SCOPE#finally-block#${module.file}#${getLine(tryNode.finalizer)}:${scopeCounterRef.value++}`;
1202
+ const finallySemanticId = this.generateSemanticId('finally-block', scopeTracker);
1203
+ scopes.push({
1204
+ id: finallyScopeId,
1205
+ type: 'SCOPE',
1206
+ scopeType: 'finally-block',
1207
+ semanticId: finallySemanticId,
1208
+ file: module.file,
1209
+ line: getLine(tryNode.finalizer),
1210
+ parentScopeId
1211
+ });
1212
+ if (scopeTracker) {
1213
+ scopeTracker.enterCountedScope('finally');
1214
+ }
1215
+ const finalizerPath = tryPath.get('finalizer');
1216
+ if (finalizerPath.node) {
1217
+ this.processBlockVariables(finalizerPath, finallyScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
1218
+ }
1219
+ if (scopeTracker) {
1220
+ scopeTracker.exitScope();
1221
+ }
1222
+ }
1223
+ tryPath.skip();
1224
+ }
1225
+ /**
1226
+ * Factory method to create IfStatement handler.
1227
+ * Creates if scope with condition parsing and optional else scope.
1228
+ * Tracks if/else scope transitions via ifElseScopeMap.
1229
+ *
1230
+ * @param parentScopeId - Parent scope ID for the scope nodes
1231
+ * @param module - Module context
1232
+ * @param scopes - Collection to push scope nodes to
1233
+ * @param ifScopeCounterRef - Counter for unique if scope IDs
1234
+ * @param scopeTracker - Tracker for semantic ID generation
1235
+ * @param sourceCode - Source code for extracting condition text
1236
+ * @param ifElseScopeMap - Map to track if/else scope transitions
1237
+ */
1238
+ createIfStatementHandler(parentScopeId, module, scopes, ifScopeCounterRef, scopeTracker, sourceCode, ifElseScopeMap) {
1239
+ return {
1240
+ enter: (ifPath) => {
1241
+ const ifNode = ifPath.node;
1242
+ const condition = sourceCode.substring(ifNode.test.start, ifNode.test.end) || 'condition';
1243
+ const counterId = ifScopeCounterRef.value++;
1244
+ const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
1245
+ // Parse condition to extract constraints
1246
+ const constraints = ConditionParser.parse(ifNode.test);
1247
+ const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
1248
+ scopes.push({
1249
+ id: ifScopeId,
1250
+ type: 'SCOPE',
1251
+ scopeType: 'if_statement',
1252
+ name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
1253
+ semanticId: ifSemanticId,
1254
+ conditional: true,
1255
+ condition,
1256
+ constraints: constraints.length > 0 ? constraints : undefined,
1257
+ file: module.file,
1258
+ line: getLine(ifNode),
1259
+ parentScopeId
1260
+ });
1261
+ // Enter scope for semantic ID generation
1262
+ if (scopeTracker) {
1263
+ scopeTracker.enterCountedScope('if');
1264
+ }
1265
+ // Handle else branch if present
1266
+ if (ifNode.alternate && !t.isIfStatement(ifNode.alternate)) {
1267
+ // Only create else scope for actual else block, not else-if
1268
+ const elseCounterId = ifScopeCounterRef.value++;
1269
+ const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
1270
+ const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
1271
+ const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
1272
+ scopes.push({
1273
+ id: elseScopeId,
1274
+ type: 'SCOPE',
1275
+ scopeType: 'else_statement',
1276
+ name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
1277
+ semanticId: elseSemanticId,
1278
+ conditional: true,
1279
+ constraints: negatedConstraints,
1280
+ file: module.file,
1281
+ line: getLine(ifNode.alternate),
1282
+ parentScopeId
1283
+ });
1284
+ // Store info to switch to else scope when we enter alternate
1285
+ ifElseScopeMap.set(ifNode, { inElse: false, hasElse: true });
1286
+ }
1287
+ else {
1288
+ ifElseScopeMap.set(ifNode, { inElse: false, hasElse: false });
1289
+ }
1290
+ },
1291
+ exit: (ifPath) => {
1292
+ const ifNode = ifPath.node;
1293
+ // Exit the current scope (either if or else)
1294
+ if (scopeTracker) {
1295
+ scopeTracker.exitScope();
1296
+ }
1297
+ // If we were in else, we already exited else scope
1298
+ // If we only had if, we exit if scope (done above)
1299
+ ifElseScopeMap.delete(ifNode);
1300
+ }
1301
+ };
846
1302
  }
847
1303
  /**
848
- * Анализирует тело функции и извлекает переменные, вызовы, условные блоки
1304
+ * Factory method to create BlockStatement handler for tracking if/else transitions.
1305
+ * When entering an else block, switches scope from if to else.
1306
+ *
1307
+ * @param scopeTracker - Tracker for semantic ID generation
1308
+ * @param ifElseScopeMap - Map to track if/else scope transitions
849
1309
  */
850
- analyzeFunctionBody(funcPath, parentScopeId, module, collections, scopeCtx) {
1310
+ createIfElseBlockStatementHandler(scopeTracker, ifElseScopeMap) {
1311
+ return {
1312
+ enter: (blockPath) => {
1313
+ // Check if this block is the alternate of an IfStatement
1314
+ const parent = blockPath.parent;
1315
+ if (t.isIfStatement(parent) && parent.alternate === blockPath.node) {
1316
+ const scopeInfo = ifElseScopeMap.get(parent);
1317
+ if (scopeInfo && scopeInfo.hasElse && !scopeInfo.inElse && scopeTracker) {
1318
+ // Exit if scope, enter else scope
1319
+ scopeTracker.exitScope();
1320
+ scopeTracker.enterCountedScope('else');
1321
+ scopeInfo.inElse = true;
1322
+ }
1323
+ }
1324
+ }
1325
+ };
1326
+ }
1327
+ /**
1328
+ * Анализирует тело функции и извлекает переменные, вызовы, условные блоки.
1329
+ * Uses ScopeTracker from collections for semantic ID generation.
1330
+ */
1331
+ analyzeFunctionBody(funcPath, parentScopeId, module, collections) {
851
1332
  // Extract with defaults for optional properties
852
1333
  const functions = (collections.functions ?? []);
853
1334
  const scopes = (collections.scopes ?? []);
@@ -868,6 +1349,7 @@ export class JSASTAnalyzer extends Plugin {
868
1349
  const httpRequestCounterRef = (collections.httpRequestCounterRef ?? { value: 0 });
869
1350
  const literalCounterRef = (collections.literalCounterRef ?? { value: 0 });
870
1351
  const anonymousFunctionCounterRef = (collections.anonymousFunctionCounterRef ?? { value: 0 });
1352
+ const scopeTracker = collections.scopeTracker;
871
1353
  const processedNodes = collections.processedNodes ?? {
872
1354
  functions: new Set(),
873
1355
  classes: new Set(),
@@ -884,223 +1366,73 @@ export class JSASTAnalyzer extends Plugin {
884
1366
  const processedVarDecls = processedNodes.varDecls;
885
1367
  const processedMethodCalls = processedNodes.methodCalls;
886
1368
  const processedEventListeners = processedNodes.eventListeners;
1369
+ // Track if/else scope transitions
1370
+ const ifElseScopeMap = new Map();
887
1371
  funcPath.traverse({
888
1372
  VariableDeclaration: (varPath) => {
889
- const varNode = varPath.node;
890
- const isConst = varNode.kind === 'const';
891
- varNode.declarations.forEach(declarator => {
892
- const variables = this.extractVariableNamesFromPattern(declarator.id);
893
- variables.forEach(varInfo => {
894
- const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
895
- const isLiteral = literalValue !== null;
896
- const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
897
- const shouldBeConstant = isConst && (isLiteral || isNewExpression);
898
- const varId = shouldBeConstant
899
- ? `CONSTANT#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`
900
- : `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
901
- parentScopeVariables.add({
902
- name: varInfo.name,
903
- id: varId,
904
- scopeId: parentScopeId
905
- });
906
- if (shouldBeConstant) {
907
- const constantData = {
908
- id: varId,
909
- type: 'CONSTANT',
910
- name: varInfo.name,
911
- file: module.file,
912
- line: varInfo.loc.start.line,
913
- parentScopeId
914
- };
915
- if (isLiteral) {
916
- constantData.value = literalValue;
917
- }
918
- variableDeclarations.push(constantData);
919
- const init = declarator.init;
920
- if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
921
- const className = init.callee.name;
922
- classInstantiations.push({
923
- variableId: varId,
924
- variableName: varInfo.name,
925
- className: className,
926
- line: varInfo.loc.start.line,
927
- parentScopeId
928
- });
929
- }
930
- }
931
- else {
932
- variableDeclarations.push({
933
- id: varId,
934
- type: 'VARIABLE',
935
- name: varInfo.name,
936
- file: module.file,
937
- line: varInfo.loc.start.line,
938
- parentScopeId
939
- });
940
- }
941
- if (declarator.init) {
942
- this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
943
- }
944
- });
945
- });
946
- },
947
- ForStatement: (forPath) => {
948
- const forNode = forPath.node;
949
- const scopeId = `SCOPE#for-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
950
- const semanticId = this.generateSemanticId('for-loop', scopeCtx);
951
- scopes.push({
952
- id: scopeId,
953
- type: 'SCOPE',
954
- scopeType: 'for-loop',
955
- semanticId,
956
- file: module.file,
957
- line: forNode.loc.start.line,
958
- parentScopeId
959
- });
960
- },
961
- ForInStatement: (forPath) => {
962
- const forNode = forPath.node;
963
- const scopeId = `SCOPE#for-in-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
964
- const semanticId = this.generateSemanticId('for-in-loop', scopeCtx);
965
- scopes.push({
966
- id: scopeId,
967
- type: 'SCOPE',
968
- scopeType: 'for-in-loop',
969
- semanticId,
970
- file: module.file,
971
- line: forNode.loc.start.line,
972
- parentScopeId
973
- });
974
- },
975
- ForOfStatement: (forPath) => {
976
- const forNode = forPath.node;
977
- const scopeId = `SCOPE#for-of-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
978
- const semanticId = this.generateSemanticId('for-of-loop', scopeCtx);
979
- scopes.push({
980
- id: scopeId,
981
- type: 'SCOPE',
982
- scopeType: 'for-of-loop',
983
- semanticId,
984
- file: module.file,
985
- line: forNode.loc.start.line,
986
- parentScopeId
987
- });
988
- },
989
- WhileStatement: (whilePath) => {
990
- const whileNode = whilePath.node;
991
- const scopeId = `SCOPE#while-loop#${module.file}#${whileNode.loc.start.line}:${scopeCounterRef.value++}`;
992
- const semanticId = this.generateSemanticId('while-loop', scopeCtx);
993
- scopes.push({
994
- id: scopeId,
995
- type: 'SCOPE',
996
- scopeType: 'while-loop',
997
- semanticId,
998
- file: module.file,
999
- line: whileNode.loc.start.line,
1000
- parentScopeId
1001
- });
1002
- },
1003
- DoWhileStatement: (doPath) => {
1004
- const doNode = doPath.node;
1005
- const scopeId = `SCOPE#do-while-loop#${module.file}#${doNode.loc.start.line}:${scopeCounterRef.value++}`;
1006
- const semanticId = this.generateSemanticId('do-while-loop', scopeCtx);
1007
- scopes.push({
1008
- id: scopeId,
1009
- type: 'SCOPE',
1010
- scopeType: 'do-while-loop',
1011
- semanticId,
1012
- file: module.file,
1013
- line: doNode.loc.start.line,
1014
- parentScopeId
1015
- });
1373
+ this.handleVariableDeclaration(varPath, parentScopeId, module, variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker, parentScopeVariables);
1016
1374
  },
1017
- TryStatement: (tryPath) => {
1018
- const tryNode = tryPath.node;
1019
- const tryScopeId = `SCOPE#try-block#${module.file}#${tryNode.loc.start.line}:${scopeCounterRef.value++}`;
1020
- const trySemanticId = this.generateSemanticId('try-block', scopeCtx);
1021
- scopes.push({
1022
- id: tryScopeId,
1023
- type: 'SCOPE',
1024
- scopeType: 'try-block',
1025
- semanticId: trySemanticId,
1026
- file: module.file,
1027
- line: tryNode.loc.start.line,
1028
- parentScopeId
1029
- });
1030
- if (tryNode.handler) {
1031
- const catchBlock = tryNode.handler;
1032
- const catchScopeId = `SCOPE#catch-block#${module.file}#${catchBlock.loc.start.line}:${scopeCounterRef.value++}`;
1033
- const catchSemanticId = this.generateSemanticId('catch-block', scopeCtx);
1034
- scopes.push({
1035
- id: catchScopeId,
1036
- type: 'SCOPE',
1037
- scopeType: 'catch-block',
1038
- semanticId: catchSemanticId,
1039
- file: module.file,
1040
- line: catchBlock.loc.start.line,
1041
- parentScopeId
1042
- });
1043
- if (catchBlock.param) {
1044
- const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
1045
- errorVarInfo.forEach(varInfo => {
1046
- const varId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
1047
- variableDeclarations.push({
1048
- id: varId,
1049
- type: 'VARIABLE',
1050
- name: varInfo.name,
1051
- file: module.file,
1052
- line: varInfo.loc.start.line,
1053
- parentScopeId: catchScopeId
1054
- });
1055
- });
1056
- }
1375
+ // Detect indexed array assignments: arr[i] = value
1376
+ AssignmentExpression: (assignPath) => {
1377
+ const assignNode = assignPath.node;
1378
+ // Initialize collection if not exists
1379
+ if (!collections.arrayMutations) {
1380
+ collections.arrayMutations = [];
1057
1381
  }
1058
- if (tryNode.finalizer) {
1059
- const finallyScopeId = `SCOPE#finally-block#${module.file}#${tryNode.finalizer.loc.start.line}:${scopeCounterRef.value++}`;
1060
- const finallySemanticId = this.generateSemanticId('finally-block', scopeCtx);
1061
- scopes.push({
1062
- id: finallyScopeId,
1063
- type: 'SCOPE',
1064
- scopeType: 'finally-block',
1065
- semanticId: finallySemanticId,
1066
- file: module.file,
1067
- line: tryNode.finalizer.loc.start.line,
1068
- parentScopeId
1069
- });
1382
+ const arrayMutations = collections.arrayMutations;
1383
+ // Check for indexed array assignment: arr[i] = value
1384
+ this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
1385
+ // Initialize object mutations collection if not exists
1386
+ if (!collections.objectMutations) {
1387
+ collections.objectMutations = [];
1070
1388
  }
1389
+ const objectMutations = collections.objectMutations;
1390
+ // Check for object property assignment: obj.prop = value
1391
+ this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
1392
+ },
1393
+ ForStatement: this.createLoopScopeHandler('for', 'for-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
1394
+ ForInStatement: this.createLoopScopeHandler('for-in', 'for-in-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
1395
+ ForOfStatement: this.createLoopScopeHandler('for-of', 'for-of-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
1396
+ WhileStatement: this.createLoopScopeHandler('while', 'while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
1397
+ DoWhileStatement: this.createLoopScopeHandler('do-while', 'do-while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
1398
+ TryStatement: (tryPath) => {
1399
+ this.handleTryStatement(tryPath, parentScopeId, module, scopes, variableDeclarations, literals, variableAssignments, scopeCounterRef, varDeclCounterRef, literalCounterRef, scopeTracker);
1071
1400
  },
1072
1401
  SwitchStatement: (switchPath) => {
1073
1402
  const switchNode = switchPath.node;
1074
- const scopeId = `SCOPE#switch-case#${module.file}#${switchNode.loc.start.line}:${scopeCounterRef.value++}`;
1075
- const semanticId = this.generateSemanticId('switch-case', scopeCtx);
1403
+ const scopeId = `SCOPE#switch-case#${module.file}#${getLine(switchNode)}:${scopeCounterRef.value++}`;
1404
+ const semanticId = this.generateSemanticId('switch-case', scopeTracker);
1076
1405
  scopes.push({
1077
1406
  id: scopeId,
1078
1407
  type: 'SCOPE',
1079
1408
  scopeType: 'switch-case',
1080
1409
  semanticId,
1081
1410
  file: module.file,
1082
- line: switchNode.loc.start.line,
1411
+ line: getLine(switchNode),
1083
1412
  parentScopeId
1084
1413
  });
1085
1414
  },
1086
1415
  FunctionExpression: (funcPath) => {
1087
1416
  const node = funcPath.node;
1088
- const funcName = node.id ? node.id.name : this.generateAnonymousName(scopeCtx);
1089
- const functionId = `FUNCTION#${funcName}#${module.file}#${node.loc.start.line}:${node.loc.start.column}:${functionCounterRef.value++}`;
1417
+ const funcName = node.id ? node.id.name : this.generateAnonymousName(scopeTracker);
1418
+ // Use semantic ID as primary ID when scopeTracker available
1419
+ const legacyId = `FUNCTION#${funcName}#${module.file}#${getLine(node)}:${getColumn(node)}:${functionCounterRef.value++}`;
1420
+ const functionId = scopeTracker
1421
+ ? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
1422
+ : legacyId;
1090
1423
  functions.push({
1091
1424
  id: functionId,
1092
- stableId: functionId,
1093
1425
  type: 'FUNCTION',
1094
1426
  name: funcName,
1095
1427
  file: module.file,
1096
- line: node.loc.start.line,
1097
- column: node.loc.start.column,
1428
+ line: getLine(node),
1429
+ column: getColumn(node),
1098
1430
  async: node.async || false,
1099
1431
  generator: node.generator || false,
1100
1432
  parentScopeId
1101
1433
  });
1102
- const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${node.loc.start.line}`;
1103
- const closureSemanticId = this.generateSemanticId('closure', scopeCtx);
1434
+ const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(node)}`;
1435
+ const closureSemanticId = this.generateSemanticId('closure', scopeTracker);
1104
1436
  scopes.push({
1105
1437
  id: nestedScopeId,
1106
1438
  type: 'SCOPE',
@@ -1109,22 +1441,24 @@ export class JSASTAnalyzer extends Plugin {
1109
1441
  semanticId: closureSemanticId,
1110
1442
  conditional: false,
1111
1443
  file: module.file,
1112
- line: node.loc.start.line,
1444
+ line: getLine(node),
1113
1445
  parentFunctionId: functionId,
1114
1446
  capturesFrom: parentScopeId
1115
1447
  });
1116
- // For nested function, create new context with function name as semantic path
1117
- const nestedFuncCtx = {
1118
- semanticPath: scopeCtx ? `${scopeCtx.semanticPath}.${funcName}` : funcName,
1119
- siblingCounters: new Map()
1120
- };
1121
- this.analyzeFunctionBody(funcPath, nestedScopeId, module, collections, nestedFuncCtx);
1448
+ // Enter nested function scope for semantic ID generation
1449
+ if (scopeTracker) {
1450
+ scopeTracker.enterScope(funcName, 'function');
1451
+ }
1452
+ this.analyzeFunctionBody(funcPath, nestedScopeId, module, collections);
1453
+ if (scopeTracker) {
1454
+ scopeTracker.exitScope();
1455
+ }
1122
1456
  funcPath.skip();
1123
1457
  },
1124
1458
  ArrowFunctionExpression: (arrowPath) => {
1125
1459
  const node = arrowPath.node;
1126
- const line = node.loc.start.line;
1127
- const column = node.loc.start.column;
1460
+ const line = getLine(node);
1461
+ const column = getColumn(node);
1128
1462
  // Определяем имя (anonymous если не присвоено переменной)
1129
1463
  const parent = arrowPath.parent;
1130
1464
  let funcName;
@@ -1133,12 +1467,15 @@ export class JSASTAnalyzer extends Plugin {
1133
1467
  }
1134
1468
  else {
1135
1469
  // Используем scope-level счётчик для стабильного semanticId
1136
- funcName = this.generateAnonymousName(scopeCtx);
1470
+ funcName = this.generateAnonymousName(scopeTracker);
1137
1471
  }
1138
- const functionId = `FUNCTION#${funcName}:${line}:${column}:${functionCounterRef.value++}`;
1472
+ // Use semantic ID as primary ID when scopeTracker available
1473
+ const legacyId = `FUNCTION#${funcName}:${line}:${column}:${functionCounterRef.value++}`;
1474
+ const functionId = scopeTracker
1475
+ ? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
1476
+ : legacyId;
1139
1477
  functions.push({
1140
1478
  id: functionId,
1141
- stableId: functionId,
1142
1479
  type: 'FUNCTION',
1143
1480
  name: funcName,
1144
1481
  file: module.file,
@@ -1150,7 +1487,7 @@ export class JSASTAnalyzer extends Plugin {
1150
1487
  });
1151
1488
  if (node.body.type === 'BlockStatement') {
1152
1489
  const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${line}`;
1153
- const arrowSemanticId = this.generateSemanticId('arrow_body', scopeCtx);
1490
+ const arrowSemanticId = this.generateSemanticId('arrow_body', scopeTracker);
1154
1491
  scopes.push({
1155
1492
  id: nestedScopeId,
1156
1493
  type: 'SCOPE',
@@ -1163,12 +1500,14 @@ export class JSASTAnalyzer extends Plugin {
1163
1500
  parentFunctionId: functionId,
1164
1501
  capturesFrom: parentScopeId
1165
1502
  });
1166
- // For arrow function, create new context with function name as semantic path
1167
- const arrowFuncCtx = {
1168
- semanticPath: scopeCtx ? `${scopeCtx.semanticPath}.${funcName}` : funcName,
1169
- siblingCounters: new Map()
1170
- };
1171
- this.analyzeFunctionBody(arrowPath, nestedScopeId, module, collections, arrowFuncCtx);
1503
+ // Enter arrow function scope for semantic ID generation
1504
+ if (scopeTracker) {
1505
+ scopeTracker.enterScope(funcName, 'arrow');
1506
+ }
1507
+ this.analyzeFunctionBody(arrowPath, nestedScopeId, module, collections);
1508
+ if (scopeTracker) {
1509
+ scopeTracker.exitScope();
1510
+ }
1172
1511
  }
1173
1512
  arrowPath.skip();
1174
1513
  },
@@ -1188,191 +1527,552 @@ export class JSASTAnalyzer extends Plugin {
1188
1527
  scope.modifies.push({
1189
1528
  variableId: variable.id,
1190
1529
  variableName: varName,
1191
- line: updateNode.loc.start.line
1530
+ line: getLine(updateNode)
1192
1531
  });
1193
1532
  }
1194
1533
  }
1195
1534
  }
1196
1535
  },
1197
1536
  // IF statements - создаём условные scope и обходим содержимое для CALL узлов
1198
- IfStatement: (ifPath) => {
1199
- const ifNode = ifPath.node;
1200
- const sourceCode = collections.code ?? '';
1201
- const condition = sourceCode.substring(ifNode.test.start, ifNode.test.end) || 'condition';
1202
- const counterId = ifScopeCounterRef.value++;
1203
- const ifScopeId = `SCOPE#if#${module.file}#${ifNode.loc.start.line}:${ifNode.loc.start.column}:${counterId}`;
1204
- // Parse condition to extract constraints
1205
- const constraints = ConditionParser.parse(ifNode.test);
1206
- const ifSemanticId = this.generateSemanticId('if_statement', scopeCtx);
1207
- scopes.push({
1208
- id: ifScopeId,
1209
- type: 'SCOPE',
1210
- scopeType: 'if_statement',
1211
- name: `if:${ifNode.loc.start.line}:${ifNode.loc.start.column}:${counterId}`,
1212
- semanticId: ifSemanticId,
1213
- conditional: true,
1214
- condition,
1215
- constraints: constraints.length > 0 ? constraints : undefined,
1216
- file: module.file,
1217
- line: ifNode.loc.start.line,
1218
- parentScopeId
1219
- });
1220
- // Обходим содержимое if-блока (consequent)
1221
- ifPath.get('consequent').traverse({
1222
- CallExpression: (callPath) => {
1223
- const callNode = callPath.node;
1224
- if (callNode.callee.type === 'Identifier') {
1225
- const nodeKey = `${callNode.start}:${callNode.end}`;
1226
- if (processedCallSites.has(nodeKey)) {
1227
- return;
1228
- }
1229
- processedCallSites.add(nodeKey);
1230
- callSites.push({
1231
- id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc.start.line}:${callNode.loc.start.column}:${callSiteCounterRef.value++}`,
1232
- type: 'CALL',
1233
- name: callNode.callee.name,
1234
- file: module.file,
1235
- line: callNode.loc.start.line,
1236
- parentScopeId: ifScopeId,
1237
- targetFunctionName: callNode.callee.name
1238
- });
1239
- }
1240
- },
1241
- NewExpression: (newPath) => {
1242
- const newNode = newPath.node;
1243
- if (newNode.callee.type === 'Identifier') {
1244
- const nodeKey = `new:${newNode.start}:${newNode.end}`;
1245
- if (processedCallSites.has(nodeKey)) {
1246
- return;
1247
- }
1248
- processedCallSites.add(nodeKey);
1249
- callSites.push({
1250
- id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc.start.line}:${newNode.loc.start.column}:${callSiteCounterRef.value++}`,
1251
- type: 'CALL',
1252
- name: newNode.callee.name,
1253
- file: module.file,
1254
- line: newNode.loc.start.line,
1255
- parentScopeId: ifScopeId,
1256
- targetFunctionName: newNode.callee.name,
1257
- isNew: true
1258
- });
1259
- }
1537
+ IfStatement: this.createIfStatementHandler(parentScopeId, module, scopes, ifScopeCounterRef, scopeTracker, collections.code ?? '', ifElseScopeMap),
1538
+ // Track when we enter the alternate (else) block of an IfStatement
1539
+ BlockStatement: this.createIfElseBlockStatementHandler(scopeTracker, ifElseScopeMap),
1540
+ // Function call expressions
1541
+ CallExpression: (callPath) => {
1542
+ this.handleCallExpression(callPath.node, processedCallSites, processedMethodCalls, callSites, methodCalls, module, callSiteCounterRef, scopeTracker, parentScopeId, collections);
1543
+ },
1544
+ // NewExpression (constructor calls)
1545
+ NewExpression: (newPath) => {
1546
+ const newNode = newPath.node;
1547
+ // Handle simple constructor: new Foo()
1548
+ if (newNode.callee.type === 'Identifier') {
1549
+ const nodeKey = `new:${newNode.start}:${newNode.end}`;
1550
+ if (processedCallSites.has(nodeKey)) {
1551
+ return;
1260
1552
  }
1261
- });
1262
- // Handle else branch if present
1263
- if (ifNode.alternate) {
1264
- const elseCounterId = ifScopeCounterRef.value++;
1265
- const elseScopeId = `SCOPE#else#${module.file}#${ifNode.alternate.loc.start.line}:${ifNode.alternate.loc.start.column}:${elseCounterId}`;
1266
- // Negate constraints for else branch
1267
- const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
1268
- const elseSemanticId = this.generateSemanticId('else_statement', scopeCtx);
1269
- scopes.push({
1270
- id: elseScopeId,
1271
- type: 'SCOPE',
1272
- scopeType: 'else_statement',
1273
- name: `else:${ifNode.alternate.loc.start.line}:${ifNode.alternate.loc.start.column}:${elseCounterId}`,
1274
- semanticId: elseSemanticId,
1275
- conditional: true,
1276
- constraints: negatedConstraints,
1553
+ processedCallSites.add(nodeKey);
1554
+ // Generate semantic ID (primary) or legacy ID (fallback)
1555
+ const constructorName = newNode.callee.name;
1556
+ const legacyId = `CALL#new:${constructorName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
1557
+ let newCallId = legacyId;
1558
+ if (scopeTracker) {
1559
+ const discriminator = scopeTracker.getItemCounter(`CALL:new:${constructorName}`);
1560
+ newCallId = computeSemanticId('CALL', `new:${constructorName}`, scopeTracker.getContext(), { discriminator });
1561
+ }
1562
+ callSites.push({
1563
+ id: newCallId,
1564
+ type: 'CALL',
1565
+ name: constructorName,
1277
1566
  file: module.file,
1278
- line: ifNode.alternate.loc.start.line,
1279
- parentScopeId
1280
- });
1281
- // Traverse else block
1282
- ifPath.get('alternate').traverse({
1283
- CallExpression: (callPath) => {
1284
- const callNode = callPath.node;
1285
- if (callNode.callee.type === 'Identifier') {
1286
- const nodeKey = `${callNode.start}:${callNode.end}`;
1287
- if (processedCallSites.has(nodeKey)) {
1288
- return;
1289
- }
1290
- processedCallSites.add(nodeKey);
1291
- callSites.push({
1292
- id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc.start.line}:${callNode.loc.start.column}:${callSiteCounterRef.value++}`,
1293
- type: 'CALL',
1294
- name: callNode.callee.name,
1295
- file: module.file,
1296
- line: callNode.loc.start.line,
1297
- parentScopeId: elseScopeId,
1298
- targetFunctionName: callNode.callee.name
1299
- });
1300
- }
1301
- },
1302
- NewExpression: (newPath) => {
1303
- const newNode = newPath.node;
1304
- if (newNode.callee.type === 'Identifier') {
1305
- const nodeKey = `new:${newNode.start}:${newNode.end}`;
1306
- if (processedCallSites.has(nodeKey)) {
1307
- return;
1308
- }
1309
- processedCallSites.add(nodeKey);
1310
- callSites.push({
1311
- id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc.start.line}:${newNode.loc.start.column}:${callSiteCounterRef.value++}`,
1312
- type: 'CALL',
1313
- name: newNode.callee.name,
1314
- file: module.file,
1315
- line: newNode.loc.start.line,
1316
- parentScopeId: elseScopeId,
1317
- targetFunctionName: newNode.callee.name,
1318
- isNew: true
1319
- });
1320
- }
1321
- }
1567
+ line: getLine(newNode),
1568
+ parentScopeId,
1569
+ targetFunctionName: constructorName,
1570
+ isNew: true
1322
1571
  });
1323
1572
  }
1324
- // Останавливаем дальнейший обход, чтобы не обрабатывать вызовы дважды
1325
- ifPath.skip();
1326
- },
1327
- // Вызовы функций на безусловном уровне (вне if/for/while)
1328
- CallExpression: (callPath) => {
1329
- // Проверяем что вызов не внутри if/for/while (их мы обрабатываем отдельно)
1330
- const parent = callPath.parent;
1331
- if (parent.type !== 'IfStatement' && parent.type !== 'ForStatement' && parent.type !== 'WhileStatement') {
1332
- const callNode = callPath.node;
1333
- // Обычные вызовы функций (greet(), main())
1334
- if (callNode.callee.type === 'Identifier') {
1335
- const nodeKey = `${callNode.start}:${callNode.end}`;
1336
- if (processedCallSites.has(nodeKey)) {
1573
+ // Handle namespaced constructor: new ns.Constructor()
1574
+ else if (newNode.callee.type === 'MemberExpression') {
1575
+ const memberCallee = newNode.callee;
1576
+ const object = memberCallee.object;
1577
+ const property = memberCallee.property;
1578
+ if (object.type === 'Identifier' && property.type === 'Identifier') {
1579
+ const nodeKey = `new:${newNode.start}:${newNode.end}`;
1580
+ if (processedMethodCalls.has(nodeKey)) {
1337
1581
  return;
1338
1582
  }
1339
- processedCallSites.add(nodeKey);
1340
- callSites.push({
1341
- id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc.start.line}:${callNode.loc.start.column}:${callSiteCounterRef.value++}`,
1583
+ processedMethodCalls.add(nodeKey);
1584
+ const objectName = object.name;
1585
+ const constructorName = property.name;
1586
+ const fullName = `${objectName}.${constructorName}`;
1587
+ // Generate semantic ID for method-style constructor call
1588
+ const legacyId = `CALL#new:${fullName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
1589
+ let newMethodCallId = legacyId;
1590
+ if (scopeTracker) {
1591
+ const discriminator = scopeTracker.getItemCounter(`CALL:new:${fullName}`);
1592
+ newMethodCallId = computeSemanticId('CALL', `new:${fullName}`, scopeTracker.getContext(), { discriminator });
1593
+ }
1594
+ methodCalls.push({
1595
+ id: newMethodCallId,
1342
1596
  type: 'CALL',
1343
- name: callNode.callee.name,
1597
+ name: fullName,
1598
+ object: objectName,
1599
+ method: constructorName,
1344
1600
  file: module.file,
1345
- line: callNode.loc.start.line,
1601
+ line: getLine(newNode),
1602
+ column: getColumn(newNode),
1346
1603
  parentScopeId,
1347
- targetFunctionName: callNode.callee.name
1604
+ isNew: true
1348
1605
  });
1349
1606
  }
1350
1607
  }
1351
- },
1352
- // NewExpression на безусловном уровне
1353
- NewExpression: (newPath) => {
1354
- const parent = newPath.parent;
1355
- if (parent.type !== 'IfStatement' && parent.type !== 'ForStatement' && parent.type !== 'WhileStatement') {
1356
- const newNode = newPath.node;
1357
- if (newNode.callee.type === 'Identifier') {
1358
- const nodeKey = `new:${newNode.start}:${newNode.end}`;
1359
- if (processedCallSites.has(nodeKey)) {
1360
- return;
1608
+ }
1609
+ });
1610
+ }
1611
+ /**
1612
+ * Handle CallExpression nodes: direct function calls (greet(), main())
1613
+ * and method calls (obj.method(), data.process()).
1614
+ *
1615
+ * Handles:
1616
+ * - Direct function calls (Identifier callee) → callSites collection
1617
+ * - Method calls (MemberExpression callee) → methodCalls collection
1618
+ * - Array mutation detection (push, unshift, splice)
1619
+ * - Object.assign() detection
1620
+ *
1621
+ * @param callNode - The call expression AST node
1622
+ * @param processedCallSites - Set of already processed call site keys to avoid duplicates
1623
+ * @param processedMethodCalls - Set of already processed method call keys to avoid duplicates
1624
+ * @param callSites - Collection for direct function calls
1625
+ * @param methodCalls - Collection for method calls
1626
+ * @param module - Current module being analyzed
1627
+ * @param callSiteCounterRef - Counter for legacy ID generation
1628
+ * @param scopeTracker - Optional scope tracker for semantic ID generation
1629
+ * @param parentScopeId - ID of the parent scope containing this call
1630
+ * @param collections - Full collections object for array/object mutations
1631
+ */
1632
+ handleCallExpression(callNode, processedCallSites, processedMethodCalls, callSites, methodCalls, module, callSiteCounterRef, scopeTracker, parentScopeId, collections) {
1633
+ // Handle direct function calls (greet(), main())
1634
+ if (callNode.callee.type === 'Identifier') {
1635
+ const nodeKey = `${callNode.start}:${callNode.end}`;
1636
+ if (processedCallSites.has(nodeKey)) {
1637
+ return;
1638
+ }
1639
+ processedCallSites.add(nodeKey);
1640
+ // Generate semantic ID (primary) or legacy ID (fallback)
1641
+ const calleeName = callNode.callee.name;
1642
+ const legacyId = `CALL#${calleeName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
1643
+ let callId = legacyId;
1644
+ if (scopeTracker) {
1645
+ const discriminator = scopeTracker.getItemCounter(`CALL:${calleeName}`);
1646
+ callId = computeSemanticId('CALL', calleeName, scopeTracker.getContext(), { discriminator });
1647
+ }
1648
+ callSites.push({
1649
+ id: callId,
1650
+ type: 'CALL',
1651
+ name: calleeName,
1652
+ file: module.file,
1653
+ line: getLine(callNode),
1654
+ parentScopeId,
1655
+ targetFunctionName: calleeName
1656
+ });
1657
+ }
1658
+ // Handle method calls (obj.method(), data.process())
1659
+ else if (callNode.callee.type === 'MemberExpression') {
1660
+ const memberCallee = callNode.callee;
1661
+ const object = memberCallee.object;
1662
+ const property = memberCallee.property;
1663
+ const isComputed = memberCallee.computed;
1664
+ if ((object.type === 'Identifier' || object.type === 'ThisExpression') && property.type === 'Identifier') {
1665
+ const nodeKey = `${callNode.start}:${callNode.end}`;
1666
+ if (processedMethodCalls.has(nodeKey)) {
1667
+ return;
1668
+ }
1669
+ processedMethodCalls.add(nodeKey);
1670
+ const objectName = object.type === 'Identifier' ? object.name : 'this';
1671
+ const methodName = isComputed ? '<computed>' : property.name;
1672
+ const fullName = `${objectName}.${methodName}`;
1673
+ // Generate semantic ID (primary) or legacy ID (fallback)
1674
+ const legacyId = `CALL#${fullName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
1675
+ let methodCallId = legacyId;
1676
+ if (scopeTracker) {
1677
+ const discriminator = scopeTracker.getItemCounter(`CALL:${fullName}`);
1678
+ methodCallId = computeSemanticId('CALL', fullName, scopeTracker.getContext(), { discriminator });
1679
+ }
1680
+ methodCalls.push({
1681
+ id: methodCallId,
1682
+ type: 'CALL',
1683
+ name: fullName,
1684
+ object: objectName,
1685
+ method: methodName,
1686
+ computed: isComputed,
1687
+ computedPropertyVar: isComputed ? property.name : null,
1688
+ file: module.file,
1689
+ line: getLine(callNode),
1690
+ column: getColumn(callNode),
1691
+ parentScopeId
1692
+ });
1693
+ // Check for array mutation methods (push, unshift, splice)
1694
+ const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
1695
+ if (ARRAY_MUTATION_METHODS.includes(methodName)) {
1696
+ // Initialize collection if not exists
1697
+ if (!collections.arrayMutations) {
1698
+ collections.arrayMutations = [];
1699
+ }
1700
+ const arrayMutations = collections.arrayMutations;
1701
+ this.detectArrayMutationInFunction(callNode, objectName, methodName, module, arrayMutations, scopeTracker);
1702
+ }
1703
+ // Check for Object.assign() calls
1704
+ if (objectName === 'Object' && methodName === 'assign') {
1705
+ // Initialize collection if not exists
1706
+ if (!collections.objectMutations) {
1707
+ collections.objectMutations = [];
1708
+ }
1709
+ const objectMutations = collections.objectMutations;
1710
+ this.detectObjectAssignInFunction(callNode, module, objectMutations, scopeTracker);
1711
+ }
1712
+ }
1713
+ // REG-117: Nested array mutations like obj.arr.push(item)
1714
+ // object is MemberExpression, property is the method name
1715
+ else if (object.type === 'MemberExpression' && property.type === 'Identifier') {
1716
+ const nestedMember = object;
1717
+ const methodName = property.name;
1718
+ const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
1719
+ if (ARRAY_MUTATION_METHODS.includes(methodName)) {
1720
+ // Extract base object and property from nested MemberExpression
1721
+ const base = nestedMember.object;
1722
+ const prop = nestedMember.property;
1723
+ // Only handle single-level nesting: obj.arr.push() or this.items.push()
1724
+ if ((base.type === 'Identifier' || base.type === 'ThisExpression') &&
1725
+ !nestedMember.computed &&
1726
+ prop.type === 'Identifier') {
1727
+ const baseObjectName = base.type === 'Identifier' ? base.name : 'this';
1728
+ const propertyName = prop.name;
1729
+ // Initialize collection if not exists
1730
+ if (!collections.arrayMutations) {
1731
+ collections.arrayMutations = [];
1361
1732
  }
1362
- processedCallSites.add(nodeKey);
1363
- callSites.push({
1364
- id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc.start.line}:${newNode.loc.start.column}:${callSiteCounterRef.value++}`,
1365
- type: 'CALL',
1366
- name: newNode.callee.name,
1367
- file: module.file,
1368
- line: newNode.loc.start.line,
1369
- parentScopeId,
1370
- targetFunctionName: newNode.callee.name,
1371
- isNew: true
1372
- });
1733
+ const arrayMutations = collections.arrayMutations;
1734
+ this.detectArrayMutationInFunction(callNode, `${baseObjectName}.${propertyName}`, // arrayName for ID purposes
1735
+ methodName, module, arrayMutations, scopeTracker, true, // isNested
1736
+ baseObjectName, propertyName);
1373
1737
  }
1374
1738
  }
1375
1739
  }
1740
+ }
1741
+ }
1742
+ /**
1743
+ * Detect array mutation calls (push, unshift, splice) inside functions
1744
+ * and collect mutation info for FLOWS_INTO edge creation in GraphBuilder
1745
+ *
1746
+ * REG-117: Added isNested, baseObjectName, propertyName for nested mutations
1747
+ *
1748
+ * @param callNode - The call expression node
1749
+ * @param arrayName - Name of the array being mutated
1750
+ * @param method - The mutation method (push, unshift, splice)
1751
+ * @param module - Current module being analyzed
1752
+ * @param arrayMutations - Collection to push mutation info into
1753
+ * @param scopeTracker - Optional scope tracker for semantic IDs
1754
+ * @param isNested - REG-117: true if this is a nested mutation (obj.arr.push)
1755
+ * @param baseObjectName - REG-117: base object name for nested mutations
1756
+ * @param propertyName - REG-117: property name for nested mutations
1757
+ */
1758
+ detectArrayMutationInFunction(callNode, arrayName, method, module, arrayMutations, scopeTracker, isNested, baseObjectName, propertyName) {
1759
+ const mutationArgs = [];
1760
+ // For splice, only arguments from index 2 onwards are insertions
1761
+ // splice(start, deleteCount, item1, item2, ...)
1762
+ callNode.arguments.forEach((arg, index) => {
1763
+ // Skip start and deleteCount for splice
1764
+ if (method === 'splice' && index < 2)
1765
+ return;
1766
+ const argInfo = {
1767
+ argIndex: method === 'splice' ? index - 2 : index,
1768
+ isSpread: arg.type === 'SpreadElement',
1769
+ valueType: 'EXPRESSION' // Default
1770
+ };
1771
+ let actualArg = arg;
1772
+ if (arg.type === 'SpreadElement') {
1773
+ actualArg = arg.argument;
1774
+ }
1775
+ // Determine value type
1776
+ const literalValue = ExpressionEvaluator.extractLiteralValue(actualArg);
1777
+ if (literalValue !== null) {
1778
+ argInfo.valueType = 'LITERAL';
1779
+ argInfo.literalValue = literalValue;
1780
+ }
1781
+ else if (actualArg.type === 'Identifier') {
1782
+ argInfo.valueType = 'VARIABLE';
1783
+ argInfo.valueName = actualArg.name;
1784
+ }
1785
+ else if (actualArg.type === 'ObjectExpression') {
1786
+ argInfo.valueType = 'OBJECT_LITERAL';
1787
+ }
1788
+ else if (actualArg.type === 'ArrayExpression') {
1789
+ argInfo.valueType = 'ARRAY_LITERAL';
1790
+ }
1791
+ else if (actualArg.type === 'CallExpression') {
1792
+ argInfo.valueType = 'CALL';
1793
+ argInfo.callLine = actualArg.loc?.start.line;
1794
+ argInfo.callColumn = actualArg.loc?.start.column;
1795
+ }
1796
+ mutationArgs.push(argInfo);
1376
1797
  });
1798
+ // Only record if there are actual insertions
1799
+ if (mutationArgs.length > 0) {
1800
+ const line = callNode.loc?.start.line ?? 0;
1801
+ const column = callNode.loc?.start.column ?? 0;
1802
+ // Generate semantic ID for array mutation if scopeTracker available
1803
+ let mutationId;
1804
+ if (scopeTracker) {
1805
+ const discriminator = scopeTracker.getItemCounter(`ARRAY_MUTATION:${arrayName}.${method}`);
1806
+ mutationId = computeSemanticId('ARRAY_MUTATION', `${arrayName}.${method}`, scopeTracker.getContext(), { discriminator });
1807
+ }
1808
+ arrayMutations.push({
1809
+ id: mutationId,
1810
+ arrayName,
1811
+ mutationMethod: method,
1812
+ file: module.file,
1813
+ line,
1814
+ column,
1815
+ insertedValues: mutationArgs,
1816
+ // REG-117: Nested mutation fields
1817
+ isNested,
1818
+ baseObjectName,
1819
+ propertyName
1820
+ });
1821
+ }
1822
+ }
1823
+ /**
1824
+ * Detect indexed array assignment: arr[i] = value
1825
+ * Creates ArrayMutationInfo for FLOWS_INTO edge generation in GraphBuilder
1826
+ *
1827
+ * @param assignNode - The assignment expression node
1828
+ * @param module - Current module being analyzed
1829
+ * @param arrayMutations - Collection to push mutation info into
1830
+ */
1831
+ detectIndexedArrayAssignment(assignNode, module, arrayMutations) {
1832
+ // Check for indexed array assignment: arr[i] = value
1833
+ if (assignNode.left.type === 'MemberExpression' && assignNode.left.computed) {
1834
+ const memberExpr = assignNode.left;
1835
+ // Only process NumericLiteral keys - those are clearly array indexed assignments
1836
+ // e.g., arr[0] = value, arr[1] = value
1837
+ // All other computed keys (StringLiteral, Identifier, expressions) are handled as object mutations
1838
+ // This avoids duplicate edge creation for ambiguous cases like obj[key] = value
1839
+ if (memberExpr.property.type !== 'NumericLiteral') {
1840
+ return;
1841
+ }
1842
+ // Get array name (only simple identifiers for now)
1843
+ if (memberExpr.object.type === 'Identifier') {
1844
+ const arrayName = memberExpr.object.name;
1845
+ const value = assignNode.right;
1846
+ const argInfo = {
1847
+ argIndex: 0,
1848
+ isSpread: false,
1849
+ valueType: 'EXPRESSION'
1850
+ };
1851
+ // Determine value type
1852
+ const literalValue = ExpressionEvaluator.extractLiteralValue(value);
1853
+ if (literalValue !== null) {
1854
+ argInfo.valueType = 'LITERAL';
1855
+ argInfo.literalValue = literalValue;
1856
+ }
1857
+ else if (value.type === 'Identifier') {
1858
+ argInfo.valueType = 'VARIABLE';
1859
+ argInfo.valueName = value.name;
1860
+ }
1861
+ else if (value.type === 'ObjectExpression') {
1862
+ argInfo.valueType = 'OBJECT_LITERAL';
1863
+ }
1864
+ else if (value.type === 'ArrayExpression') {
1865
+ argInfo.valueType = 'ARRAY_LITERAL';
1866
+ }
1867
+ else if (value.type === 'CallExpression') {
1868
+ argInfo.valueType = 'CALL';
1869
+ argInfo.callLine = value.loc?.start.line;
1870
+ argInfo.callColumn = value.loc?.start.column;
1871
+ }
1872
+ // Use defensive loc checks instead of ! assertions
1873
+ const line = assignNode.loc?.start.line ?? 0;
1874
+ const column = assignNode.loc?.start.column ?? 0;
1875
+ arrayMutations.push({
1876
+ arrayName,
1877
+ mutationMethod: 'indexed',
1878
+ file: module.file,
1879
+ line: line,
1880
+ column: column,
1881
+ insertedValues: [argInfo]
1882
+ });
1883
+ }
1884
+ }
1885
+ }
1886
+ /**
1887
+ * Detect object property assignment: obj.prop = value, obj['prop'] = value
1888
+ * Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
1889
+ *
1890
+ * @param assignNode - The assignment expression node
1891
+ * @param module - Current module being analyzed
1892
+ * @param objectMutations - Collection to push mutation info into
1893
+ * @param scopeTracker - Optional scope tracker for semantic IDs
1894
+ */
1895
+ detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker) {
1896
+ // Check for property assignment: obj.prop = value or obj['prop'] = value
1897
+ if (assignNode.left.type !== 'MemberExpression')
1898
+ return;
1899
+ const memberExpr = assignNode.left;
1900
+ // Skip NumericLiteral indexed assignment (handled by array mutation handler)
1901
+ // Array mutation handler processes: arr[0] (numeric literal index)
1902
+ // Object mutation handler processes: obj.prop, obj['prop'], obj[key], obj[expr]
1903
+ if (memberExpr.computed && memberExpr.property.type === 'NumericLiteral') {
1904
+ return; // Let array mutation handler deal with this
1905
+ }
1906
+ // Get object name and enclosing class context for 'this'
1907
+ let objectName;
1908
+ let enclosingClassName;
1909
+ if (memberExpr.object.type === 'Identifier') {
1910
+ objectName = memberExpr.object.name;
1911
+ }
1912
+ else if (memberExpr.object.type === 'ThisExpression') {
1913
+ objectName = 'this';
1914
+ // REG-152: Extract enclosing class name from scope context
1915
+ if (scopeTracker) {
1916
+ enclosingClassName = scopeTracker.getEnclosingScope('CLASS');
1917
+ }
1918
+ }
1919
+ else {
1920
+ // Complex expressions like obj.nested.prop = value
1921
+ // For now, skip these (documented limitation)
1922
+ return;
1923
+ }
1924
+ // Get property name
1925
+ let propertyName;
1926
+ let mutationType;
1927
+ let computedPropertyVar;
1928
+ if (!memberExpr.computed) {
1929
+ // obj.prop
1930
+ if (memberExpr.property.type === 'Identifier') {
1931
+ propertyName = memberExpr.property.name;
1932
+ mutationType = 'property';
1933
+ }
1934
+ else {
1935
+ return; // Unexpected property type
1936
+ }
1937
+ }
1938
+ else {
1939
+ // obj['prop'] or obj[key]
1940
+ if (memberExpr.property.type === 'StringLiteral') {
1941
+ propertyName = memberExpr.property.value;
1942
+ mutationType = 'property'; // String literal is effectively a property name
1943
+ }
1944
+ else {
1945
+ propertyName = '<computed>';
1946
+ mutationType = 'computed';
1947
+ // Capture variable name for later resolution in enrichment phase
1948
+ if (memberExpr.property.type === 'Identifier') {
1949
+ computedPropertyVar = memberExpr.property.name;
1950
+ }
1951
+ }
1952
+ }
1953
+ // Extract value info
1954
+ const value = assignNode.right;
1955
+ const valueInfo = this.extractMutationValue(value);
1956
+ // Use defensive loc checks
1957
+ const line = assignNode.loc?.start.line ?? 0;
1958
+ const column = assignNode.loc?.start.column ?? 0;
1959
+ // Generate semantic ID if scopeTracker available
1960
+ let mutationId;
1961
+ if (scopeTracker) {
1962
+ const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:${objectName}.${propertyName}`);
1963
+ mutationId = computeSemanticId('OBJECT_MUTATION', `${objectName}.${propertyName}`, scopeTracker.getContext(), { discriminator });
1964
+ }
1965
+ objectMutations.push({
1966
+ id: mutationId,
1967
+ objectName,
1968
+ enclosingClassName, // REG-152: Class name for 'this' mutations
1969
+ propertyName,
1970
+ mutationType,
1971
+ computedPropertyVar,
1972
+ file: module.file,
1973
+ line,
1974
+ column,
1975
+ value: valueInfo
1976
+ });
1977
+ }
1978
+ /**
1979
+ * Extract value information from an expression for mutation tracking
1980
+ */
1981
+ extractMutationValue(value) {
1982
+ const valueInfo = {
1983
+ valueType: 'EXPRESSION' // Default
1984
+ };
1985
+ const literalValue = ExpressionEvaluator.extractLiteralValue(value);
1986
+ if (literalValue !== null) {
1987
+ valueInfo.valueType = 'LITERAL';
1988
+ valueInfo.literalValue = literalValue;
1989
+ }
1990
+ else if (value.type === 'Identifier') {
1991
+ valueInfo.valueType = 'VARIABLE';
1992
+ valueInfo.valueName = value.name;
1993
+ }
1994
+ else if (value.type === 'ObjectExpression') {
1995
+ valueInfo.valueType = 'OBJECT_LITERAL';
1996
+ }
1997
+ else if (value.type === 'ArrayExpression') {
1998
+ valueInfo.valueType = 'ARRAY_LITERAL';
1999
+ }
2000
+ else if (value.type === 'CallExpression') {
2001
+ valueInfo.valueType = 'CALL';
2002
+ valueInfo.callLine = value.loc?.start.line;
2003
+ valueInfo.callColumn = value.loc?.start.column;
2004
+ }
2005
+ return valueInfo;
2006
+ }
2007
+ /**
2008
+ * Detect Object.assign() calls inside functions
2009
+ * Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
2010
+ */
2011
+ detectObjectAssignInFunction(callNode, module, objectMutations, scopeTracker) {
2012
+ // Need at least 2 arguments: target and at least one source
2013
+ if (callNode.arguments.length < 2)
2014
+ return;
2015
+ // First argument is target
2016
+ const targetArg = callNode.arguments[0];
2017
+ let targetName;
2018
+ if (targetArg.type === 'Identifier') {
2019
+ targetName = targetArg.name;
2020
+ }
2021
+ else if (targetArg.type === 'ObjectExpression') {
2022
+ targetName = '<anonymous>';
2023
+ }
2024
+ else {
2025
+ return;
2026
+ }
2027
+ const line = callNode.loc?.start.line ?? 0;
2028
+ const column = callNode.loc?.start.column ?? 0;
2029
+ for (let i = 1; i < callNode.arguments.length; i++) {
2030
+ let arg = callNode.arguments[i];
2031
+ let isSpread = false;
2032
+ if (arg.type === 'SpreadElement') {
2033
+ isSpread = true;
2034
+ arg = arg.argument;
2035
+ }
2036
+ const valueInfo = {
2037
+ valueType: 'EXPRESSION',
2038
+ argIndex: i - 1,
2039
+ isSpread
2040
+ };
2041
+ const literalValue = ExpressionEvaluator.extractLiteralValue(arg);
2042
+ if (literalValue !== null) {
2043
+ valueInfo.valueType = 'LITERAL';
2044
+ valueInfo.literalValue = literalValue;
2045
+ }
2046
+ else if (arg.type === 'Identifier') {
2047
+ valueInfo.valueType = 'VARIABLE';
2048
+ valueInfo.valueName = arg.name;
2049
+ }
2050
+ else if (arg.type === 'ObjectExpression') {
2051
+ valueInfo.valueType = 'OBJECT_LITERAL';
2052
+ }
2053
+ else if (arg.type === 'ArrayExpression') {
2054
+ valueInfo.valueType = 'ARRAY_LITERAL';
2055
+ }
2056
+ else if (arg.type === 'CallExpression') {
2057
+ valueInfo.valueType = 'CALL';
2058
+ valueInfo.callLine = arg.loc?.start.line;
2059
+ valueInfo.callColumn = arg.loc?.start.column;
2060
+ }
2061
+ let mutationId;
2062
+ if (scopeTracker) {
2063
+ const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:Object.assign:${targetName}`);
2064
+ mutationId = computeSemanticId('OBJECT_MUTATION', `Object.assign:${targetName}`, scopeTracker.getContext(), { discriminator });
2065
+ }
2066
+ objectMutations.push({
2067
+ id: mutationId,
2068
+ objectName: targetName,
2069
+ propertyName: '<assign>',
2070
+ mutationType: 'assign',
2071
+ file: module.file,
2072
+ line,
2073
+ column,
2074
+ value: valueInfo
2075
+ });
2076
+ }
1377
2077
  }
1378
2078
  }