@grafema/core 0.2.5-beta → 0.2.7
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/README.md +61 -23
- package/dist/DiscoveryManager.d.ts +59 -0
- package/dist/DiscoveryManager.d.ts.map +1 -0
- package/dist/DiscoveryManager.js +249 -0
- package/dist/DiscoveryManager.js.map +1 -0
- package/dist/GraphInitializer.d.ts +44 -0
- package/dist/GraphInitializer.d.ts.map +1 -0
- package/dist/GraphInitializer.js +121 -0
- package/dist/GraphInitializer.js.map +1 -0
- package/dist/GuaranteeChecker.d.ts +35 -0
- package/dist/GuaranteeChecker.d.ts.map +1 -0
- package/dist/GuaranteeChecker.js +81 -0
- package/dist/GuaranteeChecker.js.map +1 -0
- package/dist/Orchestrator.d.ts +34 -151
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +173 -741
- package/dist/Orchestrator.js.map +1 -1
- package/dist/OrchestratorTypes.d.ts +115 -0
- package/dist/OrchestratorTypes.d.ts.map +1 -0
- package/dist/OrchestratorTypes.js +6 -0
- package/dist/OrchestratorTypes.js.map +1 -0
- package/dist/ParallelAnalysisRunner.d.ts +43 -0
- package/dist/ParallelAnalysisRunner.d.ts.map +1 -0
- package/dist/ParallelAnalysisRunner.js +161 -0
- package/dist/ParallelAnalysisRunner.js.map +1 -0
- package/dist/PhaseRunner.d.ts +94 -0
- package/dist/PhaseRunner.d.ts.map +1 -0
- package/dist/PhaseRunner.js +332 -0
- package/dist/PhaseRunner.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +51 -1
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +121 -1
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/core/ASTWorker.d.ts +2 -0
- package/dist/core/ASTWorker.d.ts.map +1 -1
- package/dist/core/ASTWorker.js +6 -2
- package/dist/core/ASTWorker.js.map +1 -1
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +257 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +1 -1
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -1
- package/dist/core/GraphFreshnessChecker.js +7 -5
- package/dist/core/GraphFreshnessChecker.js.map +1 -1
- package/dist/core/GuaranteeManager.d.ts +13 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -1
- package/dist/core/GuaranteeManager.js +63 -2
- package/dist/core/GuaranteeManager.js.map +1 -1
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -1
- package/dist/core/IncrementalReanalyzer.js +6 -3
- package/dist/core/IncrementalReanalyzer.js.map +1 -1
- package/dist/core/NodeFactory.d.ts +81 -415
- package/dist/core/NodeFactory.d.ts.map +1 -1
- package/dist/core/NodeFactory.js +139 -336
- package/dist/core/NodeFactory.js.map +1 -1
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/ScopeTracker.d.ts +12 -0
- package/dist/core/ScopeTracker.d.ts.map +1 -1
- package/dist/core/ScopeTracker.js +24 -0
- package/dist/core/ScopeTracker.js.map +1 -1
- package/dist/core/SemanticId.d.ts +69 -0
- package/dist/core/SemanticId.d.ts.map +1 -1
- package/dist/core/SemanticId.js +134 -0
- package/dist/core/SemanticId.js.map +1 -1
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/buildDependencyGraph.d.ts +36 -0
- package/dist/core/buildDependencyGraph.d.ts.map +1 -0
- package/dist/core/buildDependencyGraph.js +78 -0
- package/dist/core/buildDependencyGraph.js.map +1 -0
- package/dist/core/factories/CoreFactory.d.ts +223 -0
- package/dist/core/factories/CoreFactory.d.ts.map +1 -0
- package/dist/core/factories/CoreFactory.js +127 -0
- package/dist/core/factories/CoreFactory.js.map +1 -0
- package/dist/core/factories/DatabaseFactory.d.ts +29 -0
- package/dist/core/factories/DatabaseFactory.d.ts.map +1 -0
- package/dist/core/factories/DatabaseFactory.js +25 -0
- package/dist/core/factories/DatabaseFactory.js.map +1 -0
- package/dist/core/factories/ExternalFactory.d.ts +11 -0
- package/dist/core/factories/ExternalFactory.d.ts.map +1 -0
- package/dist/core/factories/ExternalFactory.js +16 -0
- package/dist/core/factories/ExternalFactory.js.map +1 -0
- package/dist/core/factories/HttpFactory.d.ts +22 -0
- package/dist/core/factories/HttpFactory.d.ts.map +1 -0
- package/dist/core/factories/HttpFactory.js +32 -0
- package/dist/core/factories/HttpFactory.js.map +1 -0
- package/dist/core/factories/ReactFactory.d.ts +14 -0
- package/dist/core/factories/ReactFactory.d.ts.map +1 -0
- package/dist/core/factories/ReactFactory.js +13 -0
- package/dist/core/factories/ReactFactory.js.map +1 -0
- package/dist/core/factories/RustFactory.d.ts +62 -0
- package/dist/core/factories/RustFactory.d.ts.map +1 -0
- package/dist/core/factories/RustFactory.js +32 -0
- package/dist/core/factories/RustFactory.js.map +1 -0
- package/dist/core/factories/ServiceFactory.d.ts +12 -0
- package/dist/core/factories/ServiceFactory.d.ts.map +1 -0
- package/dist/core/factories/ServiceFactory.js +22 -0
- package/dist/core/factories/ServiceFactory.js.map +1 -0
- package/dist/core/factories/SocketFactory.d.ts +31 -0
- package/dist/core/factories/SocketFactory.d.ts.map +1 -0
- package/dist/core/factories/SocketFactory.js +35 -0
- package/dist/core/factories/SocketFactory.js.map +1 -0
- package/dist/core/nodes/DatabaseNode.d.ts +85 -0
- package/dist/core/nodes/DatabaseNode.d.ts.map +1 -0
- package/dist/core/nodes/DatabaseNode.js +118 -0
- package/dist/core/nodes/DatabaseNode.js.map +1 -0
- package/dist/core/nodes/ExpressMiddlewareNode.d.ts +47 -0
- package/dist/core/nodes/ExpressMiddlewareNode.d.ts.map +1 -0
- package/dist/core/nodes/ExpressMiddlewareNode.js +63 -0
- package/dist/core/nodes/ExpressMiddlewareNode.js.map +1 -0
- package/dist/core/nodes/ExpressMountNode.d.ts +44 -0
- package/dist/core/nodes/ExpressMountNode.d.ts.map +1 -0
- package/dist/core/nodes/ExpressMountNode.js +61 -0
- package/dist/core/nodes/ExpressMountNode.js.map +1 -0
- package/dist/core/nodes/ExternalApiNode.d.ts +29 -0
- package/dist/core/nodes/ExternalApiNode.d.ts.map +1 -0
- package/dist/core/nodes/ExternalApiNode.js +41 -0
- package/dist/core/nodes/ExternalApiNode.js.map +1 -0
- package/dist/core/nodes/ExternalFunctionNode.d.ts +40 -0
- package/dist/core/nodes/ExternalFunctionNode.d.ts.map +1 -0
- package/dist/core/nodes/ExternalFunctionNode.js +54 -0
- package/dist/core/nodes/ExternalFunctionNode.js.map +1 -0
- package/dist/core/nodes/FetchRequestNode.d.ts +54 -0
- package/dist/core/nodes/FetchRequestNode.d.ts.map +1 -0
- package/dist/core/nodes/FetchRequestNode.js +67 -0
- package/dist/core/nodes/FetchRequestNode.js.map +1 -0
- package/dist/core/nodes/HttpRouteNode.d.ts +58 -0
- package/dist/core/nodes/HttpRouteNode.d.ts.map +1 -0
- package/dist/core/nodes/HttpRouteNode.js +72 -0
- package/dist/core/nodes/HttpRouteNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +1 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -1
- package/dist/core/nodes/NodeKind.js +1 -0
- package/dist/core/nodes/NodeKind.js.map +1 -1
- package/dist/core/nodes/ReactNode.d.ts +53 -0
- package/dist/core/nodes/ReactNode.d.ts.map +1 -0
- package/dist/core/nodes/ReactNode.js +70 -0
- package/dist/core/nodes/ReactNode.js.map +1 -0
- package/dist/core/nodes/RustCallNode.d.ts +46 -0
- package/dist/core/nodes/RustCallNode.d.ts.map +1 -0
- package/dist/core/nodes/RustCallNode.js +62 -0
- package/dist/core/nodes/RustCallNode.js.map +1 -0
- package/dist/core/nodes/RustFunctionNode.d.ts +58 -0
- package/dist/core/nodes/RustFunctionNode.d.ts.map +1 -0
- package/dist/core/nodes/RustFunctionNode.js +67 -0
- package/dist/core/nodes/RustFunctionNode.js.map +1 -0
- package/dist/core/nodes/RustImplNode.d.ts +35 -0
- package/dist/core/nodes/RustImplNode.d.ts.map +1 -0
- package/dist/core/nodes/RustImplNode.js +55 -0
- package/dist/core/nodes/RustImplNode.js.map +1 -0
- package/dist/core/nodes/RustMethodNode.d.ts +64 -0
- package/dist/core/nodes/RustMethodNode.d.ts.map +1 -0
- package/dist/core/nodes/RustMethodNode.js +76 -0
- package/dist/core/nodes/RustMethodNode.js.map +1 -0
- package/dist/core/nodes/RustModuleNode.d.ts +40 -0
- package/dist/core/nodes/RustModuleNode.d.ts.map +1 -0
- package/dist/core/nodes/RustModuleNode.js +57 -0
- package/dist/core/nodes/RustModuleNode.js.map +1 -0
- package/dist/core/nodes/RustStructNode.d.ts +38 -0
- package/dist/core/nodes/RustStructNode.d.ts.map +1 -0
- package/dist/core/nodes/RustStructNode.js +54 -0
- package/dist/core/nodes/RustStructNode.js.map +1 -0
- package/dist/core/nodes/RustTraitNode.d.ts +40 -0
- package/dist/core/nodes/RustTraitNode.d.ts.map +1 -0
- package/dist/core/nodes/RustTraitNode.js +52 -0
- package/dist/core/nodes/RustTraitNode.js.map +1 -0
- package/dist/core/nodes/ServiceLayerNode.d.ts +85 -0
- package/dist/core/nodes/ServiceLayerNode.d.ts.map +1 -0
- package/dist/core/nodes/ServiceLayerNode.js +122 -0
- package/dist/core/nodes/ServiceLayerNode.js.map +1 -0
- package/dist/core/nodes/SocketIONode.d.ts +71 -0
- package/dist/core/nodes/SocketIONode.d.ts.map +1 -0
- package/dist/core/nodes/SocketIONode.js +111 -0
- package/dist/core/nodes/SocketIONode.js.map +1 -0
- package/dist/core/nodes/SocketNode.d.ts +87 -0
- package/dist/core/nodes/SocketNode.d.ts.map +1 -0
- package/dist/core/nodes/SocketNode.js +124 -0
- package/dist/core/nodes/SocketNode.js.map +1 -0
- package/dist/core/nodes/TypeNode.d.ts +26 -1
- package/dist/core/nodes/TypeNode.d.ts.map +1 -1
- package/dist/core/nodes/TypeNode.js +21 -3
- package/dist/core/nodes/TypeNode.js.map +1 -1
- package/dist/core/nodes/TypeParameterNode.d.ts +44 -0
- package/dist/core/nodes/TypeParameterNode.d.ts.map +1 -0
- package/dist/core/nodes/TypeParameterNode.js +64 -0
- package/dist/core/nodes/TypeParameterNode.js.map +1 -0
- package/dist/core/nodes/index.d.ts +19 -0
- package/dist/core/nodes/index.d.ts.map +1 -1
- package/dist/core/nodes/index.js +26 -0
- package/dist/core/nodes/index.js.map +1 -1
- package/dist/index.d.ts +33 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins/InfraAnalyzer.d.ts +110 -0
- package/dist/plugins/InfraAnalyzer.d.ts.map +1 -0
- package/dist/plugins/InfraAnalyzer.js +170 -0
- package/dist/plugins/InfraAnalyzer.js.map +1 -0
- package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/DatabaseAnalyzer.js +18 -15
- package/dist/plugins/analysis/DatabaseAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressAnalyzer.js +27 -26
- package/dist/plugins/analysis/ExpressAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressResponseAnalyzer.js +5 -3
- package/dist/plugins/analysis/ExpressResponseAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressRouteAnalyzer.js +36 -39
- package/dist/plugins/analysis/ExpressRouteAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/FetchAnalyzer.js +23 -39
- package/dist/plugins/analysis/FetchAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts.map +1 -1
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.js +3 -2
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.js.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts +23 -85
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.js +351 -1887
- package/dist/plugins/analysis/JSASTAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/NestJSRouteAnalyzer.d.ts +28 -0
- package/dist/plugins/analysis/NestJSRouteAnalyzer.d.ts.map +1 -0
- package/dist/plugins/analysis/NestJSRouteAnalyzer.js +196 -0
- package/dist/plugins/analysis/NestJSRouteAnalyzer.js.map +1 -0
- package/dist/plugins/analysis/ReactAnalyzer.d.ts +1 -61
- package/dist/plugins/analysis/ReactAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ReactAnalyzer.js +24 -915
- package/dist/plugins/analysis/ReactAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/RustAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/RustAnalyzer.js +31 -66
- package/dist/plugins/analysis/RustAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SQLiteAnalyzer.js +13 -6
- package/dist/plugins/analysis/SQLiteAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ServiceLayerAnalyzer.js +10 -7
- package/dist/plugins/analysis/ServiceLayerAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/SocketAnalyzer.d.ts +81 -0
- package/dist/plugins/analysis/SocketAnalyzer.d.ts.map +1 -0
- package/dist/plugins/analysis/SocketAnalyzer.js +475 -0
- package/dist/plugins/analysis/SocketAnalyzer.js.map +1 -0
- package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SocketIOAnalyzer.js +13 -18
- package/dist/plugins/analysis/SocketIOAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/SystemDbAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SystemDbAnalyzer.js +8 -5
- package/dist/plugins/analysis/SystemDbAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ast/CollisionResolver.d.ts +65 -0
- package/dist/plugins/analysis/ast/CollisionResolver.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/CollisionResolver.js +106 -0
- package/dist/plugins/analysis/ast/CollisionResolver.js.map +1 -0
- package/dist/plugins/analysis/ast/FunctionBodyContext.d.ts +124 -0
- package/dist/plugins/analysis/ast/FunctionBodyContext.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/FunctionBodyContext.js +151 -0
- package/dist/plugins/analysis/ast/FunctionBodyContext.js.map +1 -0
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts +26 -261
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.js +251 -2494
- package/dist/plugins/analysis/ast/GraphBuilder.js.map +1 -1
- package/dist/plugins/analysis/ast/IdGenerator.d.ts +42 -0
- package/dist/plugins/analysis/ast/IdGenerator.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/IdGenerator.js +61 -1
- package/dist/plugins/analysis/ast/IdGenerator.js.map +1 -1
- package/dist/plugins/analysis/ast/builders/AssignmentBuilder.d.ts +15 -0
- package/dist/plugins/analysis/ast/builders/AssignmentBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/AssignmentBuilder.js +274 -0
- package/dist/plugins/analysis/ast/builders/AssignmentBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/CallFlowBuilder.d.ts +22 -0
- package/dist/plugins/analysis/ast/builders/CallFlowBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/CallFlowBuilder.js +178 -0
- package/dist/plugins/analysis/ast/builders/CallFlowBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/ControlFlowBuilder.d.ts +76 -0
- package/dist/plugins/analysis/ast/builders/ControlFlowBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/ControlFlowBuilder.js +387 -0
- package/dist/plugins/analysis/ast/builders/ControlFlowBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/CoreBuilder.d.ts +38 -0
- package/dist/plugins/analysis/ast/builders/CoreBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/CoreBuilder.js +240 -0
- package/dist/plugins/analysis/ast/builders/CoreBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.d.ts +53 -0
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.js +355 -0
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/MutationBuilder.d.ts +46 -0
- package/dist/plugins/analysis/ast/builders/MutationBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/MutationBuilder.js +264 -0
- package/dist/plugins/analysis/ast/builders/MutationBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/ReturnBuilder.d.ts +23 -0
- package/dist/plugins/analysis/ast/builders/ReturnBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/ReturnBuilder.js +206 -0
- package/dist/plugins/analysis/ast/builders/ReturnBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/TypeSystemBuilder.d.ts +64 -0
- package/dist/plugins/analysis/ast/builders/TypeSystemBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/TypeSystemBuilder.js +370 -0
- package/dist/plugins/analysis/ast/builders/TypeSystemBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/UpdateExpressionBuilder.d.ts +46 -0
- package/dist/plugins/analysis/ast/builders/UpdateExpressionBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/UpdateExpressionBuilder.js +191 -0
- package/dist/plugins/analysis/ast/builders/UpdateExpressionBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/YieldBuilder.d.ts +30 -0
- package/dist/plugins/analysis/ast/builders/YieldBuilder.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/YieldBuilder.js +214 -0
- package/dist/plugins/analysis/ast/builders/YieldBuilder.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/index.d.ts +12 -0
- package/dist/plugins/analysis/ast/builders/index.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/index.js +11 -0
- package/dist/plugins/analysis/ast/builders/index.js.map +1 -0
- package/dist/plugins/analysis/ast/builders/types.d.ts +30 -0
- package/dist/plugins/analysis/ast/builders/types.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/builders/types.js +8 -0
- package/dist/plugins/analysis/ast/builders/types.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/AnalyzerDelegate.d.ts +50 -0
- package/dist/plugins/analysis/ast/handlers/AnalyzerDelegate.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/AnalyzerDelegate.js +2 -0
- package/dist/plugins/analysis/ast/handlers/AnalyzerDelegate.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/BranchHandler.d.ts +18 -0
- package/dist/plugins/analysis/ast/handlers/BranchHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/BranchHandler.js +244 -0
- package/dist/plugins/analysis/ast/handlers/BranchHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/CallExpressionHandler.d.ts +7 -0
- package/dist/plugins/analysis/ast/handlers/CallExpressionHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/CallExpressionHandler.js +295 -0
- package/dist/plugins/analysis/ast/handlers/CallExpressionHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/FunctionBodyHandler.d.ts +22 -0
- package/dist/plugins/analysis/ast/handlers/FunctionBodyHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/FunctionBodyHandler.js +9 -0
- package/dist/plugins/analysis/ast/handlers/FunctionBodyHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/LoopHandler.d.ts +13 -0
- package/dist/plugins/analysis/ast/handlers/LoopHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/LoopHandler.js +207 -0
- package/dist/plugins/analysis/ast/handlers/LoopHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/NestedFunctionHandler.d.ts +13 -0
- package/dist/plugins/analysis/ast/handlers/NestedFunctionHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/NestedFunctionHandler.js +174 -0
- package/dist/plugins/analysis/ast/handlers/NestedFunctionHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.d.ts +12 -0
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.js +135 -0
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/PropertyAccessHandler.d.ts +13 -0
- package/dist/plugins/analysis/ast/handlers/PropertyAccessHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/PropertyAccessHandler.js +71 -0
- package/dist/plugins/analysis/ast/handlers/PropertyAccessHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/ReturnYieldHandler.d.ts +12 -0
- package/dist/plugins/analysis/ast/handlers/ReturnYieldHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/ReturnYieldHandler.js +135 -0
- package/dist/plugins/analysis/ast/handlers/ReturnYieldHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/ThrowHandler.d.ts +12 -0
- package/dist/plugins/analysis/ast/handlers/ThrowHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/ThrowHandler.js +82 -0
- package/dist/plugins/analysis/ast/handlers/ThrowHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/TryCatchHandler.d.ts +14 -0
- package/dist/plugins/analysis/ast/handlers/TryCatchHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/TryCatchHandler.js +220 -0
- package/dist/plugins/analysis/ast/handlers/TryCatchHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/VariableHandler.d.ts +12 -0
- package/dist/plugins/analysis/ast/handlers/VariableHandler.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/VariableHandler.js +57 -0
- package/dist/plugins/analysis/ast/handlers/VariableHandler.js.map +1 -0
- package/dist/plugins/analysis/ast/handlers/index.d.ts +13 -0
- package/dist/plugins/analysis/ast/handlers/index.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/handlers/index.js +12 -0
- package/dist/plugins/analysis/ast/handlers/index.js.map +1 -0
- package/dist/plugins/analysis/ast/types.d.ts +57 -6
- package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts +5 -4
- package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/utils/createParameterNodes.js +94 -13
- package/dist/plugins/analysis/ast/utils/createParameterNodes.js.map +1 -1
- package/dist/plugins/analysis/ast/utils/extractNamesFromPattern.d.ts +81 -0
- package/dist/plugins/analysis/ast/utils/extractNamesFromPattern.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/extractNamesFromPattern.js +140 -0
- package/dist/plugins/analysis/ast/utils/extractNamesFromPattern.js.map +1 -0
- package/dist/plugins/analysis/ast/utils/getExpressionValue.d.ts +22 -0
- package/dist/plugins/analysis/ast/utils/getExpressionValue.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/getExpressionValue.js +35 -0
- package/dist/plugins/analysis/ast/utils/getExpressionValue.js.map +1 -0
- package/dist/plugins/analysis/ast/utils/getMemberExpressionName.d.ts +25 -0
- package/dist/plugins/analysis/ast/utils/getMemberExpressionName.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/getMemberExpressionName.js +21 -0
- package/dist/plugins/analysis/ast/utils/getMemberExpressionName.js.map +1 -0
- package/dist/plugins/analysis/ast/utils/index.d.ts +2 -0
- package/dist/plugins/analysis/ast/utils/index.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/utils/index.js +2 -0
- package/dist/plugins/analysis/ast/utils/index.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ArgumentExtractor.d.ts +23 -0
- package/dist/plugins/analysis/ast/visitors/ArgumentExtractor.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/ArgumentExtractor.js +241 -0
- package/dist/plugins/analysis/ast/visitors/ArgumentExtractor.js.map +1 -0
- package/dist/plugins/analysis/ast/visitors/ArrayElementExtractor.d.ts +20 -0
- package/dist/plugins/analysis/ast/visitors/ArrayElementExtractor.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/ArrayElementExtractor.js +110 -0
- package/dist/plugins/analysis/ast/visitors/ArrayElementExtractor.js.map +1 -0
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +15 -142
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +304 -937
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.js +26 -11
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +21 -6
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/MutationDetector.d.ts +25 -0
- package/dist/plugins/analysis/ast/visitors/MutationDetector.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/MutationDetector.js +181 -0
- package/dist/plugins/analysis/ast/visitors/MutationDetector.js.map +1 -0
- package/dist/plugins/analysis/ast/visitors/ObjectPropertyExtractor.d.ts +20 -0
- package/dist/plugins/analysis/ast/visitors/ObjectPropertyExtractor.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/ObjectPropertyExtractor.js +155 -0
- package/dist/plugins/analysis/ast/visitors/ObjectPropertyExtractor.js.map +1 -0
- package/dist/plugins/analysis/ast/visitors/PropertyAccessVisitor.d.ts +9 -1
- package/dist/plugins/analysis/ast/visitors/PropertyAccessVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/PropertyAccessVisitor.js +51 -3
- package/dist/plugins/analysis/ast/visitors/PropertyAccessVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts +20 -0
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js +188 -12
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +6 -4
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.js.map +1 -1
- package/dist/plugins/analysis/ast/visitors/call-expression-helpers.d.ts +19 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-helpers.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-helpers.js +57 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-helpers.js.map +1 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-types.d.ts +168 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-types.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-types.js +7 -0
- package/dist/plugins/analysis/ast/visitors/call-expression-types.js.map +1 -0
- package/dist/plugins/analysis/react-internal/browser-api.d.ts +20 -0
- package/dist/plugins/analysis/react-internal/browser-api.d.ts.map +1 -0
- package/dist/plugins/analysis/react-internal/browser-api.js +140 -0
- package/dist/plugins/analysis/react-internal/browser-api.js.map +1 -0
- package/dist/plugins/analysis/react-internal/hooks.d.ts +31 -0
- package/dist/plugins/analysis/react-internal/hooks.d.ts.map +1 -0
- package/dist/plugins/analysis/react-internal/hooks.js +465 -0
- package/dist/plugins/analysis/react-internal/hooks.js.map +1 -0
- package/dist/plugins/analysis/react-internal/jsx.d.ts +43 -0
- package/dist/plugins/analysis/react-internal/jsx.d.ts.map +1 -0
- package/dist/plugins/analysis/react-internal/jsx.js +231 -0
- package/dist/plugins/analysis/react-internal/jsx.js.map +1 -0
- package/dist/plugins/analysis/react-internal/types.d.ts +116 -0
- package/dist/plugins/analysis/react-internal/types.d.ts.map +1 -0
- package/dist/plugins/analysis/react-internal/types.js +83 -0
- package/dist/plugins/analysis/react-internal/types.js.map +1 -0
- package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts.map +1 -1
- package/dist/plugins/discovery/MonorepoServiceDiscovery.js +6 -13
- package/dist/plugins/discovery/MonorepoServiceDiscovery.js.map +1 -1
- package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
- package/dist/plugins/enrichment/AliasTracker.js +3 -1
- package/dist/plugins/enrichment/AliasTracker.js.map +1 -1
- package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts.map +1 -1
- package/dist/plugins/enrichment/ArgumentParameterLinker.js +3 -1
- package/dist/plugins/enrichment/ArgumentParameterLinker.js.map +1 -1
- package/dist/plugins/enrichment/CallbackCallResolver.d.ts +42 -0
- package/dist/plugins/enrichment/CallbackCallResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/CallbackCallResolver.js +311 -0
- package/dist/plugins/enrichment/CallbackCallResolver.js.map +1 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/ClosureCaptureEnricher.js +3 -1
- package/dist/plugins/enrichment/ClosureCaptureEnricher.js.map +1 -1
- package/dist/plugins/enrichment/ConfigRoutingMapBuilder.d.ts +17 -0
- package/dist/plugins/enrichment/ConfigRoutingMapBuilder.d.ts.map +1 -0
- package/dist/plugins/enrichment/ConfigRoutingMapBuilder.js +55 -0
- package/dist/plugins/enrichment/ConfigRoutingMapBuilder.js.map +1 -0
- package/dist/plugins/enrichment/ExpressHandlerLinker.d.ts.map +1 -1
- package/dist/plugins/enrichment/ExpressHandlerLinker.js +3 -1
- package/dist/plugins/enrichment/ExpressHandlerLinker.js.map +1 -1
- package/dist/plugins/enrichment/ExternalCallResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/ExternalCallResolver.js +5 -8
- package/dist/plugins/enrichment/ExternalCallResolver.js.map +1 -1
- package/dist/plugins/enrichment/FunctionCallResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/FunctionCallResolver.js +6 -9
- package/dist/plugins/enrichment/FunctionCallResolver.js.map +1 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.js +3 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.js.map +1 -1
- package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -1
- package/dist/plugins/enrichment/ImportExportLinker.js +5 -3
- package/dist/plugins/enrichment/ImportExportLinker.js.map +1 -1
- package/dist/plugins/enrichment/InstanceOfResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/InstanceOfResolver.js +3 -1
- package/dist/plugins/enrichment/InstanceOfResolver.js.map +1 -1
- package/dist/plugins/enrichment/MethodCallResolver.d.ts +17 -68
- package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MethodCallResolver.js +42 -517
- package/dist/plugins/enrichment/MethodCallResolver.js.map +1 -1
- package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MountPointResolver.js +9 -2
- package/dist/plugins/enrichment/MountPointResolver.js.map +1 -1
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.js +7 -16
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.js.map +1 -1
- package/dist/plugins/enrichment/PrefixEvaluator.d.ts.map +1 -1
- package/dist/plugins/enrichment/PrefixEvaluator.js +6 -2
- package/dist/plugins/enrichment/PrefixEvaluator.js.map +1 -1
- package/dist/plugins/enrichment/RejectionPropagationEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/RejectionPropagationEnricher.js +3 -1
- package/dist/plugins/enrichment/RejectionPropagationEnricher.js.map +1 -1
- package/dist/plugins/enrichment/RustFFIEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/RustFFIEnricher.js +3 -1
- package/dist/plugins/enrichment/RustFFIEnricher.js.map +1 -1
- package/dist/plugins/enrichment/ServiceConnectionEnricher.d.ts +76 -0
- package/dist/plugins/enrichment/ServiceConnectionEnricher.d.ts.map +1 -0
- package/dist/plugins/enrichment/ServiceConnectionEnricher.js +355 -0
- package/dist/plugins/enrichment/ServiceConnectionEnricher.js.map +1 -0
- package/dist/plugins/enrichment/SocketConnectionEnricher.d.ts +42 -0
- package/dist/plugins/enrichment/SocketConnectionEnricher.d.ts.map +1 -0
- package/dist/plugins/enrichment/SocketConnectionEnricher.js +166 -0
- package/dist/plugins/enrichment/SocketConnectionEnricher.js.map +1 -0
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
- package/dist/plugins/enrichment/ValueDomainAnalyzer.js +3 -1
- package/dist/plugins/enrichment/ValueDomainAnalyzer.js.map +1 -1
- package/dist/plugins/enrichment/method-call/MethodCallData.d.ts +68 -0
- package/dist/plugins/enrichment/method-call/MethodCallData.d.ts.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallData.js +227 -0
- package/dist/plugins/enrichment/method-call/MethodCallData.js.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallDetectors.d.ts +21 -0
- package/dist/plugins/enrichment/method-call/MethodCallDetectors.d.ts.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallDetectors.js +52 -0
- package/dist/plugins/enrichment/method-call/MethodCallDetectors.js.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallErrorAnalysis.d.ts +22 -0
- package/dist/plugins/enrichment/method-call/MethodCallErrorAnalysis.d.ts.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallErrorAnalysis.js +105 -0
- package/dist/plugins/enrichment/method-call/MethodCallErrorAnalysis.js.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallIndexers.d.ts +19 -0
- package/dist/plugins/enrichment/method-call/MethodCallIndexers.d.ts.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallIndexers.js +63 -0
- package/dist/plugins/enrichment/method-call/MethodCallIndexers.js.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallResolution.d.ts +30 -0
- package/dist/plugins/enrichment/method-call/MethodCallResolution.d.ts.map +1 -0
- package/dist/plugins/enrichment/method-call/MethodCallResolution.js +138 -0
- package/dist/plugins/enrichment/method-call/MethodCallResolution.js.map +1 -0
- package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/IncrementalModuleIndexer.js +2 -8
- package/dist/plugins/indexing/IncrementalModuleIndexer.js.map +1 -1
- package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/JSModuleIndexer.js +13 -20
- package/dist/plugins/indexing/JSModuleIndexer.js.map +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.js +4 -8
- package/dist/plugins/indexing/RustModuleIndexer.js.map +1 -1
- package/dist/plugins/validation/AwaitInLoopValidator.d.ts +24 -0
- package/dist/plugins/validation/AwaitInLoopValidator.d.ts.map +1 -0
- package/dist/plugins/validation/AwaitInLoopValidator.js +69 -0
- package/dist/plugins/validation/AwaitInLoopValidator.js.map +1 -0
- package/dist/plugins/validation/PackageCoverageValidator.d.ts +33 -0
- package/dist/plugins/validation/PackageCoverageValidator.d.ts.map +1 -0
- package/dist/plugins/validation/PackageCoverageValidator.js +149 -0
- package/dist/plugins/validation/PackageCoverageValidator.js.map +1 -0
- package/dist/plugins/validation/UnconnectedRouteValidator.d.ts +18 -0
- package/dist/plugins/validation/UnconnectedRouteValidator.d.ts.map +1 -0
- package/dist/plugins/validation/UnconnectedRouteValidator.js +68 -0
- package/dist/plugins/validation/UnconnectedRouteValidator.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +193 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -1
- package/dist/queries/findCallsInFunction.js +10 -2
- package/dist/queries/findCallsInFunction.js.map +1 -1
- package/dist/queries/findContainingFunction.d.ts +3 -2
- package/dist/queries/findContainingFunction.d.ts.map +1 -1
- package/dist/queries/findContainingFunction.js +13 -3
- package/dist/queries/findContainingFunction.js.map +1 -1
- package/dist/queries/index.d.ts +2 -0
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/index.js +1 -0
- package/dist/queries/index.js.map +1 -1
- package/dist/resources/InfraResourceMapImpl.d.ts +31 -0
- package/dist/resources/InfraResourceMapImpl.d.ts.map +1 -0
- package/dist/resources/InfraResourceMapImpl.js +110 -0
- package/dist/resources/InfraResourceMapImpl.js.map +1 -0
- package/dist/resources/RoutingMapImpl.d.ts +33 -0
- package/dist/resources/RoutingMapImpl.d.ts.map +1 -0
- package/dist/resources/RoutingMapImpl.js +115 -0
- package/dist/resources/RoutingMapImpl.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +35 -6
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.js +102 -70
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -1
- package/dist/utils/findRfdbBinary.d.ts +3 -2
- package/dist/utils/findRfdbBinary.d.ts.map +1 -1
- package/dist/utils/findRfdbBinary.js +22 -7
- package/dist/utils/findRfdbBinary.js.map +1 -1
- package/dist/utils/moduleResolution.d.ts.map +1 -1
- package/dist/utils/moduleResolution.js +26 -1
- package/dist/utils/moduleResolution.js.map +1 -1
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -3
- package/src/DiscoveryManager.ts +279 -0
- package/src/GraphInitializer.ts +131 -0
- package/src/GuaranteeChecker.ts +90 -0
- package/src/Orchestrator.ts +222 -963
- package/src/OrchestratorTypes.ts +122 -0
- package/src/ParallelAnalysisRunner.ts +188 -0
- package/src/PhaseRunner.ts +450 -0
- package/src/config/ConfigLoader.ts +176 -2
- package/src/config/index.ts +2 -0
- package/src/core/ASTWorker.ts +9 -2
- package/src/core/FileOverview.ts +374 -0
- package/src/core/GraphFreshnessChecker.ts +7 -5
- package/src/core/GuaranteeManager.ts +70 -2
- package/src/core/IncrementalReanalyzer.ts +6 -3
- package/src/core/NodeFactory.ts +173 -652
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/ScopeTracker.ts +23 -0
- package/src/core/SemanticId.ts +183 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/buildDependencyGraph.ts +98 -0
- package/src/core/factories/CoreFactory.ts +489 -0
- package/src/core/factories/DatabaseFactory.ts +63 -0
- package/src/core/factories/ExternalFactory.ts +23 -0
- package/src/core/factories/HttpFactory.ts +57 -0
- package/src/core/factories/ReactFactory.ts +15 -0
- package/src/core/factories/RustFactory.ts +128 -0
- package/src/core/factories/ServiceFactory.ts +27 -0
- package/src/core/factories/SocketFactory.ts +94 -0
- package/src/core/nodes/DatabaseNode.ts +175 -0
- package/src/core/nodes/ExpressMiddlewareNode.ts +98 -0
- package/src/core/nodes/ExpressMountNode.ts +94 -0
- package/src/core/nodes/ExternalApiNode.ts +53 -0
- package/src/core/nodes/ExternalFunctionNode.ts +77 -0
- package/src/core/nodes/FetchRequestNode.ts +105 -0
- package/src/core/nodes/HttpRouteNode.ts +113 -0
- package/src/core/nodes/NodeKind.ts +1 -0
- package/src/core/nodes/ReactNode.ts +78 -0
- package/src/core/nodes/RustCallNode.ts +96 -0
- package/src/core/nodes/RustFunctionNode.ts +112 -0
- package/src/core/nodes/RustImplNode.ts +78 -0
- package/src/core/nodes/RustMethodNode.ts +125 -0
- package/src/core/nodes/RustModuleNode.ts +84 -0
- package/src/core/nodes/RustStructNode.ts +80 -0
- package/src/core/nodes/RustTraitNode.ts +82 -0
- package/src/core/nodes/ServiceLayerNode.ts +183 -0
- package/src/core/nodes/SocketIONode.ts +177 -0
- package/src/core/nodes/SocketNode.ts +206 -0
- package/src/core/nodes/TypeNode.ts +46 -3
- package/src/core/nodes/TypeParameterNode.ts +91 -0
- package/src/core/nodes/index.ts +57 -0
- package/src/index.ts +60 -4
- package/src/plugins/InfraAnalyzer.ts +208 -0
- package/src/plugins/analysis/DatabaseAnalyzer.ts +27 -17
- package/src/plugins/analysis/ExpressAnalyzer.ts +51 -38
- package/src/plugins/analysis/ExpressResponseAnalyzer.ts +15 -12
- package/src/plugins/analysis/ExpressRouteAnalyzer.ts +56 -56
- package/src/plugins/analysis/FetchAnalyzer.ts +42 -52
- package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +3 -2
- package/src/plugins/analysis/JSASTAnalyzer.ts +391 -2304
- package/src/plugins/analysis/NestJSRouteAnalyzer.ts +241 -0
- package/src/plugins/analysis/ReactAnalyzer.ts +33 -1085
- package/src/plugins/analysis/RustAnalyzer.ts +112 -116
- package/src/plugins/analysis/SQLiteAnalyzer.ts +23 -9
- package/src/plugins/analysis/ServiceLayerAnalyzer.ts +32 -10
- package/src/plugins/analysis/SocketAnalyzer.ts +601 -0
- package/src/plugins/analysis/SocketIOAnalyzer.ts +25 -34
- package/src/plugins/analysis/SystemDbAnalyzer.ts +15 -12
- package/src/plugins/analysis/ast/CollisionResolver.ts +137 -0
- package/src/plugins/analysis/ast/FunctionBodyContext.ts +291 -0
- package/src/plugins/analysis/ast/GraphBuilder.ts +274 -3180
- package/src/plugins/analysis/ast/IdGenerator.ts +81 -1
- package/src/plugins/analysis/ast/builders/AssignmentBuilder.ts +407 -0
- package/src/plugins/analysis/ast/builders/CallFlowBuilder.ts +255 -0
- package/src/plugins/analysis/ast/builders/ControlFlowBuilder.ts +470 -0
- package/src/plugins/analysis/ast/builders/CoreBuilder.ts +306 -0
- package/src/plugins/analysis/ast/builders/ModuleRuntimeBuilder.ts +452 -0
- package/src/plugins/analysis/ast/builders/MutationBuilder.ts +372 -0
- package/src/plugins/analysis/ast/builders/ReturnBuilder.ts +279 -0
- package/src/plugins/analysis/ast/builders/TypeSystemBuilder.ts +475 -0
- package/src/plugins/analysis/ast/builders/UpdateExpressionBuilder.ts +262 -0
- package/src/plugins/analysis/ast/builders/YieldBuilder.ts +287 -0
- package/src/plugins/analysis/ast/builders/index.ts +11 -0
- package/src/plugins/analysis/ast/builders/types.ts +65 -0
- package/src/plugins/analysis/ast/handlers/AnalyzerDelegate.ts +183 -0
- package/src/plugins/analysis/ast/handlers/BranchHandler.ts +313 -0
- package/src/plugins/analysis/ast/handlers/CallExpressionHandler.ts +347 -0
- package/src/plugins/analysis/ast/handlers/FunctionBodyHandler.ts +24 -0
- package/src/plugins/analysis/ast/handlers/LoopHandler.ts +240 -0
- package/src/plugins/analysis/ast/handlers/NestedFunctionHandler.ts +201 -0
- package/src/plugins/analysis/ast/handlers/NewExpressionHandler.ts +159 -0
- package/src/plugins/analysis/ast/handlers/PropertyAccessHandler.ts +112 -0
- package/src/plugins/analysis/ast/handlers/ReturnYieldHandler.ts +166 -0
- package/src/plugins/analysis/ast/handlers/ThrowHandler.ts +101 -0
- package/src/plugins/analysis/ast/handlers/TryCatchHandler.ts +262 -0
- package/src/plugins/analysis/ast/handlers/VariableHandler.ts +93 -0
- package/src/plugins/analysis/ast/handlers/index.ts +12 -0
- package/src/plugins/analysis/ast/types.ts +68 -9
- package/src/plugins/analysis/ast/utils/createParameterNodes.ts +118 -13
- package/src/plugins/analysis/ast/utils/extractNamesFromPattern.ts +166 -0
- package/src/plugins/analysis/ast/utils/getExpressionValue.ts +34 -0
- package/src/plugins/analysis/ast/utils/getMemberExpressionName.ts +33 -0
- package/src/plugins/analysis/ast/utils/index.ts +2 -0
- package/src/plugins/analysis/ast/visitors/ArgumentExtractor.ts +307 -0
- package/src/plugins/analysis/ast/visitors/ArrayElementExtractor.ts +172 -0
- package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +425 -1374
- package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +43 -12
- package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +39 -8
- package/src/plugins/analysis/ast/visitors/MutationDetector.ts +211 -0
- package/src/plugins/analysis/ast/visitors/ObjectPropertyExtractor.ts +217 -0
- package/src/plugins/analysis/ast/visitors/PropertyAccessVisitor.ts +69 -4
- package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +232 -13
- package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +8 -11
- package/src/plugins/analysis/ast/visitors/call-expression-helpers.ts +65 -0
- package/src/plugins/analysis/ast/visitors/call-expression-types.ts +179 -0
- package/src/plugins/analysis/react-internal/browser-api.ts +168 -0
- package/src/plugins/analysis/react-internal/hooks.ts +517 -0
- package/src/plugins/analysis/react-internal/jsx.ts +279 -0
- package/src/plugins/analysis/react-internal/types.ts +183 -0
- package/src/plugins/discovery/MonorepoServiceDiscovery.ts +6 -14
- package/src/plugins/enrichment/AliasTracker.ts +3 -1
- package/src/plugins/enrichment/ArgumentParameterLinker.ts +3 -1
- package/src/plugins/enrichment/CallbackCallResolver.ts +398 -0
- package/src/plugins/enrichment/ClosureCaptureEnricher.ts +3 -1
- package/src/plugins/enrichment/ConfigRoutingMapBuilder.ts +67 -0
- package/src/plugins/enrichment/ExpressHandlerLinker.ts +3 -1
- package/src/plugins/enrichment/ExternalCallResolver.ts +5 -8
- package/src/plugins/enrichment/FunctionCallResolver.ts +6 -9
- package/src/plugins/enrichment/HTTPConnectionEnricher.ts +3 -1
- package/src/plugins/enrichment/ImportExportLinker.ts +5 -3
- package/src/plugins/enrichment/InstanceOfResolver.ts +3 -1
- package/src/plugins/enrichment/MethodCallResolver.ts +48 -659
- package/src/plugins/enrichment/MountPointResolver.ts +9 -2
- package/src/plugins/enrichment/NodejsBuiltinsResolver.ts +13 -18
- package/src/plugins/enrichment/PrefixEvaluator.ts +6 -2
- package/src/plugins/enrichment/RejectionPropagationEnricher.ts +3 -1
- package/src/plugins/enrichment/RustFFIEnricher.ts +3 -1
- package/src/plugins/enrichment/ServiceConnectionEnricher.ts +472 -0
- package/src/plugins/enrichment/SocketConnectionEnricher.ts +228 -0
- package/src/plugins/enrichment/ValueDomainAnalyzer.ts +3 -1
- package/src/plugins/enrichment/method-call/MethodCallData.ts +299 -0
- package/src/plugins/enrichment/method-call/MethodCallDetectors.ts +70 -0
- package/src/plugins/enrichment/method-call/MethodCallErrorAnalysis.ts +131 -0
- package/src/plugins/enrichment/method-call/MethodCallIndexers.ts +83 -0
- package/src/plugins/enrichment/method-call/MethodCallResolution.ts +181 -0
- package/src/plugins/indexing/IncrementalModuleIndexer.ts +5 -10
- package/src/plugins/indexing/JSModuleIndexer.ts +17 -21
- package/src/plugins/indexing/RustModuleIndexer.ts +14 -13
- package/src/plugins/validation/AwaitInLoopValidator.ts +91 -0
- package/src/plugins/validation/PackageCoverageValidator.ts +181 -0
- package/src/plugins/validation/UnconnectedRouteValidator.ts +93 -0
- package/src/queries/NodeContext.ts +277 -0
- package/src/queries/findCallsInFunction.ts +11 -2
- package/src/queries/findContainingFunction.ts +14 -3
- package/src/queries/index.ts +13 -0
- package/src/resources/InfraResourceMapImpl.ts +119 -0
- package/src/resources/RoutingMapImpl.ts +133 -0
- package/src/storage/backends/RFDBServerBackend.ts +106 -77
- package/src/utils/findRfdbBinary.ts +22 -7
- package/src/utils/moduleResolution.ts +28 -1
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/version.ts +28 -0
|
@@ -1,63 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GraphBuilder -
|
|
2
|
+
* GraphBuilder - orchestrator that delegates to domain-specific builders
|
|
3
3
|
* OPTIMIZED: Uses batched writes to reduce FFI overhead
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { basename } from 'path';
|
|
7
6
|
import type { GraphBackend, NodeRecord } from '@grafema/types';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { EnumNode } from '../../../core/nodes/EnumNode.js';
|
|
11
|
-
import { DecoratorNode } from '../../../core/nodes/DecoratorNode.js';
|
|
12
|
-
import { NetworkRequestNode } from '../../../core/nodes/NetworkRequestNode.js';
|
|
13
|
-
import { NodeFactory } from '../../../core/NodeFactory.js';
|
|
14
|
-
import { computeSemanticId, parseSemanticId } from '../../../core/SemanticId.js';
|
|
7
|
+
import { brandNodeInternal } from '../../../core/brandNodeInternal.js';
|
|
8
|
+
import { parseSemanticId } from '../../../core/SemanticId.js';
|
|
15
9
|
import type {
|
|
16
10
|
ModuleNode,
|
|
17
11
|
FunctionInfo,
|
|
18
12
|
ParameterInfo,
|
|
19
|
-
ScopeInfo,
|
|
20
|
-
BranchInfo,
|
|
21
|
-
CaseInfo,
|
|
22
|
-
LoopInfo,
|
|
23
13
|
VariableDeclarationInfo,
|
|
24
|
-
CallSiteInfo,
|
|
25
|
-
MethodCallInfo,
|
|
26
|
-
MethodCallbackInfo,
|
|
27
|
-
EventListenerInfo,
|
|
28
|
-
ClassInstantiationInfo,
|
|
29
|
-
ClassDeclarationInfo,
|
|
30
|
-
CallArgumentInfo,
|
|
31
|
-
ImportInfo,
|
|
32
|
-
ExportInfo,
|
|
33
|
-
HttpRequestInfo,
|
|
34
|
-
LiteralInfo,
|
|
35
14
|
VariableAssignmentInfo,
|
|
36
|
-
InterfaceDeclarationInfo,
|
|
37
|
-
TypeAliasInfo,
|
|
38
|
-
EnumDeclarationInfo,
|
|
39
|
-
DecoratorInfo,
|
|
40
|
-
ArrayMutationInfo,
|
|
41
|
-
ObjectMutationInfo,
|
|
42
|
-
VariableReassignmentInfo,
|
|
43
|
-
UpdateExpressionInfo,
|
|
44
|
-
ReturnStatementInfo,
|
|
45
|
-
YieldExpressionInfo,
|
|
46
|
-
ObjectLiteralInfo,
|
|
47
|
-
ObjectPropertyInfo,
|
|
48
|
-
ArrayLiteralInfo,
|
|
49
|
-
TryBlockInfo,
|
|
50
|
-
CatchBlockInfo,
|
|
51
|
-
FinallyBlockInfo,
|
|
52
|
-
PromiseResolutionInfo,
|
|
53
|
-
RejectionPatternInfo,
|
|
54
|
-
CatchesFromInfo,
|
|
55
15
|
PropertyAccessInfo,
|
|
56
16
|
ASTCollections,
|
|
57
17
|
GraphNode,
|
|
58
18
|
GraphEdge,
|
|
59
19
|
BuildResult,
|
|
60
20
|
} from './types.js';
|
|
21
|
+
import type { BuilderContext } from './builders/types.js';
|
|
22
|
+
import {
|
|
23
|
+
CoreBuilder,
|
|
24
|
+
ControlFlowBuilder,
|
|
25
|
+
AssignmentBuilder,
|
|
26
|
+
CallFlowBuilder,
|
|
27
|
+
MutationBuilder,
|
|
28
|
+
UpdateExpressionBuilder,
|
|
29
|
+
ReturnBuilder,
|
|
30
|
+
YieldBuilder,
|
|
31
|
+
TypeSystemBuilder,
|
|
32
|
+
ModuleRuntimeBuilder,
|
|
33
|
+
} from './builders/index.js';
|
|
61
34
|
|
|
62
35
|
export class GraphBuilder {
|
|
63
36
|
// Track singleton nodes to avoid duplicates (net:stdio, net:request, etc.)
|
|
@@ -67,6 +40,49 @@ export class GraphBuilder {
|
|
|
67
40
|
private _nodeBuffer: GraphNode[] = [];
|
|
68
41
|
private _edgeBuffer: GraphEdge[] = [];
|
|
69
42
|
|
|
43
|
+
// Domain builders
|
|
44
|
+
private readonly _coreBuilder: CoreBuilder;
|
|
45
|
+
private readonly _controlFlowBuilder: ControlFlowBuilder;
|
|
46
|
+
private readonly _assignmentBuilder: AssignmentBuilder;
|
|
47
|
+
private readonly _callFlowBuilder: CallFlowBuilder;
|
|
48
|
+
private readonly _mutationBuilder: MutationBuilder;
|
|
49
|
+
private readonly _updateExpressionBuilder: UpdateExpressionBuilder;
|
|
50
|
+
private readonly _returnBuilder: ReturnBuilder;
|
|
51
|
+
private readonly _yieldBuilder: YieldBuilder;
|
|
52
|
+
private readonly _typeSystemBuilder: TypeSystemBuilder;
|
|
53
|
+
private readonly _moduleRuntimeBuilder: ModuleRuntimeBuilder;
|
|
54
|
+
|
|
55
|
+
constructor() {
|
|
56
|
+
const ctx = this._createContext();
|
|
57
|
+
this._coreBuilder = new CoreBuilder(ctx);
|
|
58
|
+
this._controlFlowBuilder = new ControlFlowBuilder(ctx);
|
|
59
|
+
this._assignmentBuilder = new AssignmentBuilder(ctx);
|
|
60
|
+
this._callFlowBuilder = new CallFlowBuilder(ctx);
|
|
61
|
+
this._mutationBuilder = new MutationBuilder(ctx);
|
|
62
|
+
this._updateExpressionBuilder = new UpdateExpressionBuilder(ctx);
|
|
63
|
+
this._returnBuilder = new ReturnBuilder(ctx);
|
|
64
|
+
this._yieldBuilder = new YieldBuilder(ctx);
|
|
65
|
+
this._typeSystemBuilder = new TypeSystemBuilder(ctx);
|
|
66
|
+
this._moduleRuntimeBuilder = new ModuleRuntimeBuilder(ctx);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private _createContext(): BuilderContext {
|
|
70
|
+
return {
|
|
71
|
+
bufferNode: (node) => this._bufferNode(node),
|
|
72
|
+
bufferEdge: (edge) => this._bufferEdge(edge),
|
|
73
|
+
isCreated: (key) => this._createdSingletons.has(key),
|
|
74
|
+
markCreated: (key) => { this._createdSingletons.add(key); },
|
|
75
|
+
findBufferedNode: (id) => this._nodeBuffer.find(n => n.id === id),
|
|
76
|
+
findFunctionByName: (functions, name, file, callScopeId) =>
|
|
77
|
+
this.findFunctionByName(functions, name, file, callScopeId),
|
|
78
|
+
resolveVariableInScope: (name, scopePath, file, variables) =>
|
|
79
|
+
this.resolveVariableInScope(name, scopePath, file, variables),
|
|
80
|
+
resolveParameterInScope: (name, scopePath, file, parameters) =>
|
|
81
|
+
this.resolveParameterInScope(name, scopePath, file, parameters),
|
|
82
|
+
scopePathsMatch: (a, b) => this.scopePathsMatch(a, b),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
/**
|
|
71
87
|
* Buffer a node for batched writing
|
|
72
88
|
*/
|
|
@@ -86,8 +102,9 @@ export class GraphBuilder {
|
|
|
86
102
|
*/
|
|
87
103
|
private async _flushNodes(graph: GraphBackend): Promise<number> {
|
|
88
104
|
if (this._nodeBuffer.length > 0) {
|
|
89
|
-
//
|
|
90
|
-
|
|
105
|
+
// Brand nodes before flushing - they're validated by builders
|
|
106
|
+
const brandedNodes = this._nodeBuffer.map(node => brandNodeInternal(node as unknown as NodeRecord));
|
|
107
|
+
await graph.addNodes(brandedNodes);
|
|
91
108
|
const count = this._nodeBuffer.length;
|
|
92
109
|
this._nodeBuffer = [];
|
|
93
110
|
return count;
|
|
@@ -113,62 +130,24 @@ export class GraphBuilder {
|
|
|
113
130
|
* Создаёт ноды и рёбра в графе (BATCHED VERSION)
|
|
114
131
|
*/
|
|
115
132
|
async build(module: ModuleNode, graph: GraphBackend, projectPath: string, data: ASTCollections): Promise<BuildResult> {
|
|
133
|
+
// Phase 1 node buffering + post-flush fields only; builders receive `data` directly
|
|
116
134
|
const {
|
|
117
135
|
functions,
|
|
118
136
|
parameters = [],
|
|
119
137
|
scopes,
|
|
120
|
-
// Branching
|
|
121
138
|
branches = [],
|
|
122
139
|
cases = [],
|
|
123
|
-
// Control flow (loops)
|
|
124
140
|
loops = [],
|
|
125
|
-
// Control flow (try/catch/finally) - Phase 4
|
|
126
141
|
tryBlocks = [],
|
|
127
142
|
catchBlocks = [],
|
|
128
143
|
finallyBlocks = [],
|
|
129
144
|
variableDeclarations,
|
|
130
145
|
callSites,
|
|
131
|
-
methodCalls = [],
|
|
132
|
-
eventListeners = [],
|
|
133
|
-
classInstantiations = [],
|
|
134
146
|
constructorCalls = [],
|
|
135
|
-
|
|
136
|
-
methodCallbacks = [],
|
|
137
|
-
callArguments = [],
|
|
138
|
-
imports = [],
|
|
139
|
-
exports = [],
|
|
140
|
-
httpRequests = [],
|
|
141
|
-
literals = [],
|
|
147
|
+
// Post-flush fields
|
|
142
148
|
variableAssignments = [],
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
typeAliases = [],
|
|
146
|
-
enums = [],
|
|
147
|
-
decorators = [],
|
|
148
|
-
// Array mutation tracking for FLOWS_INTO edges
|
|
149
|
-
arrayMutations = [],
|
|
150
|
-
// Object mutation tracking for FLOWS_INTO edges
|
|
151
|
-
objectMutations = [],
|
|
152
|
-
// Variable reassignment tracking for FLOWS_INTO edges (REG-290)
|
|
153
|
-
variableReassignments = [],
|
|
154
|
-
// Update expression tracking for UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
|
|
155
|
-
updateExpressions = [],
|
|
156
|
-
// Return statement tracking for RETURNS edges
|
|
157
|
-
returnStatements = [],
|
|
158
|
-
// Yield expression tracking for YIELDS/DELEGATES_TO edges (REG-270)
|
|
159
|
-
yieldExpressions = [],
|
|
160
|
-
// Promise resolution tracking for RESOLVES_TO edges (REG-334)
|
|
161
|
-
promiseResolutions = [],
|
|
162
|
-
// Object/Array literal tracking
|
|
163
|
-
objectLiterals = [],
|
|
164
|
-
objectProperties = [],
|
|
165
|
-
arrayLiterals = [],
|
|
166
|
-
// REG-311: Rejection pattern tracking for async error analysis
|
|
167
|
-
rejectionPatterns = [],
|
|
168
|
-
// REG-311: CATCHES_FROM tracking for catch parameter error sources
|
|
169
|
-
catchesFromInfos = [],
|
|
170
|
-
// Property access tracking for PROPERTY_ACCESS nodes (REG-395)
|
|
171
|
-
propertyAccesses = []
|
|
149
|
+
propertyAccesses = [],
|
|
150
|
+
hasTopLevelAwait = false
|
|
172
151
|
} = data;
|
|
173
152
|
|
|
174
153
|
// Reset buffers for this build
|
|
@@ -176,9 +155,24 @@ export class GraphBuilder {
|
|
|
176
155
|
this._edgeBuffer = [];
|
|
177
156
|
|
|
178
157
|
// 1. Buffer all functions (without edges)
|
|
158
|
+
// REG-401: Strip invokesParamIndexes from node data and store in metadata
|
|
179
159
|
for (const func of functions) {
|
|
180
|
-
const { parentScopeId: _parentScopeId, ...funcData } = func;
|
|
181
|
-
|
|
160
|
+
const { parentScopeId: _parentScopeId, invokesParamIndexes: _invokesParamIndexes, invokesParamBindings: _invokesParamBindings, ...funcData } = func;
|
|
161
|
+
const node = funcData as GraphNode;
|
|
162
|
+
if (_invokesParamIndexes && _invokesParamIndexes.length > 0) {
|
|
163
|
+
if (!node.metadata) {
|
|
164
|
+
node.metadata = {};
|
|
165
|
+
}
|
|
166
|
+
(node.metadata as Record<string, unknown>).invokesParamIndexes = _invokesParamIndexes;
|
|
167
|
+
}
|
|
168
|
+
// REG-417: Store property paths for destructured param bindings
|
|
169
|
+
if (_invokesParamBindings && _invokesParamBindings.length > 0) {
|
|
170
|
+
if (!node.metadata) {
|
|
171
|
+
node.metadata = {};
|
|
172
|
+
}
|
|
173
|
+
(node.metadata as Record<string, unknown>).invokesParamBindings = _invokesParamBindings;
|
|
174
|
+
}
|
|
175
|
+
this._bufferNode(node);
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
// 2. Buffer all SCOPE (without edges)
|
|
@@ -269,132 +263,17 @@ export class GraphBuilder {
|
|
|
269
263
|
} as GraphNode);
|
|
270
264
|
}
|
|
271
265
|
|
|
272
|
-
//
|
|
273
|
-
this.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.
|
|
283
|
-
|
|
284
|
-
// 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
|
|
285
|
-
this.bufferLoopConditionExpressions(loops);
|
|
286
|
-
|
|
287
|
-
// 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
|
|
288
|
-
// Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
|
|
289
|
-
this.bufferBranchEdges(branches, callSites, scopes);
|
|
290
|
-
|
|
291
|
-
// 6.6. Buffer edges for CASE
|
|
292
|
-
this.bufferCaseEdges(cases);
|
|
293
|
-
|
|
294
|
-
// 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
|
|
295
|
-
this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
|
|
296
|
-
|
|
297
|
-
// 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
|
|
298
|
-
this.bufferDiscriminantExpressions(branches, callSites);
|
|
299
|
-
|
|
300
|
-
// 7. Buffer edges for variables
|
|
301
|
-
this.bufferVariableEdges(variableDeclarations);
|
|
302
|
-
|
|
303
|
-
// 8. Buffer edges for CALL_SITE
|
|
304
|
-
this.bufferCallSiteEdges(callSites, functions);
|
|
305
|
-
|
|
306
|
-
// 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
|
|
307
|
-
this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
|
|
308
|
-
|
|
309
|
-
// 9.5. Buffer PROPERTY_ACCESS nodes and CONTAINS edges (REG-395)
|
|
310
|
-
this.bufferPropertyAccessNodes(module, propertyAccesses);
|
|
311
|
-
|
|
312
|
-
// 10. Buffer net:stdio and WRITES_TO edges for console.log/error
|
|
313
|
-
this.bufferStdioNodes(methodCalls);
|
|
314
|
-
|
|
315
|
-
// 11. Buffer CLASS nodes for class declarations and CONTAINS edges
|
|
316
|
-
this.bufferClassDeclarationNodes(classDeclarations);
|
|
317
|
-
|
|
318
|
-
// 12. Buffer CLASS nodes and INSTANCE_OF edges for NewExpression
|
|
319
|
-
this.bufferClassNodes(module, classInstantiations, classDeclarations);
|
|
320
|
-
|
|
321
|
-
// 13. Buffer PASSES_ARGUMENT edges (METHOD_CALL -> FUNCTION)
|
|
322
|
-
this.bufferCallbackEdges(methodCallbacks, functions);
|
|
323
|
-
|
|
324
|
-
// 14. Buffer IMPORT nodes
|
|
325
|
-
this.bufferImportNodes(module, imports);
|
|
326
|
-
|
|
327
|
-
// 15. Buffer EXPORT nodes
|
|
328
|
-
this.bufferExportNodes(module, exports);
|
|
329
|
-
|
|
330
|
-
// 16. Buffer EVENT_LISTENER nodes and HANDLED_BY edges
|
|
331
|
-
this.bufferEventListeners(eventListeners, functions);
|
|
332
|
-
|
|
333
|
-
// 17. Buffer HTTP requests
|
|
334
|
-
this.bufferHttpRequests(httpRequests, functions);
|
|
335
|
-
|
|
336
|
-
// 18. Buffer LITERAL nodes
|
|
337
|
-
this.bufferLiterals(literals);
|
|
338
|
-
|
|
339
|
-
// 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
|
|
340
|
-
this.bufferObjectLiteralNodes(objectLiterals);
|
|
341
|
-
|
|
342
|
-
// 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
|
|
343
|
-
this.bufferArrayLiteralNodes(arrayLiterals);
|
|
344
|
-
|
|
345
|
-
// 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
|
|
346
|
-
// REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
|
|
347
|
-
this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
|
|
348
|
-
|
|
349
|
-
// 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
|
|
350
|
-
this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
|
|
351
|
-
|
|
352
|
-
// 20. Buffer PASSES_ARGUMENT edges (CALL -> argument)
|
|
353
|
-
this.bufferArgumentEdges(callArguments, variableDeclarations, functions, callSites, methodCalls);
|
|
354
|
-
|
|
355
|
-
// 21. Buffer INTERFACE nodes and EXTENDS edges
|
|
356
|
-
this.bufferInterfaceNodes(module, interfaces);
|
|
357
|
-
|
|
358
|
-
// 22. Buffer TYPE nodes
|
|
359
|
-
this.bufferTypeAliasNodes(module, typeAliases);
|
|
360
|
-
|
|
361
|
-
// 23. Buffer ENUM nodes
|
|
362
|
-
this.bufferEnumNodes(module, enums);
|
|
363
|
-
|
|
364
|
-
// 24. Buffer DECORATOR nodes and DECORATED_BY edges
|
|
365
|
-
this.bufferDecoratorNodes(decorators);
|
|
366
|
-
|
|
367
|
-
// 25. Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
|
|
368
|
-
this.bufferImplementsEdges(classDeclarations, interfaces);
|
|
369
|
-
|
|
370
|
-
// 26. Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
371
|
-
// REG-392: Pass literals, objectLiterals, arrayLiterals, callSites for non-variable value lookups
|
|
372
|
-
this.bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters, literals, objectLiterals, arrayLiterals, callSites);
|
|
373
|
-
|
|
374
|
-
// 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
375
|
-
// REG-152: Now includes classDeclarations for this.prop = value patterns
|
|
376
|
-
this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
|
|
377
|
-
|
|
378
|
-
// 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
|
|
379
|
-
this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
|
|
380
|
-
|
|
381
|
-
// 29. Buffer RETURNS edges for return statements
|
|
382
|
-
this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
|
|
383
|
-
|
|
384
|
-
// 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
|
|
385
|
-
this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
|
|
386
|
-
|
|
387
|
-
// 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
|
|
388
|
-
this.bufferPromiseResolutionEdges(promiseResolutions);
|
|
389
|
-
|
|
390
|
-
// 32. Buffer YIELDS/DELEGATES_TO edges for generator yields (REG-270)
|
|
391
|
-
this.bufferYieldEdges(yieldExpressions, callSites, methodCalls, variableDeclarations, parameters);
|
|
392
|
-
|
|
393
|
-
// 33. Buffer REJECTS edges for async error tracking (REG-311)
|
|
394
|
-
this.bufferRejectionEdges(functions, rejectionPatterns);
|
|
395
|
-
|
|
396
|
-
// 34. Buffer CATCHES_FROM edges linking catch blocks to error sources (REG-311)
|
|
397
|
-
this.bufferCatchesFromEdges(catchesFromInfos);
|
|
266
|
+
// Phase 2: Delegate to domain builders
|
|
267
|
+
this._coreBuilder.buffer(module, data);
|
|
268
|
+
this._controlFlowBuilder.buffer(module, data);
|
|
269
|
+
this._callFlowBuilder.buffer(module, data);
|
|
270
|
+
this._assignmentBuilder.buffer(module, data);
|
|
271
|
+
this._mutationBuilder.buffer(module, data);
|
|
272
|
+
this._updateExpressionBuilder.buffer(module, data);
|
|
273
|
+
this._returnBuilder.buffer(module, data);
|
|
274
|
+
this._yieldBuilder.buffer(module, data);
|
|
275
|
+
this._typeSystemBuilder.buffer(module, data);
|
|
276
|
+
this._moduleRuntimeBuilder.buffer(module, data);
|
|
398
277
|
|
|
399
278
|
// FLUSH: Write all nodes first, then edges in single batch calls
|
|
400
279
|
const nodesCreated = await this._flushNodes(graph);
|
|
@@ -403,2915 +282,243 @@ export class GraphBuilder {
|
|
|
403
282
|
// Handle async operations for ASSIGNED_FROM with CLASS lookups
|
|
404
283
|
const classAssignmentEdges = await this.createClassAssignmentEdges(variableAssignments, graph);
|
|
405
284
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// ============= BUFFERED METHODS (synchronous, no awaits) =============
|
|
285
|
+
// REG-300: Update MODULE node with import.meta metadata
|
|
286
|
+
const importMetaProps = this.collectImportMetaProperties(propertyAccesses);
|
|
287
|
+
await this.updateModuleImportMetaMetadata(module, graph, importMetaProps);
|
|
410
288
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const { parentScopeId, ...funcData } = func;
|
|
289
|
+
// REG-297: Update MODULE node with hasTopLevelAwait metadata
|
|
290
|
+
await this.updateModuleTopLevelAwaitMetadata(module, graph, hasTopLevelAwait);
|
|
414
291
|
|
|
415
|
-
|
|
416
|
-
// или SCOPE -> CONTAINS -> FUNCTION (для вложенных функций)
|
|
417
|
-
if (parentScopeId) {
|
|
418
|
-
this._bufferEdge({
|
|
419
|
-
type: 'CONTAINS',
|
|
420
|
-
src: parentScopeId,
|
|
421
|
-
dst: funcData.id
|
|
422
|
-
});
|
|
423
|
-
} else {
|
|
424
|
-
this._bufferEdge({
|
|
425
|
-
type: 'CONTAINS',
|
|
426
|
-
src: module.id,
|
|
427
|
-
dst: funcData.id
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
}
|
|
292
|
+
return { nodes: nodesCreated, edges: edgesCreated + classAssignmentEdges };
|
|
431
293
|
}
|
|
432
294
|
|
|
433
|
-
|
|
434
|
-
for (const scope of scopes) {
|
|
435
|
-
const { parentFunctionId, parentScopeId, capturesFrom, ...scopeData } = scope;
|
|
295
|
+
// ============= SHARED UTILITY METHODS =============
|
|
436
296
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Scope-aware function lookup: when multiple functions share the same name
|
|
299
|
+
* (e.g., inner function shadows outer), prefer the one in the same scope.
|
|
300
|
+
* Falls back to module-level function if no scope match found.
|
|
301
|
+
*/
|
|
302
|
+
private findFunctionByName(
|
|
303
|
+
functions: FunctionInfo[],
|
|
304
|
+
name: string | undefined,
|
|
305
|
+
file: string,
|
|
306
|
+
callScopeId: string
|
|
307
|
+
): FunctionInfo | undefined {
|
|
308
|
+
if (!name) return undefined;
|
|
445
309
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
this._bufferEdge({
|
|
449
|
-
type: 'CONTAINS',
|
|
450
|
-
src: parentScopeId,
|
|
451
|
-
dst: scopeData.id
|
|
452
|
-
});
|
|
453
|
-
}
|
|
310
|
+
// Find all functions with matching name in the same file
|
|
311
|
+
const candidates = functions.filter(f => f.name === name && f.file === file);
|
|
454
312
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
this._bufferEdge({
|
|
460
|
-
type: 'CAPTURES',
|
|
461
|
-
src: scopeData.id,
|
|
462
|
-
dst: parentVar.id
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
}
|
|
313
|
+
if (candidates.length === 0) {
|
|
314
|
+
// Fallback: try without file constraint (legacy behavior)
|
|
315
|
+
return functions.find(f => f.name === name);
|
|
316
|
+
}
|
|
466
317
|
|
|
467
|
-
|
|
318
|
+
if (candidates.length === 1) {
|
|
319
|
+
return candidates[0];
|
|
468
320
|
}
|
|
321
|
+
|
|
322
|
+
// Multiple candidates: prefer same scope, then module-level
|
|
323
|
+
const sameScope = candidates.find(f => f.parentScopeId === callScopeId);
|
|
324
|
+
if (sameScope) return sameScope;
|
|
325
|
+
|
|
326
|
+
// Fallback: prefer module-level function (parentScopeId contains MODULE)
|
|
327
|
+
const moduleLevel = candidates.find(f =>
|
|
328
|
+
(f.parentScopeId as string)?.includes(':MODULE:')
|
|
329
|
+
);
|
|
330
|
+
return moduleLevel || candidates[0];
|
|
469
331
|
}
|
|
470
332
|
|
|
471
333
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
* Creates edges for:
|
|
475
|
-
* - Parent -> CONTAINS -> LOOP
|
|
476
|
-
* - LOOP -> HAS_BODY -> body SCOPE
|
|
477
|
-
* - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
|
|
334
|
+
* Resolve variable by name using scope chain lookup (REG-309).
|
|
335
|
+
* Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
|
|
478
336
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
337
|
+
* @param name - Variable name
|
|
338
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
339
|
+
* @param file - File path
|
|
340
|
+
* @param variables - All variable declarations
|
|
341
|
+
* @returns Variable declaration or null if not found
|
|
483
342
|
*/
|
|
484
|
-
private
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
):
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
this._bufferEdge({
|
|
494
|
-
type: 'CONTAINS',
|
|
495
|
-
src: loop.parentScopeId,
|
|
496
|
-
dst: loop.id
|
|
497
|
-
});
|
|
498
|
-
}
|
|
343
|
+
private resolveVariableInScope(
|
|
344
|
+
name: string,
|
|
345
|
+
scopePath: string[],
|
|
346
|
+
file: string,
|
|
347
|
+
variables: VariableDeclarationInfo[]
|
|
348
|
+
): VariableDeclarationInfo | null {
|
|
349
|
+
// Try current scope, then parent, then grandparent, etc.
|
|
350
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
351
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
499
352
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
|
|
503
|
-
if (bodyScope) {
|
|
504
|
-
this._bufferEdge({
|
|
505
|
-
type: 'HAS_BODY',
|
|
506
|
-
src: loop.id,
|
|
507
|
-
dst: bodyScope.id
|
|
508
|
-
});
|
|
509
|
-
}
|
|
353
|
+
const matchingVar = variables.find(v => {
|
|
354
|
+
if (v.name !== name || v.file !== file) return false;
|
|
510
355
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
? loop.iteratesOverName.split('.')[0]
|
|
516
|
-
: loop.iteratesOverName;
|
|
517
|
-
|
|
518
|
-
// Scope-aware lookup: prefer parameters over variables
|
|
519
|
-
// Parameters are function-local and shadow outer variables
|
|
520
|
-
const param = parameters.find(p =>
|
|
521
|
-
p.name === iterableName && p.file === loop.file
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
// Determine iteration type: for-in iterates keys, for-of iterates values
|
|
525
|
-
const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
|
|
526
|
-
|
|
527
|
-
if (param) {
|
|
528
|
-
// Parameter found - most local binding
|
|
529
|
-
this._bufferEdge({
|
|
530
|
-
type: 'ITERATES_OVER',
|
|
531
|
-
src: loop.id,
|
|
532
|
-
dst: param.id,
|
|
533
|
-
metadata: { iterates }
|
|
534
|
-
});
|
|
535
|
-
} else {
|
|
536
|
-
// Find variable by name and line proximity (scope-aware heuristic)
|
|
537
|
-
// Prefer variables declared before the loop in the same file
|
|
538
|
-
const candidateVars = variableDeclarations.filter(v =>
|
|
539
|
-
v.name === iterableName &&
|
|
540
|
-
v.file === loop.file &&
|
|
541
|
-
(v.line ?? 0) <= loop.line // Declared before or on loop line
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
// Sort by line descending to find closest declaration
|
|
545
|
-
candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
|
|
546
|
-
|
|
547
|
-
if (candidateVars.length > 0) {
|
|
548
|
-
this._bufferEdge({
|
|
549
|
-
type: 'ITERATES_OVER',
|
|
550
|
-
src: loop.id,
|
|
551
|
-
dst: candidateVars[0].id,
|
|
552
|
-
metadata: { iterates }
|
|
553
|
-
});
|
|
356
|
+
// REG-464: v2 path — use scopePath field if available (set by visitors)
|
|
357
|
+
if (v.scopePath) {
|
|
358
|
+
if (searchScopePath.length === 0) {
|
|
359
|
+
return v.scopePath.length === 0;
|
|
554
360
|
}
|
|
361
|
+
return this.scopePathsMatch(v.scopePath, searchScopePath);
|
|
555
362
|
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
|
|
559
|
-
if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
|
|
560
|
-
// Find the variable declared in the init on this line
|
|
561
|
-
const initVar = variableDeclarations.find(v =>
|
|
562
|
-
v.name === loop.initVariableName &&
|
|
563
|
-
v.file === loop.file &&
|
|
564
|
-
v.line === loop.initLine
|
|
565
|
-
);
|
|
566
|
-
if (initVar) {
|
|
567
|
-
this._bufferEdge({
|
|
568
|
-
type: 'HAS_INIT',
|
|
569
|
-
src: loop.id,
|
|
570
|
-
dst: initVar.id
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
363
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
id: loop.testExpressionId,
|
|
580
|
-
type: 'EXPRESSION',
|
|
581
|
-
name: loop.testExpressionType,
|
|
582
|
-
file: loop.file,
|
|
583
|
-
line: loop.testLine,
|
|
584
|
-
column: loop.testColumn,
|
|
585
|
-
expressionType: loop.testExpressionType
|
|
586
|
-
});
|
|
364
|
+
// v1 fallback: parse semanticId
|
|
365
|
+
// Variable ID IS the semantic ID (when scopeTracker was available during analysis)
|
|
366
|
+
// Format: file->scope1->scope2->TYPE->name
|
|
367
|
+
// Legacy format: VARIABLE#name#file#line:column:counter
|
|
587
368
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
369
|
+
// Try parsing as semantic ID
|
|
370
|
+
const parsed = parseSemanticId(v.id);
|
|
371
|
+
// REG-329: Check for both VARIABLE and CONSTANT (const declarations)
|
|
372
|
+
if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
|
|
373
|
+
// FIXED (REG-309): Handle module-level scope matching
|
|
374
|
+
// Empty search scope [] should match semantic ID scope ['global']
|
|
375
|
+
if (searchScopePath.length === 0) {
|
|
376
|
+
return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
|
|
377
|
+
}
|
|
378
|
+
// Non-empty scope: exact match
|
|
379
|
+
return this.scopePathsMatch(parsed.scopePath, searchScopePath);
|
|
380
|
+
}
|
|
594
381
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this._bufferNode({
|
|
599
|
-
id: loop.updateExpressionId,
|
|
600
|
-
type: 'EXPRESSION',
|
|
601
|
-
name: loop.updateExpressionType,
|
|
602
|
-
file: loop.file,
|
|
603
|
-
line: loop.updateLine,
|
|
604
|
-
column: loop.updateColumn,
|
|
605
|
-
expressionType: loop.updateExpressionType
|
|
606
|
-
});
|
|
382
|
+
// Legacy ID - assume module-level if no semantic ID
|
|
383
|
+
return searchScopePath.length === 0;
|
|
384
|
+
});
|
|
607
385
|
|
|
608
|
-
|
|
609
|
-
type: 'HAS_UPDATE',
|
|
610
|
-
src: loop.id,
|
|
611
|
-
dst: loop.updateExpressionId
|
|
612
|
-
});
|
|
613
|
-
}
|
|
386
|
+
if (matchingVar) return matchingVar;
|
|
614
387
|
}
|
|
388
|
+
|
|
389
|
+
return null;
|
|
615
390
|
}
|
|
616
391
|
|
|
617
392
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
|
|
622
|
-
* condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
|
|
393
|
+
* Resolve parameter by name using scope chain lookup (REG-309).
|
|
394
|
+
* Same semantics as resolveVariableInScope but for parameters.
|
|
623
395
|
*
|
|
624
|
-
*
|
|
396
|
+
* @param name - Parameter name
|
|
397
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
398
|
+
* @param file - File path
|
|
399
|
+
* @param parameters - All parameter declarations
|
|
400
|
+
* @returns Parameter declaration or null if not found
|
|
625
401
|
*/
|
|
626
|
-
private
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
402
|
+
private resolveParameterInScope(
|
|
403
|
+
name: string,
|
|
404
|
+
scopePath: string[],
|
|
405
|
+
file: string,
|
|
406
|
+
parameters: ParameterInfo[]
|
|
407
|
+
): ParameterInfo | null {
|
|
408
|
+
// Parameters have semanticId field populated (unlike variables which use id field)
|
|
409
|
+
return parameters.find(p => {
|
|
410
|
+
if (p.name !== name || p.file !== file) return false;
|
|
637
411
|
|
|
638
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
cs.column === loop.conditionColumn
|
|
648
|
-
);
|
|
649
|
-
if (callSite) {
|
|
650
|
-
targetId = callSite.id;
|
|
412
|
+
// REG-464: v2 path — use scopePath field if available (set by visitors)
|
|
413
|
+
if (p.scopePath) {
|
|
414
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
415
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
416
|
+
if (searchScopePath.length === 0) {
|
|
417
|
+
if (p.scopePath.length === 0) return true;
|
|
418
|
+
} else {
|
|
419
|
+
if (this.scopePathsMatch(p.scopePath, searchScopePath)) return true;
|
|
420
|
+
}
|
|
651
421
|
}
|
|
422
|
+
return false;
|
|
652
423
|
}
|
|
653
424
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
|
|
664
|
-
* Similar to bufferDiscriminantExpressions but for loops.
|
|
665
|
-
*
|
|
666
|
-
* REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
|
|
667
|
-
* CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
|
|
668
|
-
*/
|
|
669
|
-
private bufferLoopConditionExpressions(loops: LoopInfo[]): void {
|
|
670
|
-
for (const loop of loops) {
|
|
671
|
-
// Skip for-in/for-of loops - they don't have test expressions
|
|
672
|
-
if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (loop.conditionExpressionId && loop.conditionExpressionType) {
|
|
677
|
-
// Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
|
|
678
|
-
if (loop.conditionExpressionType === 'CallExpression') {
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
425
|
+
// v1 fallback: parse semanticId
|
|
426
|
+
if (p.semanticId) {
|
|
427
|
+
const parsed = parseSemanticId(p.semanticId);
|
|
428
|
+
if (parsed && parsed.type === 'PARAMETER') {
|
|
429
|
+
// Check if parameter's scope matches any scope in the chain
|
|
430
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
431
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
681
432
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
433
|
+
// FIXED (REG-309): Handle module-level scope matching for parameters
|
|
434
|
+
if (searchScopePath.length === 0) {
|
|
435
|
+
if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
693
444
|
}
|
|
694
445
|
}
|
|
695
|
-
|
|
446
|
+
return false;
|
|
447
|
+
}) ?? null;
|
|
696
448
|
}
|
|
697
449
|
|
|
698
450
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
* REG-275: For CallExpression discriminants (switch(getType())), looks up the
|
|
702
|
-
* actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
|
|
703
|
-
*
|
|
704
|
-
* Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
705
|
-
* pointing to the if-body and else-body SCOPEs.
|
|
451
|
+
* Check if two scope paths match (REG-309).
|
|
452
|
+
* Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
|
|
706
453
|
*/
|
|
707
|
-
private
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
this._bufferEdge({
|
|
712
|
-
type: 'CONTAINS',
|
|
713
|
-
src: branch.parentScopeId,
|
|
714
|
-
dst: branch.id
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
|
|
719
|
-
if (branch.discriminantExpressionId) {
|
|
720
|
-
let targetId = branch.discriminantExpressionId;
|
|
721
|
-
|
|
722
|
-
// For CallExpression discriminants, look up the actual CALL_SITE by coordinates
|
|
723
|
-
// because CALL_SITE uses semantic IDs that don't match the generated ID
|
|
724
|
-
if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
|
|
725
|
-
const callSite = callSites.find(cs =>
|
|
726
|
-
cs.file === branch.file &&
|
|
727
|
-
cs.line === branch.discriminantLine &&
|
|
728
|
-
cs.column === branch.discriminantColumn
|
|
729
|
-
);
|
|
730
|
-
if (callSite) {
|
|
731
|
-
targetId = callSite.id;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
this._bufferEdge({
|
|
736
|
-
type: 'HAS_CONDITION',
|
|
737
|
-
src: branch.id,
|
|
738
|
-
dst: targetId
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
743
|
-
if (branch.branchType === 'if') {
|
|
744
|
-
// Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
|
|
745
|
-
const consequentScope = scopes.find(s =>
|
|
746
|
-
s.parentScopeId === branch.id && s.scopeType === 'if_statement'
|
|
747
|
-
);
|
|
748
|
-
if (consequentScope) {
|
|
749
|
-
this._bufferEdge({
|
|
750
|
-
type: 'HAS_CONSEQUENT',
|
|
751
|
-
src: branch.id,
|
|
752
|
-
dst: consequentScope.id
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
|
|
757
|
-
const alternateScope = scopes.find(s =>
|
|
758
|
-
s.parentScopeId === branch.id && s.scopeType === 'else_statement'
|
|
759
|
-
);
|
|
760
|
-
if (alternateScope) {
|
|
761
|
-
this._bufferEdge({
|
|
762
|
-
type: 'HAS_ALTERNATE',
|
|
763
|
-
src: branch.id,
|
|
764
|
-
dst: alternateScope.id
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
// For else-if chains: if this branch is the alternate of another branch
|
|
769
|
-
// This is handled differently - see below
|
|
770
|
-
}
|
|
454
|
+
private scopePathsMatch(a: string[], b: string[]): boolean {
|
|
455
|
+
if (a.length !== b.length) return false;
|
|
456
|
+
return a.every((item, idx) => item === b[idx]);
|
|
457
|
+
}
|
|
771
458
|
|
|
772
|
-
|
|
773
|
-
if (branch.branchType === 'ternary') {
|
|
774
|
-
if (branch.consequentExpressionId) {
|
|
775
|
-
this._bufferEdge({
|
|
776
|
-
type: 'HAS_CONSEQUENT',
|
|
777
|
-
src: branch.id,
|
|
778
|
-
dst: branch.consequentExpressionId
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
if (branch.alternateExpressionId) {
|
|
782
|
-
this._bufferEdge({
|
|
783
|
-
type: 'HAS_ALTERNATE',
|
|
784
|
-
src: branch.id,
|
|
785
|
-
dst: branch.alternateExpressionId
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
459
|
+
// ============= POST-FLUSH METHODS (need graph queries) =============
|
|
789
460
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Collect unique import.meta property names from property accesses (REG-300).
|
|
463
|
+
* Returns deduplicated array of property names (e.g., ["url", "env"]).
|
|
464
|
+
*/
|
|
465
|
+
private collectImportMetaProperties(propertyAccesses: PropertyAccessInfo[]): string[] {
|
|
466
|
+
const metaProps = new Set<string>();
|
|
467
|
+
for (const propAccess of propertyAccesses) {
|
|
468
|
+
if (propAccess.objectName === 'import.meta') {
|
|
469
|
+
metaProps.add(propAccess.propertyName);
|
|
797
470
|
}
|
|
798
471
|
}
|
|
472
|
+
return [...metaProps];
|
|
799
473
|
}
|
|
800
474
|
|
|
801
475
|
/**
|
|
802
|
-
*
|
|
476
|
+
* Update MODULE node with import.meta metadata (REG-300).
|
|
477
|
+
* Reads existing MODULE node, adds importMeta property list, re-adds it.
|
|
803
478
|
*/
|
|
804
|
-
private
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
479
|
+
private async updateModuleImportMetaMetadata(
|
|
480
|
+
module: ModuleNode,
|
|
481
|
+
graph: GraphBackend,
|
|
482
|
+
importMetaProps: string[]
|
|
483
|
+
): Promise<void> {
|
|
484
|
+
if (importMetaProps.length === 0) return;
|
|
485
|
+
|
|
486
|
+
const existingNode = await graph.getNode(module.id);
|
|
487
|
+
if (!existingNode) return;
|
|
488
|
+
|
|
489
|
+
// Re-add with importMeta at top level — addNode is upsert in RFDB,
|
|
490
|
+
// and backend spreads metadata fields to top level on read
|
|
491
|
+
await graph.addNode({
|
|
492
|
+
...existingNode,
|
|
493
|
+
importMeta: importMetaProps
|
|
494
|
+
} as unknown as Parameters<GraphBackend['addNode']>[0]);
|
|
814
495
|
}
|
|
815
496
|
|
|
816
497
|
/**
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
* Creates edges for:
|
|
820
|
-
* - Parent -> CONTAINS -> TRY_BLOCK
|
|
821
|
-
* - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
822
|
-
* - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
|
|
498
|
+
* Update MODULE node with hasTopLevelAwait metadata (REG-297).
|
|
499
|
+
* Reads existing MODULE node, adds hasTopLevelAwait flag, re-adds it.
|
|
823
500
|
*/
|
|
824
|
-
private
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
): void {
|
|
829
|
-
|
|
830
|
-
for (const tryBlock of tryBlocks) {
|
|
831
|
-
// Parent -> CONTAINS -> TRY_BLOCK
|
|
832
|
-
if (tryBlock.parentScopeId) {
|
|
833
|
-
this._bufferEdge({
|
|
834
|
-
type: 'CONTAINS',
|
|
835
|
-
src: tryBlock.parentScopeId,
|
|
836
|
-
dst: tryBlock.id
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
}
|
|
501
|
+
private async updateModuleTopLevelAwaitMetadata(
|
|
502
|
+
module: ModuleNode,
|
|
503
|
+
graph: GraphBackend,
|
|
504
|
+
hasTopLevelAwait: boolean
|
|
505
|
+
): Promise<void> {
|
|
506
|
+
if (!hasTopLevelAwait) return;
|
|
840
507
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
// TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
844
|
-
this._bufferEdge({
|
|
845
|
-
type: 'HAS_CATCH',
|
|
846
|
-
src: catchBlock.parentTryBlockId,
|
|
847
|
-
dst: catchBlock.id
|
|
848
|
-
});
|
|
849
|
-
}
|
|
508
|
+
const existingNode = await graph.getNode(module.id);
|
|
509
|
+
if (!existingNode) return;
|
|
850
510
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
type: 'HAS_FINALLY',
|
|
856
|
-
src: finallyBlock.parentTryBlockId,
|
|
857
|
-
dst: finallyBlock.id
|
|
858
|
-
});
|
|
859
|
-
}
|
|
511
|
+
await graph.addNode({
|
|
512
|
+
...existingNode,
|
|
513
|
+
hasTopLevelAwait: true
|
|
514
|
+
} as unknown as Parameters<GraphBackend['addNode']>[0]);
|
|
860
515
|
}
|
|
861
516
|
|
|
862
517
|
/**
|
|
863
|
-
*
|
|
864
|
-
* Uses stored metadata directly instead of parsing from ID (Linus improvement)
|
|
865
|
-
*
|
|
866
|
-
* REG-275: For CallExpression discriminants, we don't create nodes here since
|
|
867
|
-
* bufferBranchEdges links to the existing CALL_SITE node by coordinates.
|
|
518
|
+
* Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
|
|
868
519
|
*/
|
|
869
|
-
private
|
|
870
|
-
|
|
871
|
-
if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
|
|
872
|
-
// Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
|
|
873
|
-
if (branch.discriminantExpressionType === 'CallExpression') {
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Only create if it looks like an EXPRESSION ID
|
|
878
|
-
if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
|
|
879
|
-
this._bufferNode({
|
|
880
|
-
id: branch.discriminantExpressionId,
|
|
881
|
-
type: 'EXPRESSION',
|
|
882
|
-
name: branch.discriminantExpressionType,
|
|
883
|
-
file: branch.file,
|
|
884
|
-
line: branch.discriminantLine,
|
|
885
|
-
column: branch.discriminantColumn,
|
|
886
|
-
expressionType: branch.discriminantExpressionType
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
private bufferVariableEdges(variableDeclarations: VariableDeclarationInfo[]): void {
|
|
894
|
-
for (const varDecl of variableDeclarations) {
|
|
895
|
-
const { parentScopeId, isClassProperty, ...varData } = varDecl;
|
|
896
|
-
|
|
897
|
-
// REG-271: Skip class properties - they get HAS_PROPERTY edges from CLASS, not DECLARES from SCOPE
|
|
898
|
-
if (isClassProperty) {
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// SCOPE -> DECLARES -> VARIABLE
|
|
903
|
-
this._bufferEdge({
|
|
904
|
-
type: 'DECLARES',
|
|
905
|
-
src: parentScopeId as string,
|
|
906
|
-
dst: varData.id
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
private bufferCallSiteEdges(callSites: CallSiteInfo[], functions: FunctionInfo[]): void {
|
|
912
|
-
for (const callSite of callSites) {
|
|
913
|
-
const { parentScopeId, targetFunctionName, ...callData } = callSite;
|
|
914
|
-
|
|
915
|
-
// SCOPE -> CONTAINS -> CALL_SITE
|
|
916
|
-
this._bufferEdge({
|
|
917
|
-
type: 'CONTAINS',
|
|
918
|
-
src: parentScopeId as string,
|
|
919
|
-
dst: callData.id
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
// CALL_SITE -> CALLS -> FUNCTION
|
|
923
|
-
const targetFunction = functions.find(f => f.name === targetFunctionName);
|
|
924
|
-
if (targetFunction) {
|
|
925
|
-
this._bufferEdge({
|
|
926
|
-
type: 'CALLS',
|
|
927
|
-
src: callData.id,
|
|
928
|
-
dst: targetFunction.id
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
private bufferMethodCalls(
|
|
935
|
-
methodCalls: MethodCallInfo[],
|
|
936
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
937
|
-
parameters: ParameterInfo[]
|
|
938
|
-
): void {
|
|
939
|
-
for (const methodCall of methodCalls) {
|
|
940
|
-
// Keep parentScopeId on node for queries
|
|
941
|
-
this._bufferNode(methodCall as unknown as GraphNode);
|
|
942
|
-
|
|
943
|
-
// SCOPE -> CONTAINS -> METHOD_CALL
|
|
944
|
-
this._bufferEdge({
|
|
945
|
-
type: 'CONTAINS',
|
|
946
|
-
src: methodCall.parentScopeId as string,
|
|
947
|
-
dst: methodCall.id
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
// REG-262: Create USES edge from METHOD_CALL to receiver variable
|
|
951
|
-
// Skip 'this' - it's not a variable node
|
|
952
|
-
if (methodCall.object && methodCall.object !== 'this') {
|
|
953
|
-
// Handle nested member expressions: obj.nested.method() -> use base 'obj'
|
|
954
|
-
const receiverName = methodCall.object.includes('.')
|
|
955
|
-
? methodCall.object.split('.')[0]
|
|
956
|
-
: methodCall.object;
|
|
957
|
-
|
|
958
|
-
// Find receiver variable in current file
|
|
959
|
-
const receiverVar = variableDeclarations.find(v =>
|
|
960
|
-
v.name === receiverName && v.file === methodCall.file
|
|
961
|
-
);
|
|
962
|
-
|
|
963
|
-
if (receiverVar) {
|
|
964
|
-
this._bufferEdge({
|
|
965
|
-
type: 'USES',
|
|
966
|
-
src: methodCall.id,
|
|
967
|
-
dst: receiverVar.id
|
|
968
|
-
});
|
|
969
|
-
} else {
|
|
970
|
-
// Check parameters (function arguments)
|
|
971
|
-
const receiverParam = parameters.find(p =>
|
|
972
|
-
p.name === receiverName && p.file === methodCall.file
|
|
973
|
-
);
|
|
974
|
-
|
|
975
|
-
if (receiverParam) {
|
|
976
|
-
this._bufferEdge({
|
|
977
|
-
type: 'USES',
|
|
978
|
-
src: methodCall.id,
|
|
979
|
-
dst: receiverParam.id
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
/**
|
|
988
|
-
* Buffer PROPERTY_ACCESS nodes and CONTAINS edges (REG-395).
|
|
989
|
-
*
|
|
990
|
-
* Creates nodes for property reads (obj.prop, a.b.c) and
|
|
991
|
-
* CONTAINS edges from the enclosing scope (function or module).
|
|
992
|
-
*/
|
|
993
|
-
private bufferPropertyAccessNodes(module: ModuleNode, propertyAccesses: PropertyAccessInfo[]): void {
|
|
994
|
-
for (const propAccess of propertyAccesses) {
|
|
995
|
-
// Buffer node with all relevant fields
|
|
996
|
-
this._bufferNode({
|
|
997
|
-
id: propAccess.id,
|
|
998
|
-
type: 'PROPERTY_ACCESS',
|
|
999
|
-
name: propAccess.propertyName,
|
|
1000
|
-
objectName: propAccess.objectName,
|
|
1001
|
-
file: propAccess.file,
|
|
1002
|
-
line: propAccess.line,
|
|
1003
|
-
column: propAccess.column,
|
|
1004
|
-
semanticId: propAccess.semanticId,
|
|
1005
|
-
optional: propAccess.optional,
|
|
1006
|
-
computed: propAccess.computed
|
|
1007
|
-
} as GraphNode);
|
|
1008
|
-
|
|
1009
|
-
// SCOPE/FUNCTION/MODULE -> CONTAINS -> PROPERTY_ACCESS
|
|
1010
|
-
const containsSrc = propAccess.parentScopeId ?? module.id;
|
|
1011
|
-
this._bufferEdge({
|
|
1012
|
-
type: 'CONTAINS',
|
|
1013
|
-
src: containsSrc,
|
|
1014
|
-
dst: propAccess.id
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
private bufferStdioNodes(methodCalls: MethodCallInfo[]): void {
|
|
1020
|
-
const consoleIOMethods = methodCalls.filter(mc =>
|
|
1021
|
-
(mc.object === 'console' && (mc.method === 'log' || mc.method === 'error'))
|
|
1022
|
-
);
|
|
1023
|
-
|
|
1024
|
-
if (consoleIOMethods.length > 0) {
|
|
1025
|
-
const stdioNode = NodeFactory.createExternalStdio();
|
|
1026
|
-
|
|
1027
|
-
// Buffer net:stdio node only once (singleton)
|
|
1028
|
-
if (!this._createdSingletons.has(stdioNode.id)) {
|
|
1029
|
-
this._bufferNode(stdioNode as unknown as GraphNode);
|
|
1030
|
-
this._createdSingletons.add(stdioNode.id);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// Buffer WRITES_TO edges for console.log/error
|
|
1034
|
-
for (const methodCall of consoleIOMethods) {
|
|
1035
|
-
this._bufferEdge({
|
|
1036
|
-
type: 'WRITES_TO',
|
|
1037
|
-
src: methodCall.id,
|
|
1038
|
-
dst: stdioNode.id
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
private bufferClassDeclarationNodes(classDeclarations: ClassDeclarationInfo[]): void {
|
|
1045
|
-
for (const classDecl of classDeclarations) {
|
|
1046
|
-
const { id, type, name, file, line, column, superClass, methods, properties, staticBlocks } = classDecl;
|
|
1047
|
-
|
|
1048
|
-
// Buffer CLASS node
|
|
1049
|
-
this._bufferNode({
|
|
1050
|
-
id,
|
|
1051
|
-
type,
|
|
1052
|
-
name,
|
|
1053
|
-
file,
|
|
1054
|
-
line,
|
|
1055
|
-
column,
|
|
1056
|
-
superClass
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
// Buffer CONTAINS edges: CLASS -> METHOD
|
|
1060
|
-
for (const methodId of methods) {
|
|
1061
|
-
this._bufferEdge({
|
|
1062
|
-
type: 'CONTAINS',
|
|
1063
|
-
src: id,
|
|
1064
|
-
dst: methodId
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// REG-271: Buffer HAS_PROPERTY edges: CLASS -> VARIABLE (private fields)
|
|
1069
|
-
if (properties) {
|
|
1070
|
-
for (const propertyId of properties) {
|
|
1071
|
-
this._bufferEdge({
|
|
1072
|
-
type: 'HAS_PROPERTY',
|
|
1073
|
-
src: id,
|
|
1074
|
-
dst: propertyId
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// REG-271: Buffer CONTAINS edges: CLASS -> SCOPE (static blocks)
|
|
1080
|
-
if (staticBlocks) {
|
|
1081
|
-
for (const staticBlockId of staticBlocks) {
|
|
1082
|
-
this._bufferEdge({
|
|
1083
|
-
type: 'CONTAINS',
|
|
1084
|
-
src: id,
|
|
1085
|
-
dst: staticBlockId
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// If superClass, buffer DERIVES_FROM edge with computed ID
|
|
1091
|
-
if (superClass) {
|
|
1092
|
-
// Compute superclass ID using semantic ID format
|
|
1093
|
-
// Assume superclass is in same file at global scope (most common case)
|
|
1094
|
-
// When superclass is in different file, edge will be dangling until that file analyzed
|
|
1095
|
-
const globalContext = { file, scopePath: [] as string[] };
|
|
1096
|
-
const superClassId = computeSemanticId('CLASS', superClass, globalContext);
|
|
1097
|
-
|
|
1098
|
-
this._bufferEdge({
|
|
1099
|
-
type: 'DERIVES_FROM',
|
|
1100
|
-
src: id,
|
|
1101
|
-
dst: superClassId
|
|
1102
|
-
});
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
private bufferClassNodes(module: ModuleNode, classInstantiations: ClassInstantiationInfo[], classDeclarations: ClassDeclarationInfo[]): void {
|
|
1108
|
-
// Create lookup map: className → declaration ID
|
|
1109
|
-
// Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
|
|
1110
|
-
const moduleBasename = basename(module.file);
|
|
1111
|
-
const declarationMap = new Map<string, string>();
|
|
1112
|
-
for (const decl of classDeclarations) {
|
|
1113
|
-
if (decl.file === moduleBasename) {
|
|
1114
|
-
declarationMap.set(decl.name, decl.id);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
for (const instantiation of classInstantiations) {
|
|
1119
|
-
const { variableId, className, line: _line } = instantiation;
|
|
1120
|
-
|
|
1121
|
-
let classId = declarationMap.get(className);
|
|
1122
|
-
|
|
1123
|
-
if (!classId) {
|
|
1124
|
-
// External class - compute semantic ID
|
|
1125
|
-
// Use basename to match CLASS node format (scopeTracker uses basename)
|
|
1126
|
-
// When class is in different file, edge will be dangling until that file analyzed
|
|
1127
|
-
const globalContext = { file: moduleBasename, scopePath: [] as string[] };
|
|
1128
|
-
classId = computeSemanticId('CLASS', className, globalContext);
|
|
1129
|
-
|
|
1130
|
-
// NO node creation - node will exist when class file analyzed
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
// Buffer INSTANCE_OF edge
|
|
1134
|
-
this._bufferEdge({
|
|
1135
|
-
type: 'INSTANCE_OF',
|
|
1136
|
-
src: variableId,
|
|
1137
|
-
dst: classId
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
private bufferCallbackEdges(methodCallbacks: MethodCallbackInfo[], functions: FunctionInfo[]): void {
|
|
1143
|
-
for (const callback of methodCallbacks) {
|
|
1144
|
-
const { methodCallId, callbackLine, callbackColumn } = callback;
|
|
1145
|
-
|
|
1146
|
-
const callbackFunction = functions.find(f =>
|
|
1147
|
-
f.line === callbackLine && f.column === callbackColumn
|
|
1148
|
-
);
|
|
1149
|
-
|
|
1150
|
-
if (callbackFunction) {
|
|
1151
|
-
this._bufferEdge({
|
|
1152
|
-
type: 'HAS_CALLBACK',
|
|
1153
|
-
src: methodCallId,
|
|
1154
|
-
dst: callbackFunction.id
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
private bufferImportNodes(module: ModuleNode, imports: ImportInfo[]): void {
|
|
1161
|
-
for (const imp of imports) {
|
|
1162
|
-
const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
|
|
1163
|
-
|
|
1164
|
-
// REG-273: Handle side-effect-only imports (no specifiers)
|
|
1165
|
-
if (specifiers.length === 0) {
|
|
1166
|
-
// Side-effect import: import './polyfill.js'
|
|
1167
|
-
const importNode = ImportNode.create(
|
|
1168
|
-
source, // name = source (no local binding)
|
|
1169
|
-
module.file, // file
|
|
1170
|
-
line, // line (stored as field, not in ID)
|
|
1171
|
-
column || 0, // column
|
|
1172
|
-
source, // source module
|
|
1173
|
-
{
|
|
1174
|
-
imported: '*', // no specific export
|
|
1175
|
-
local: source, // source becomes local
|
|
1176
|
-
sideEffect: true // mark as side-effect import
|
|
1177
|
-
}
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
|
-
this._bufferNode(importNode as unknown as GraphNode);
|
|
1181
|
-
|
|
1182
|
-
// MODULE -> CONTAINS -> IMPORT
|
|
1183
|
-
this._bufferEdge({
|
|
1184
|
-
type: 'CONTAINS',
|
|
1185
|
-
src: module.id,
|
|
1186
|
-
dst: importNode.id
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
// Create EXTERNAL_MODULE node for external modules
|
|
1190
|
-
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
1191
|
-
if (!isRelative) {
|
|
1192
|
-
const externalModule = NodeFactory.createExternalModule(source);
|
|
1193
|
-
|
|
1194
|
-
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
1195
|
-
if (!this._createdSingletons.has(externalModule.id)) {
|
|
1196
|
-
this._bufferNode(externalModule as unknown as GraphNode);
|
|
1197
|
-
this._createdSingletons.add(externalModule.id);
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
this._bufferEdge({
|
|
1201
|
-
type: 'IMPORTS',
|
|
1202
|
-
src: module.id,
|
|
1203
|
-
dst: externalModule.id
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
// Regular imports with specifiers
|
|
1208
|
-
for (const spec of specifiers) {
|
|
1209
|
-
// Use ImportNode factory for proper semantic IDs and field population
|
|
1210
|
-
const importNode = ImportNode.create(
|
|
1211
|
-
spec.local, // name = local binding
|
|
1212
|
-
module.file, // file
|
|
1213
|
-
line, // line (stored as field, not in ID)
|
|
1214
|
-
column || 0, // column
|
|
1215
|
-
source, // source module
|
|
1216
|
-
{
|
|
1217
|
-
imported: spec.imported,
|
|
1218
|
-
local: spec.local,
|
|
1219
|
-
sideEffect: false, // regular imports are not side-effects
|
|
1220
|
-
// importType is auto-detected from imported field
|
|
1221
|
-
// Dynamic import fields
|
|
1222
|
-
isDynamic,
|
|
1223
|
-
isResolvable,
|
|
1224
|
-
dynamicPath
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
|
|
1228
|
-
this._bufferNode(importNode as unknown as GraphNode);
|
|
1229
|
-
|
|
1230
|
-
// MODULE -> CONTAINS -> IMPORT
|
|
1231
|
-
this._bufferEdge({
|
|
1232
|
-
type: 'CONTAINS',
|
|
1233
|
-
src: module.id,
|
|
1234
|
-
dst: importNode.id
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
// Create EXTERNAL_MODULE node for external modules
|
|
1238
|
-
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
1239
|
-
if (!isRelative) {
|
|
1240
|
-
const externalModule = NodeFactory.createExternalModule(source);
|
|
1241
|
-
|
|
1242
|
-
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
1243
|
-
if (!this._createdSingletons.has(externalModule.id)) {
|
|
1244
|
-
this._bufferNode(externalModule as unknown as GraphNode);
|
|
1245
|
-
this._createdSingletons.add(externalModule.id);
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
this._bufferEdge({
|
|
1249
|
-
type: 'IMPORTS',
|
|
1250
|
-
src: module.id,
|
|
1251
|
-
dst: externalModule.id
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
private bufferExportNodes(module: ModuleNode, exports: ExportInfo[]): void {
|
|
1260
|
-
for (const exp of exports) {
|
|
1261
|
-
const { type, line, name, specifiers, source } = exp;
|
|
1262
|
-
|
|
1263
|
-
if (type === 'default') {
|
|
1264
|
-
const exportNode = NodeFactory.createExport(
|
|
1265
|
-
'default',
|
|
1266
|
-
module.file,
|
|
1267
|
-
line,
|
|
1268
|
-
0,
|
|
1269
|
-
{ default: true, exportType: 'default' }
|
|
1270
|
-
);
|
|
1271
|
-
|
|
1272
|
-
this._bufferNode(exportNode as unknown as GraphNode);
|
|
1273
|
-
|
|
1274
|
-
this._bufferEdge({
|
|
1275
|
-
type: 'CONTAINS',
|
|
1276
|
-
src: module.id,
|
|
1277
|
-
dst: exportNode.id
|
|
1278
|
-
});
|
|
1279
|
-
} else if (type === 'named') {
|
|
1280
|
-
if (specifiers) {
|
|
1281
|
-
for (const spec of specifiers) {
|
|
1282
|
-
const exportNode = NodeFactory.createExport(
|
|
1283
|
-
spec.exported,
|
|
1284
|
-
module.file,
|
|
1285
|
-
line,
|
|
1286
|
-
0,
|
|
1287
|
-
{
|
|
1288
|
-
local: spec.local,
|
|
1289
|
-
source: source,
|
|
1290
|
-
exportType: 'named'
|
|
1291
|
-
}
|
|
1292
|
-
);
|
|
1293
|
-
|
|
1294
|
-
this._bufferNode(exportNode as unknown as GraphNode);
|
|
1295
|
-
|
|
1296
|
-
this._bufferEdge({
|
|
1297
|
-
type: 'CONTAINS',
|
|
1298
|
-
src: module.id,
|
|
1299
|
-
dst: exportNode.id
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
} else if (name) {
|
|
1303
|
-
const exportNode = NodeFactory.createExport(
|
|
1304
|
-
name,
|
|
1305
|
-
module.file,
|
|
1306
|
-
line,
|
|
1307
|
-
0,
|
|
1308
|
-
{ exportType: 'named' }
|
|
1309
|
-
);
|
|
1310
|
-
|
|
1311
|
-
this._bufferNode(exportNode as unknown as GraphNode);
|
|
1312
|
-
|
|
1313
|
-
this._bufferEdge({
|
|
1314
|
-
type: 'CONTAINS',
|
|
1315
|
-
src: module.id,
|
|
1316
|
-
dst: exportNode.id
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
} else if (type === 'all') {
|
|
1320
|
-
const exportNode = NodeFactory.createExport(
|
|
1321
|
-
'*',
|
|
1322
|
-
module.file,
|
|
1323
|
-
line,
|
|
1324
|
-
0,
|
|
1325
|
-
{
|
|
1326
|
-
source: source,
|
|
1327
|
-
exportType: 'all'
|
|
1328
|
-
}
|
|
1329
|
-
);
|
|
1330
|
-
|
|
1331
|
-
this._bufferNode(exportNode as unknown as GraphNode);
|
|
1332
|
-
|
|
1333
|
-
this._bufferEdge({
|
|
1334
|
-
type: 'CONTAINS',
|
|
1335
|
-
src: module.id,
|
|
1336
|
-
dst: exportNode.id
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
private bufferEventListeners(eventListeners: EventListenerInfo[], functions: FunctionInfo[]): void {
|
|
1343
|
-
for (const eventListener of eventListeners) {
|
|
1344
|
-
const { parentScopeId, callbackArg, ...listenerData } = eventListener;
|
|
1345
|
-
|
|
1346
|
-
this._bufferNode(listenerData as GraphNode);
|
|
1347
|
-
|
|
1348
|
-
this._bufferEdge({
|
|
1349
|
-
type: 'CONTAINS',
|
|
1350
|
-
src: parentScopeId as string,
|
|
1351
|
-
dst: listenerData.id
|
|
1352
|
-
});
|
|
1353
|
-
|
|
1354
|
-
if (callbackArg && callbackArg.type === 'ArrowFunctionExpression') {
|
|
1355
|
-
const callbackLine = (callbackArg.loc as { start: { line: number } }).start.line;
|
|
1356
|
-
const callbackFunction = functions.find(f =>
|
|
1357
|
-
f.line === callbackLine && f.arrowFunction
|
|
1358
|
-
);
|
|
1359
|
-
|
|
1360
|
-
if (callbackFunction) {
|
|
1361
|
-
this._bufferEdge({
|
|
1362
|
-
type: 'HANDLED_BY',
|
|
1363
|
-
src: listenerData.id,
|
|
1364
|
-
dst: callbackFunction.id
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
private bufferHttpRequests(httpRequests: HttpRequestInfo[], functions: FunctionInfo[]): void {
|
|
1372
|
-
if (httpRequests.length > 0) {
|
|
1373
|
-
// Create net:request singleton using factory
|
|
1374
|
-
const networkNode = NetworkRequestNode.create();
|
|
1375
|
-
|
|
1376
|
-
if (!this._createdSingletons.has(networkNode.id)) {
|
|
1377
|
-
this._bufferNode(networkNode as unknown as GraphNode);
|
|
1378
|
-
this._createdSingletons.add(networkNode.id);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
for (const request of httpRequests) {
|
|
1382
|
-
const { parentScopeId, ...requestData } = request;
|
|
1383
|
-
|
|
1384
|
-
this._bufferNode(requestData as GraphNode);
|
|
1385
|
-
|
|
1386
|
-
this._bufferEdge({
|
|
1387
|
-
type: 'CALLS',
|
|
1388
|
-
src: request.id,
|
|
1389
|
-
dst: networkNode.id
|
|
1390
|
-
});
|
|
1391
|
-
|
|
1392
|
-
if (parentScopeId) {
|
|
1393
|
-
const scopeParts = parentScopeId.split(':');
|
|
1394
|
-
if (scopeParts.length >= 3 && scopeParts[1] === 'SCOPE') {
|
|
1395
|
-
const functionName = scopeParts[2];
|
|
1396
|
-
const file = scopeParts[0];
|
|
1397
|
-
|
|
1398
|
-
const parentFunction = functions.find(f =>
|
|
1399
|
-
f.file === file && f.name === functionName
|
|
1400
|
-
);
|
|
1401
|
-
|
|
1402
|
-
if (parentFunction) {
|
|
1403
|
-
this._bufferEdge({
|
|
1404
|
-
type: 'MAKES_REQUEST',
|
|
1405
|
-
src: parentFunction.id,
|
|
1406
|
-
dst: request.id
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
private bufferLiterals(literals: LiteralInfo[]): void {
|
|
1416
|
-
for (const literal of literals) {
|
|
1417
|
-
const { parentCallId: _parentCallId, argIndex: _argIndex, ...literalData } = literal;
|
|
1418
|
-
this._bufferNode(literalData as GraphNode);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
private bufferAssignmentEdges(
|
|
1423
|
-
variableAssignments: VariableAssignmentInfo[],
|
|
1424
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
1425
|
-
callSites: CallSiteInfo[],
|
|
1426
|
-
methodCalls: MethodCallInfo[],
|
|
1427
|
-
functions: FunctionInfo[],
|
|
1428
|
-
classInstantiations: ClassInstantiationInfo[],
|
|
1429
|
-
parameters: ParameterInfo[]
|
|
1430
|
-
): void {
|
|
1431
|
-
for (const assignment of variableAssignments) {
|
|
1432
|
-
const {
|
|
1433
|
-
variableId,
|
|
1434
|
-
sourceId,
|
|
1435
|
-
sourceType,
|
|
1436
|
-
sourceName,
|
|
1437
|
-
sourceLine,
|
|
1438
|
-
sourceColumn,
|
|
1439
|
-
sourceFile,
|
|
1440
|
-
functionName,
|
|
1441
|
-
line,
|
|
1442
|
-
column,
|
|
1443
|
-
className
|
|
1444
|
-
} = assignment;
|
|
1445
|
-
|
|
1446
|
-
// Skip CLASS sourceType - handled async in createClassAssignmentEdges
|
|
1447
|
-
if (sourceType === 'CLASS') {
|
|
1448
|
-
continue;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
// CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
|
|
1452
|
-
// Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
|
|
1453
|
-
if (sourceType === 'CONSTRUCTOR_CALL' && className) {
|
|
1454
|
-
const constructorLine = line ?? 0;
|
|
1455
|
-
const constructorColumn = column ?? 0;
|
|
1456
|
-
const constructorFile = assignment.file ?? '';
|
|
1457
|
-
|
|
1458
|
-
// Generate ID matching the one created in NewExpression visitor
|
|
1459
|
-
const constructorCallId = NodeFactory.generateConstructorCallId(
|
|
1460
|
-
className,
|
|
1461
|
-
constructorFile,
|
|
1462
|
-
constructorLine,
|
|
1463
|
-
constructorColumn
|
|
1464
|
-
);
|
|
1465
|
-
|
|
1466
|
-
this._bufferEdge({
|
|
1467
|
-
type: 'ASSIGNED_FROM',
|
|
1468
|
-
src: variableId,
|
|
1469
|
-
dst: constructorCallId
|
|
1470
|
-
});
|
|
1471
|
-
continue;
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
// Direct LITERAL assignment
|
|
1475
|
-
if (sourceId && sourceType !== 'EXPRESSION') {
|
|
1476
|
-
this._bufferEdge({
|
|
1477
|
-
type: 'ASSIGNED_FROM',
|
|
1478
|
-
src: variableId,
|
|
1479
|
-
dst: sourceId
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
// METHOD_CALL by coordinates
|
|
1483
|
-
else if (sourceType === 'METHOD_CALL' && sourceLine && sourceColumn) {
|
|
1484
|
-
const methodCall = methodCalls.find(mc =>
|
|
1485
|
-
mc.line === sourceLine &&
|
|
1486
|
-
mc.column === sourceColumn &&
|
|
1487
|
-
mc.file === sourceFile
|
|
1488
|
-
);
|
|
1489
|
-
|
|
1490
|
-
if (methodCall) {
|
|
1491
|
-
this._bufferEdge({
|
|
1492
|
-
type: 'ASSIGNED_FROM',
|
|
1493
|
-
src: variableId,
|
|
1494
|
-
dst: methodCall.id
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
// CALL_SITE by coordinates
|
|
1499
|
-
else if (sourceType === 'CALL_SITE') {
|
|
1500
|
-
const searchLine = sourceLine || assignment.callLine;
|
|
1501
|
-
const searchColumn = sourceColumn || assignment.callColumn;
|
|
1502
|
-
const searchName = assignment.callName;
|
|
1503
|
-
|
|
1504
|
-
if (searchLine && searchColumn) {
|
|
1505
|
-
const callSite = callSites.find(cs =>
|
|
1506
|
-
cs.line === searchLine &&
|
|
1507
|
-
cs.column === searchColumn &&
|
|
1508
|
-
(searchName ? cs.name === searchName : true)
|
|
1509
|
-
);
|
|
1510
|
-
|
|
1511
|
-
if (callSite) {
|
|
1512
|
-
this._bufferEdge({
|
|
1513
|
-
type: 'ASSIGNED_FROM',
|
|
1514
|
-
src: variableId,
|
|
1515
|
-
dst: callSite.id
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
// VARIABLE by name
|
|
1521
|
-
else if (sourceType === 'VARIABLE' && sourceName) {
|
|
1522
|
-
// Find the current variable's file by looking it up in variableDeclarations
|
|
1523
|
-
// (semantic IDs don't have predictable file positions like old hash-based IDs)
|
|
1524
|
-
const currentVar = variableDeclarations.find(v => v.id === variableId);
|
|
1525
|
-
const varFile = currentVar?.file ?? null;
|
|
1526
|
-
const sourceVariable = variableDeclarations.find(v =>
|
|
1527
|
-
v.name === sourceName && v.file === varFile
|
|
1528
|
-
);
|
|
1529
|
-
|
|
1530
|
-
if (sourceVariable) {
|
|
1531
|
-
this._bufferEdge({
|
|
1532
|
-
type: 'ASSIGNED_FROM',
|
|
1533
|
-
src: variableId,
|
|
1534
|
-
dst: sourceVariable.id
|
|
1535
|
-
});
|
|
1536
|
-
} else {
|
|
1537
|
-
const sourceParam = parameters.find(p =>
|
|
1538
|
-
p.name === sourceName && p.file === varFile
|
|
1539
|
-
);
|
|
1540
|
-
|
|
1541
|
-
if (sourceParam) {
|
|
1542
|
-
this._bufferEdge({
|
|
1543
|
-
type: 'DERIVES_FROM',
|
|
1544
|
-
src: variableId,
|
|
1545
|
-
dst: sourceParam.id
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
// FUNCTION (arrow function assigned to variable)
|
|
1551
|
-
else if (sourceType === 'FUNCTION' && functionName && line) {
|
|
1552
|
-
const sourceFunction = functions.find(f =>
|
|
1553
|
-
f.name === functionName && f.line === line
|
|
1554
|
-
);
|
|
1555
|
-
|
|
1556
|
-
if (sourceFunction) {
|
|
1557
|
-
this._bufferEdge({
|
|
1558
|
-
type: 'ASSIGNED_FROM',
|
|
1559
|
-
src: variableId,
|
|
1560
|
-
dst: sourceFunction.id
|
|
1561
|
-
});
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
// EXPRESSION node creation using NodeFactory
|
|
1565
|
-
else if (sourceType === 'EXPRESSION' && sourceId) {
|
|
1566
|
-
const {
|
|
1567
|
-
expressionType,
|
|
1568
|
-
object,
|
|
1569
|
-
property,
|
|
1570
|
-
computed,
|
|
1571
|
-
computedPropertyVar,
|
|
1572
|
-
operator,
|
|
1573
|
-
objectSourceName,
|
|
1574
|
-
leftSourceName,
|
|
1575
|
-
rightSourceName,
|
|
1576
|
-
consequentSourceName,
|
|
1577
|
-
alternateSourceName,
|
|
1578
|
-
file: exprFile,
|
|
1579
|
-
line: exprLine,
|
|
1580
|
-
column: exprColumn,
|
|
1581
|
-
// Destructuring support (REG-201)
|
|
1582
|
-
path,
|
|
1583
|
-
baseName,
|
|
1584
|
-
propertyPath,
|
|
1585
|
-
arrayIndex
|
|
1586
|
-
} = assignment;
|
|
1587
|
-
|
|
1588
|
-
// Create node from upstream metadata using factory
|
|
1589
|
-
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
1590
|
-
expressionType || 'Unknown',
|
|
1591
|
-
exprFile || '',
|
|
1592
|
-
exprLine || 0,
|
|
1593
|
-
exprColumn || 0,
|
|
1594
|
-
{
|
|
1595
|
-
id: sourceId, // ID from JSASTAnalyzer
|
|
1596
|
-
object,
|
|
1597
|
-
property,
|
|
1598
|
-
computed,
|
|
1599
|
-
computedPropertyVar: computedPropertyVar ?? undefined,
|
|
1600
|
-
operator,
|
|
1601
|
-
// Destructuring support (REG-201)
|
|
1602
|
-
path,
|
|
1603
|
-
baseName,
|
|
1604
|
-
propertyPath,
|
|
1605
|
-
arrayIndex
|
|
1606
|
-
}
|
|
1607
|
-
);
|
|
1608
|
-
|
|
1609
|
-
this._bufferNode(expressionNode);
|
|
1610
|
-
|
|
1611
|
-
this._bufferEdge({
|
|
1612
|
-
type: 'ASSIGNED_FROM',
|
|
1613
|
-
src: variableId,
|
|
1614
|
-
dst: sourceId
|
|
1615
|
-
});
|
|
1616
|
-
|
|
1617
|
-
// Buffer DERIVES_FROM edges
|
|
1618
|
-
const varParts = variableId.split('#');
|
|
1619
|
-
const varFile = varParts.length >= 3 ? varParts[2] : null;
|
|
1620
|
-
|
|
1621
|
-
if (expressionType === 'MemberExpression' && objectSourceName) {
|
|
1622
|
-
const objectVar = variableDeclarations.find(v =>
|
|
1623
|
-
v.name === objectSourceName && (!varFile || v.file === varFile)
|
|
1624
|
-
);
|
|
1625
|
-
if (objectVar) {
|
|
1626
|
-
this._bufferEdge({
|
|
1627
|
-
type: 'DERIVES_FROM',
|
|
1628
|
-
src: sourceId,
|
|
1629
|
-
dst: objectVar.id
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
// Call-based source lookup (REG-223)
|
|
1634
|
-
else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
|
|
1635
|
-
const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
|
|
1636
|
-
|
|
1637
|
-
// Try CALL_SITE first (direct function calls)
|
|
1638
|
-
const callSite = callSites.find(cs =>
|
|
1639
|
-
cs.line === callSourceLine &&
|
|
1640
|
-
cs.column === callSourceColumn &&
|
|
1641
|
-
(callSourceName ? cs.name === callSourceName : true)
|
|
1642
|
-
);
|
|
1643
|
-
|
|
1644
|
-
if (callSite) {
|
|
1645
|
-
this._bufferEdge({
|
|
1646
|
-
type: 'DERIVES_FROM',
|
|
1647
|
-
src: sourceId,
|
|
1648
|
-
dst: callSite.id
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
// Fall back to methodCalls (arr.map(), obj.getConfig())
|
|
1652
|
-
else {
|
|
1653
|
-
const methodCall = methodCalls.find(mc =>
|
|
1654
|
-
mc.line === callSourceLine &&
|
|
1655
|
-
mc.column === callSourceColumn &&
|
|
1656
|
-
(callSourceName ? mc.name === callSourceName : true)
|
|
1657
|
-
);
|
|
1658
|
-
|
|
1659
|
-
if (methodCall) {
|
|
1660
|
-
this._bufferEdge({
|
|
1661
|
-
type: 'DERIVES_FROM',
|
|
1662
|
-
src: sourceId,
|
|
1663
|
-
dst: methodCall.id
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
// Log warning when lookup fails (per Linus review - no silent failures)
|
|
1667
|
-
else {
|
|
1668
|
-
console.warn(
|
|
1669
|
-
`[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
|
|
1670
|
-
`at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
|
|
1671
|
-
`Expected CALL_SITE or methodCall for "${callSourceName}". ` +
|
|
1672
|
-
`This indicates a coordinate mismatch or missing call node.`
|
|
1673
|
-
);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
|
|
1679
|
-
if (leftSourceName) {
|
|
1680
|
-
const leftVar = variableDeclarations.find(v =>
|
|
1681
|
-
v.name === leftSourceName && (!varFile || v.file === varFile)
|
|
1682
|
-
);
|
|
1683
|
-
if (leftVar) {
|
|
1684
|
-
this._bufferEdge({
|
|
1685
|
-
type: 'DERIVES_FROM',
|
|
1686
|
-
src: sourceId,
|
|
1687
|
-
dst: leftVar.id
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
if (rightSourceName) {
|
|
1692
|
-
const rightVar = variableDeclarations.find(v =>
|
|
1693
|
-
v.name === rightSourceName && (!varFile || v.file === varFile)
|
|
1694
|
-
);
|
|
1695
|
-
if (rightVar) {
|
|
1696
|
-
this._bufferEdge({
|
|
1697
|
-
type: 'DERIVES_FROM',
|
|
1698
|
-
src: sourceId,
|
|
1699
|
-
dst: rightVar.id
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
if (expressionType === 'ConditionalExpression') {
|
|
1706
|
-
if (consequentSourceName) {
|
|
1707
|
-
const consequentVar = variableDeclarations.find(v =>
|
|
1708
|
-
v.name === consequentSourceName && (!varFile || v.file === varFile)
|
|
1709
|
-
);
|
|
1710
|
-
if (consequentVar) {
|
|
1711
|
-
this._bufferEdge({
|
|
1712
|
-
type: 'DERIVES_FROM',
|
|
1713
|
-
src: sourceId,
|
|
1714
|
-
dst: consequentVar.id
|
|
1715
|
-
});
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
if (alternateSourceName) {
|
|
1719
|
-
const alternateVar = variableDeclarations.find(v =>
|
|
1720
|
-
v.name === alternateSourceName && (!varFile || v.file === varFile)
|
|
1721
|
-
);
|
|
1722
|
-
if (alternateVar) {
|
|
1723
|
-
this._bufferEdge({
|
|
1724
|
-
type: 'DERIVES_FROM',
|
|
1725
|
-
src: sourceId,
|
|
1726
|
-
dst: alternateVar.id
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
if (expressionType === 'TemplateLiteral') {
|
|
1733
|
-
const { expressionSourceNames } = assignment;
|
|
1734
|
-
if (expressionSourceNames && expressionSourceNames.length > 0) {
|
|
1735
|
-
for (const exprSourceName of expressionSourceNames) {
|
|
1736
|
-
const sourceVar = variableDeclarations.find(v =>
|
|
1737
|
-
v.name === exprSourceName && (!varFile || v.file === varFile)
|
|
1738
|
-
);
|
|
1739
|
-
if (sourceVar) {
|
|
1740
|
-
this._bufferEdge({
|
|
1741
|
-
type: 'DERIVES_FROM',
|
|
1742
|
-
src: sourceId,
|
|
1743
|
-
dst: sourceVar.id
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
// DERIVES_FROM_VARIABLE
|
|
1751
|
-
else if (sourceType === 'DERIVES_FROM_VARIABLE' && sourceName) {
|
|
1752
|
-
const expressionId = variableId;
|
|
1753
|
-
const exprParts = expressionId.split('#');
|
|
1754
|
-
const exprFile = exprParts.length >= 3 ? exprParts[2] : assignment.file;
|
|
1755
|
-
|
|
1756
|
-
const sourceVariable = variableDeclarations.find(v =>
|
|
1757
|
-
v.name === sourceName && v.file === exprFile
|
|
1758
|
-
);
|
|
1759
|
-
|
|
1760
|
-
if (sourceVariable) {
|
|
1761
|
-
this._bufferEdge({
|
|
1762
|
-
type: 'DERIVES_FROM',
|
|
1763
|
-
src: expressionId,
|
|
1764
|
-
dst: sourceVariable.id
|
|
1765
|
-
});
|
|
1766
|
-
} else {
|
|
1767
|
-
const sourceParam = parameters.find(p =>
|
|
1768
|
-
p.name === sourceName && p.file === exprFile
|
|
1769
|
-
);
|
|
1770
|
-
|
|
1771
|
-
if (sourceParam) {
|
|
1772
|
-
this._bufferEdge({
|
|
1773
|
-
type: 'DERIVES_FROM',
|
|
1774
|
-
src: expressionId,
|
|
1775
|
-
dst: sourceParam.id
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
private bufferArgumentEdges(
|
|
1784
|
-
callArguments: CallArgumentInfo[],
|
|
1785
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
1786
|
-
functions: FunctionInfo[],
|
|
1787
|
-
callSites: CallSiteInfo[],
|
|
1788
|
-
methodCalls: MethodCallInfo[]
|
|
1789
|
-
): void {
|
|
1790
|
-
for (const arg of callArguments) {
|
|
1791
|
-
const {
|
|
1792
|
-
callId,
|
|
1793
|
-
argIndex,
|
|
1794
|
-
targetType,
|
|
1795
|
-
targetId,
|
|
1796
|
-
targetName,
|
|
1797
|
-
file,
|
|
1798
|
-
isSpread,
|
|
1799
|
-
functionLine,
|
|
1800
|
-
functionColumn,
|
|
1801
|
-
nestedCallLine,
|
|
1802
|
-
nestedCallColumn
|
|
1803
|
-
} = arg;
|
|
1804
|
-
|
|
1805
|
-
let targetNodeId = targetId;
|
|
1806
|
-
|
|
1807
|
-
if (targetType === 'VARIABLE' && targetName) {
|
|
1808
|
-
const varNode = variableDeclarations.find(v =>
|
|
1809
|
-
v.name === targetName && v.file === file
|
|
1810
|
-
);
|
|
1811
|
-
if (varNode) {
|
|
1812
|
-
targetNodeId = varNode.id;
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
else if (targetType === 'FUNCTION' && functionLine && functionColumn) {
|
|
1816
|
-
const funcNode = functions.find(f =>
|
|
1817
|
-
f.file === file && f.line === functionLine && f.column === functionColumn
|
|
1818
|
-
);
|
|
1819
|
-
if (funcNode) {
|
|
1820
|
-
targetNodeId = funcNode.id;
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
else if (targetType === 'CALL' && nestedCallLine && nestedCallColumn) {
|
|
1824
|
-
const nestedCall = callSites.find(c =>
|
|
1825
|
-
c.file === file && c.line === nestedCallLine && c.column === nestedCallColumn
|
|
1826
|
-
) || methodCalls.find(c =>
|
|
1827
|
-
c.file === file && c.line === nestedCallLine && c.column === nestedCallColumn
|
|
1828
|
-
);
|
|
1829
|
-
if (nestedCall) {
|
|
1830
|
-
targetNodeId = nestedCall.id;
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
else if (targetType === 'LITERAL' ||
|
|
1834
|
-
targetType === 'OBJECT_LITERAL' ||
|
|
1835
|
-
targetType === 'ARRAY_LITERAL') {
|
|
1836
|
-
// targetId is already set by CallExpressionVisitor
|
|
1837
|
-
targetNodeId = targetId;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
if (targetNodeId) {
|
|
1841
|
-
const edgeData: GraphEdge = {
|
|
1842
|
-
type: 'PASSES_ARGUMENT',
|
|
1843
|
-
src: callId,
|
|
1844
|
-
dst: targetNodeId,
|
|
1845
|
-
metadata: { argIndex }
|
|
1846
|
-
};
|
|
1847
|
-
|
|
1848
|
-
if (isSpread) {
|
|
1849
|
-
edgeData.metadata = { ...edgeData.metadata, isSpread: true };
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
this._bufferEdge(edgeData);
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
// ============= TypeScript-specific buffer methods =============
|
|
1858
|
-
|
|
1859
|
-
/**
|
|
1860
|
-
* Buffer INTERFACE nodes and EXTENDS edges
|
|
1861
|
-
*
|
|
1862
|
-
* Uses two-pass approach:
|
|
1863
|
-
* 1. First pass: create all interface nodes, store in Map
|
|
1864
|
-
* 2. Second pass: create EXTENDS edges using stored node IDs
|
|
1865
|
-
*/
|
|
1866
|
-
private bufferInterfaceNodes(module: ModuleNode, interfaces: InterfaceDeclarationInfo[]): void {
|
|
1867
|
-
// First pass: create all interface nodes and store them
|
|
1868
|
-
const interfaceNodes = new Map<string, InterfaceNodeRecord>();
|
|
1869
|
-
|
|
1870
|
-
for (const iface of interfaces) {
|
|
1871
|
-
const interfaceNode = InterfaceNode.create(
|
|
1872
|
-
iface.name,
|
|
1873
|
-
iface.file,
|
|
1874
|
-
iface.line,
|
|
1875
|
-
iface.column || 0,
|
|
1876
|
-
{
|
|
1877
|
-
extends: iface.extends,
|
|
1878
|
-
properties: iface.properties
|
|
1879
|
-
}
|
|
1880
|
-
);
|
|
1881
|
-
interfaceNodes.set(iface.name, interfaceNode);
|
|
1882
|
-
this._bufferNode(interfaceNode as unknown as GraphNode);
|
|
1883
|
-
|
|
1884
|
-
// MODULE -> CONTAINS -> INTERFACE
|
|
1885
|
-
this._bufferEdge({
|
|
1886
|
-
type: 'CONTAINS',
|
|
1887
|
-
src: module.id,
|
|
1888
|
-
dst: interfaceNode.id
|
|
1889
|
-
});
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
// Second pass: create EXTENDS edges
|
|
1893
|
-
for (const iface of interfaces) {
|
|
1894
|
-
if (iface.extends && iface.extends.length > 0) {
|
|
1895
|
-
const srcNode = interfaceNodes.get(iface.name)!;
|
|
1896
|
-
|
|
1897
|
-
for (const parentName of iface.extends) {
|
|
1898
|
-
const parentNode = interfaceNodes.get(parentName);
|
|
1899
|
-
|
|
1900
|
-
if (parentNode) {
|
|
1901
|
-
// Same-file interface
|
|
1902
|
-
this._bufferEdge({
|
|
1903
|
-
type: 'EXTENDS',
|
|
1904
|
-
src: srcNode.id,
|
|
1905
|
-
dst: parentNode.id
|
|
1906
|
-
});
|
|
1907
|
-
} else {
|
|
1908
|
-
// External interface - create a reference node
|
|
1909
|
-
const externalInterface = NodeFactory.createInterface(
|
|
1910
|
-
parentName,
|
|
1911
|
-
iface.file,
|
|
1912
|
-
iface.line,
|
|
1913
|
-
0,
|
|
1914
|
-
{ isExternal: true }
|
|
1915
|
-
);
|
|
1916
|
-
this._bufferNode(externalInterface as unknown as GraphNode);
|
|
1917
|
-
this._bufferEdge({
|
|
1918
|
-
type: 'EXTENDS',
|
|
1919
|
-
src: srcNode.id,
|
|
1920
|
-
dst: externalInterface.id
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
/**
|
|
1929
|
-
* Buffer TYPE alias nodes
|
|
1930
|
-
*/
|
|
1931
|
-
private bufferTypeAliasNodes(module: ModuleNode, typeAliases: TypeAliasInfo[]): void {
|
|
1932
|
-
for (const typeAlias of typeAliases) {
|
|
1933
|
-
// Create TYPE node using factory
|
|
1934
|
-
const typeNode = NodeFactory.createType(
|
|
1935
|
-
typeAlias.name,
|
|
1936
|
-
typeAlias.file,
|
|
1937
|
-
typeAlias.line,
|
|
1938
|
-
typeAlias.column || 0,
|
|
1939
|
-
{ aliasOf: typeAlias.aliasOf }
|
|
1940
|
-
);
|
|
1941
|
-
this._bufferNode(typeNode as unknown as GraphNode);
|
|
1942
|
-
|
|
1943
|
-
// MODULE -> CONTAINS -> TYPE
|
|
1944
|
-
this._bufferEdge({
|
|
1945
|
-
type: 'CONTAINS',
|
|
1946
|
-
src: module.id,
|
|
1947
|
-
dst: typeNode.id
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
/**
|
|
1953
|
-
* Buffer ENUM nodes
|
|
1954
|
-
* Uses EnumNode.create() to ensure consistent ID format (colon separator)
|
|
1955
|
-
*/
|
|
1956
|
-
private bufferEnumNodes(module: ModuleNode, enums: EnumDeclarationInfo[]): void {
|
|
1957
|
-
for (const enumDecl of enums) {
|
|
1958
|
-
// Use EnumNode.create() to generate proper ID (colon format)
|
|
1959
|
-
// Do NOT use enumDecl.id which has legacy # format from TypeScriptVisitor
|
|
1960
|
-
const enumNode = EnumNode.create(
|
|
1961
|
-
enumDecl.name,
|
|
1962
|
-
enumDecl.file,
|
|
1963
|
-
enumDecl.line,
|
|
1964
|
-
enumDecl.column || 0,
|
|
1965
|
-
{
|
|
1966
|
-
isConst: enumDecl.isConst || false,
|
|
1967
|
-
members: enumDecl.members || []
|
|
1968
|
-
}
|
|
1969
|
-
);
|
|
1970
|
-
|
|
1971
|
-
this._bufferNode(enumNode as unknown as GraphNode);
|
|
1972
|
-
|
|
1973
|
-
// MODULE -> CONTAINS -> ENUM
|
|
1974
|
-
this._bufferEdge({
|
|
1975
|
-
type: 'CONTAINS',
|
|
1976
|
-
src: module.id,
|
|
1977
|
-
dst: enumNode.id // Use factory-generated ID (colon format)
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
/**
|
|
1983
|
-
* Buffer DECORATOR nodes and DECORATED_BY edges
|
|
1984
|
-
*/
|
|
1985
|
-
private bufferDecoratorNodes(decorators: DecoratorInfo[]): void {
|
|
1986
|
-
for (const decorator of decorators) {
|
|
1987
|
-
// Create DECORATOR node using factory (generates colon-format ID)
|
|
1988
|
-
const decoratorNode = DecoratorNode.create(
|
|
1989
|
-
decorator.name,
|
|
1990
|
-
decorator.file,
|
|
1991
|
-
decorator.line,
|
|
1992
|
-
decorator.column || 0,
|
|
1993
|
-
decorator.targetId, // Now included in the node!
|
|
1994
|
-
decorator.targetType,
|
|
1995
|
-
{ arguments: decorator.arguments }
|
|
1996
|
-
);
|
|
1997
|
-
|
|
1998
|
-
this._bufferNode(decoratorNode as unknown as GraphNode);
|
|
1999
|
-
|
|
2000
|
-
// TARGET -> DECORATED_BY -> DECORATOR
|
|
2001
|
-
this._bufferEdge({
|
|
2002
|
-
type: 'DECORATED_BY',
|
|
2003
|
-
src: decorator.targetId,
|
|
2004
|
-
dst: decoratorNode.id // Use factory-generated ID (colon format)
|
|
2005
|
-
});
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
/**
|
|
2010
|
-
* Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
|
|
2011
|
-
*/
|
|
2012
|
-
private bufferImplementsEdges(classDeclarations: ClassDeclarationInfo[], interfaces: InterfaceDeclarationInfo[]): void {
|
|
2013
|
-
for (const classDecl of classDeclarations) {
|
|
2014
|
-
if (classDecl.implements && classDecl.implements.length > 0) {
|
|
2015
|
-
for (const ifaceName of classDecl.implements) {
|
|
2016
|
-
// Try to find the interface in the same file
|
|
2017
|
-
const iface = interfaces.find(i => i.name === ifaceName);
|
|
2018
|
-
if (iface) {
|
|
2019
|
-
// Compute interface ID using same formula as InterfaceNode.create()
|
|
2020
|
-
// Format: {file}:INTERFACE:{name}:{line}
|
|
2021
|
-
const interfaceId = `${iface.file}:INTERFACE:${iface.name}:${iface.line}`;
|
|
2022
|
-
this._bufferEdge({
|
|
2023
|
-
type: 'IMPLEMENTS',
|
|
2024
|
-
src: classDecl.id,
|
|
2025
|
-
dst: interfaceId
|
|
2026
|
-
});
|
|
2027
|
-
} else {
|
|
2028
|
-
// External interface - create a reference node
|
|
2029
|
-
const externalInterface = NodeFactory.createInterface(
|
|
2030
|
-
ifaceName,
|
|
2031
|
-
classDecl.file,
|
|
2032
|
-
classDecl.line,
|
|
2033
|
-
0,
|
|
2034
|
-
{ isExternal: true }
|
|
2035
|
-
);
|
|
2036
|
-
this._bufferNode(externalInterface as unknown as GraphNode);
|
|
2037
|
-
this._bufferEdge({
|
|
2038
|
-
type: 'IMPLEMENTS',
|
|
2039
|
-
src: classDecl.id,
|
|
2040
|
-
dst: externalInterface.id
|
|
2041
|
-
});
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
/**
|
|
2049
|
-
* Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
2050
|
-
* Creates edges from inserted values to the array variable.
|
|
2051
|
-
*
|
|
2052
|
-
* REG-117: Handles nested mutations like obj.arr.push(item)
|
|
2053
|
-
* REG-392: Handles non-variable values (LITERAL, OBJECT_LITERAL, ARRAY_LITERAL, CALL)
|
|
2054
|
-
*/
|
|
2055
|
-
private bufferArrayMutationEdges(
|
|
2056
|
-
arrayMutations: ArrayMutationInfo[],
|
|
2057
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2058
|
-
parameters: ParameterInfo[],
|
|
2059
|
-
literals: LiteralInfo[],
|
|
2060
|
-
objectLiterals: ObjectLiteralInfo[],
|
|
2061
|
-
arrayLiterals: ArrayLiteralInfo[],
|
|
2062
|
-
callSites: CallSiteInfo[]
|
|
2063
|
-
): void {
|
|
2064
|
-
for (const mutation of arrayMutations) {
|
|
2065
|
-
const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
|
|
2066
|
-
|
|
2067
|
-
const scopePath = mutationScopePath ?? [];
|
|
2068
|
-
|
|
2069
|
-
// REG-117: For nested mutations (obj.arr.push), resolve target node
|
|
2070
|
-
let targetNodeId: string | null = null;
|
|
2071
|
-
let nestedProperty: string | undefined;
|
|
2072
|
-
|
|
2073
|
-
if (isNested && baseObjectName) {
|
|
2074
|
-
// Skip 'this.items.push' - 'this' is not a variable node
|
|
2075
|
-
if (baseObjectName === 'this') continue;
|
|
2076
|
-
|
|
2077
|
-
// Nested mutation: try base object lookup with scope chain (REG-309)
|
|
2078
|
-
const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
|
|
2079
|
-
const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
|
|
2080
|
-
targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
|
|
2081
|
-
nestedProperty = propertyName;
|
|
2082
|
-
} else {
|
|
2083
|
-
// Direct mutation: arr.push() (REG-309)
|
|
2084
|
-
const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
|
|
2085
|
-
const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
|
|
2086
|
-
targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
if (!targetNodeId) continue;
|
|
2090
|
-
|
|
2091
|
-
// Create FLOWS_INTO edges for each inserted value
|
|
2092
|
-
for (const arg of insertedValues) {
|
|
2093
|
-
let sourceNodeId: string | undefined;
|
|
2094
|
-
|
|
2095
|
-
if (arg.valueType === 'VARIABLE' && arg.valueName) {
|
|
2096
|
-
// Scope-aware lookup for source variable (REG-309)
|
|
2097
|
-
const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
|
|
2098
|
-
const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
|
|
2099
|
-
sourceNodeId = sourceVar?.id ?? sourceParam?.id;
|
|
2100
|
-
} else if (arg.valueNodeId) {
|
|
2101
|
-
// REG-392: Direct node ID for indexed assignments (LITERAL, OBJECT_LITERAL, ARRAY_LITERAL)
|
|
2102
|
-
sourceNodeId = arg.valueNodeId;
|
|
2103
|
-
} else if (arg.valueType === 'LITERAL' && arg.valueLine !== undefined && arg.valueColumn !== undefined) {
|
|
2104
|
-
// REG-392: Find LITERAL node by coordinates (push/unshift — nodes created by extractArguments)
|
|
2105
|
-
const literalNode = literals.find(l =>
|
|
2106
|
-
l.line === arg.valueLine && l.column === arg.valueColumn && l.file === file
|
|
2107
|
-
);
|
|
2108
|
-
sourceNodeId = literalNode?.id;
|
|
2109
|
-
} else if (arg.valueType === 'OBJECT_LITERAL' && arg.valueLine !== undefined && arg.valueColumn !== undefined) {
|
|
2110
|
-
// REG-392: Find OBJECT_LITERAL node by coordinates
|
|
2111
|
-
const objNode = objectLiterals.find(o =>
|
|
2112
|
-
o.line === arg.valueLine && o.column === arg.valueColumn && o.file === file
|
|
2113
|
-
);
|
|
2114
|
-
sourceNodeId = objNode?.id;
|
|
2115
|
-
} else if (arg.valueType === 'ARRAY_LITERAL' && arg.valueLine !== undefined && arg.valueColumn !== undefined) {
|
|
2116
|
-
// REG-392: Find ARRAY_LITERAL node by coordinates
|
|
2117
|
-
const arrNode = arrayLiterals.find(a =>
|
|
2118
|
-
a.line === arg.valueLine && a.column === arg.valueColumn && a.file === file
|
|
2119
|
-
);
|
|
2120
|
-
sourceNodeId = arrNode?.id;
|
|
2121
|
-
} else if (arg.valueType === 'CALL' && arg.callLine !== undefined && arg.callColumn !== undefined) {
|
|
2122
|
-
// REG-392: Find CALL_SITE node by coordinates
|
|
2123
|
-
const callSite = callSites.find(cs =>
|
|
2124
|
-
cs.line === arg.callLine && cs.column === arg.callColumn && cs.file === file
|
|
2125
|
-
);
|
|
2126
|
-
sourceNodeId = callSite?.id;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
if (sourceNodeId) {
|
|
2130
|
-
const edgeData: GraphEdge = {
|
|
2131
|
-
type: 'FLOWS_INTO',
|
|
2132
|
-
src: sourceNodeId,
|
|
2133
|
-
dst: targetNodeId,
|
|
2134
|
-
mutationMethod,
|
|
2135
|
-
argIndex: arg.argIndex
|
|
2136
|
-
};
|
|
2137
|
-
if (arg.isSpread) {
|
|
2138
|
-
edgeData.isSpread = true;
|
|
2139
|
-
}
|
|
2140
|
-
if (nestedProperty) {
|
|
2141
|
-
edgeData.nestedProperty = nestedProperty;
|
|
2142
|
-
}
|
|
2143
|
-
this._bufferEdge(edgeData);
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
/**
|
|
2150
|
-
* Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
2151
|
-
* Creates edges from source values to the object variable being mutated.
|
|
2152
|
-
*
|
|
2153
|
-
* REG-152: For 'this.prop = value' patterns inside classes, creates edges
|
|
2154
|
-
* to the CLASS node with mutationType: 'this_property'.
|
|
2155
|
-
*/
|
|
2156
|
-
private bufferObjectMutationEdges(
|
|
2157
|
-
objectMutations: ObjectMutationInfo[],
|
|
2158
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2159
|
-
parameters: ParameterInfo[],
|
|
2160
|
-
functions: FunctionInfo[],
|
|
2161
|
-
classDeclarations: ClassDeclarationInfo[]
|
|
2162
|
-
): void {
|
|
2163
|
-
for (const mutation of objectMutations) {
|
|
2164
|
-
const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
|
|
2165
|
-
|
|
2166
|
-
const scopePath = mutationScopePath ?? [];
|
|
2167
|
-
|
|
2168
|
-
// Find the target node (object variable, parameter, or class for 'this')
|
|
2169
|
-
let objectNodeId: string | null = null;
|
|
2170
|
-
let effectiveMutationType: 'property' | 'computed' | 'assign' | 'spread' | 'this_property' = mutationType;
|
|
2171
|
-
|
|
2172
|
-
if (objectName !== 'this') {
|
|
2173
|
-
// Regular object - find variable, parameter, or function using scope chain (REG-309)
|
|
2174
|
-
const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
|
|
2175
|
-
const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
|
|
2176
|
-
const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
|
|
2177
|
-
objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
|
|
2178
|
-
if (!objectNodeId) continue;
|
|
2179
|
-
} else {
|
|
2180
|
-
// REG-152: 'this' mutations - find the CLASS node
|
|
2181
|
-
if (!enclosingClassName) continue; // Skip if no class context (e.g., standalone function)
|
|
2182
|
-
|
|
2183
|
-
// Compare using basename since classes use scopeTracker.file (basename)
|
|
2184
|
-
// but mutations use module.file (full path)
|
|
2185
|
-
const fileBasename = basename(file);
|
|
2186
|
-
const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
|
|
2187
|
-
objectNodeId = classDecl?.id ?? null;
|
|
2188
|
-
|
|
2189
|
-
if (!objectNodeId) continue; // Skip if class not found
|
|
2190
|
-
|
|
2191
|
-
// Use special mutation type to distinguish from regular property mutations
|
|
2192
|
-
effectiveMutationType = 'this_property';
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// Create FLOWS_INTO edge for VARIABLE value type
|
|
2196
|
-
if (value.valueType === 'VARIABLE' && value.valueName) {
|
|
2197
|
-
// Find the source: can be variable, parameter, or function using scope chain (REG-309)
|
|
2198
|
-
const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
|
|
2199
|
-
const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
|
|
2200
|
-
const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
|
|
2201
|
-
const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
|
|
2202
|
-
|
|
2203
|
-
if (sourceNodeId && objectNodeId) {
|
|
2204
|
-
const edgeData: GraphEdge = {
|
|
2205
|
-
type: 'FLOWS_INTO',
|
|
2206
|
-
src: sourceNodeId,
|
|
2207
|
-
dst: objectNodeId,
|
|
2208
|
-
mutationType: effectiveMutationType,
|
|
2209
|
-
propertyName,
|
|
2210
|
-
computedPropertyVar // For enrichment phase resolution
|
|
2211
|
-
};
|
|
2212
|
-
if (value.argIndex !== undefined) {
|
|
2213
|
-
edgeData.argIndex = value.argIndex;
|
|
2214
|
-
}
|
|
2215
|
-
if (value.isSpread) {
|
|
2216
|
-
edgeData.isSpread = true;
|
|
2217
|
-
}
|
|
2218
|
-
this._bufferEdge(edgeData);
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
// For literals, object literals, etc. - we just track variable -> object flows for now
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
/**
|
|
2226
|
-
* Resolve variable by name using scope chain lookup (REG-309).
|
|
2227
|
-
* Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
|
|
2228
|
-
*
|
|
2229
|
-
* @param name - Variable name
|
|
2230
|
-
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
2231
|
-
* @param file - File path
|
|
2232
|
-
* @param variables - All variable declarations
|
|
2233
|
-
* @returns Variable declaration or null if not found
|
|
2234
|
-
*/
|
|
2235
|
-
private resolveVariableInScope(
|
|
2236
|
-
name: string,
|
|
2237
|
-
scopePath: string[],
|
|
2238
|
-
file: string,
|
|
2239
|
-
variables: VariableDeclarationInfo[]
|
|
2240
|
-
): VariableDeclarationInfo | null {
|
|
2241
|
-
// Try current scope, then parent, then grandparent, etc.
|
|
2242
|
-
for (let i = scopePath.length; i >= 0; i--) {
|
|
2243
|
-
const searchScopePath = scopePath.slice(0, i);
|
|
2244
|
-
|
|
2245
|
-
const matchingVar = variables.find(v => {
|
|
2246
|
-
if (v.name !== name || v.file !== file) return false;
|
|
2247
|
-
|
|
2248
|
-
// Variable ID IS the semantic ID (when scopeTracker was available during analysis)
|
|
2249
|
-
// Format: file->scope1->scope2->TYPE->name
|
|
2250
|
-
// Legacy format: VARIABLE#name#file#line:column:counter
|
|
2251
|
-
|
|
2252
|
-
// Try parsing as semantic ID
|
|
2253
|
-
const parsed = parseSemanticId(v.id);
|
|
2254
|
-
// REG-329: Check for both VARIABLE and CONSTANT (const declarations)
|
|
2255
|
-
if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
|
|
2256
|
-
// FIXED (REG-309): Handle module-level scope matching
|
|
2257
|
-
// Empty search scope [] should match semantic ID scope ['global']
|
|
2258
|
-
if (searchScopePath.length === 0) {
|
|
2259
|
-
return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
|
|
2260
|
-
}
|
|
2261
|
-
// Non-empty scope: exact match
|
|
2262
|
-
return this.scopePathsMatch(parsed.scopePath, searchScopePath);
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
// Legacy ID - assume module-level if no semantic ID
|
|
2266
|
-
return searchScopePath.length === 0;
|
|
2267
|
-
});
|
|
2268
|
-
|
|
2269
|
-
if (matchingVar) return matchingVar;
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
return null;
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
/**
|
|
2276
|
-
* Resolve parameter by name using scope chain lookup (REG-309).
|
|
2277
|
-
* Same semantics as resolveVariableInScope but for parameters.
|
|
2278
|
-
*
|
|
2279
|
-
* @param name - Parameter name
|
|
2280
|
-
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
2281
|
-
* @param file - File path
|
|
2282
|
-
* @param parameters - All parameter declarations
|
|
2283
|
-
* @returns Parameter declaration or null if not found
|
|
2284
|
-
*/
|
|
2285
|
-
private resolveParameterInScope(
|
|
2286
|
-
name: string,
|
|
2287
|
-
scopePath: string[],
|
|
2288
|
-
file: string,
|
|
2289
|
-
parameters: ParameterInfo[]
|
|
2290
|
-
): ParameterInfo | null {
|
|
2291
|
-
// Parameters have semanticId field populated (unlike variables which use id field)
|
|
2292
|
-
return parameters.find(p => {
|
|
2293
|
-
if (p.name !== name || p.file !== file) return false;
|
|
2294
|
-
|
|
2295
|
-
if (p.semanticId) {
|
|
2296
|
-
const parsed = parseSemanticId(p.semanticId);
|
|
2297
|
-
if (parsed && parsed.type === 'PARAMETER') {
|
|
2298
|
-
// Check if parameter's scope matches any scope in the chain
|
|
2299
|
-
for (let i = scopePath.length; i >= 0; i--) {
|
|
2300
|
-
const searchScopePath = scopePath.slice(0, i);
|
|
2301
|
-
|
|
2302
|
-
// FIXED (REG-309): Handle module-level scope matching for parameters
|
|
2303
|
-
if (searchScopePath.length === 0) {
|
|
2304
|
-
if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
|
|
2305
|
-
return true;
|
|
2306
|
-
}
|
|
2307
|
-
} else {
|
|
2308
|
-
if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
|
|
2309
|
-
return true;
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
return false;
|
|
2316
|
-
}) ?? null;
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
/**
|
|
2320
|
-
* Check if two scope paths match (REG-309).
|
|
2321
|
-
* Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
|
|
2322
|
-
*/
|
|
2323
|
-
private scopePathsMatch(a: string[], b: string[]): boolean {
|
|
2324
|
-
if (a.length !== b.length) return false;
|
|
2325
|
-
return a.every((item, idx) => item === b[idx]);
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
/**
|
|
2329
|
-
* Buffer FLOWS_INTO edges for variable reassignments.
|
|
2330
|
-
* Handles: x = y, x += y (when x is already declared, not initialization)
|
|
2331
|
-
*
|
|
2332
|
-
* Edge patterns:
|
|
2333
|
-
* - Simple assignment (=): source --FLOWS_INTO--> variable
|
|
2334
|
-
* - Compound operators (+=, -=, etc.):
|
|
2335
|
-
* - source --FLOWS_INTO--> variable (write new value)
|
|
2336
|
-
* - variable --READS_FROM--> variable (self-loop: reads current value before write)
|
|
2337
|
-
*
|
|
2338
|
-
* REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
|
|
2339
|
-
*
|
|
2340
|
-
* REG-290: Complete implementation with inline node creation (no continue statements).
|
|
2341
|
-
*/
|
|
2342
|
-
private bufferVariableReassignmentEdges(
|
|
2343
|
-
variableReassignments: VariableReassignmentInfo[],
|
|
2344
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2345
|
-
callSites: CallSiteInfo[],
|
|
2346
|
-
methodCalls: MethodCallInfo[],
|
|
2347
|
-
parameters: ParameterInfo[]
|
|
2348
|
-
): void {
|
|
2349
|
-
// Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
|
|
2350
|
-
// Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
|
|
2351
|
-
|
|
2352
|
-
for (const reassignment of variableReassignments) {
|
|
2353
|
-
const {
|
|
2354
|
-
variableName,
|
|
2355
|
-
mutationScopePath,
|
|
2356
|
-
valueType,
|
|
2357
|
-
valueName,
|
|
2358
|
-
valueId,
|
|
2359
|
-
callLine,
|
|
2360
|
-
callColumn,
|
|
2361
|
-
operator,
|
|
2362
|
-
literalValue,
|
|
2363
|
-
expressionType,
|
|
2364
|
-
expressionMetadata,
|
|
2365
|
-
file,
|
|
2366
|
-
line,
|
|
2367
|
-
column
|
|
2368
|
-
} = reassignment;
|
|
2369
|
-
|
|
2370
|
-
// Find target variable node using scope chain resolution (REG-309)
|
|
2371
|
-
const scopePath = mutationScopePath ?? [];
|
|
2372
|
-
const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
|
|
2373
|
-
const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
|
|
2374
|
-
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
2375
|
-
|
|
2376
|
-
if (!targetNodeId) {
|
|
2377
|
-
// Variable not found - could be external reference
|
|
2378
|
-
continue;
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
// Resolve source node based on value type
|
|
2382
|
-
let sourceNodeId: string | null = null;
|
|
2383
|
-
|
|
2384
|
-
// LITERAL: Create node inline (NO CONTINUE STATEMENT)
|
|
2385
|
-
if (valueType === 'LITERAL' && valueId) {
|
|
2386
|
-
// Create LITERAL node
|
|
2387
|
-
this._bufferNode({
|
|
2388
|
-
type: 'LITERAL',
|
|
2389
|
-
id: valueId,
|
|
2390
|
-
value: literalValue,
|
|
2391
|
-
file,
|
|
2392
|
-
line,
|
|
2393
|
-
column
|
|
2394
|
-
});
|
|
2395
|
-
sourceNodeId = valueId;
|
|
2396
|
-
}
|
|
2397
|
-
// VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
|
|
2398
|
-
else if (valueType === 'VARIABLE' && valueName) {
|
|
2399
|
-
const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
|
|
2400
|
-
const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
|
|
2401
|
-
sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
|
|
2402
|
-
}
|
|
2403
|
-
// CALL_SITE: Look up existing call node
|
|
2404
|
-
else if (valueType === 'CALL_SITE' && callLine && callColumn) {
|
|
2405
|
-
const callSite = callSites.find(cs =>
|
|
2406
|
-
cs.line === callLine && cs.column === callColumn && cs.file === file
|
|
2407
|
-
);
|
|
2408
|
-
sourceNodeId = callSite?.id ?? null;
|
|
2409
|
-
}
|
|
2410
|
-
// METHOD_CALL: Look up existing method call node
|
|
2411
|
-
else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
|
|
2412
|
-
const methodCall = methodCalls.find(mc =>
|
|
2413
|
-
mc.line === callLine && mc.column === callColumn && mc.file === file
|
|
2414
|
-
);
|
|
2415
|
-
sourceNodeId = methodCall?.id ?? null;
|
|
2416
|
-
}
|
|
2417
|
-
// EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
|
|
2418
|
-
else if (valueType === 'EXPRESSION' && valueId && expressionType) {
|
|
2419
|
-
// Create EXPRESSION node using NodeFactory
|
|
2420
|
-
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
2421
|
-
expressionType,
|
|
2422
|
-
file,
|
|
2423
|
-
line,
|
|
2424
|
-
column,
|
|
2425
|
-
{
|
|
2426
|
-
id: valueId, // ID from JSASTAnalyzer
|
|
2427
|
-
object: expressionMetadata?.object,
|
|
2428
|
-
property: expressionMetadata?.property,
|
|
2429
|
-
computed: expressionMetadata?.computed,
|
|
2430
|
-
computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
|
|
2431
|
-
operator: expressionMetadata?.operator
|
|
2432
|
-
}
|
|
2433
|
-
);
|
|
2434
|
-
|
|
2435
|
-
this._bufferNode(expressionNode);
|
|
2436
|
-
sourceNodeId = valueId;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
// Create edges if source found
|
|
2440
|
-
if (sourceNodeId && targetNodeId) {
|
|
2441
|
-
// For compound operators (operator !== '='), LHS reads its own current value
|
|
2442
|
-
// Create READS_FROM self-loop (Linus requirement)
|
|
2443
|
-
if (operator !== '=') {
|
|
2444
|
-
this._bufferEdge({
|
|
2445
|
-
type: 'READS_FROM',
|
|
2446
|
-
src: targetNodeId, // Variable reads from...
|
|
2447
|
-
dst: targetNodeId // ...itself (self-loop)
|
|
2448
|
-
});
|
|
2449
|
-
}
|
|
2450
|
-
|
|
2451
|
-
// RHS flows into LHS (write side)
|
|
2452
|
-
this._bufferEdge({
|
|
2453
|
-
type: 'FLOWS_INTO',
|
|
2454
|
-
src: sourceNodeId,
|
|
2455
|
-
dst: targetNodeId
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
/**
|
|
2462
|
-
* Buffer RETURNS edges connecting return expressions to their containing functions.
|
|
2463
|
-
*
|
|
2464
|
-
* Edge direction: returnExpression --RETURNS--> function
|
|
2465
|
-
*
|
|
2466
|
-
* This enables tracing data flow through function calls:
|
|
2467
|
-
* - Query: "What does formatDate return?"
|
|
2468
|
-
* - Answer: Follow RETURNS edges from function to see all possible return values
|
|
2469
|
-
*/
|
|
2470
|
-
private bufferReturnEdges(
|
|
2471
|
-
returnStatements: ReturnStatementInfo[],
|
|
2472
|
-
callSites: CallSiteInfo[],
|
|
2473
|
-
methodCalls: MethodCallInfo[],
|
|
2474
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2475
|
-
parameters: ParameterInfo[]
|
|
2476
|
-
): void {
|
|
2477
|
-
for (const ret of returnStatements) {
|
|
2478
|
-
const { parentFunctionId, returnValueType, file } = ret;
|
|
2479
|
-
|
|
2480
|
-
// Skip if no value returned (bare return;)
|
|
2481
|
-
if (returnValueType === 'NONE') {
|
|
2482
|
-
continue;
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
let sourceNodeId: string | null = null;
|
|
2486
|
-
|
|
2487
|
-
switch (returnValueType) {
|
|
2488
|
-
case 'LITERAL':
|
|
2489
|
-
// Direct reference to literal node
|
|
2490
|
-
sourceNodeId = ret.returnValueId ?? null;
|
|
2491
|
-
break;
|
|
2492
|
-
|
|
2493
|
-
case 'VARIABLE': {
|
|
2494
|
-
// Find variable declaration by name in same file
|
|
2495
|
-
const varName = ret.returnValueName;
|
|
2496
|
-
if (varName) {
|
|
2497
|
-
const sourceVar = variableDeclarations.find(v =>
|
|
2498
|
-
v.name === varName && v.file === file
|
|
2499
|
-
);
|
|
2500
|
-
if (sourceVar) {
|
|
2501
|
-
sourceNodeId = sourceVar.id;
|
|
2502
|
-
} else {
|
|
2503
|
-
// Check parameters
|
|
2504
|
-
const sourceParam = parameters.find(p =>
|
|
2505
|
-
p.name === varName && p.file === file
|
|
2506
|
-
);
|
|
2507
|
-
if (sourceParam) {
|
|
2508
|
-
sourceNodeId = sourceParam.id;
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
break;
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
case 'CALL_SITE': {
|
|
2516
|
-
// Find call site by coordinates
|
|
2517
|
-
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
2518
|
-
if (returnValueLine && returnValueColumn) {
|
|
2519
|
-
const callSite = callSites.find(cs =>
|
|
2520
|
-
cs.line === returnValueLine &&
|
|
2521
|
-
cs.column === returnValueColumn &&
|
|
2522
|
-
(returnValueCallName ? cs.name === returnValueCallName : true)
|
|
2523
|
-
);
|
|
2524
|
-
if (callSite) {
|
|
2525
|
-
sourceNodeId = callSite.id;
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
break;
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
|
-
case 'METHOD_CALL': {
|
|
2532
|
-
// Find method call by coordinates and method name
|
|
2533
|
-
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
2534
|
-
if (returnValueLine && returnValueColumn) {
|
|
2535
|
-
const methodCall = methodCalls.find(mc =>
|
|
2536
|
-
mc.line === returnValueLine &&
|
|
2537
|
-
mc.column === returnValueColumn &&
|
|
2538
|
-
mc.file === file &&
|
|
2539
|
-
(returnValueCallName ? mc.method === returnValueCallName : true)
|
|
2540
|
-
);
|
|
2541
|
-
if (methodCall) {
|
|
2542
|
-
sourceNodeId = methodCall.id;
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
break;
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
case 'EXPRESSION': {
|
|
2549
|
-
// REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
|
|
2550
|
-
const {
|
|
2551
|
-
expressionType,
|
|
2552
|
-
returnValueId,
|
|
2553
|
-
returnValueLine,
|
|
2554
|
-
returnValueColumn,
|
|
2555
|
-
operator,
|
|
2556
|
-
object,
|
|
2557
|
-
property,
|
|
2558
|
-
computed,
|
|
2559
|
-
objectSourceName,
|
|
2560
|
-
leftSourceName,
|
|
2561
|
-
rightSourceName,
|
|
2562
|
-
consequentSourceName,
|
|
2563
|
-
alternateSourceName,
|
|
2564
|
-
expressionSourceNames,
|
|
2565
|
-
unaryArgSourceName
|
|
2566
|
-
} = ret;
|
|
2567
|
-
|
|
2568
|
-
// Skip if no expression ID was generated
|
|
2569
|
-
if (!returnValueId) {
|
|
2570
|
-
break;
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
// Create EXPRESSION node using NodeFactory
|
|
2574
|
-
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
2575
|
-
expressionType || 'Unknown',
|
|
2576
|
-
file,
|
|
2577
|
-
returnValueLine || ret.line,
|
|
2578
|
-
returnValueColumn || ret.column,
|
|
2579
|
-
{
|
|
2580
|
-
id: returnValueId,
|
|
2581
|
-
object,
|
|
2582
|
-
property,
|
|
2583
|
-
computed,
|
|
2584
|
-
operator
|
|
2585
|
-
}
|
|
2586
|
-
);
|
|
2587
|
-
|
|
2588
|
-
this._bufferNode(expressionNode);
|
|
2589
|
-
sourceNodeId = returnValueId;
|
|
2590
|
-
|
|
2591
|
-
// Buffer DERIVES_FROM edges based on expression type
|
|
2592
|
-
// Helper function to find source variable or parameter
|
|
2593
|
-
const findSource = (name: string): string | null => {
|
|
2594
|
-
const variable = variableDeclarations.find(v =>
|
|
2595
|
-
v.name === name && v.file === file
|
|
2596
|
-
);
|
|
2597
|
-
if (variable) return variable.id;
|
|
2598
|
-
|
|
2599
|
-
const param = parameters.find(p =>
|
|
2600
|
-
p.name === name && p.file === file
|
|
2601
|
-
);
|
|
2602
|
-
if (param) return param.id;
|
|
2603
|
-
|
|
2604
|
-
return null;
|
|
2605
|
-
};
|
|
2606
|
-
|
|
2607
|
-
// MemberExpression: derives from the object
|
|
2608
|
-
if (expressionType === 'MemberExpression' && objectSourceName) {
|
|
2609
|
-
const sourceId = findSource(objectSourceName);
|
|
2610
|
-
if (sourceId) {
|
|
2611
|
-
this._bufferEdge({
|
|
2612
|
-
type: 'DERIVES_FROM',
|
|
2613
|
-
src: returnValueId,
|
|
2614
|
-
dst: sourceId
|
|
2615
|
-
});
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
// BinaryExpression / LogicalExpression: derives from left and right operands
|
|
2620
|
-
if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
2621
|
-
if (leftSourceName) {
|
|
2622
|
-
const sourceId = findSource(leftSourceName);
|
|
2623
|
-
if (sourceId) {
|
|
2624
|
-
this._bufferEdge({
|
|
2625
|
-
type: 'DERIVES_FROM',
|
|
2626
|
-
src: returnValueId,
|
|
2627
|
-
dst: sourceId
|
|
2628
|
-
});
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
if (rightSourceName) {
|
|
2632
|
-
const sourceId = findSource(rightSourceName);
|
|
2633
|
-
if (sourceId) {
|
|
2634
|
-
this._bufferEdge({
|
|
2635
|
-
type: 'DERIVES_FROM',
|
|
2636
|
-
src: returnValueId,
|
|
2637
|
-
dst: sourceId
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// ConditionalExpression: derives from consequent and alternate
|
|
2644
|
-
if (expressionType === 'ConditionalExpression') {
|
|
2645
|
-
if (consequentSourceName) {
|
|
2646
|
-
const sourceId = findSource(consequentSourceName);
|
|
2647
|
-
if (sourceId) {
|
|
2648
|
-
this._bufferEdge({
|
|
2649
|
-
type: 'DERIVES_FROM',
|
|
2650
|
-
src: returnValueId,
|
|
2651
|
-
dst: sourceId
|
|
2652
|
-
});
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
if (alternateSourceName) {
|
|
2656
|
-
const sourceId = findSource(alternateSourceName);
|
|
2657
|
-
if (sourceId) {
|
|
2658
|
-
this._bufferEdge({
|
|
2659
|
-
type: 'DERIVES_FROM',
|
|
2660
|
-
src: returnValueId,
|
|
2661
|
-
dst: sourceId
|
|
2662
|
-
});
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
// UnaryExpression: derives from the argument
|
|
2668
|
-
if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
|
|
2669
|
-
const sourceId = findSource(unaryArgSourceName);
|
|
2670
|
-
if (sourceId) {
|
|
2671
|
-
this._bufferEdge({
|
|
2672
|
-
type: 'DERIVES_FROM',
|
|
2673
|
-
src: returnValueId,
|
|
2674
|
-
dst: sourceId
|
|
2675
|
-
});
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
// TemplateLiteral: derives from all embedded expressions
|
|
2680
|
-
if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
|
|
2681
|
-
for (const sourceName of expressionSourceNames) {
|
|
2682
|
-
const sourceId = findSource(sourceName);
|
|
2683
|
-
if (sourceId) {
|
|
2684
|
-
this._bufferEdge({
|
|
2685
|
-
type: 'DERIVES_FROM',
|
|
2686
|
-
src: returnValueId,
|
|
2687
|
-
dst: sourceId
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
break;
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
// Create RETURNS edge if we found a source node
|
|
2698
|
-
if (sourceNodeId && parentFunctionId) {
|
|
2699
|
-
this._bufferEdge({
|
|
2700
|
-
type: 'RETURNS',
|
|
2701
|
-
src: sourceNodeId,
|
|
2702
|
-
dst: parentFunctionId
|
|
2703
|
-
});
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
|
-
/**
|
|
2709
|
-
* Buffer YIELDS and DELEGATES_TO edges connecting yield expressions to their generator functions.
|
|
2710
|
-
*
|
|
2711
|
-
* Edge direction:
|
|
2712
|
-
* - For yield: yieldedExpression --YIELDS--> generatorFunction
|
|
2713
|
-
* - For yield*: delegatedCall --DELEGATES_TO--> generatorFunction
|
|
2714
|
-
*
|
|
2715
|
-
* This enables tracing data flow through generator functions:
|
|
2716
|
-
* - Query: "What does this generator yield?"
|
|
2717
|
-
* - Answer: Follow YIELDS edges from function to see all possible yielded values
|
|
2718
|
-
* - Query: "What generators does this delegate to?"
|
|
2719
|
-
* - Answer: Follow DELEGATES_TO edges from function
|
|
2720
|
-
*
|
|
2721
|
-
* REG-270: Generator yield tracking
|
|
2722
|
-
*/
|
|
2723
|
-
private bufferYieldEdges(
|
|
2724
|
-
yieldExpressions: YieldExpressionInfo[],
|
|
2725
|
-
callSites: CallSiteInfo[],
|
|
2726
|
-
methodCalls: MethodCallInfo[],
|
|
2727
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2728
|
-
parameters: ParameterInfo[]
|
|
2729
|
-
): void {
|
|
2730
|
-
for (const yld of yieldExpressions) {
|
|
2731
|
-
const { parentFunctionId, yieldValueType, file, isDelegate } = yld;
|
|
2732
|
-
|
|
2733
|
-
// Skip if no value yielded (bare yield;)
|
|
2734
|
-
if (yieldValueType === 'NONE') {
|
|
2735
|
-
continue;
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
|
-
let sourceNodeId: string | null = null;
|
|
2739
|
-
|
|
2740
|
-
switch (yieldValueType) {
|
|
2741
|
-
case 'LITERAL':
|
|
2742
|
-
// Direct reference to literal node
|
|
2743
|
-
sourceNodeId = yld.yieldValueId ?? null;
|
|
2744
|
-
break;
|
|
2745
|
-
|
|
2746
|
-
case 'VARIABLE': {
|
|
2747
|
-
// Find variable declaration by name in same file
|
|
2748
|
-
const varName = yld.yieldValueName;
|
|
2749
|
-
if (varName) {
|
|
2750
|
-
const sourceVar = variableDeclarations.find(v =>
|
|
2751
|
-
v.name === varName && v.file === file
|
|
2752
|
-
);
|
|
2753
|
-
if (sourceVar) {
|
|
2754
|
-
sourceNodeId = sourceVar.id;
|
|
2755
|
-
} else {
|
|
2756
|
-
// Check parameters
|
|
2757
|
-
const sourceParam = parameters.find(p =>
|
|
2758
|
-
p.name === varName && p.file === file
|
|
2759
|
-
);
|
|
2760
|
-
if (sourceParam) {
|
|
2761
|
-
sourceNodeId = sourceParam.id;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
break;
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
case 'CALL_SITE': {
|
|
2769
|
-
// Find call site by coordinates
|
|
2770
|
-
const { yieldValueLine, yieldValueColumn, yieldValueCallName } = yld;
|
|
2771
|
-
if (yieldValueLine && yieldValueColumn) {
|
|
2772
|
-
const callSite = callSites.find(cs =>
|
|
2773
|
-
cs.line === yieldValueLine &&
|
|
2774
|
-
cs.column === yieldValueColumn &&
|
|
2775
|
-
(yieldValueCallName ? cs.name === yieldValueCallName : true)
|
|
2776
|
-
);
|
|
2777
|
-
if (callSite) {
|
|
2778
|
-
sourceNodeId = callSite.id;
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
break;
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
|
-
case 'METHOD_CALL': {
|
|
2785
|
-
// Find method call by coordinates and method name
|
|
2786
|
-
const { yieldValueLine, yieldValueColumn, yieldValueCallName } = yld;
|
|
2787
|
-
if (yieldValueLine && yieldValueColumn) {
|
|
2788
|
-
const methodCall = methodCalls.find(mc =>
|
|
2789
|
-
mc.line === yieldValueLine &&
|
|
2790
|
-
mc.column === yieldValueColumn &&
|
|
2791
|
-
mc.file === file &&
|
|
2792
|
-
(yieldValueCallName ? mc.method === yieldValueCallName : true)
|
|
2793
|
-
);
|
|
2794
|
-
if (methodCall) {
|
|
2795
|
-
sourceNodeId = methodCall.id;
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
break;
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
case 'EXPRESSION': {
|
|
2802
|
-
// Create EXPRESSION node and DERIVES_FROM edges for yield expressions
|
|
2803
|
-
const {
|
|
2804
|
-
expressionType,
|
|
2805
|
-
yieldValueId,
|
|
2806
|
-
yieldValueLine,
|
|
2807
|
-
yieldValueColumn,
|
|
2808
|
-
operator,
|
|
2809
|
-
object,
|
|
2810
|
-
property,
|
|
2811
|
-
computed,
|
|
2812
|
-
objectSourceName,
|
|
2813
|
-
leftSourceName,
|
|
2814
|
-
rightSourceName,
|
|
2815
|
-
consequentSourceName,
|
|
2816
|
-
alternateSourceName,
|
|
2817
|
-
expressionSourceNames,
|
|
2818
|
-
unaryArgSourceName
|
|
2819
|
-
} = yld;
|
|
2820
|
-
|
|
2821
|
-
// Skip if no expression ID was generated
|
|
2822
|
-
if (!yieldValueId) {
|
|
2823
|
-
break;
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
|
-
// Create EXPRESSION node using NodeFactory
|
|
2827
|
-
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
2828
|
-
expressionType || 'Unknown',
|
|
2829
|
-
file,
|
|
2830
|
-
yieldValueLine || yld.line,
|
|
2831
|
-
yieldValueColumn || yld.column,
|
|
2832
|
-
{
|
|
2833
|
-
id: yieldValueId,
|
|
2834
|
-
object,
|
|
2835
|
-
property,
|
|
2836
|
-
computed,
|
|
2837
|
-
operator
|
|
2838
|
-
}
|
|
2839
|
-
);
|
|
2840
|
-
|
|
2841
|
-
this._bufferNode(expressionNode);
|
|
2842
|
-
sourceNodeId = yieldValueId;
|
|
2843
|
-
|
|
2844
|
-
// Buffer DERIVES_FROM edges based on expression type
|
|
2845
|
-
// Helper function to find source variable or parameter
|
|
2846
|
-
const findSource = (name: string): string | null => {
|
|
2847
|
-
const variable = variableDeclarations.find(v =>
|
|
2848
|
-
v.name === name && v.file === file
|
|
2849
|
-
);
|
|
2850
|
-
if (variable) return variable.id;
|
|
2851
|
-
|
|
2852
|
-
const param = parameters.find(p =>
|
|
2853
|
-
p.name === name && p.file === file
|
|
2854
|
-
);
|
|
2855
|
-
if (param) return param.id;
|
|
2856
|
-
|
|
2857
|
-
return null;
|
|
2858
|
-
};
|
|
2859
|
-
|
|
2860
|
-
// MemberExpression: derives from the object
|
|
2861
|
-
if (expressionType === 'MemberExpression' && objectSourceName) {
|
|
2862
|
-
const srcId = findSource(objectSourceName);
|
|
2863
|
-
if (srcId) {
|
|
2864
|
-
this._bufferEdge({
|
|
2865
|
-
type: 'DERIVES_FROM',
|
|
2866
|
-
src: yieldValueId,
|
|
2867
|
-
dst: srcId
|
|
2868
|
-
});
|
|
2869
|
-
}
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
|
-
// BinaryExpression / LogicalExpression: derives from left and right operands
|
|
2873
|
-
if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
2874
|
-
if (leftSourceName) {
|
|
2875
|
-
const srcId = findSource(leftSourceName);
|
|
2876
|
-
if (srcId) {
|
|
2877
|
-
this._bufferEdge({
|
|
2878
|
-
type: 'DERIVES_FROM',
|
|
2879
|
-
src: yieldValueId,
|
|
2880
|
-
dst: srcId
|
|
2881
|
-
});
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
if (rightSourceName) {
|
|
2885
|
-
const srcId = findSource(rightSourceName);
|
|
2886
|
-
if (srcId) {
|
|
2887
|
-
this._bufferEdge({
|
|
2888
|
-
type: 'DERIVES_FROM',
|
|
2889
|
-
src: yieldValueId,
|
|
2890
|
-
dst: srcId
|
|
2891
|
-
});
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// ConditionalExpression: derives from consequent and alternate
|
|
2897
|
-
if (expressionType === 'ConditionalExpression') {
|
|
2898
|
-
if (consequentSourceName) {
|
|
2899
|
-
const srcId = findSource(consequentSourceName);
|
|
2900
|
-
if (srcId) {
|
|
2901
|
-
this._bufferEdge({
|
|
2902
|
-
type: 'DERIVES_FROM',
|
|
2903
|
-
src: yieldValueId,
|
|
2904
|
-
dst: srcId
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
if (alternateSourceName) {
|
|
2909
|
-
const srcId = findSource(alternateSourceName);
|
|
2910
|
-
if (srcId) {
|
|
2911
|
-
this._bufferEdge({
|
|
2912
|
-
type: 'DERIVES_FROM',
|
|
2913
|
-
src: yieldValueId,
|
|
2914
|
-
dst: srcId
|
|
2915
|
-
});
|
|
2916
|
-
}
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
|
|
2920
|
-
// UnaryExpression: derives from the argument
|
|
2921
|
-
if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
|
|
2922
|
-
const srcId = findSource(unaryArgSourceName);
|
|
2923
|
-
if (srcId) {
|
|
2924
|
-
this._bufferEdge({
|
|
2925
|
-
type: 'DERIVES_FROM',
|
|
2926
|
-
src: yieldValueId,
|
|
2927
|
-
dst: srcId
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
// TemplateLiteral: derives from all embedded expressions
|
|
2933
|
-
if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
|
|
2934
|
-
for (const sourceName of expressionSourceNames) {
|
|
2935
|
-
const srcId = findSource(sourceName);
|
|
2936
|
-
if (srcId) {
|
|
2937
|
-
this._bufferEdge({
|
|
2938
|
-
type: 'DERIVES_FROM',
|
|
2939
|
-
src: yieldValueId,
|
|
2940
|
-
dst: srcId
|
|
2941
|
-
});
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
|
-
break;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
// Create YIELDS or DELEGATES_TO edge if we found a source node
|
|
2951
|
-
if (sourceNodeId && parentFunctionId) {
|
|
2952
|
-
const edgeType = isDelegate ? 'DELEGATES_TO' : 'YIELDS';
|
|
2953
|
-
this._bufferEdge({
|
|
2954
|
-
type: edgeType,
|
|
2955
|
-
src: sourceNodeId,
|
|
2956
|
-
dst: parentFunctionId
|
|
2957
|
-
});
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
|
-
/**
|
|
2963
|
-
* Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
|
|
2964
|
-
*
|
|
2965
|
-
* Handles two target types:
|
|
2966
|
-
* - IDENTIFIER: Simple variable (i++, --count)
|
|
2967
|
-
* - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
|
|
2968
|
-
*
|
|
2969
|
-
* Creates:
|
|
2970
|
-
* - UPDATE_EXPRESSION node with operator and target metadata
|
|
2971
|
-
* - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
|
|
2972
|
-
* - READS_FROM self-loop: target -> target (reads current value before update)
|
|
2973
|
-
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
2974
|
-
*
|
|
2975
|
-
* REG-288: Initial implementation for IDENTIFIER targets
|
|
2976
|
-
* REG-312: Extended for MEMBER_EXPRESSION targets
|
|
2977
|
-
*/
|
|
2978
|
-
private bufferUpdateExpressionEdges(
|
|
2979
|
-
updateExpressions: UpdateExpressionInfo[],
|
|
2980
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
2981
|
-
parameters: ParameterInfo[],
|
|
2982
|
-
classDeclarations: ClassDeclarationInfo[]
|
|
2983
|
-
): void {
|
|
2984
|
-
// Build lookup caches: O(n) instead of O(n*m)
|
|
2985
|
-
const varLookup = new Map<string, VariableDeclarationInfo>();
|
|
2986
|
-
for (const v of variableDeclarations) {
|
|
2987
|
-
varLookup.set(`${v.file}:${v.name}`, v);
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
const paramLookup = new Map<string, ParameterInfo>();
|
|
2991
|
-
for (const p of parameters) {
|
|
2992
|
-
paramLookup.set(`${p.file}:${p.name}`, p);
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
for (const update of updateExpressions) {
|
|
2996
|
-
if (update.targetType === 'IDENTIFIER') {
|
|
2997
|
-
// REG-288: Simple identifier (i++, --count)
|
|
2998
|
-
this.bufferIdentifierUpdate(update, varLookup, paramLookup);
|
|
2999
|
-
} else if (update.targetType === 'MEMBER_EXPRESSION') {
|
|
3000
|
-
// REG-312: Member expression (obj.prop++, arr[i]++)
|
|
3001
|
-
this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
/**
|
|
3007
|
-
* Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
|
|
3008
|
-
* REG-288: Original implementation extracted for clarity
|
|
3009
|
-
*/
|
|
3010
|
-
private bufferIdentifierUpdate(
|
|
3011
|
-
update: UpdateExpressionInfo,
|
|
3012
|
-
varLookup: Map<string, VariableDeclarationInfo>,
|
|
3013
|
-
paramLookup: Map<string, ParameterInfo>
|
|
3014
|
-
): void {
|
|
3015
|
-
const {
|
|
3016
|
-
variableName,
|
|
3017
|
-
operator,
|
|
3018
|
-
prefix,
|
|
3019
|
-
file,
|
|
3020
|
-
line,
|
|
3021
|
-
column,
|
|
3022
|
-
parentScopeId
|
|
3023
|
-
} = update;
|
|
3024
|
-
|
|
3025
|
-
if (!variableName) return;
|
|
3026
|
-
|
|
3027
|
-
// Find target variable node
|
|
3028
|
-
const targetVar = varLookup.get(`${file}:${variableName}`);
|
|
3029
|
-
const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
|
|
3030
|
-
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
3031
|
-
|
|
3032
|
-
if (!targetNodeId) {
|
|
3033
|
-
// Variable not found - could be module-level or external reference
|
|
3034
|
-
return;
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
// Create UPDATE_EXPRESSION node
|
|
3038
|
-
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
3039
|
-
|
|
3040
|
-
this._bufferNode({
|
|
3041
|
-
type: 'UPDATE_EXPRESSION',
|
|
3042
|
-
id: updateId,
|
|
3043
|
-
name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
|
|
3044
|
-
targetType: 'IDENTIFIER',
|
|
3045
|
-
operator,
|
|
3046
|
-
prefix,
|
|
3047
|
-
variableName,
|
|
3048
|
-
file,
|
|
3049
|
-
line,
|
|
3050
|
-
column
|
|
3051
|
-
} as GraphNode);
|
|
3052
|
-
|
|
3053
|
-
// Create READS_FROM self-loop
|
|
3054
|
-
this._bufferEdge({
|
|
3055
|
-
type: 'READS_FROM',
|
|
3056
|
-
src: targetNodeId,
|
|
3057
|
-
dst: targetNodeId
|
|
3058
|
-
});
|
|
3059
|
-
|
|
3060
|
-
// Create MODIFIES edge
|
|
3061
|
-
this._bufferEdge({
|
|
3062
|
-
type: 'MODIFIES',
|
|
3063
|
-
src: updateId,
|
|
3064
|
-
dst: targetNodeId
|
|
3065
|
-
});
|
|
3066
|
-
|
|
3067
|
-
// Create CONTAINS edge
|
|
3068
|
-
if (parentScopeId) {
|
|
3069
|
-
this._bufferEdge({
|
|
3070
|
-
type: 'CONTAINS',
|
|
3071
|
-
src: parentScopeId,
|
|
3072
|
-
dst: updateId
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3077
|
-
/**
|
|
3078
|
-
* Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
|
|
3079
|
-
* REG-312: New implementation for member expression targets
|
|
3080
|
-
*
|
|
3081
|
-
* Creates:
|
|
3082
|
-
* - UPDATE_EXPRESSION node with member expression metadata
|
|
3083
|
-
* - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
|
|
3084
|
-
* - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
|
|
3085
|
-
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
3086
|
-
*/
|
|
3087
|
-
private bufferMemberExpressionUpdate(
|
|
3088
|
-
update: UpdateExpressionInfo,
|
|
3089
|
-
varLookup: Map<string, VariableDeclarationInfo>,
|
|
3090
|
-
paramLookup: Map<string, ParameterInfo>,
|
|
3091
|
-
classDeclarations: ClassDeclarationInfo[]
|
|
3092
|
-
): void {
|
|
3093
|
-
const {
|
|
3094
|
-
objectName,
|
|
3095
|
-
propertyName,
|
|
3096
|
-
mutationType,
|
|
3097
|
-
computedPropertyVar,
|
|
3098
|
-
enclosingClassName,
|
|
3099
|
-
operator,
|
|
3100
|
-
prefix,
|
|
3101
|
-
file,
|
|
3102
|
-
line,
|
|
3103
|
-
column,
|
|
3104
|
-
parentScopeId
|
|
3105
|
-
} = update;
|
|
3106
|
-
|
|
3107
|
-
if (!objectName || !propertyName) return;
|
|
3108
|
-
|
|
3109
|
-
// Find target object node
|
|
3110
|
-
let objectNodeId: string | null = null;
|
|
3111
|
-
|
|
3112
|
-
if (objectName !== 'this') {
|
|
3113
|
-
// Regular object: obj.prop++, arr[i]++
|
|
3114
|
-
const targetVar = varLookup.get(`${file}:${objectName}`);
|
|
3115
|
-
const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
|
|
3116
|
-
objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
|
|
3117
|
-
} else {
|
|
3118
|
-
// this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
|
|
3119
|
-
if (!enclosingClassName) return;
|
|
3120
|
-
|
|
3121
|
-
const fileBasename = basename(file);
|
|
3122
|
-
const classDecl = classDeclarations.find(c =>
|
|
3123
|
-
c.name === enclosingClassName && c.file === fileBasename
|
|
3124
|
-
);
|
|
3125
|
-
objectNodeId = classDecl?.id ?? null;
|
|
3126
|
-
}
|
|
3127
|
-
|
|
3128
|
-
if (!objectNodeId) {
|
|
3129
|
-
// Object not found - external reference or scope issue
|
|
3130
|
-
return;
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
// Create UPDATE_EXPRESSION node
|
|
3134
|
-
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
3135
|
-
|
|
3136
|
-
// Display name: "obj.prop++" or "this.count++" or "arr[i]++"
|
|
3137
|
-
const displayName = (() => {
|
|
3138
|
-
const opStr = prefix ? operator : '';
|
|
3139
|
-
const postOpStr = prefix ? '' : operator;
|
|
3140
|
-
|
|
3141
|
-
if (objectName === 'this') {
|
|
3142
|
-
return `${opStr}this.${propertyName}${postOpStr}`;
|
|
3143
|
-
}
|
|
3144
|
-
if (mutationType === 'computed') {
|
|
3145
|
-
const computedPart = computedPropertyVar || '?';
|
|
3146
|
-
return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
|
|
3147
|
-
}
|
|
3148
|
-
return `${opStr}${objectName}.${propertyName}${postOpStr}`;
|
|
3149
|
-
})();
|
|
3150
|
-
|
|
3151
|
-
this._bufferNode({
|
|
3152
|
-
type: 'UPDATE_EXPRESSION',
|
|
3153
|
-
id: updateId,
|
|
3154
|
-
name: displayName,
|
|
3155
|
-
targetType: 'MEMBER_EXPRESSION',
|
|
3156
|
-
operator,
|
|
3157
|
-
prefix,
|
|
3158
|
-
objectName,
|
|
3159
|
-
propertyName,
|
|
3160
|
-
mutationType,
|
|
3161
|
-
computedPropertyVar,
|
|
3162
|
-
enclosingClassName,
|
|
3163
|
-
file,
|
|
3164
|
-
line,
|
|
3165
|
-
column
|
|
3166
|
-
} as GraphNode);
|
|
3167
|
-
|
|
3168
|
-
// Create READS_FROM self-loop (object reads from itself)
|
|
3169
|
-
this._bufferEdge({
|
|
3170
|
-
type: 'READS_FROM',
|
|
3171
|
-
src: objectNodeId,
|
|
3172
|
-
dst: objectNodeId
|
|
3173
|
-
});
|
|
3174
|
-
|
|
3175
|
-
// Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
|
|
3176
|
-
this._bufferEdge({
|
|
3177
|
-
type: 'MODIFIES',
|
|
3178
|
-
src: updateId,
|
|
3179
|
-
dst: objectNodeId
|
|
3180
|
-
});
|
|
3181
|
-
|
|
3182
|
-
// Create CONTAINS edge
|
|
3183
|
-
if (parentScopeId) {
|
|
3184
|
-
this._bufferEdge({
|
|
3185
|
-
type: 'CONTAINS',
|
|
3186
|
-
src: parentScopeId,
|
|
3187
|
-
dst: updateId
|
|
3188
|
-
});
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
/**
|
|
3193
|
-
* Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
|
|
3194
|
-
*
|
|
3195
|
-
* Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
|
|
3196
|
-
* This enables traceValues to follow Promise data flow:
|
|
3197
|
-
*
|
|
3198
|
-
* Example:
|
|
3199
|
-
* ```
|
|
3200
|
-
* const result = new Promise((resolve) => {
|
|
3201
|
-
* resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
|
|
3202
|
-
* });
|
|
3203
|
-
* ```
|
|
3204
|
-
*
|
|
3205
|
-
* The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
|
|
3206
|
-
* data flows FROM resolve(value) TO the Promise result.
|
|
3207
|
-
*/
|
|
3208
|
-
private bufferPromiseResolutionEdges(promiseResolutions: PromiseResolutionInfo[]): void {
|
|
3209
|
-
for (const resolution of promiseResolutions) {
|
|
3210
|
-
this._bufferEdge({
|
|
3211
|
-
type: 'RESOLVES_TO',
|
|
3212
|
-
src: resolution.callId,
|
|
3213
|
-
dst: resolution.constructorCallId,
|
|
3214
|
-
metadata: {
|
|
3215
|
-
isReject: resolution.isReject
|
|
3216
|
-
}
|
|
3217
|
-
});
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
|
|
3221
|
-
/**
|
|
3222
|
-
* Buffer OBJECT_LITERAL nodes to the graph.
|
|
3223
|
-
* These are object literals passed as function arguments or nested in other literals.
|
|
3224
|
-
*/
|
|
3225
|
-
private bufferObjectLiteralNodes(objectLiterals: ObjectLiteralInfo[]): void {
|
|
3226
|
-
for (const obj of objectLiterals) {
|
|
3227
|
-
this._bufferNode({
|
|
3228
|
-
id: obj.id,
|
|
3229
|
-
type: obj.type,
|
|
3230
|
-
name: '<object>',
|
|
3231
|
-
file: obj.file,
|
|
3232
|
-
line: obj.line,
|
|
3233
|
-
column: obj.column,
|
|
3234
|
-
parentCallId: obj.parentCallId,
|
|
3235
|
-
argIndex: obj.argIndex
|
|
3236
|
-
} as GraphNode);
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
/**
|
|
3241
|
-
* Buffer ARRAY_LITERAL nodes to the graph.
|
|
3242
|
-
* These are array literals passed as function arguments or nested in other literals.
|
|
3243
|
-
*/
|
|
3244
|
-
private bufferArrayLiteralNodes(arrayLiterals: ArrayLiteralInfo[]): void {
|
|
3245
|
-
for (const arr of arrayLiterals) {
|
|
3246
|
-
this._bufferNode({
|
|
3247
|
-
id: arr.id,
|
|
3248
|
-
type: arr.type,
|
|
3249
|
-
name: '<array>',
|
|
3250
|
-
file: arr.file,
|
|
3251
|
-
line: arr.line,
|
|
3252
|
-
column: arr.column,
|
|
3253
|
-
parentCallId: arr.parentCallId,
|
|
3254
|
-
argIndex: arr.argIndex
|
|
3255
|
-
} as GraphNode);
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3258
|
-
|
|
3259
|
-
/**
|
|
3260
|
-
* Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
|
|
3261
|
-
* Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
|
|
3262
|
-
*
|
|
3263
|
-
* REG-329: Adds scope-aware variable resolution for VARIABLE property values.
|
|
3264
|
-
* Uses the same resolveVariableInScope infrastructure as mutation handlers.
|
|
3265
|
-
*/
|
|
3266
|
-
private bufferObjectPropertyEdges(
|
|
3267
|
-
objectProperties: ObjectPropertyInfo[],
|
|
3268
|
-
variableDeclarations: VariableDeclarationInfo[],
|
|
3269
|
-
parameters: ParameterInfo[]
|
|
3270
|
-
): void {
|
|
3271
|
-
for (const prop of objectProperties) {
|
|
3272
|
-
// REG-329: Handle VARIABLE value types with scope resolution
|
|
3273
|
-
if (prop.valueType === 'VARIABLE' && prop.valueName) {
|
|
3274
|
-
const scopePath = prop.valueScopePath ?? [];
|
|
3275
|
-
const file = prop.file;
|
|
3276
|
-
|
|
3277
|
-
// Resolve variable using scope chain
|
|
3278
|
-
const resolvedVar = this.resolveVariableInScope(
|
|
3279
|
-
prop.valueName, scopePath, file, variableDeclarations
|
|
3280
|
-
);
|
|
3281
|
-
const resolvedParam = !resolvedVar
|
|
3282
|
-
? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
|
|
3283
|
-
: null;
|
|
3284
|
-
|
|
3285
|
-
const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
|
|
3286
|
-
|
|
3287
|
-
if (resolvedNodeId) {
|
|
3288
|
-
this._bufferEdge({
|
|
3289
|
-
type: 'HAS_PROPERTY',
|
|
3290
|
-
src: prop.objectId,
|
|
3291
|
-
dst: resolvedNodeId,
|
|
3292
|
-
propertyName: prop.propertyName
|
|
3293
|
-
});
|
|
3294
|
-
}
|
|
3295
|
-
continue;
|
|
3296
|
-
}
|
|
3297
|
-
|
|
3298
|
-
// Existing logic for non-VARIABLE types
|
|
3299
|
-
if (prop.valueNodeId) {
|
|
3300
|
-
this._bufferEdge({
|
|
3301
|
-
type: 'HAS_PROPERTY',
|
|
3302
|
-
src: prop.objectId,
|
|
3303
|
-
dst: prop.valueNodeId,
|
|
3304
|
-
propertyName: prop.propertyName
|
|
3305
|
-
});
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
}
|
|
3309
|
-
|
|
3310
|
-
/**
|
|
3311
|
-
* Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
|
|
3312
|
-
*/
|
|
3313
|
-
private async createClassAssignmentEdges(variableAssignments: VariableAssignmentInfo[], graph: GraphBackend): Promise<number> {
|
|
3314
|
-
let edgesCreated = 0;
|
|
520
|
+
private async createClassAssignmentEdges(variableAssignments: VariableAssignmentInfo[], graph: GraphBackend): Promise<number> {
|
|
521
|
+
let edgesCreated = 0;
|
|
3315
522
|
|
|
3316
523
|
for (const assignment of variableAssignments) {
|
|
3317
524
|
const { variableId, sourceType, className } = assignment;
|
|
@@ -3341,117 +548,4 @@ export class GraphBuilder {
|
|
|
3341
548
|
|
|
3342
549
|
return edgesCreated;
|
|
3343
550
|
}
|
|
3344
|
-
|
|
3345
|
-
/**
|
|
3346
|
-
* Buffer REJECTS edges for async error tracking (REG-311).
|
|
3347
|
-
*
|
|
3348
|
-
* Creates edges from FUNCTION nodes to error CLASS nodes they can reject.
|
|
3349
|
-
* This enables tracking which async functions can throw which error types:
|
|
3350
|
-
*
|
|
3351
|
-
* - Promise.reject(new Error()) -> FUNCTION --REJECTS--> CLASS[Error]
|
|
3352
|
-
* - reject(new ValidationError()) in executor -> FUNCTION --REJECTS--> CLASS[ValidationError]
|
|
3353
|
-
* - throw new AuthError() in async function -> FUNCTION --REJECTS--> CLASS[AuthError]
|
|
3354
|
-
*
|
|
3355
|
-
* Also stores rejectionPatterns in function metadata for downstream enrichers.
|
|
3356
|
-
*
|
|
3357
|
-
* @param functions - All function infos from analysis
|
|
3358
|
-
* @param rejectionPatterns - Collected rejection patterns from analysis
|
|
3359
|
-
*/
|
|
3360
|
-
private bufferRejectionEdges(functions: FunctionInfo[], rejectionPatterns: RejectionPatternInfo[]): void {
|
|
3361
|
-
// Group rejection patterns by functionId for efficient lookup
|
|
3362
|
-
const patternsByFunction = new Map<string, RejectionPatternInfo[]>();
|
|
3363
|
-
for (const pattern of rejectionPatterns) {
|
|
3364
|
-
const existing = patternsByFunction.get(pattern.functionId);
|
|
3365
|
-
if (existing) {
|
|
3366
|
-
existing.push(pattern);
|
|
3367
|
-
} else {
|
|
3368
|
-
patternsByFunction.set(pattern.functionId, [pattern]);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
|
|
3372
|
-
// Process each function that has rejection patterns
|
|
3373
|
-
for (const [functionId, patterns] of patternsByFunction) {
|
|
3374
|
-
// Collect unique error class names from this function's rejection patterns
|
|
3375
|
-
const errorClassNames = new Set<string>();
|
|
3376
|
-
for (const pattern of patterns) {
|
|
3377
|
-
if (pattern.errorClassName) {
|
|
3378
|
-
errorClassNames.add(pattern.errorClassName);
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
|
|
3382
|
-
// Create REJECTS edges to error class nodes
|
|
3383
|
-
// Note: These edges target computed CLASS IDs - they will be dangling
|
|
3384
|
-
// if the class isn't declared, but that's expected behavior for
|
|
3385
|
-
// built-in classes like Error, TypeError, etc.
|
|
3386
|
-
for (const errorClassName of errorClassNames) {
|
|
3387
|
-
// Find the function's file to compute the class ID
|
|
3388
|
-
const func = functions.find(f => f.id === functionId);
|
|
3389
|
-
const file = func?.file ?? '';
|
|
3390
|
-
|
|
3391
|
-
// Compute potential class ID at global scope
|
|
3392
|
-
// For built-in errors, this will be a dangling reference (expected)
|
|
3393
|
-
const globalContext = { file, scopePath: [] as string[] };
|
|
3394
|
-
const classId = computeSemanticId('CLASS', errorClassName, globalContext);
|
|
3395
|
-
|
|
3396
|
-
this._bufferEdge({
|
|
3397
|
-
type: 'REJECTS',
|
|
3398
|
-
src: functionId,
|
|
3399
|
-
dst: classId,
|
|
3400
|
-
metadata: {
|
|
3401
|
-
errorClassName
|
|
3402
|
-
}
|
|
3403
|
-
});
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
// Store rejection patterns in function metadata for downstream enrichers
|
|
3407
|
-
// Find and update the function node in the buffer
|
|
3408
|
-
for (const node of this._nodeBuffer) {
|
|
3409
|
-
if (node.id === functionId) {
|
|
3410
|
-
// Store in metadata field for proper persistence and test compatibility
|
|
3411
|
-
if (!node.metadata) {
|
|
3412
|
-
node.metadata = {};
|
|
3413
|
-
}
|
|
3414
|
-
(node.metadata as Record<string, unknown>).rejectionPatterns = patterns.map(p => ({
|
|
3415
|
-
rejectionType: p.rejectionType,
|
|
3416
|
-
errorClassName: p.errorClassName,
|
|
3417
|
-
line: p.line,
|
|
3418
|
-
column: p.column,
|
|
3419
|
-
sourceVariableName: p.sourceVariableName,
|
|
3420
|
-
tracePath: p.tracePath
|
|
3421
|
-
}));
|
|
3422
|
-
break;
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
|
|
3428
|
-
/**
|
|
3429
|
-
* Buffer CATCHES_FROM edges linking catch blocks to error sources (REG-311).
|
|
3430
|
-
*
|
|
3431
|
-
* Creates edges from CATCH_BLOCK nodes to potential error sources within
|
|
3432
|
-
* their corresponding try blocks. This enables tracking which catch blocks
|
|
3433
|
-
* can handle which exceptions:
|
|
3434
|
-
*
|
|
3435
|
-
* - try { await fetch() } catch(e) -> CATCH_BLOCK --CATCHES_FROM--> CALL[fetch]
|
|
3436
|
-
* - try { throw new Error() } catch(e) -> CATCH_BLOCK --CATCHES_FROM--> THROW_STATEMENT
|
|
3437
|
-
*
|
|
3438
|
-
* The sourceType metadata helps distinguish different error source kinds
|
|
3439
|
-
* for more precise error flow analysis.
|
|
3440
|
-
*
|
|
3441
|
-
* @param catchesFromInfos - Collected CATCHES_FROM info from analysis
|
|
3442
|
-
*/
|
|
3443
|
-
private bufferCatchesFromEdges(catchesFromInfos: CatchesFromInfo[]): void {
|
|
3444
|
-
for (const info of catchesFromInfos) {
|
|
3445
|
-
this._bufferEdge({
|
|
3446
|
-
type: 'CATCHES_FROM',
|
|
3447
|
-
src: info.catchBlockId,
|
|
3448
|
-
dst: info.sourceId,
|
|
3449
|
-
metadata: {
|
|
3450
|
-
parameterName: info.parameterName,
|
|
3451
|
-
sourceType: info.sourceType,
|
|
3452
|
-
sourceLine: info.sourceLine
|
|
3453
|
-
}
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
551
|
}
|