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