@grafema/core 0.1.0-alpha.1

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 (402) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +76 -0
  3. package/dist/Orchestrator.d.ts +142 -0
  4. package/dist/Orchestrator.d.ts.map +1 -0
  5. package/dist/Orchestrator.js +481 -0
  6. package/dist/api/GraphAPI.d.ts +87 -0
  7. package/dist/api/GraphAPI.d.ts.map +1 -0
  8. package/dist/api/GraphAPI.js +210 -0
  9. package/dist/api/GuaranteeAPI.d.ts +147 -0
  10. package/dist/api/GuaranteeAPI.d.ts.map +1 -0
  11. package/dist/api/GuaranteeAPI.js +288 -0
  12. package/dist/core/ASTWorker.d.ts +133 -0
  13. package/dist/core/ASTWorker.d.ts.map +1 -0
  14. package/dist/core/ASTWorker.js +352 -0
  15. package/dist/core/ASTWorkerPool.d.ts +85 -0
  16. package/dist/core/ASTWorkerPool.d.ts.map +1 -0
  17. package/dist/core/ASTWorkerPool.js +207 -0
  18. package/dist/core/AnalysisQueue.d.ts +104 -0
  19. package/dist/core/AnalysisQueue.d.ts.map +1 -0
  20. package/dist/core/AnalysisQueue.js +299 -0
  21. package/dist/core/AnalysisWorker.d.ts +14 -0
  22. package/dist/core/AnalysisWorker.d.ts.map +1 -0
  23. package/dist/core/AnalysisWorker.js +307 -0
  24. package/dist/core/GraphBackend.d.ts +156 -0
  25. package/dist/core/GraphBackend.d.ts.map +1 -0
  26. package/dist/core/GraphBackend.js +85 -0
  27. package/dist/core/GuaranteeManager.d.ts +230 -0
  28. package/dist/core/GuaranteeManager.d.ts.map +1 -0
  29. package/dist/core/GuaranteeManager.js +352 -0
  30. package/dist/core/ManifestStore.d.ts +71 -0
  31. package/dist/core/ManifestStore.d.ts.map +1 -0
  32. package/dist/core/ManifestStore.js +146 -0
  33. package/dist/core/NodeFactory.d.ts +160 -0
  34. package/dist/core/NodeFactory.d.ts.map +1 -0
  35. package/dist/core/NodeFactory.js +137 -0
  36. package/dist/core/NodeId.d.ts +88 -0
  37. package/dist/core/NodeId.d.ts.map +1 -0
  38. package/dist/core/NodeId.js +170 -0
  39. package/dist/core/ParallelAnalyzer.d.ts +120 -0
  40. package/dist/core/ParallelAnalyzer.d.ts.map +1 -0
  41. package/dist/core/ParallelAnalyzer.js +331 -0
  42. package/dist/core/PriorityQueue.d.ts +106 -0
  43. package/dist/core/PriorityQueue.d.ts.map +1 -0
  44. package/dist/core/PriorityQueue.js +168 -0
  45. package/dist/core/Profiler.d.ts +75 -0
  46. package/dist/core/Profiler.d.ts.map +1 -0
  47. package/dist/core/Profiler.js +149 -0
  48. package/dist/core/QueueWorker.d.ts +12 -0
  49. package/dist/core/QueueWorker.d.ts.map +1 -0
  50. package/dist/core/QueueWorker.js +567 -0
  51. package/dist/core/RFDBClient.d.ts +179 -0
  52. package/dist/core/RFDBClient.d.ts.map +1 -0
  53. package/dist/core/RFDBClient.js +429 -0
  54. package/dist/core/Task.d.ts +56 -0
  55. package/dist/core/Task.d.ts.map +1 -0
  56. package/dist/core/Task.js +85 -0
  57. package/dist/core/TaskTypes.d.ts +20 -0
  58. package/dist/core/TaskTypes.d.ts.map +1 -0
  59. package/dist/core/TaskTypes.js +10 -0
  60. package/dist/core/VersionManager.d.ts +166 -0
  61. package/dist/core/VersionManager.d.ts.map +1 -0
  62. package/dist/core/VersionManager.js +237 -0
  63. package/dist/core/WorkerPool.d.ts +82 -0
  64. package/dist/core/WorkerPool.d.ts.map +1 -0
  65. package/dist/core/WorkerPool.js +109 -0
  66. package/dist/core/nodes/CallSiteNode.d.ts +26 -0
  67. package/dist/core/nodes/CallSiteNode.d.ts.map +1 -0
  68. package/dist/core/nodes/CallSiteNode.js +44 -0
  69. package/dist/core/nodes/ClassNode.d.ts +25 -0
  70. package/dist/core/nodes/ClassNode.d.ts.map +1 -0
  71. package/dist/core/nodes/ClassNode.js +40 -0
  72. package/dist/core/nodes/ConstantNode.d.ts +24 -0
  73. package/dist/core/nodes/ConstantNode.d.ts.map +1 -0
  74. package/dist/core/nodes/ConstantNode.js +39 -0
  75. package/dist/core/nodes/DatabaseQueryNode.d.ts +22 -0
  76. package/dist/core/nodes/DatabaseQueryNode.d.ts.map +1 -0
  77. package/dist/core/nodes/DatabaseQueryNode.js +37 -0
  78. package/dist/core/nodes/EntrypointNode.d.ts +102 -0
  79. package/dist/core/nodes/EntrypointNode.d.ts.map +1 -0
  80. package/dist/core/nodes/EntrypointNode.js +119 -0
  81. package/dist/core/nodes/EventListenerNode.d.ts +25 -0
  82. package/dist/core/nodes/EventListenerNode.d.ts.map +1 -0
  83. package/dist/core/nodes/EventListenerNode.js +39 -0
  84. package/dist/core/nodes/ExportNode.d.ts +26 -0
  85. package/dist/core/nodes/ExportNode.d.ts.map +1 -0
  86. package/dist/core/nodes/ExportNode.js +40 -0
  87. package/dist/core/nodes/ExternalStdioNode.d.ts +17 -0
  88. package/dist/core/nodes/ExternalStdioNode.d.ts.map +1 -0
  89. package/dist/core/nodes/ExternalStdioNode.js +26 -0
  90. package/dist/core/nodes/FunctionNode.d.ts +27 -0
  91. package/dist/core/nodes/FunctionNode.d.ts.map +1 -0
  92. package/dist/core/nodes/FunctionNode.js +53 -0
  93. package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
  94. package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
  95. package/dist/core/nodes/GuaranteeNode.js +117 -0
  96. package/dist/core/nodes/HttpRequestNode.d.ts +24 -0
  97. package/dist/core/nodes/HttpRequestNode.d.ts.map +1 -0
  98. package/dist/core/nodes/HttpRequestNode.js +38 -0
  99. package/dist/core/nodes/ImportNode.d.ts +27 -0
  100. package/dist/core/nodes/ImportNode.d.ts.map +1 -0
  101. package/dist/core/nodes/ImportNode.js +43 -0
  102. package/dist/core/nodes/LiteralNode.d.ts +26 -0
  103. package/dist/core/nodes/LiteralNode.d.ts.map +1 -0
  104. package/dist/core/nodes/LiteralNode.js +40 -0
  105. package/dist/core/nodes/MethodCallNode.d.ts +29 -0
  106. package/dist/core/nodes/MethodCallNode.d.ts.map +1 -0
  107. package/dist/core/nodes/MethodCallNode.js +47 -0
  108. package/dist/core/nodes/MethodNode.d.ts +29 -0
  109. package/dist/core/nodes/MethodNode.d.ts.map +1 -0
  110. package/dist/core/nodes/MethodNode.js +44 -0
  111. package/dist/core/nodes/ModuleNode.d.ts +29 -0
  112. package/dist/core/nodes/ModuleNode.d.ts.map +1 -0
  113. package/dist/core/nodes/ModuleNode.js +49 -0
  114. package/dist/core/nodes/NodeKind.d.ts +91 -0
  115. package/dist/core/nodes/NodeKind.d.ts.map +1 -0
  116. package/dist/core/nodes/NodeKind.js +146 -0
  117. package/dist/core/nodes/ParameterNode.d.ts +26 -0
  118. package/dist/core/nodes/ParameterNode.d.ts.map +1 -0
  119. package/dist/core/nodes/ParameterNode.js +43 -0
  120. package/dist/core/nodes/ScopeNode.d.ts +32 -0
  121. package/dist/core/nodes/ScopeNode.d.ts.map +1 -0
  122. package/dist/core/nodes/ScopeNode.js +47 -0
  123. package/dist/core/nodes/ServiceNode.d.ts +44 -0
  124. package/dist/core/nodes/ServiceNode.d.ts.map +1 -0
  125. package/dist/core/nodes/ServiceNode.js +49 -0
  126. package/dist/core/nodes/VariableDeclarationNode.d.ts +22 -0
  127. package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -0
  128. package/dist/core/nodes/VariableDeclarationNode.js +38 -0
  129. package/dist/core/nodes/index.d.ts +25 -0
  130. package/dist/core/nodes/index.d.ts.map +1 -0
  131. package/dist/core/nodes/index.js +30 -0
  132. package/dist/index.d.ts +57 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +63 -0
  135. package/dist/plugins/Plugin.d.ts +44 -0
  136. package/dist/plugins/Plugin.d.ts.map +1 -0
  137. package/dist/plugins/Plugin.js +46 -0
  138. package/dist/plugins/analysis/DatabaseAnalyzer.d.ts +23 -0
  139. package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -0
  140. package/dist/plugins/analysis/DatabaseAnalyzer.js +260 -0
  141. package/dist/plugins/analysis/ExpressAnalyzer.d.ts +19 -0
  142. package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -0
  143. package/dist/plugins/analysis/ExpressAnalyzer.js +306 -0
  144. package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts +17 -0
  145. package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -0
  146. package/dist/plugins/analysis/ExpressRouteAnalyzer.js +308 -0
  147. package/dist/plugins/analysis/FetchAnalyzer.d.ts +38 -0
  148. package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -0
  149. package/dist/plugins/analysis/FetchAnalyzer.js +344 -0
  150. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts +65 -0
  151. package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts.map +1 -0
  152. package/dist/plugins/analysis/IncrementalAnalysisPlugin.js +472 -0
  153. package/dist/plugins/analysis/JSASTAnalyzer.d.ts +84 -0
  154. package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -0
  155. package/dist/plugins/analysis/JSASTAnalyzer.js +1378 -0
  156. package/dist/plugins/analysis/ReactAnalyzer.d.ts +90 -0
  157. package/dist/plugins/analysis/ReactAnalyzer.d.ts.map +1 -0
  158. package/dist/plugins/analysis/ReactAnalyzer.js +1153 -0
  159. package/dist/plugins/analysis/RustAnalyzer.d.ts +13 -0
  160. package/dist/plugins/analysis/RustAnalyzer.d.ts.map +1 -0
  161. package/dist/plugins/analysis/RustAnalyzer.js +259 -0
  162. package/dist/plugins/analysis/SQLiteAnalyzer.d.ts +21 -0
  163. package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -0
  164. package/dist/plugins/analysis/SQLiteAnalyzer.js +317 -0
  165. package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts +35 -0
  166. package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts.map +1 -0
  167. package/dist/plugins/analysis/ServiceLayerAnalyzer.js +303 -0
  168. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts +33 -0
  169. package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -0
  170. package/dist/plugins/analysis/SocketIOAnalyzer.js +283 -0
  171. package/dist/plugins/analysis/SystemDbAnalyzer.d.ts +27 -0
  172. package/dist/plugins/analysis/SystemDbAnalyzer.d.ts.map +1 -0
  173. package/dist/plugins/analysis/SystemDbAnalyzer.js +211 -0
  174. package/dist/plugins/analysis/ast/ConditionParser.d.ts +85 -0
  175. package/dist/plugins/analysis/ast/ConditionParser.d.ts.map +1 -0
  176. package/dist/plugins/analysis/ast/ConditionParser.js +277 -0
  177. package/dist/plugins/analysis/ast/ExpressionEvaluator.d.ts +15 -0
  178. package/dist/plugins/analysis/ast/ExpressionEvaluator.d.ts.map +1 -0
  179. package/dist/plugins/analysis/ast/ExpressionEvaluator.js +91 -0
  180. package/dist/plugins/analysis/ast/GraphBuilder.d.ts +77 -0
  181. package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -0
  182. package/dist/plugins/analysis/ast/GraphBuilder.js +1077 -0
  183. package/dist/plugins/analysis/ast/OxcAdapter.d.ts +41 -0
  184. package/dist/plugins/analysis/ast/OxcAdapter.d.ts.map +1 -0
  185. package/dist/plugins/analysis/ast/OxcAdapter.js +40 -0
  186. package/dist/plugins/analysis/ast/types.d.ts +346 -0
  187. package/dist/plugins/analysis/ast/types.d.ts.map +1 -0
  188. package/dist/plugins/analysis/ast/types.js +4 -0
  189. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +93 -0
  190. package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -0
  191. package/dist/plugins/analysis/ast/visitors/ASTVisitor.js +24 -0
  192. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +77 -0
  193. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -0
  194. package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +377 -0
  195. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts +27 -0
  196. package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts.map +1 -0
  197. package/dist/plugins/analysis/ast/visitors/ClassVisitor.js +232 -0
  198. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +25 -0
  199. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -0
  200. package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +172 -0
  201. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts +29 -0
  202. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -0
  203. package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +180 -0
  204. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts +14 -0
  205. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts.map +1 -0
  206. package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js +200 -0
  207. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +45 -0
  208. package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -0
  209. package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +150 -0
  210. package/dist/plugins/analysis/ast/visitors/index.d.ts +17 -0
  211. package/dist/plugins/analysis/ast/visitors/index.d.ts.map +1 -0
  212. package/dist/plugins/analysis/ast/visitors/index.js +13 -0
  213. package/dist/plugins/discovery/DiscoveryPlugin.d.ts +34 -0
  214. package/dist/plugins/discovery/DiscoveryPlugin.d.ts.map +1 -0
  215. package/dist/plugins/discovery/DiscoveryPlugin.js +26 -0
  216. package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts +26 -0
  217. package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts.map +1 -0
  218. package/dist/plugins/discovery/MonorepoServiceDiscovery.js +79 -0
  219. package/dist/plugins/discovery/SimpleProjectDiscovery.d.ts +14 -0
  220. package/dist/plugins/discovery/SimpleProjectDiscovery.d.ts.map +1 -0
  221. package/dist/plugins/discovery/SimpleProjectDiscovery.js +65 -0
  222. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts +19 -0
  223. package/dist/plugins/discovery/ZonServiceDiscovery.d.ts.map +1 -0
  224. package/dist/plugins/discovery/ZonServiceDiscovery.js +204 -0
  225. package/dist/plugins/enrichment/AliasTracker.d.ts +40 -0
  226. package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -0
  227. package/dist/plugins/enrichment/AliasTracker.js +290 -0
  228. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts +30 -0
  229. package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -0
  230. package/dist/plugins/enrichment/HTTPConnectionEnricher.js +135 -0
  231. package/dist/plugins/enrichment/ImportExportLinker.d.ts +30 -0
  232. package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -0
  233. package/dist/plugins/enrichment/ImportExportLinker.js +176 -0
  234. package/dist/plugins/enrichment/InstanceOfResolver.d.ts +21 -0
  235. package/dist/plugins/enrichment/InstanceOfResolver.d.ts.map +1 -0
  236. package/dist/plugins/enrichment/InstanceOfResolver.js +117 -0
  237. package/dist/plugins/enrichment/MethodCallResolver.d.ts +41 -0
  238. package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -0
  239. package/dist/plugins/enrichment/MethodCallResolver.js +252 -0
  240. package/dist/plugins/enrichment/MountPointResolver.d.ts +26 -0
  241. package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -0
  242. package/dist/plugins/enrichment/MountPointResolver.js +189 -0
  243. package/dist/plugins/enrichment/PrefixEvaluator.d.ts +89 -0
  244. package/dist/plugins/enrichment/PrefixEvaluator.d.ts.map +1 -0
  245. package/dist/plugins/enrichment/PrefixEvaluator.js +415 -0
  246. package/dist/plugins/enrichment/RustFFIEnricher.d.ts +25 -0
  247. package/dist/plugins/enrichment/RustFFIEnricher.d.ts.map +1 -0
  248. package/dist/plugins/enrichment/RustFFIEnricher.js +170 -0
  249. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +114 -0
  250. package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -0
  251. package/dist/plugins/enrichment/ValueDomainAnalyzer.js +464 -0
  252. package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts +27 -0
  253. package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts.map +1 -0
  254. package/dist/plugins/indexing/IncrementalModuleIndexer.js +238 -0
  255. package/dist/plugins/indexing/JSModuleIndexer.d.ts +33 -0
  256. package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -0
  257. package/dist/plugins/indexing/JSModuleIndexer.js +299 -0
  258. package/dist/plugins/indexing/RustModuleIndexer.d.ts +28 -0
  259. package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -0
  260. package/dist/plugins/indexing/RustModuleIndexer.js +140 -0
  261. package/dist/plugins/indexing/ServiceDetector.d.ts +46 -0
  262. package/dist/plugins/indexing/ServiceDetector.d.ts.map +1 -0
  263. package/dist/plugins/indexing/ServiceDetector.js +164 -0
  264. package/dist/plugins/validation/CallResolverValidator.d.ts +23 -0
  265. package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -0
  266. package/dist/plugins/validation/CallResolverValidator.js +108 -0
  267. package/dist/plugins/validation/DataFlowValidator.d.ts +24 -0
  268. package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -0
  269. package/dist/plugins/validation/DataFlowValidator.js +148 -0
  270. package/dist/plugins/validation/EvalBanValidator.d.ts +25 -0
  271. package/dist/plugins/validation/EvalBanValidator.d.ts.map +1 -0
  272. package/dist/plugins/validation/EvalBanValidator.js +123 -0
  273. package/dist/plugins/validation/GraphConnectivityValidator.d.ts +11 -0
  274. package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -0
  275. package/dist/plugins/validation/GraphConnectivityValidator.js +135 -0
  276. package/dist/plugins/validation/SQLInjectionValidator.d.ts +43 -0
  277. package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -0
  278. package/dist/plugins/validation/SQLInjectionValidator.js +251 -0
  279. package/dist/plugins/validation/ShadowingDetector.d.ts +26 -0
  280. package/dist/plugins/validation/ShadowingDetector.d.ts.map +1 -0
  281. package/dist/plugins/validation/ShadowingDetector.js +119 -0
  282. package/dist/plugins/validation/TypeScriptDeadCodeValidator.d.ts +21 -0
  283. package/dist/plugins/validation/TypeScriptDeadCodeValidator.d.ts.map +1 -0
  284. package/dist/plugins/validation/TypeScriptDeadCodeValidator.js +151 -0
  285. package/dist/plugins/vcs/GitPlugin.d.ts +84 -0
  286. package/dist/plugins/vcs/GitPlugin.d.ts.map +1 -0
  287. package/dist/plugins/vcs/GitPlugin.js +295 -0
  288. package/dist/plugins/vcs/VCSPlugin.d.ts +133 -0
  289. package/dist/plugins/vcs/VCSPlugin.d.ts.map +1 -0
  290. package/dist/plugins/vcs/VCSPlugin.js +82 -0
  291. package/dist/plugins/vcs/index.d.ts +10 -0
  292. package/dist/plugins/vcs/index.d.ts.map +1 -0
  293. package/dist/plugins/vcs/index.js +18 -0
  294. package/dist/storage/backends/RFDBServerBackend.d.ts +258 -0
  295. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
  296. package/dist/storage/backends/RFDBServerBackend.js +565 -0
  297. package/dist/storage/backends/typeValidation.d.ts +47 -0
  298. package/dist/storage/backends/typeValidation.d.ts.map +1 -0
  299. package/dist/storage/backends/typeValidation.js +137 -0
  300. package/dist/validation/PathValidator.d.ts +81 -0
  301. package/dist/validation/PathValidator.d.ts.map +1 -0
  302. package/dist/validation/PathValidator.js +251 -0
  303. package/package.json +57 -0
  304. package/src/.rfguard/current-session.txt +1 -0
  305. package/src/Orchestrator.ts +673 -0
  306. package/src/api/GraphAPI.ts +305 -0
  307. package/src/api/GuaranteeAPI.ts +401 -0
  308. package/src/core/ASTWorker.ts +567 -0
  309. package/src/core/ASTWorkerPool.ts +299 -0
  310. package/src/core/AnalysisQueue.ts +447 -0
  311. package/src/core/AnalysisWorker.ts +410 -0
  312. package/src/core/GraphBackend.ts +265 -0
  313. package/src/core/GuaranteeManager.ts +581 -0
  314. package/src/core/ManifestStore.ts +196 -0
  315. package/src/core/NodeFactory.ts +274 -0
  316. package/src/core/NodeId.ts +257 -0
  317. package/src/core/ParallelAnalyzer.ts +476 -0
  318. package/src/core/PriorityQueue.ts +227 -0
  319. package/src/core/Profiler.ts +188 -0
  320. package/src/core/QueueWorker.ts +780 -0
  321. package/src/core/Task.ts +107 -0
  322. package/src/core/TaskTypes.ts +40 -0
  323. package/src/core/VersionManager.ts +404 -0
  324. package/src/core/WorkerPool.ts +180 -0
  325. package/src/core/nodes/CallSiteNode.ts +72 -0
  326. package/src/core/nodes/ClassNode.ts +69 -0
  327. package/src/core/nodes/ConstantNode.ts +63 -0
  328. package/src/core/nodes/DatabaseQueryNode.ts +60 -0
  329. package/src/core/nodes/EntrypointNode.ts +164 -0
  330. package/src/core/nodes/EventListenerNode.ts +64 -0
  331. package/src/core/nodes/ExportNode.ts +71 -0
  332. package/src/core/nodes/ExternalStdioNode.ts +36 -0
  333. package/src/core/nodes/FunctionNode.ts +78 -0
  334. package/src/core/nodes/GuaranteeNode.ts +162 -0
  335. package/src/core/nodes/HttpRequestNode.ts +63 -0
  336. package/src/core/nodes/ImportNode.ts +75 -0
  337. package/src/core/nodes/LiteralNode.ts +67 -0
  338. package/src/core/nodes/MethodCallNode.ts +79 -0
  339. package/src/core/nodes/MethodNode.ts +78 -0
  340. package/src/core/nodes/ModuleNode.ts +74 -0
  341. package/src/core/nodes/NodeKind.ts +171 -0
  342. package/src/core/nodes/ParameterNode.ts +73 -0
  343. package/src/core/nodes/ScopeNode.ts +80 -0
  344. package/src/core/nodes/ServiceNode.ts +86 -0
  345. package/src/core/nodes/VariableDeclarationNode.ts +60 -0
  346. package/src/core/nodes/index.ts +49 -0
  347. package/src/index.ts +93 -0
  348. package/src/plugins/Plugin.ts +74 -0
  349. package/src/plugins/analysis/DatabaseAnalyzer.ts +322 -0
  350. package/src/plugins/analysis/ExpressAnalyzer.ts +401 -0
  351. package/src/plugins/analysis/ExpressRouteAnalyzer.ts +414 -0
  352. package/src/plugins/analysis/FetchAnalyzer.ts +441 -0
  353. package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +686 -0
  354. package/src/plugins/analysis/JSASTAnalyzer.ts +1680 -0
  355. package/src/plugins/analysis/ReactAnalyzer.ts +1368 -0
  356. package/src/plugins/analysis/RustAnalyzer.ts +438 -0
  357. package/src/plugins/analysis/SQLiteAnalyzer.ts +388 -0
  358. package/src/plugins/analysis/ServiceLayerAnalyzer.ts +429 -0
  359. package/src/plugins/analysis/SocketIOAnalyzer.ts +395 -0
  360. package/src/plugins/analysis/SystemDbAnalyzer.ts +284 -0
  361. package/src/plugins/analysis/ast/ConditionParser.ts +333 -0
  362. package/src/plugins/analysis/ast/ExpressionEvaluator.ts +117 -0
  363. package/src/plugins/analysis/ast/GraphBuilder.ts +1371 -0
  364. package/src/plugins/analysis/ast/OxcAdapter.ts +63 -0
  365. package/src/plugins/analysis/ast/types.ts +400 -0
  366. package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +137 -0
  367. package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +528 -0
  368. package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +339 -0
  369. package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +273 -0
  370. package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +259 -0
  371. package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +235 -0
  372. package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +268 -0
  373. package/src/plugins/analysis/ast/visitors/index.ts +36 -0
  374. package/src/plugins/discovery/DiscoveryPlugin.ts +50 -0
  375. package/src/plugins/discovery/MonorepoServiceDiscovery.ts +117 -0
  376. package/src/plugins/discovery/SimpleProjectDiscovery.ts +102 -0
  377. package/src/plugins/enrichment/AliasTracker.ts +399 -0
  378. package/src/plugins/enrichment/HTTPConnectionEnricher.ts +192 -0
  379. package/src/plugins/enrichment/ImportExportLinker.ts +221 -0
  380. package/src/plugins/enrichment/InstanceOfResolver.ts +165 -0
  381. package/src/plugins/enrichment/MethodCallResolver.ts +333 -0
  382. package/src/plugins/enrichment/MountPointResolver.ts +264 -0
  383. package/src/plugins/enrichment/PrefixEvaluator.ts +527 -0
  384. package/src/plugins/enrichment/RustFFIEnricher.ts +218 -0
  385. package/src/plugins/enrichment/ValueDomainAnalyzer.ts +682 -0
  386. package/src/plugins/indexing/IncrementalModuleIndexer.ts +287 -0
  387. package/src/plugins/indexing/JSModuleIndexer.ts +374 -0
  388. package/src/plugins/indexing/RustModuleIndexer.ts +160 -0
  389. package/src/plugins/indexing/ServiceDetector.ts +230 -0
  390. package/src/plugins/validation/CallResolverValidator.ts +170 -0
  391. package/src/plugins/validation/DataFlowValidator.ts +233 -0
  392. package/src/plugins/validation/EvalBanValidator.ts +175 -0
  393. package/src/plugins/validation/GraphConnectivityValidator.ts +201 -0
  394. package/src/plugins/validation/SQLInjectionValidator.ts +363 -0
  395. package/src/plugins/validation/ShadowingDetector.ts +173 -0
  396. package/src/plugins/validation/TypeScriptDeadCodeValidator.ts +203 -0
  397. package/src/plugins/vcs/GitPlugin.ts +344 -0
  398. package/src/plugins/vcs/VCSPlugin.ts +190 -0
  399. package/src/plugins/vcs/index.ts +32 -0
  400. package/src/storage/backends/RFDBServerBackend.ts +687 -0
  401. package/src/storage/backends/typeValidation.ts +151 -0
  402. package/src/validation/PathValidator.ts +342 -0
@@ -0,0 +1,1368 @@
1
+ /**
2
+ * ReactAnalyzer - React/Browser domain-specific analysis
3
+ *
4
+ * Detects React patterns:
5
+ * - Components and rendering tree
6
+ * - Props flow between components
7
+ * - Event handlers (onClick, onSubmit, etc.)
8
+ * - Hooks (useState, useEffect, useCallback, useMemo, useRef, useReducer, useContext)
9
+ * - Browser APIs (localStorage, timers, DOM, observers)
10
+ * - Edge cases (stale closures, missing cleanup, RAF bugs)
11
+ */
12
+
13
+ import { readFileSync } from 'fs';
14
+ import { parse, ParserPlugin } from '@babel/parser';
15
+ import traverseModule from '@babel/traverse';
16
+ import type { NodePath } from '@babel/traverse';
17
+ import type { Node, CallExpression, JSXElement, JSXAttribute, VariableDeclarator, FunctionDeclaration } from '@babel/types';
18
+ import { Plugin, createSuccessResult, createErrorResult } from '../Plugin.js';
19
+ import type { PluginContext, PluginResult, PluginMetadata } from '../Plugin.js';
20
+ import type { NodeRecord } from '@grafema/types';
21
+
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ const traverse = (traverseModule as any).default || traverseModule;
24
+
25
+ // React event handlers mapping
26
+ const REACT_EVENTS: Record<string, string> = {
27
+ // Mouse events
28
+ onClick: 'click', onDoubleClick: 'dblclick', onContextMenu: 'contextmenu',
29
+ onMouseDown: 'mousedown', onMouseUp: 'mouseup', onMouseEnter: 'mouseenter',
30
+ onMouseLeave: 'mouseleave', onMouseMove: 'mousemove', onMouseOver: 'mouseover',
31
+ onMouseOut: 'mouseout',
32
+ // Keyboard events
33
+ onKeyDown: 'keydown', onKeyUp: 'keyup', onKeyPress: 'keypress',
34
+ // Focus events
35
+ onFocus: 'focus', onBlur: 'blur', onFocusCapture: 'focus:capture',
36
+ // Form events
37
+ onSubmit: 'submit', onReset: 'reset', onChange: 'change', onInput: 'input',
38
+ onInvalid: 'invalid',
39
+ // Touch events
40
+ onTouchStart: 'touchstart', onTouchMove: 'touchmove', onTouchEnd: 'touchend',
41
+ onTouchCancel: 'touchcancel',
42
+ // Drag events
43
+ onDragStart: 'dragstart', onDrag: 'drag', onDragEnd: 'dragend',
44
+ onDragEnter: 'dragenter', onDragOver: 'dragover', onDragLeave: 'dragleave',
45
+ onDrop: 'drop',
46
+ // Scroll/Wheel events
47
+ onScroll: 'scroll', onWheel: 'wheel',
48
+ // Clipboard events
49
+ onCopy: 'copy', onCut: 'cut', onPaste: 'paste',
50
+ // Composition events
51
+ onCompositionStart: 'compositionstart', onCompositionUpdate: 'compositionupdate',
52
+ onCompositionEnd: 'compositionend',
53
+ // Media events
54
+ onPlay: 'play', onPause: 'pause', onEnded: 'ended', onTimeUpdate: 'timeupdate',
55
+ onLoadedData: 'loadeddata', onLoadedMetadata: 'loadedmetadata',
56
+ onCanPlay: 'canplay', onWaiting: 'waiting', onSeeking: 'seeking',
57
+ onSeeked: 'seeked', onError: 'error', onVolumeChange: 'volumechange',
58
+ // Image events
59
+ onLoad: 'load',
60
+ // Animation events
61
+ onAnimationStart: 'animationstart', onAnimationEnd: 'animationend',
62
+ onAnimationIteration: 'animationiteration',
63
+ // Transition events
64
+ onTransitionEnd: 'transitionend',
65
+ // Pointer events
66
+ onPointerDown: 'pointerdown', onPointerUp: 'pointerup', onPointerMove: 'pointermove',
67
+ onPointerEnter: 'pointerenter', onPointerLeave: 'pointerleave',
68
+ onPointerCancel: 'pointercancel', onGotPointerCapture: 'gotpointercapture',
69
+ onLostPointerCapture: 'lostpointercapture'
70
+ };
71
+
72
+ // React hooks that need tracking
73
+ const REACT_HOOKS = [
74
+ 'useState', 'useEffect', 'useLayoutEffect', 'useInsertionEffect',
75
+ 'useCallback', 'useMemo', 'useRef', 'useReducer', 'useContext',
76
+ 'useImperativeHandle', 'useDebugValue', 'useDeferredValue',
77
+ 'useTransition', 'useId', 'useSyncExternalStore'
78
+ ];
79
+
80
+ // Browser APIs that create side effects
81
+ const BROWSER_APIS = {
82
+ timers: ['setTimeout', 'setInterval', 'requestAnimationFrame', 'requestIdleCallback'],
83
+ cleanup: {
84
+ setTimeout: 'clearTimeout',
85
+ setInterval: 'clearInterval',
86
+ requestAnimationFrame: 'cancelAnimationFrame',
87
+ requestIdleCallback: 'cancelIdleCallback'
88
+ } as Record<string, string>,
89
+ observers: ['IntersectionObserver', 'ResizeObserver', 'MutationObserver', 'PerformanceObserver'],
90
+ storage: ['localStorage', 'sessionStorage'],
91
+ async: ['fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource'],
92
+ dom: ['document', 'getElementById', 'querySelector', 'querySelectorAll'],
93
+ workers: ['Worker', 'SharedWorker', 'ServiceWorker'],
94
+ geolocation: ['grafemagator.geolocation'],
95
+ notifications: ['Notification'],
96
+ fullscreen: ['requestFullscreen', 'exitFullscreen'],
97
+ clipboard: ['grafemagator.clipboard'],
98
+ history: ['history.pushState', 'history.replaceState'],
99
+ blocking: ['alert', 'confirm', 'prompt']
100
+ };
101
+
102
+ /**
103
+ * Component node
104
+ */
105
+ interface ComponentNode {
106
+ id: string;
107
+ type: 'react:component';
108
+ name: string;
109
+ file: string;
110
+ line: number;
111
+ column: number;
112
+ kind: 'arrow' | 'function' | 'forwardRef';
113
+ }
114
+
115
+ /**
116
+ * Hook node
117
+ */
118
+ interface HookNode {
119
+ id: string;
120
+ type: string;
121
+ file: string;
122
+ line: number;
123
+ column: number;
124
+ hookName: string;
125
+ [key: string]: unknown;
126
+ }
127
+
128
+ /**
129
+ * Event node
130
+ */
131
+ interface EventNode {
132
+ id: string;
133
+ type: 'dom:event';
134
+ eventType: string;
135
+ reactProp: string;
136
+ handler: string;
137
+ file: string;
138
+ line: number;
139
+ }
140
+
141
+ /**
142
+ * Browser API node
143
+ */
144
+ interface BrowserAPINode {
145
+ id: string;
146
+ type: string;
147
+ file: string;
148
+ line: number;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ /**
153
+ * Issue node
154
+ */
155
+ interface IssueNode {
156
+ id: string;
157
+ type: string;
158
+ file: string;
159
+ line: number;
160
+ [key: string]: unknown;
161
+ }
162
+
163
+ /**
164
+ * Edge info
165
+ */
166
+ interface EdgeInfo {
167
+ edgeType: string;
168
+ src: string;
169
+ dst: string;
170
+ file: string;
171
+ line: number;
172
+ [key: string]: unknown;
173
+ }
174
+
175
+ /**
176
+ * Analysis result
177
+ */
178
+ interface AnalysisResult {
179
+ components: ComponentNode[];
180
+ hooks: HookNode[];
181
+ events: EventNode[];
182
+ browserAPIs: BrowserAPINode[];
183
+ issues: IssueNode[];
184
+ edges: EdgeInfo[];
185
+ }
186
+
187
+ /**
188
+ * Analysis stats
189
+ */
190
+ interface AnalysisStats {
191
+ components: number;
192
+ hooks: number;
193
+ events: number;
194
+ browserAPIs: number;
195
+ issues: number;
196
+ edges: number;
197
+ [key: string]: unknown;
198
+ }
199
+
200
+ export class ReactAnalyzer extends Plugin {
201
+ get metadata(): PluginMetadata {
202
+ return {
203
+ name: 'ReactAnalyzer',
204
+ phase: 'ANALYSIS',
205
+ priority: 70, // After JSASTAnalyzer and ExpressAnalyzer
206
+ creates: {
207
+ nodes: [
208
+ 'react:component', 'react:state', 'react:effect', 'react:callback',
209
+ 'react:memo', 'react:ref', 'react:reducer', 'react:context',
210
+ 'dom:event', 'browser:storage', 'browser:timer', 'browser:observer',
211
+ 'browser:async', 'browser:worker', 'browser:api',
212
+ 'canvas:context', 'canvas:draw',
213
+ 'issue:stale-closure', 'issue:missing-cleanup', 'issue:raf-leak',
214
+ 'issue:canvas-leak', 'issue:state-after-unmount'
215
+ ],
216
+ edges: [
217
+ 'RENDERS', 'PASSES_PROP', 'HANDLES_EVENT', 'UPDATES_STATE',
218
+ 'DEPENDS_ON', 'SCHEDULES', 'CLEANS_UP', 'DISPATCHES',
219
+ 'PROVIDES', 'CONSUMES', 'FORWARDS_REF', 'OBSERVES'
220
+ ]
221
+ },
222
+ dependencies: ['JSASTAnalyzer']
223
+ };
224
+ }
225
+
226
+ async execute(context: PluginContext): Promise<PluginResult> {
227
+ try {
228
+ const { graph } = context;
229
+ const modules = await this.getModules(graph);
230
+
231
+ const stats: AnalysisStats = {
232
+ components: 0,
233
+ hooks: 0,
234
+ events: 0,
235
+ browserAPIs: 0,
236
+ issues: 0,
237
+ edges: 0
238
+ };
239
+
240
+ for (const module of modules) {
241
+ // Only analyze .jsx, .tsx, or files that import React
242
+ if (!this.isReactFile(module.file!)) {
243
+ continue;
244
+ }
245
+
246
+ try {
247
+ const result = await this.analyzeModule(module, graph);
248
+ stats.components += result.components;
249
+ stats.hooks += result.hooks;
250
+ stats.events += result.events;
251
+ stats.browserAPIs += result.browserAPIs;
252
+ stats.issues += result.issues;
253
+ stats.edges += result.edges;
254
+ } catch (err) {
255
+ console.error(`[ReactAnalyzer] Error analyzing ${module.file}:`, (err as Error).message);
256
+ }
257
+ }
258
+
259
+ console.log(`[ReactAnalyzer] Found ${stats.components} components, ${stats.hooks} hooks, ${stats.events} events, ${stats.issues} issues`);
260
+
261
+ return createSuccessResult(
262
+ {
263
+ nodes: stats.components + stats.hooks + stats.events + stats.browserAPIs + stats.issues,
264
+ edges: stats.edges
265
+ },
266
+ stats
267
+ );
268
+ } catch (error) {
269
+ console.error(`[ReactAnalyzer] Error:`, error);
270
+ return createErrorResult(error as Error);
271
+ }
272
+ }
273
+
274
+ private isReactFile(filePath: string): boolean {
275
+ if (filePath.endsWith('.jsx') || filePath.endsWith('.tsx')) {
276
+ return true;
277
+ }
278
+ // Could also check for React import in .js/.ts files
279
+ return false;
280
+ }
281
+
282
+ private async analyzeModule(module: NodeRecord, graph: PluginContext['graph']): Promise<AnalysisStats> {
283
+ const code = readFileSync(module.file!, 'utf-8');
284
+ const ast = parse(code, {
285
+ sourceType: 'module',
286
+ plugins: ['jsx', 'typescript'] as ParserPlugin[]
287
+ });
288
+
289
+ return this.analyzeAST(ast, module.file!, graph, module.id);
290
+ }
291
+
292
+ /**
293
+ * Main AST analysis - can be called directly for testing
294
+ */
295
+ async analyzeAST(
296
+ ast: Node,
297
+ filePath: string,
298
+ graph: PluginContext['graph'],
299
+ moduleId: string | null = null
300
+ ): Promise<AnalysisStats> {
301
+ const analysis: AnalysisResult = {
302
+ components: [],
303
+ hooks: [],
304
+ events: [],
305
+ browserAPIs: [],
306
+ issues: [],
307
+ edges: []
308
+ };
309
+
310
+ const importedIdentifiers = new Set<string>();
311
+
312
+ // Collect imported identifiers (stable references)
313
+ traverse(ast, {
314
+ ImportDeclaration: (path: NodePath) => {
315
+ const node = path.node as { specifiers: Array<{ local?: { name: string } }> };
316
+ for (const specifier of node.specifiers) {
317
+ if (specifier.local?.name) {
318
+ importedIdentifiers.add(specifier.local.name);
319
+ }
320
+ }
321
+ }
322
+ });
323
+
324
+ // First pass: collect component definitions
325
+ traverse(ast, {
326
+ // Arrow function components: const App = () => ...
327
+ VariableDeclarator: (path: NodePath<VariableDeclarator>) => {
328
+ if (this.isReactComponent(path)) {
329
+ const node = path.node;
330
+ const name = (node.id as { name: string }).name;
331
+ const loc = node.loc!;
332
+ const component: ComponentNode = {
333
+ id: `react:component#${name}#${filePath}:${loc.start.line}`,
334
+ type: 'react:component',
335
+ name,
336
+ file: filePath,
337
+ line: loc.start.line,
338
+ column: loc.start.column,
339
+ kind: 'arrow'
340
+ };
341
+ analysis.components.push(component);
342
+ }
343
+ },
344
+
345
+ // Function declaration components: function App() {...}
346
+ FunctionDeclaration: (path: NodePath<FunctionDeclaration>) => {
347
+ if (this.isReactComponent(path)) {
348
+ const name = path.node.id?.name;
349
+ if (!name) return;
350
+
351
+ const loc = path.node.loc!;
352
+ const component: ComponentNode = {
353
+ id: `react:component#${name}#${filePath}:${loc.start.line}`,
354
+ type: 'react:component',
355
+ name,
356
+ file: filePath,
357
+ line: loc.start.line,
358
+ column: loc.start.column,
359
+ kind: 'function'
360
+ };
361
+ analysis.components.push(component);
362
+ }
363
+ }
364
+ });
365
+
366
+ // Second pass: analyze hooks, events, JSX
367
+ traverse(ast, {
368
+ CallExpression: (path: NodePath<CallExpression>) => {
369
+ const callee = path.node.callee;
370
+
371
+ // Detect React hooks
372
+ if (callee.type === 'Identifier' && REACT_HOOKS.includes(callee.name)) {
373
+ const hookData = this.analyzeHook(path, filePath);
374
+ if (hookData) {
375
+ analysis.hooks.push(hookData);
376
+
377
+ // Check for issues in hooks
378
+ if (callee.name === 'useEffect' || callee.name === 'useLayoutEffect') {
379
+ this.checkEffectIssues(path, filePath, analysis, hookData, importedIdentifiers);
380
+ }
381
+ }
382
+ }
383
+
384
+ // Detect forwardRef
385
+ if (callee.type === 'Identifier' && callee.name === 'forwardRef') {
386
+ this.analyzeForwardRef(path, filePath, analysis);
387
+ }
388
+
389
+ // Detect createContext
390
+ if (callee.type === 'Identifier' && callee.name === 'createContext') {
391
+ this.analyzeCreateContext(path, filePath, analysis);
392
+ }
393
+
394
+ // Detect browser APIs
395
+ this.analyzeBrowserAPI(path, filePath, analysis);
396
+ },
397
+
398
+ // JSX elements
399
+ JSXElement: (path: NodePath<JSXElement>) => {
400
+ this.analyzeJSXElement(path, filePath, analysis);
401
+ },
402
+
403
+ // JSX attributes (for event handlers and props)
404
+ JSXAttribute: (path: NodePath<JSXAttribute>) => {
405
+ this.analyzeJSXAttribute(path, filePath, analysis);
406
+ }
407
+ });
408
+
409
+ // Add all nodes and edges to graph
410
+ await this.addToGraph(analysis, graph, moduleId);
411
+
412
+ return {
413
+ components: analysis.components.length,
414
+ hooks: analysis.hooks.length,
415
+ events: analysis.events.length,
416
+ browserAPIs: analysis.browserAPIs.length,
417
+ issues: analysis.issues.length,
418
+ edges: analysis.edges.length
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Check if a function is a React component (returns JSX)
424
+ */
425
+ private isReactComponent(path: NodePath): boolean {
426
+ let hasJSXReturn = false;
427
+
428
+ // Check for arrow function or function
429
+ const node = path.node as { init?: Node };
430
+ const func = node.init || path.node;
431
+ if (!func) return false;
432
+
433
+ // Must be a function
434
+ if (func.type !== 'ArrowFunctionExpression' &&
435
+ func.type !== 'FunctionExpression' &&
436
+ func.type !== 'FunctionDeclaration') {
437
+ return false;
438
+ }
439
+
440
+ // Name must start with uppercase (React component convention)
441
+ const pathNode = path.node as { id?: { name: string } };
442
+ const name = pathNode.id?.name;
443
+ if (!name || !/^[A-Z]/.test(name)) {
444
+ return false;
445
+ }
446
+
447
+ // Check if body contains JSX
448
+ path.traverse({
449
+ JSXElement: () => { hasJSXReturn = true; },
450
+ JSXFragment: () => { hasJSXReturn = true; }
451
+ });
452
+
453
+ return hasJSXReturn;
454
+ }
455
+
456
+ /**
457
+ * Analyze React hooks
458
+ */
459
+ private analyzeHook(path: NodePath<CallExpression>, filePath: string): HookNode | null {
460
+ const callee = path.node.callee as { name: string };
461
+ const hookName = callee.name;
462
+ const loc = path.node.loc!;
463
+ const args = path.node.arguments;
464
+
465
+ const hookBase = {
466
+ file: filePath,
467
+ line: loc.start.line,
468
+ column: loc.start.column,
469
+ hookName
470
+ };
471
+
472
+ switch (hookName) {
473
+ case 'useState': {
474
+ // const [state, setState] = useState(initialValue)
475
+ const parent = path.parent as { type: string; id?: { type: string; elements?: Array<{ name?: string }> } };
476
+ if (parent.type === 'VariableDeclarator' &&
477
+ parent.id?.type === 'ArrayPattern' &&
478
+ parent.id.elements?.length === 2) {
479
+ const stateName = parent.id.elements[0]?.name;
480
+ const setterName = parent.id.elements[1]?.name;
481
+ const initialValue = args[0];
482
+
483
+ return {
484
+ id: `react:state#${stateName}#${filePath}:${loc.start.line}`,
485
+ type: 'react:state',
486
+ ...hookBase,
487
+ stateName,
488
+ setterName,
489
+ initialValue: this.getExpressionValue(initialValue as Node)
490
+ };
491
+ }
492
+ break;
493
+ }
494
+
495
+ case 'useEffect':
496
+ case 'useLayoutEffect':
497
+ case 'useInsertionEffect': {
498
+ // useEffect(() => {...}, [deps])
499
+ const callback = args[0];
500
+ const depsArg = args[1];
501
+ const deps = this.extractDeps(depsArg as Node);
502
+ const hasCleanup = this.hasCleanupReturn(callback as Node);
503
+
504
+ const effectType = hookName === 'useEffect' ? 'react:effect' :
505
+ hookName === 'useLayoutEffect' ? 'react:layout-effect' :
506
+ 'react:insertion-effect';
507
+
508
+ return {
509
+ id: `${effectType}#${filePath}:${loc.start.line}`,
510
+ type: effectType,
511
+ ...hookBase,
512
+ deps,
513
+ hasCleanup,
514
+ depsType: !depsArg ? 'none' : (deps?.length === 0 ? 'empty' : 'array')
515
+ };
516
+ }
517
+
518
+ case 'useCallback': {
519
+ // const fn = useCallback(() => {...}, [deps])
520
+ const parent = path.parent as { type: string; id?: { name: string } };
521
+ const callbackName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
522
+ const depsArg = args[1];
523
+ const deps = this.extractDeps(depsArg as Node);
524
+
525
+ return {
526
+ id: `react:callback#${callbackName || 'anonymous'}#${filePath}:${loc.start.line}`,
527
+ type: 'react:callback',
528
+ ...hookBase,
529
+ callbackName,
530
+ deps
531
+ };
532
+ }
533
+
534
+ case 'useMemo': {
535
+ // const value = useMemo(() => computation, [deps])
536
+ const parent = path.parent as { type: string; id?: { name: string } };
537
+ const memoName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
538
+ const depsArg = args[1];
539
+ const deps = this.extractDeps(depsArg as Node);
540
+
541
+ return {
542
+ id: `react:memo#${memoName || 'anonymous'}#${filePath}:${loc.start.line}`,
543
+ type: 'react:memo',
544
+ ...hookBase,
545
+ memoName,
546
+ deps
547
+ };
548
+ }
549
+
550
+ case 'useRef': {
551
+ // const ref = useRef(initialValue)
552
+ const parent = path.parent as { type: string; id?: { name: string } };
553
+ const refName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
554
+ const initialValue = args[0];
555
+
556
+ return {
557
+ id: `react:ref#${refName || 'anonymous'}#${filePath}:${loc.start.line}`,
558
+ type: 'react:ref',
559
+ ...hookBase,
560
+ refName,
561
+ initialValue: this.getExpressionValue(initialValue as Node)
562
+ };
563
+ }
564
+
565
+ case 'useReducer': {
566
+ // const [state, dispatch] = useReducer(reducer, initialState)
567
+ const parent = path.parent as { type: string; id?: { type: string; elements?: Array<{ name?: string }> } };
568
+ if (parent.type === 'VariableDeclarator' &&
569
+ parent.id?.type === 'ArrayPattern' &&
570
+ parent.id.elements && parent.id.elements.length >= 2) {
571
+ const stateName = parent.id.elements[0]?.name;
572
+ const dispatchName = parent.id.elements[1]?.name;
573
+ const reducerArg = args[0] as Node | undefined;
574
+ const reducerName = reducerArg?.type === 'Identifier' ? (reducerArg as { name: string }).name : null;
575
+
576
+ return {
577
+ id: `react:reducer#${stateName}#${filePath}:${loc.start.line}`,
578
+ type: 'react:reducer',
579
+ ...hookBase,
580
+ stateName,
581
+ dispatchName,
582
+ reducerName
583
+ };
584
+ }
585
+ break;
586
+ }
587
+
588
+ case 'useContext': {
589
+ // const value = useContext(Context)
590
+ const parent = path.parent as { type: string; id?: { name: string } };
591
+ const valueName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
592
+ const contextArg = args[0] as Node | undefined;
593
+ const contextName = contextArg?.type === 'Identifier' ? (contextArg as { name: string }).name : null;
594
+
595
+ return {
596
+ id: `react:context-use#${contextName || 'unknown'}#${filePath}:${loc.start.line}`,
597
+ type: 'react:context-use',
598
+ ...hookBase,
599
+ valueName,
600
+ contextName
601
+ };
602
+ }
603
+
604
+ case 'useImperativeHandle': {
605
+ // useImperativeHandle(ref, () => ({ method1, method2 }), [deps])
606
+ const refArg = args[0] as Node | undefined;
607
+ const refName = refArg?.type === 'Identifier' ? (refArg as { name: string }).name : null;
608
+ const createHandle = args[1] as Node | undefined;
609
+
610
+ // Extract exposed methods
611
+ const exposedMethods: string[] = [];
612
+ if (createHandle?.type === 'ArrowFunctionExpression' ||
613
+ createHandle?.type === 'FunctionExpression') {
614
+ const body = (createHandle as { body: Node }).body;
615
+ if (body.type === 'ObjectExpression') {
616
+ const objExpr = body as { properties: Array<{ key?: { name: string } }> };
617
+ for (const prop of objExpr.properties) {
618
+ if (prop.key?.name) {
619
+ exposedMethods.push(prop.key.name);
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+ return {
626
+ id: `react:imperative-handle#${filePath}:${loc.start.line}`,
627
+ type: 'react:imperative-handle',
628
+ ...hookBase,
629
+ refName,
630
+ exposedMethods
631
+ };
632
+ }
633
+ }
634
+
635
+ return null;
636
+ }
637
+
638
+ /**
639
+ * Extract dependency array from hook
640
+ */
641
+ private extractDeps(depsArg: Node | undefined): string[] | null {
642
+ if (!depsArg) return null; // No deps argument
643
+ if (depsArg.type !== 'ArrayExpression') return ['<dynamic>'];
644
+
645
+ const arrExpr = depsArg as { elements: Array<Node | null> };
646
+ return arrExpr.elements.map(el => {
647
+ if (!el) return '<empty>';
648
+ if (el.type === 'Identifier') return (el as { name: string }).name;
649
+ if (el.type === 'MemberExpression') {
650
+ return this.getMemberExpressionName(el);
651
+ }
652
+ return '<expression>';
653
+ });
654
+ }
655
+
656
+ private getMemberExpressionName(node: Node): string {
657
+ if (node.type !== 'MemberExpression') {
658
+ return (node as { name?: string }).name || '<unknown>';
659
+ }
660
+ const memExpr = node as { object: Node; property: { name?: string; value?: string } };
661
+ const object = this.getMemberExpressionName(memExpr.object);
662
+ const property = memExpr.property.name || memExpr.property.value || '<computed>';
663
+ return `${object}.${property}`;
664
+ }
665
+
666
+ /**
667
+ * Check if effect callback has cleanup return
668
+ */
669
+ private hasCleanupReturn(callback: Node | undefined): boolean {
670
+ if (!callback) return false;
671
+ if (callback.type !== 'ArrowFunctionExpression' &&
672
+ callback.type !== 'FunctionExpression') {
673
+ return false;
674
+ }
675
+
676
+ // Simple AST traversal without using babel traverse
677
+ const checkForCleanupReturn = (node: Node | null): boolean => {
678
+ if (!node) return false;
679
+
680
+ if (node.type === 'ReturnStatement') {
681
+ const retStmt = node as { argument?: Node };
682
+ const arg = retStmt.argument;
683
+ if (arg && (arg.type === 'ArrowFunctionExpression' ||
684
+ arg.type === 'FunctionExpression')) {
685
+ return true;
686
+ }
687
+ }
688
+
689
+ // Check body
690
+ const nodeWithBody = node as { body?: Node | Node[] };
691
+ if (nodeWithBody.body) {
692
+ if (Array.isArray(nodeWithBody.body)) {
693
+ return nodeWithBody.body.some(n => checkForCleanupReturn(n));
694
+ } else if (nodeWithBody.body.type === 'BlockStatement') {
695
+ const blockStmt = nodeWithBody.body as { body?: Node[] };
696
+ if (blockStmt.body) {
697
+ return blockStmt.body.some(n => checkForCleanupReturn(n));
698
+ }
699
+ } else {
700
+ return checkForCleanupReturn(nodeWithBody.body);
701
+ }
702
+ }
703
+
704
+ return false;
705
+ };
706
+
707
+ return checkForCleanupReturn(callback);
708
+ }
709
+
710
+ /**
711
+ * Check for issues in useEffect/useLayoutEffect
712
+ */
713
+ private checkEffectIssues(
714
+ path: NodePath<CallExpression>,
715
+ filePath: string,
716
+ analysis: AnalysisResult,
717
+ hookData: HookNode,
718
+ importedIdentifiers: Set<string> = new Set()
719
+ ): void {
720
+ const callback = path.node.arguments[0];
721
+ if (!callback) return;
722
+
723
+ const deps = hookData.deps as string[] | null;
724
+ const usedVars = new Set<string>();
725
+ const setterCalls = new Set<string>();
726
+ const callbackParams = new Set<string>(); // Track parameters of nested callback functions
727
+
728
+ // Simple recursive AST walker to collect identifiers
729
+ const collectIdentifiers = (node: Node | null, parentType: string | null = null, isPropertyKey = false): void => {
730
+ if (!node) return;
731
+
732
+ if (node.type === 'Identifier') {
733
+ const id = node as { name: string };
734
+ // Skip if it's a property access key
735
+ if (!isPropertyKey && !callbackParams.has(id.name)) {
736
+ usedVars.add(id.name);
737
+ }
738
+ return;
739
+ }
740
+
741
+ if (node.type === 'MemberExpression') {
742
+ const memExpr = node as { object: Node; property: { type: string; name?: string } };
743
+ // Check for ref.current pattern (valid for stable refs)
744
+ if (memExpr.property?.type === 'Identifier' && memExpr.property.name === 'current') {
745
+ // This is likely a ref.current access - collect only the ref name, not 'current'
746
+ collectIdentifiers(memExpr.object, 'MemberExpression', false);
747
+ return;
748
+ }
749
+ collectIdentifiers(memExpr.object, 'MemberExpression', false);
750
+ // Skip property name
751
+ return;
752
+ }
753
+
754
+ if (node.type === 'CallExpression') {
755
+ const callExpr = node as { callee: Node & { name?: string }; arguments: Node[] };
756
+ const callee = callExpr.callee;
757
+ if (callee.type === 'Identifier' && callee.name?.startsWith('set')) {
758
+ const arg = callExpr.arguments[0];
759
+ if (arg?.type === 'ArrowFunctionExpression' ||
760
+ arg?.type === 'FunctionExpression') {
761
+ setterCalls.add(callee.name);
762
+ }
763
+ }
764
+ collectIdentifiers(callee, 'CallExpression', false);
765
+ callExpr.arguments?.forEach(arg => collectIdentifiers(arg, 'CallExpression', false));
766
+ return;
767
+ }
768
+
769
+ if (node.type === 'ObjectProperty') {
770
+ const prop = node as { value: Node };
771
+ // Skip key, process value
772
+ collectIdentifiers(prop.value, 'ObjectProperty', false);
773
+ return;
774
+ }
775
+
776
+ // Handle function parameters - skip them (AC-1: callback parameters are not external deps)
777
+ if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
778
+ const func = node as { params?: Array<{ type: string; name?: string }>; body: Node };
779
+ // Collect parameter names from nested callbacks
780
+ func.params?.forEach(p => {
781
+ if (p.type === 'Identifier' && p.name) {
782
+ callbackParams.add(p.name); // Add to global tracking
783
+ }
784
+ });
785
+
786
+ const collectInBody = (bodyNode: Node | null): void => {
787
+ if (!bodyNode) return;
788
+ if (bodyNode.type === 'Identifier') {
789
+ const id = bodyNode as { name: string };
790
+ if (!callbackParams.has(id.name)) {
791
+ usedVars.add(id.name);
792
+ }
793
+ } else if (typeof bodyNode === 'object') {
794
+ Object.values(bodyNode).forEach(child => {
795
+ if (child && typeof child === 'object') {
796
+ if (Array.isArray(child)) {
797
+ child.forEach(c => collectInBody(c as Node));
798
+ } else {
799
+ collectInBody(child as Node);
800
+ }
801
+ }
802
+ });
803
+ }
804
+ };
805
+ collectInBody(func.body);
806
+ return;
807
+ }
808
+
809
+ // Recurse into child nodes
810
+ if (typeof node === 'object') {
811
+ Object.entries(node).forEach(([key, child]) => {
812
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'type') return;
813
+ if (child && typeof child === 'object') {
814
+ if (Array.isArray(child)) {
815
+ child.forEach(c => collectIdentifiers(c as Node, node.type, false));
816
+ } else {
817
+ collectIdentifiers(child as Node, node.type, false);
818
+ }
819
+ }
820
+ });
821
+ }
822
+ };
823
+
824
+ collectIdentifiers(callback as Node);
825
+
826
+ // Check for stale closures
827
+ if (deps !== null && deps.length >= 0) { // Has deps array
828
+ const depsSet = new Set(deps);
829
+
830
+ // Hooks that don't cause stale closure issues
831
+ const safeHooks = ['useState', 'useReducer', 'useRef', 'useCallback', 'useMemo',
832
+ 'useEffect', 'useLayoutEffect', 'useContext'];
833
+ const setterPrefixes = ['set'];
834
+
835
+ for (const used of usedVars) {
836
+ // Skip safe identifiers
837
+ if (safeHooks.includes(used)) continue;
838
+ if (setterPrefixes.some(p => used.startsWith(p))) continue;
839
+ if (depsSet.has(used)) continue;
840
+ if (used === 'console' || used === 'window' || used === 'document') continue;
841
+ if (used === 'Math' || used === 'JSON' || used === 'Date') continue;
842
+ if (used === 'undefined' || used === 'null' || used === 'true' || used === 'false') continue;
843
+ // AC-2: Skip imported identifiers (stable references)
844
+ if (importedIdentifiers.has(used)) continue;
845
+
846
+ // This variable is used but not in deps - potential stale closure
847
+ const issue: IssueNode = {
848
+ id: `issue:stale-closure#${used}#${filePath}:${hookData.line}`,
849
+ type: 'issue:stale-closure',
850
+ file: filePath,
851
+ line: hookData.line,
852
+ variable: used,
853
+ hookType: hookData.hookName,
854
+ deps: deps,
855
+ message: `Variable '${used}' is used in ${hookData.hookName} but not listed in dependencies`
856
+ };
857
+ analysis.issues.push(issue);
858
+ }
859
+ }
860
+
861
+ // Check for missing cleanup
862
+ this.checkMissingCleanup(callback as Node, filePath, analysis, hookData);
863
+ }
864
+
865
+ /**
866
+ * Check for missing cleanup in effect callback
867
+ */
868
+ private checkMissingCleanup(
869
+ callback: Node,
870
+ filePath: string,
871
+ analysis: AnalysisResult,
872
+ hookData: HookNode
873
+ ): void {
874
+ const hasCleanup = hookData.hasCleanup as boolean;
875
+
876
+ // Simple recursive AST walker
877
+ const checkNode = (node: Node | null, parent: Node | null = null): void => {
878
+ if (!node || typeof node !== 'object') return;
879
+
880
+ if (node.type === 'CallExpression') {
881
+ const callExpr = node as { callee?: { type: string; name?: string }; loc?: { start: { line: number } } };
882
+ const callee = callExpr.callee;
883
+ const loc = callExpr.loc;
884
+
885
+ // Check for timer APIs
886
+ if (callee?.type === 'Identifier') {
887
+ const api = callee.name;
888
+ if (api && BROWSER_APIS.timers.includes(api)) {
889
+ // Timer called without storing reference
890
+ if (!hasCleanup && api === 'requestAnimationFrame') {
891
+ analysis.issues.push({
892
+ id: `issue:raf-leak#${filePath}:${loc?.start?.line || 0}`,
893
+ type: 'issue:raf-leak',
894
+ file: filePath,
895
+ line: loc?.start?.line || 0,
896
+ message: `requestAnimationFrame called without cleanup - will leak on unmount`
897
+ });
898
+ }
899
+ }
900
+ }
901
+ }
902
+
903
+ if (node.type === 'NewExpression') {
904
+ const newExpr = node as { callee?: { type: string; name?: string }; loc?: { start: { line: number } } };
905
+ const callee = newExpr.callee;
906
+ const loc = newExpr.loc;
907
+
908
+ // Check for WebSocket without cleanup
909
+ if (callee?.type === 'Identifier' && callee.name === 'WebSocket') {
910
+ if (!hasCleanup) {
911
+ analysis.issues.push({
912
+ id: `issue:missing-cleanup#websocket#${filePath}:${loc?.start?.line || 0}`,
913
+ type: 'issue:missing-cleanup',
914
+ file: filePath,
915
+ line: loc?.start?.line || 0,
916
+ api: 'WebSocket',
917
+ message: `WebSocket created without cleanup - connection will leak on unmount`
918
+ });
919
+ }
920
+ }
921
+
922
+ // Check for observers without cleanup
923
+ if (callee?.type === 'Identifier' && callee.name && BROWSER_APIS.observers.includes(callee.name)) {
924
+ if (!hasCleanup) {
925
+ analysis.issues.push({
926
+ id: `issue:missing-cleanup#${callee.name}#${filePath}:${loc?.start?.line || 0}`,
927
+ type: 'issue:missing-cleanup',
928
+ file: filePath,
929
+ line: loc?.start?.line || 0,
930
+ api: callee.name,
931
+ message: `${callee.name} created without disconnect in cleanup`
932
+ });
933
+ }
934
+ }
935
+ }
936
+
937
+ // Recurse into child nodes
938
+ Object.entries(node).forEach(([key, child]) => {
939
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'type') return;
940
+ if (child && typeof child === 'object') {
941
+ if (Array.isArray(child)) {
942
+ child.forEach(c => checkNode(c as Node, node));
943
+ } else {
944
+ checkNode(child as Node, node);
945
+ }
946
+ }
947
+ });
948
+ };
949
+
950
+ checkNode(callback);
951
+ }
952
+
953
+ /**
954
+ * Analyze JSX element for component rendering
955
+ */
956
+ private analyzeJSXElement(path: NodePath<JSXElement>, filePath: string, analysis: AnalysisResult): void {
957
+ const openingElement = path.node.openingElement;
958
+ const elementName = this.getJSXElementName(openingElement.name);
959
+
960
+ // Skip native HTML elements (lowercase)
961
+ if (/^[a-z]/.test(elementName)) {
962
+ return;
963
+ }
964
+
965
+ // This is a React component being rendered
966
+ const loc = openingElement.loc!;
967
+
968
+ // Find parent component
969
+ let parentComponent: string | null = null;
970
+ let parentPath: NodePath<Node> | null = path.parentPath;
971
+ while (parentPath) {
972
+ if (parentPath.node.type === 'FunctionDeclaration' ||
973
+ parentPath.node.type === 'ArrowFunctionExpression' ||
974
+ parentPath.node.type === 'FunctionExpression') {
975
+ // Check if this function is a component
976
+ const funcName = this.getFunctionName(parentPath);
977
+ if (funcName && /^[A-Z]/.test(funcName)) {
978
+ parentComponent = funcName;
979
+ break;
980
+ }
981
+ }
982
+ parentPath = parentPath.parentPath;
983
+ }
984
+
985
+ if (parentComponent) {
986
+ analysis.edges.push({
987
+ edgeType: 'RENDERS',
988
+ src: `react:component#${parentComponent}`,
989
+ dst: `react:component#${elementName}`,
990
+ file: filePath,
991
+ line: loc.start.line
992
+ });
993
+ }
994
+ }
995
+
996
+ private getJSXElementName(nameNode: Node): string {
997
+ if (nameNode.type === 'JSXIdentifier') {
998
+ return (nameNode as { name: string }).name;
999
+ }
1000
+ if (nameNode.type === 'JSXMemberExpression') {
1001
+ const memExpr = nameNode as { object: Node; property: { name: string } };
1002
+ return `${this.getJSXElementName(memExpr.object)}.${memExpr.property.name}`;
1003
+ }
1004
+ return '<unknown>';
1005
+ }
1006
+
1007
+ private getFunctionName(path: NodePath): string | null {
1008
+ // Arrow function assigned to variable
1009
+ const parent = path.parent as { type: string; id?: { name: string } };
1010
+ if (parent?.type === 'VariableDeclarator') {
1011
+ return parent.id?.name || null;
1012
+ }
1013
+ // Function declaration
1014
+ const node = path.node as { id?: { name: string } };
1015
+ if (node.id?.name) {
1016
+ return node.id.name;
1017
+ }
1018
+ return null;
1019
+ }
1020
+
1021
+ /**
1022
+ * Analyze JSX attribute for props and event handlers
1023
+ */
1024
+ private analyzeJSXAttribute(path: NodePath<JSXAttribute>, filePath: string, analysis: AnalysisResult): void {
1025
+ const attr = path.node;
1026
+ if (!attr.name || attr.name.type !== 'JSXIdentifier') return;
1027
+
1028
+ const attrName = attr.name.name;
1029
+ const loc = attr.loc!;
1030
+
1031
+ // Get parent JSX element info first
1032
+ const jsxOpeningElement = path.parent as { type: string; name?: Node };
1033
+ let componentName: string | null = null;
1034
+ let isReactComponent = false;
1035
+
1036
+ if (jsxOpeningElement?.type === 'JSXOpeningElement' && jsxOpeningElement.name) {
1037
+ componentName = this.getJSXElementName(jsxOpeningElement.name);
1038
+ isReactComponent = /^[A-Z]/.test(componentName);
1039
+ }
1040
+
1041
+ // Check if it's an event handler
1042
+ if (REACT_EVENTS[attrName]) {
1043
+ const eventType = REACT_EVENTS[attrName];
1044
+ const handler = attr.value as { type: string; expression?: Node } | null;
1045
+
1046
+ let handlerName = '<inline>';
1047
+ if (handler?.type === 'JSXExpressionContainer') {
1048
+ const expr = handler.expression;
1049
+ if (expr?.type === 'Identifier') {
1050
+ handlerName = (expr as { name: string }).name;
1051
+ } else if (expr?.type === 'MemberExpression') {
1052
+ handlerName = this.getMemberExpressionName(expr);
1053
+ }
1054
+ }
1055
+
1056
+ const event: EventNode = {
1057
+ id: `dom:event#${eventType}#${filePath}:${loc.start.line}`,
1058
+ type: 'dom:event',
1059
+ eventType,
1060
+ reactProp: attrName,
1061
+ handler: handlerName,
1062
+ file: filePath,
1063
+ line: loc.start.line
1064
+ };
1065
+ analysis.events.push(event);
1066
+ }
1067
+
1068
+ // For React components (uppercase), create PASSES_PROP edges for all props
1069
+ if (isReactComponent && componentName && attrName !== 'key' && attrName !== 'ref' && attrName !== 'children') {
1070
+ let propValue = '<expression>';
1071
+ const value = attr.value as { type: string; value?: string; expression?: Node } | null;
1072
+ if (value?.type === 'StringLiteral') {
1073
+ propValue = value.value || '';
1074
+ } else if (value?.type === 'JSXExpressionContainer') {
1075
+ propValue = this.getExpressionValue(value.expression!);
1076
+ } else if (value === null) {
1077
+ propValue = 'true'; // Boolean shorthand
1078
+ }
1079
+
1080
+ // Find parent component
1081
+ let parentComponent: string | null = null;
1082
+ let parentPath: NodePath | null = path.parentPath;
1083
+ while (parentPath) {
1084
+ const node = parentPath.node as { type: string; id?: { name: string } };
1085
+ if (node.type === 'FunctionDeclaration' && node.id?.name) {
1086
+ if (/^[A-Z]/.test(node.id.name)) {
1087
+ parentComponent = node.id.name;
1088
+ break;
1089
+ }
1090
+ } else if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
1091
+ const funcName = this.getFunctionName(parentPath);
1092
+ if (funcName && /^[A-Z]/.test(funcName)) {
1093
+ parentComponent = funcName;
1094
+ break;
1095
+ }
1096
+ }
1097
+ parentPath = parentPath.parentPath;
1098
+ }
1099
+
1100
+ if (parentComponent) {
1101
+ analysis.edges.push({
1102
+ edgeType: 'PASSES_PROP',
1103
+ src: `react:component#${parentComponent}`,
1104
+ dst: `react:component#${componentName}`,
1105
+ propName: attrName,
1106
+ propValue,
1107
+ file: filePath,
1108
+ line: loc.start.line
1109
+ });
1110
+ }
1111
+ }
1112
+ }
1113
+
1114
+ /**
1115
+ * Analyze forwardRef usage
1116
+ */
1117
+ private analyzeForwardRef(path: NodePath<CallExpression>, filePath: string, analysis: AnalysisResult): void {
1118
+ const loc = path.node.loc!;
1119
+ const parent = path.parent as { type: string; id?: { name: string } };
1120
+ const componentName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
1121
+
1122
+ if (componentName) {
1123
+ analysis.components.push({
1124
+ id: `react:component#${componentName}#${filePath}:${loc.start.line}`,
1125
+ type: 'react:component',
1126
+ name: componentName,
1127
+ file: filePath,
1128
+ line: loc.start.line,
1129
+ column: loc.start.column,
1130
+ kind: 'forwardRef'
1131
+ });
1132
+ }
1133
+ }
1134
+
1135
+ /**
1136
+ * Analyze createContext usage
1137
+ */
1138
+ private analyzeCreateContext(path: NodePath<CallExpression>, filePath: string, analysis: AnalysisResult): void {
1139
+ const loc = path.node.loc!;
1140
+ const parent = path.parent as { type: string; id?: { name: string } };
1141
+ const contextName = parent.type === 'VariableDeclarator' ? parent.id?.name : null;
1142
+
1143
+ if (contextName) {
1144
+ const defaultValue = path.node.arguments[0];
1145
+ analysis.hooks.push({
1146
+ id: `react:context#${contextName}#${filePath}:${loc.start.line}`,
1147
+ type: 'react:context',
1148
+ contextName,
1149
+ file: filePath,
1150
+ line: loc.start.line,
1151
+ column: loc.start.column,
1152
+ hookName: 'createContext',
1153
+ defaultValue: this.getExpressionValue(defaultValue as Node)
1154
+ });
1155
+ }
1156
+ }
1157
+
1158
+ /**
1159
+ * Analyze browser API calls
1160
+ */
1161
+ private analyzeBrowserAPI(path: NodePath<CallExpression>, filePath: string, analysis: AnalysisResult): void {
1162
+ const callee = path.node.callee;
1163
+ const loc = path.node.loc!;
1164
+
1165
+ // Direct function call: setTimeout, fetch, alert
1166
+ if (callee.type === 'Identifier') {
1167
+ const name = callee.name;
1168
+
1169
+ // Timers
1170
+ if (BROWSER_APIS.timers.includes(name)) {
1171
+ analysis.browserAPIs.push({
1172
+ id: `browser:timer#${name}#${filePath}:${loc.start.line}`,
1173
+ type: 'browser:timer',
1174
+ api: name,
1175
+ file: filePath,
1176
+ line: loc.start.line
1177
+ });
1178
+ return;
1179
+ }
1180
+
1181
+ // Blocking APIs
1182
+ if (BROWSER_APIS.blocking.includes(name)) {
1183
+ analysis.browserAPIs.push({
1184
+ id: `browser:blocking#${name}#${filePath}:${loc.start.line}`,
1185
+ type: 'browser:blocking',
1186
+ api: name,
1187
+ file: filePath,
1188
+ line: loc.start.line
1189
+ });
1190
+ return;
1191
+ }
1192
+
1193
+ // Fetch
1194
+ if (name === 'fetch') {
1195
+ analysis.browserAPIs.push({
1196
+ id: `browser:async#fetch#${filePath}:${loc.start.line}`,
1197
+ type: 'browser:async',
1198
+ api: 'fetch',
1199
+ file: filePath,
1200
+ line: loc.start.line
1201
+ });
1202
+ return;
1203
+ }
1204
+ }
1205
+
1206
+ // Member expression: localStorage.setItem, document.querySelector
1207
+ if (callee.type === 'MemberExpression') {
1208
+ const fullName = this.getMemberExpressionName(callee);
1209
+
1210
+ // localStorage/sessionStorage
1211
+ if (fullName.startsWith('localStorage.') || fullName.startsWith('sessionStorage.')) {
1212
+ const [storage, method] = fullName.split('.');
1213
+ const operation = method === 'getItem' ? 'read' :
1214
+ method === 'setItem' ? 'write' :
1215
+ method === 'removeItem' ? 'delete' : method;
1216
+
1217
+ analysis.browserAPIs.push({
1218
+ id: `browser:storage#${storage}:${operation}#${filePath}:${loc.start.line}`,
1219
+ type: 'browser:storage',
1220
+ storage,
1221
+ operation,
1222
+ file: filePath,
1223
+ line: loc.start.line
1224
+ });
1225
+ return;
1226
+ }
1227
+
1228
+ // DOM queries
1229
+ if (fullName.startsWith('document.') &&
1230
+ (fullName.includes('querySelector') || fullName.includes('getElementById'))) {
1231
+ analysis.browserAPIs.push({
1232
+ id: `browser:dom#query#${filePath}:${loc.start.line}`,
1233
+ type: 'browser:dom',
1234
+ operation: 'query',
1235
+ api: fullName,
1236
+ file: filePath,
1237
+ line: loc.start.line
1238
+ });
1239
+ return;
1240
+ }
1241
+
1242
+ // History API
1243
+ if (fullName.startsWith('history.') || fullName.startsWith('window.history.')) {
1244
+ analysis.browserAPIs.push({
1245
+ id: `browser:history#${filePath}:${loc.start.line}`,
1246
+ type: 'browser:history',
1247
+ api: fullName,
1248
+ file: filePath,
1249
+ line: loc.start.line
1250
+ });
1251
+ return;
1252
+ }
1253
+
1254
+ // Clipboard API
1255
+ if (fullName.includes('clipboard')) {
1256
+ analysis.browserAPIs.push({
1257
+ id: `browser:clipboard#${filePath}:${loc.start.line}`,
1258
+ type: 'browser:clipboard',
1259
+ api: fullName,
1260
+ file: filePath,
1261
+ line: loc.start.line
1262
+ });
1263
+ return;
1264
+ }
1265
+
1266
+ // Geolocation
1267
+ if (fullName.includes('geolocation')) {
1268
+ analysis.browserAPIs.push({
1269
+ id: `browser:geolocation#${filePath}:${loc.start.line}`,
1270
+ type: 'browser:geolocation',
1271
+ api: fullName,
1272
+ file: filePath,
1273
+ line: loc.start.line
1274
+ });
1275
+ return;
1276
+ }
1277
+
1278
+ // Canvas context
1279
+ if (fullName.match(/\.(fillRect|strokeRect|fillText|strokeText|beginPath|closePath|moveTo|lineTo|arc|fill|stroke|clearRect|drawImage|save|restore|translate|rotate|scale)$/)) {
1280
+ const method = fullName.split('.').pop();
1281
+ analysis.browserAPIs.push({
1282
+ id: `canvas:draw#${method}#${filePath}:${loc.start.line}`,
1283
+ type: 'canvas:draw',
1284
+ method,
1285
+ file: filePath,
1286
+ line: loc.start.line
1287
+ });
1288
+ return;
1289
+ }
1290
+
1291
+ // matchMedia
1292
+ if (fullName === 'window.matchMedia' || fullName === 'matchMedia') {
1293
+ analysis.browserAPIs.push({
1294
+ id: `browser:media-query#${filePath}:${loc.start.line}`,
1295
+ type: 'browser:media-query',
1296
+ api: 'matchMedia',
1297
+ file: filePath,
1298
+ line: loc.start.line
1299
+ });
1300
+ return;
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ private getExpressionValue(expr: Node | undefined): string {
1306
+ if (!expr) return 'undefined';
1307
+ if (expr.type === 'StringLiteral') return `"${(expr as { value: string }).value}"`;
1308
+ if (expr.type === 'NumericLiteral') return String((expr as { value: number }).value);
1309
+ if (expr.type === 'BooleanLiteral') return String((expr as { value: boolean }).value);
1310
+ if (expr.type === 'NullLiteral') return 'null';
1311
+ if (expr.type === 'Identifier') return (expr as { name: string }).name;
1312
+ if (expr.type === 'ObjectExpression') return '{...}';
1313
+ if (expr.type === 'ArrayExpression') return '[...]';
1314
+ if (expr.type === 'ArrowFunctionExpression') return '() => {...}';
1315
+ if (expr.type === 'FunctionExpression') return 'function() {...}';
1316
+ return '<expression>';
1317
+ }
1318
+
1319
+ /**
1320
+ * Add all analysis results to graph
1321
+ */
1322
+ private async addToGraph(
1323
+ analysis: AnalysisResult,
1324
+ graph: PluginContext['graph'],
1325
+ moduleId: string | null
1326
+ ): Promise<void> {
1327
+ // Add component nodes
1328
+ for (const component of analysis.components) {
1329
+ await graph.addNode(component as unknown as NodeRecord);
1330
+ if (moduleId) {
1331
+ await graph.addEdge({
1332
+ type: 'DEFINES',
1333
+ src: moduleId,
1334
+ dst: component.id
1335
+ });
1336
+ }
1337
+ }
1338
+
1339
+ // Add hook nodes
1340
+ for (const hook of analysis.hooks) {
1341
+ await graph.addNode(hook as unknown as NodeRecord);
1342
+ }
1343
+
1344
+ // Add event nodes
1345
+ for (const event of analysis.events) {
1346
+ await graph.addNode(event as unknown as NodeRecord);
1347
+ }
1348
+
1349
+ // Add browser API nodes
1350
+ for (const api of analysis.browserAPIs) {
1351
+ await graph.addNode(api as unknown as NodeRecord);
1352
+ }
1353
+
1354
+ // Add issue nodes
1355
+ for (const issue of analysis.issues) {
1356
+ await graph.addNode(issue as unknown as NodeRecord);
1357
+ }
1358
+
1359
+ // Add edges
1360
+ for (const edge of analysis.edges) {
1361
+ const { edgeType, ...rest } = edge;
1362
+ await graph.addEdge({
1363
+ type: edgeType,
1364
+ ...rest
1365
+ });
1366
+ }
1367
+ }
1368
+ }