@grafema/core 0.1.0-alpha.5 → 0.2.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/Orchestrator.d.ts +31 -2
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +222 -27
- package/dist/config/ConfigLoader.d.ts +90 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +249 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/core/ASTWorker.d.ts +11 -36
- package/dist/core/ASTWorker.d.ts.map +1 -1
- package/dist/core/ASTWorker.js +93 -99
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +198 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +139 -0
- package/dist/core/FileNodeManager.d.ts +40 -0
- package/dist/core/FileNodeManager.d.ts.map +1 -0
- package/dist/core/FileNodeManager.js +84 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +101 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +45 -0
- package/dist/core/IncrementalReanalyzer.d.ts +36 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +132 -0
- package/dist/core/NodeFactory.d.ts +266 -19
- package/dist/core/NodeFactory.d.ts.map +1 -1
- package/dist/core/NodeFactory.js +256 -21
- package/dist/core/ScopeTracker.d.ts +84 -0
- package/dist/core/ScopeTracker.d.ts.map +1 -0
- package/dist/core/ScopeTracker.js +116 -0
- package/dist/core/SemanticId.d.ts +90 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +115 -0
- package/dist/core/VersionManager.d.ts.map +1 -1
- package/dist/core/VersionManager.js +3 -2
- package/dist/core/nodes/ArgumentExpressionNode.d.ts +43 -0
- package/dist/core/nodes/ArgumentExpressionNode.d.ts.map +1 -0
- package/dist/core/nodes/ArgumentExpressionNode.js +60 -0
- package/dist/core/nodes/ArrayLiteralNode.d.ts +27 -0
- package/dist/core/nodes/ArrayLiteralNode.d.ts.map +1 -0
- package/dist/core/nodes/ArrayLiteralNode.js +43 -0
- package/dist/core/nodes/BranchNode.d.ts +41 -0
- package/dist/core/nodes/BranchNode.d.ts.map +1 -0
- package/dist/core/nodes/BranchNode.js +82 -0
- package/dist/core/nodes/CallSiteNode.d.ts +30 -2
- package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
- package/dist/core/nodes/CallSiteNode.js +54 -4
- package/dist/core/nodes/CaseNode.d.ts +43 -0
- package/dist/core/nodes/CaseNode.d.ts.map +1 -0
- package/dist/core/nodes/CaseNode.js +81 -0
- package/dist/core/nodes/ClassNode.d.ts +34 -2
- package/dist/core/nodes/ClassNode.d.ts.map +1 -1
- package/dist/core/nodes/ClassNode.js +52 -4
- package/dist/core/nodes/ConstantNode.d.ts +2 -2
- package/dist/core/nodes/ConstantNode.d.ts.map +1 -1
- package/dist/core/nodes/ConstantNode.js +6 -4
- package/dist/core/nodes/ConstructorCallNode.d.ts +51 -0
- package/dist/core/nodes/ConstructorCallNode.d.ts.map +1 -0
- package/dist/core/nodes/ConstructorCallNode.js +171 -0
- package/dist/core/nodes/DatabaseQueryNode.d.ts +3 -2
- package/dist/core/nodes/DatabaseQueryNode.d.ts.map +1 -1
- package/dist/core/nodes/DatabaseQueryNode.js +5 -2
- package/dist/core/nodes/DecoratorNode.d.ts +42 -0
- package/dist/core/nodes/DecoratorNode.d.ts.map +1 -0
- package/dist/core/nodes/DecoratorNode.js +64 -0
- package/dist/core/nodes/EnumNode.d.ts +42 -0
- package/dist/core/nodes/EnumNode.d.ts.map +1 -0
- package/dist/core/nodes/EnumNode.js +56 -0
- package/dist/core/nodes/EventListenerNode.d.ts +4 -4
- package/dist/core/nodes/EventListenerNode.d.ts.map +1 -1
- package/dist/core/nodes/EventListenerNode.js +7 -4
- package/dist/core/nodes/ExportNode.d.ts +38 -2
- package/dist/core/nodes/ExportNode.d.ts.map +1 -1
- package/dist/core/nodes/ExportNode.js +54 -4
- package/dist/core/nodes/ExpressionNode.d.ts +97 -0
- package/dist/core/nodes/ExpressionNode.d.ts.map +1 -0
- package/dist/core/nodes/ExpressionNode.js +180 -0
- package/dist/core/nodes/ExternalModuleNode.d.ts +32 -0
- package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -0
- package/dist/core/nodes/ExternalModuleNode.js +49 -0
- package/dist/core/nodes/ExternalStdioNode.d.ts +13 -6
- package/dist/core/nodes/ExternalStdioNode.d.ts.map +1 -1
- package/dist/core/nodes/ExternalStdioNode.js +15 -8
- package/dist/core/nodes/FunctionNode.d.ts +36 -0
- package/dist/core/nodes/FunctionNode.d.ts.map +1 -1
- package/dist/core/nodes/FunctionNode.js +80 -1
- package/dist/core/nodes/HttpRequestNode.d.ts +4 -4
- package/dist/core/nodes/HttpRequestNode.d.ts.map +1 -1
- package/dist/core/nodes/HttpRequestNode.js +7 -4
- package/dist/core/nodes/ImportNode.d.ts +28 -6
- package/dist/core/nodes/ImportNode.d.ts.map +1 -1
- package/dist/core/nodes/ImportNode.js +43 -8
- package/dist/core/nodes/InterfaceNode.d.ts +46 -0
- package/dist/core/nodes/InterfaceNode.d.ts.map +1 -0
- package/dist/core/nodes/InterfaceNode.js +57 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +129 -0
- package/dist/core/nodes/LiteralNode.d.ts +2 -2
- package/dist/core/nodes/LiteralNode.d.ts.map +1 -1
- package/dist/core/nodes/LiteralNode.js +6 -4
- package/dist/core/nodes/MethodCallNode.d.ts +32 -2
- package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodCallNode.js +57 -4
- package/dist/core/nodes/MethodNode.d.ts +34 -2
- package/dist/core/nodes/MethodNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodNode.js +55 -3
- package/dist/core/nodes/ModuleNode.d.ts +31 -0
- package/dist/core/nodes/ModuleNode.d.ts.map +1 -1
- package/dist/core/nodes/ModuleNode.js +37 -0
- package/dist/core/nodes/NetworkRequestNode.d.ts +54 -0
- package/dist/core/nodes/NetworkRequestNode.d.ts.map +1 -0
- package/dist/core/nodes/NetworkRequestNode.js +65 -0
- package/dist/core/nodes/ObjectLiteralNode.d.ts +27 -0
- package/dist/core/nodes/ObjectLiteralNode.d.ts.map +1 -0
- package/dist/core/nodes/ObjectLiteralNode.js +43 -0
- package/dist/core/nodes/ParameterNode.d.ts +2 -2
- package/dist/core/nodes/ParameterNode.d.ts.map +1 -1
- package/dist/core/nodes/ParameterNode.js +5 -3
- package/dist/core/nodes/ScopeNode.d.ts +31 -0
- package/dist/core/nodes/ScopeNode.d.ts.map +1 -1
- package/dist/core/nodes/ScopeNode.js +49 -0
- package/dist/core/nodes/TypeNode.d.ts +36 -0
- package/dist/core/nodes/TypeNode.d.ts.map +1 -0
- package/dist/core/nodes/TypeNode.js +55 -0
- package/dist/core/nodes/VariableDeclarationNode.d.ts +29 -2
- package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
- package/dist/core/nodes/VariableDeclarationNode.js +48 -4
- package/dist/core/nodes/index.d.ts +15 -1
- package/dist/core/nodes/index.d.ts.map +1 -1
- package/dist/core/nodes/index.js +17 -0
- package/dist/data/builtins/BuiltinRegistry.d.ts +78 -0
- package/dist/data/builtins/BuiltinRegistry.d.ts.map +1 -0
- package/dist/data/builtins/BuiltinRegistry.js +110 -0
- package/dist/data/builtins/definitions.d.ts +28 -0
- package/dist/data/builtins/definitions.d.ts.map +1 -0
- package/dist/data/builtins/definitions.js +250 -0
- package/dist/data/builtins/index.d.ts +10 -0
- package/dist/data/builtins/index.d.ts.map +1 -0
- package/dist/data/builtins/index.js +8 -0
- package/dist/data/builtins/jsGlobals.d.ts +18 -0
- package/dist/data/builtins/jsGlobals.d.ts.map +1 -0
- package/dist/data/builtins/jsGlobals.js +26 -0
- package/dist/data/builtins/types.d.ts +34 -0
- package/dist/data/builtins/types.d.ts.map +1 -0
- package/dist/data/builtins/types.js +7 -0
- package/dist/data/globals/definitions.d.ts +27 -0
- package/dist/data/globals/definitions.d.ts.map +1 -0
- package/dist/data/globals/definitions.js +117 -0
- package/dist/data/globals/index.d.ts +36 -0
- package/dist/data/globals/index.d.ts.map +1 -0
- package/dist/data/globals/index.js +52 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +98 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +129 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +100 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +247 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +43 -0
- package/dist/diagnostics/index.d.ts +14 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +11 -0
- package/dist/errors/GrafemaError.d.ts +161 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +181 -0
- package/dist/index.d.ts +73 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -1
- package/dist/logging/Logger.d.ts +48 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +134 -0
- package/dist/plugins/Plugin.d.ts +5 -1
- package/dist/plugins/Plugin.d.ts.map +1 -1
- package/dist/plugins/Plugin.js +33 -0
- package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/DatabaseAnalyzer.js +14 -6
- package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressAnalyzer.js +29 -19
- package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts +148 -0
- package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts.map +1 -0
- package/dist/plugins/analysis/ExpressResponseAnalyzer.js +495 -0
- package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressRouteAnalyzer.js +71 -29
- package/dist/plugins/analysis/FetchAnalyzer.d.ts +41 -0
- package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/FetchAnalyzer.js +187 -19
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts +6 -3
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.d.ts.map +1 -1
- package/dist/plugins/analysis/IncrementalAnalysisPlugin.js +76 -80
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts +313 -19
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.js +3430 -503
- package/dist/plugins/analysis/ReactAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ReactAnalyzer.js +56 -57
- package/dist/plugins/analysis/RustAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/RustAnalyzer.js +16 -11
- package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SQLiteAnalyzer.js +11 -7
- package/dist/plugins/analysis/ServiceLayerAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ServiceLayerAnalyzer.js +21 -9
- package/dist/plugins/analysis/SocketIOAnalyzer.d.ts +9 -0
- package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SocketIOAnalyzer.js +117 -21
- package/dist/plugins/analysis/SystemDbAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SystemDbAnalyzer.js +15 -5
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts +207 -4
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.js +1527 -316
- package/dist/plugins/analysis/ast/IdGenerator.d.ts +105 -0
- package/dist/plugins/analysis/ast/IdGenerator.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/IdGenerator.js +116 -0
- package/dist/plugins/analysis/ast/types.d.ts +470 -5
- package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts +33 -0
- package/dist/plugins/analysis/ast/utils/createParameterNodes.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/createParameterNodes.js +89 -0
- package/dist/plugins/analysis/ast/utils/index.d.ts +6 -0
- package/dist/plugins/analysis/ast/utils/index.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/index.js +5 -0
- package/dist/plugins/analysis/ast/utils/location.d.ts +87 -0
- package/dist/plugins/analysis/ast/utils/location.d.ts.map +1 -0
- package/dist/plugins/analysis/ast/utils/location.js +78 -0
- package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +14 -5
- package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ASTVisitor.js +6 -5
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +100 -9
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +674 -125
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts +4 -1
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ClassVisitor.js +72 -32
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +14 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +190 -63
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts +4 -0
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +112 -8
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts +12 -1
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/TypeScriptVisitor.js +36 -14
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +20 -2
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +243 -45
- package/dist/plugins/discovery/MonorepoServiceDiscovery.d.ts.map +1 -1
- package/dist/plugins/discovery/MonorepoServiceDiscovery.js +3 -2
- package/dist/plugins/discovery/SimpleProjectDiscovery.d.ts.map +1 -1
- package/dist/plugins/discovery/SimpleProjectDiscovery.js +5 -1
- package/dist/plugins/discovery/WorkspaceDiscovery.d.ts +22 -0
- package/dist/plugins/discovery/WorkspaceDiscovery.d.ts.map +1 -0
- package/dist/plugins/discovery/WorkspaceDiscovery.js +141 -0
- package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts +46 -0
- package/dist/plugins/discovery/resolveSourceEntrypoint.d.ts.map +1 -0
- package/dist/plugins/discovery/resolveSourceEntrypoint.js +86 -0
- package/dist/plugins/discovery/workspaces/detector.d.ts +21 -0
- package/dist/plugins/discovery/workspaces/detector.d.ts.map +1 -0
- package/dist/plugins/discovery/workspaces/detector.js +49 -0
- package/dist/plugins/discovery/workspaces/globResolver.d.ts +35 -0
- package/dist/plugins/discovery/workspaces/globResolver.d.ts.map +1 -0
- package/dist/plugins/discovery/workspaces/globResolver.js +184 -0
- package/dist/plugins/discovery/workspaces/index.d.ts +9 -0
- package/dist/plugins/discovery/workspaces/index.d.ts.map +1 -0
- package/dist/plugins/discovery/workspaces/index.js +8 -0
- package/dist/plugins/discovery/workspaces/parsers.d.ts +38 -0
- package/dist/plugins/discovery/workspaces/parsers.d.ts.map +1 -0
- package/dist/plugins/discovery/workspaces/parsers.js +80 -0
- package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
- package/dist/plugins/enrichment/AliasTracker.js +29 -8
- package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts +32 -0
- package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts.map +1 -0
- package/dist/plugins/enrichment/ArgumentParameterLinker.js +175 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts +51 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts.map +1 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.js +205 -0
- package/dist/plugins/enrichment/ExternalCallResolver.d.ts +42 -0
- package/dist/plugins/enrichment/ExternalCallResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/ExternalCallResolver.js +213 -0
- package/dist/plugins/enrichment/FunctionCallResolver.d.ts +58 -0
- package/dist/plugins/enrichment/FunctionCallResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/FunctionCallResolver.js +340 -0
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts +16 -3
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.js +78 -27
- package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -1
- package/dist/plugins/enrichment/ImportExportLinker.js +23 -6
- package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MethodCallResolver.js +33 -13
- package/dist/plugins/enrichment/MountPointResolver.d.ts +14 -12
- package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MountPointResolver.js +173 -147
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts +44 -0
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.js +271 -0
- package/dist/plugins/enrichment/PrefixEvaluator.d.ts.map +1 -1
- package/dist/plugins/enrichment/PrefixEvaluator.js +16 -7
- package/dist/plugins/enrichment/RustFFIEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/RustFFIEnricher.js +6 -5
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +22 -27
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
- package/dist/plugins/enrichment/ValueDomainAnalyzer.js +185 -143
- package/dist/plugins/indexing/IncrementalModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/IncrementalModuleIndexer.js +23 -14
- package/dist/plugins/indexing/JSModuleIndexer.d.ts +15 -0
- package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/JSModuleIndexer.js +121 -31
- package/dist/plugins/indexing/RustModuleIndexer.d.ts +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.js +8 -7
- package/dist/plugins/indexing/ServiceDetector.d.ts +10 -0
- package/dist/plugins/indexing/ServiceDetector.d.ts.map +1 -1
- package/dist/plugins/indexing/ServiceDetector.js +28 -15
- package/dist/plugins/validation/BrokenImportValidator.d.ts +31 -0
- package/dist/plugins/validation/BrokenImportValidator.d.ts.map +1 -0
- package/dist/plugins/validation/BrokenImportValidator.js +249 -0
- package/dist/plugins/validation/CallResolverValidator.d.ts +21 -10
- package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -1
- package/dist/plugins/validation/CallResolverValidator.js +103 -77
- package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
- package/dist/plugins/validation/DataFlowValidator.js +62 -49
- package/dist/plugins/validation/EvalBanValidator.d.ts.map +1 -1
- package/dist/plugins/validation/EvalBanValidator.js +17 -16
- package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -1
- package/dist/plugins/validation/GraphConnectivityValidator.js +44 -24
- package/dist/plugins/validation/NodeCreationValidator.d.ts +85 -0
- package/dist/plugins/validation/NodeCreationValidator.d.ts.map +1 -0
- package/dist/plugins/validation/NodeCreationValidator.js +415 -0
- package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -1
- package/dist/plugins/validation/SQLInjectionValidator.js +61 -19
- package/dist/plugins/validation/ShadowingDetector.d.ts.map +1 -1
- package/dist/plugins/validation/ShadowingDetector.js +6 -5
- package/dist/plugins/validation/TypeScriptDeadCodeValidator.d.ts.map +1 -1
- package/dist/plugins/validation/TypeScriptDeadCodeValidator.js +12 -11
- package/dist/plugins/vcs/GitPlugin.d.ts.map +1 -1
- package/dist/plugins/vcs/GitPlugin.js +10 -12
- package/dist/plugins/vcs/VCSPlugin.d.ts +3 -2
- package/dist/plugins/vcs/VCSPlugin.d.ts.map +1 -1
- package/dist/plugins/vcs/VCSPlugin.js +5 -5
- package/dist/queries/findCallsInFunction.d.ts +52 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +135 -0
- package/dist/queries/findContainingFunction.d.ts +45 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +54 -0
- package/dist/queries/index.d.ts +14 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +11 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +299 -0
- package/dist/queries/types.d.ts +163 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +9 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +124 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +112 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +21 -34
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.js +72 -62
- package/dist/storage/backends/typeValidation.d.ts.map +1 -1
- package/dist/storage/backends/typeValidation.js +1 -0
- package/dist/validation/PathValidator.d.ts +1 -2
- package/dist/validation/PathValidator.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/Orchestrator.ts +272 -27
- package/src/config/ConfigLoader.ts +354 -0
- package/src/config/index.ts +5 -0
- package/src/core/ASTWorker.ts +143 -139
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileNodeManager.ts +100 -0
- package/src/core/GraphFreshnessChecker.ts +143 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +192 -0
- package/src/core/NodeFactory.ts +470 -23
- package/src/core/ScopeTracker.ts +154 -0
- package/src/core/SemanticId.ts +192 -0
- package/src/core/VersionManager.ts +3 -2
- package/src/core/nodes/ArgumentExpressionNode.ts +89 -0
- package/src/core/nodes/ArrayLiteralNode.ts +66 -0
- package/src/core/nodes/BranchNode.ts +113 -0
- package/src/core/nodes/CallSiteNode.ts +64 -4
- package/src/core/nodes/CaseNode.ts +123 -0
- package/src/core/nodes/ClassNode.ts +67 -4
- package/src/core/nodes/ConstantNode.ts +5 -4
- package/src/core/nodes/ConstructorCallNode.ts +217 -0
- package/src/core/nodes/DatabaseQueryNode.ts +5 -1
- package/src/core/nodes/DecoratorNode.ts +92 -0
- package/src/core/nodes/EnumNode.ts +87 -0
- package/src/core/nodes/EventListenerNode.ts +7 -4
- package/src/core/nodes/ExportNode.ts +74 -4
- package/src/core/nodes/ExpressionNode.ts +232 -0
- package/src/core/nodes/ExternalModuleNode.ts +65 -0
- package/src/core/nodes/ExternalStdioNode.ts +17 -9
- package/src/core/nodes/FunctionNode.ts +101 -1
- package/src/core/nodes/HttpRequestNode.ts +7 -4
- package/src/core/nodes/ImportNode.ts +62 -13
- package/src/core/nodes/InterfaceNode.ts +92 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/LiteralNode.ts +5 -4
- package/src/core/nodes/MethodCallNode.ts +70 -4
- package/src/core/nodes/MethodNode.ts +68 -3
- package/src/core/nodes/ModuleNode.ts +50 -0
- package/src/core/nodes/NetworkRequestNode.ts +77 -0
- package/src/core/nodes/ObjectLiteralNode.ts +66 -0
- package/src/core/nodes/ParameterNode.ts +4 -3
- package/src/core/nodes/ScopeNode.ts +65 -0
- package/src/core/nodes/TypeNode.ts +79 -0
- package/src/core/nodes/VariableDeclarationNode.ts +58 -4
- package/src/core/nodes/index.ts +21 -1
- package/src/data/builtins/BuiltinRegistry.ts +124 -0
- package/src/data/builtins/definitions.ts +267 -0
- package/src/data/builtins/index.ts +10 -0
- package/src/data/builtins/jsGlobals.ts +28 -0
- package/src/data/builtins/types.ts +36 -0
- package/src/data/globals/definitions.ts +156 -0
- package/src/data/globals/index.ts +66 -0
- package/src/diagnostics/DiagnosticCollector.ts +163 -0
- package/src/diagnostics/DiagnosticReporter.ts +324 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/index.ts +16 -0
- package/src/errors/GrafemaError.ts +239 -0
- package/src/index.ts +193 -1
- package/src/logging/Logger.ts +152 -0
- package/src/plugins/Plugin.ts +42 -0
- package/src/plugins/analysis/DatabaseAnalyzer.ts +16 -8
- package/src/plugins/analysis/ExpressAnalyzer.ts +33 -19
- package/src/plugins/analysis/ExpressResponseAnalyzer.ts +636 -0
- package/src/plugins/analysis/ExpressRouteAnalyzer.ts +76 -36
- package/src/plugins/analysis/FetchAnalyzer.ts +232 -21
- package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +84 -101
- package/src/plugins/analysis/JSASTAnalyzer.ts +4265 -587
- package/src/plugins/analysis/ReactAnalyzer.ts +57 -57
- package/src/plugins/analysis/RustAnalyzer.ts +16 -11
- package/src/plugins/analysis/SQLiteAnalyzer.ts +13 -7
- package/src/plugins/analysis/ServiceLayerAnalyzer.ts +22 -16
- package/src/plugins/analysis/SocketIOAnalyzer.ts +151 -28
- package/src/plugins/analysis/SystemDbAnalyzer.ts +16 -11
- package/src/plugins/analysis/ast/GraphBuilder.ts +1947 -327
- package/src/plugins/analysis/ast/IdGenerator.ts +177 -0
- package/src/plugins/analysis/ast/types.ts +596 -6
- package/src/plugins/analysis/ast/utils/createParameterNodes.ts +104 -0
- package/src/plugins/analysis/ast/utils/index.ts +12 -0
- package/src/plugins/analysis/ast/utils/location.ts +103 -0
- package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +19 -8
- package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +924 -83
- package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +97 -44
- package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +234 -93
- package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +124 -9
- package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +41 -14
- package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +294 -49
- package/src/plugins/discovery/MonorepoServiceDiscovery.ts +3 -2
- package/src/plugins/discovery/SimpleProjectDiscovery.ts +6 -1
- package/src/plugins/discovery/WorkspaceDiscovery.ts +184 -0
- package/src/plugins/discovery/resolveSourceEntrypoint.ts +103 -0
- package/src/plugins/discovery/workspaces/detector.ts +63 -0
- package/src/plugins/discovery/workspaces/globResolver.ts +229 -0
- package/src/plugins/discovery/workspaces/index.ts +23 -0
- package/src/plugins/discovery/workspaces/parsers.ts +99 -0
- package/src/plugins/enrichment/AliasTracker.ts +35 -8
- package/src/plugins/enrichment/ArgumentParameterLinker.ts +240 -0
- package/src/plugins/enrichment/ClosureCaptureEnricher.ts +267 -0
- package/src/plugins/enrichment/ExternalCallResolver.ts +262 -0
- package/src/plugins/enrichment/FunctionCallResolver.ts +456 -0
- package/src/plugins/enrichment/HTTPConnectionEnricher.ts +84 -27
- package/src/plugins/enrichment/ImportExportLinker.ts +24 -6
- package/src/plugins/enrichment/MethodCallResolver.ts +39 -13
- package/src/plugins/enrichment/MountPointResolver.ts +208 -195
- package/src/plugins/enrichment/NodejsBuiltinsResolver.ts +365 -0
- package/src/plugins/enrichment/PrefixEvaluator.ts +16 -7
- package/src/plugins/enrichment/RustFFIEnricher.ts +6 -5
- package/src/plugins/enrichment/ValueDomainAnalyzer.ts +209 -189
- package/src/plugins/indexing/IncrementalModuleIndexer.ts +23 -14
- package/src/plugins/indexing/JSModuleIndexer.ts +140 -34
- package/src/plugins/indexing/RustModuleIndexer.ts +8 -7
- package/src/plugins/validation/BrokenImportValidator.ts +325 -0
- package/src/plugins/validation/CallResolverValidator.ts +131 -110
- package/src/plugins/validation/DataFlowValidator.ts +88 -67
- package/src/plugins/validation/EvalBanValidator.ts +17 -16
- package/src/plugins/validation/GraphConnectivityValidator.ts +58 -24
- package/src/plugins/validation/NodeCreationValidator.ts +554 -0
- package/src/plugins/validation/SQLInjectionValidator.ts +63 -20
- package/src/plugins/validation/ShadowingDetector.ts +6 -5
- package/src/plugins/validation/TypeScriptDeadCodeValidator.ts +12 -11
- package/src/plugins/vcs/GitPlugin.ts +40 -12
- package/src/plugins/vcs/VCSPlugin.ts +7 -5
- package/src/queries/README.md +46 -0
- package/src/queries/findCallsInFunction.ts +206 -0
- package/src/queries/findContainingFunction.ts +83 -0
- package/src/queries/index.ts +23 -0
- package/src/queries/traceValues.ts +398 -0
- package/src/queries/types.ts +187 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +100 -98
- package/src/storage/backends/typeValidation.ts +1 -0
- package/src/validation/PathValidator.ts +1 -1
- package/dist/core/AnalysisWorker.d.ts +0 -14
- package/dist/core/AnalysisWorker.d.ts.map +0 -1
- package/dist/core/AnalysisWorker.js +0 -307
- package/dist/core/ParallelAnalyzer.d.ts +0 -120
- package/dist/core/ParallelAnalyzer.d.ts.map +0 -1
- package/dist/core/ParallelAnalyzer.js +0 -331
- package/dist/core/QueueWorker.d.ts +0 -12
- package/dist/core/QueueWorker.d.ts.map +0 -1
- package/dist/core/QueueWorker.js +0 -567
- package/dist/core/RFDBClient.d.ts +0 -179
- package/dist/core/RFDBClient.d.ts.map +0 -1
- package/dist/core/RFDBClient.js +0 -429
- package/dist/plugins/discovery/ZonServiceDiscovery.d.ts +0 -19
- package/dist/plugins/discovery/ZonServiceDiscovery.d.ts.map +0 -1
- package/dist/plugins/discovery/ZonServiceDiscovery.js +0 -204
- package/src/core/AnalysisWorker.ts +0 -410
- package/src/core/ParallelAnalyzer.ts +0 -476
- package/src/core/QueueWorker.ts +0 -780
- package/src/plugins/indexing/ServiceDetector.ts +0 -230
|
@@ -3,18 +3,29 @@
|
|
|
3
3
|
* OPTIMIZED: Uses batched writes to reduce FFI overhead
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { dirname, resolve } from 'path';
|
|
6
|
+
import { dirname, resolve, basename } from 'path';
|
|
7
7
|
import type { GraphBackend } from '@grafema/types';
|
|
8
|
+
import { ImportNode } from '../../../core/nodes/ImportNode.js';
|
|
9
|
+
import { InterfaceNode, type InterfaceNodeRecord } from '../../../core/nodes/InterfaceNode.js';
|
|
10
|
+
import { EnumNode, type EnumNodeRecord } 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';
|
|
8
15
|
import type {
|
|
9
16
|
ModuleNode,
|
|
10
17
|
FunctionInfo,
|
|
11
18
|
ParameterInfo,
|
|
12
19
|
ScopeInfo,
|
|
20
|
+
BranchInfo,
|
|
21
|
+
CaseInfo,
|
|
22
|
+
LoopInfo,
|
|
13
23
|
VariableDeclarationInfo,
|
|
14
24
|
CallSiteInfo,
|
|
15
25
|
MethodCallInfo,
|
|
16
26
|
EventListenerInfo,
|
|
17
27
|
ClassInstantiationInfo,
|
|
28
|
+
ConstructorCallInfo,
|
|
18
29
|
ClassDeclarationInfo,
|
|
19
30
|
MethodCallbackInfo,
|
|
20
31
|
CallArgumentInfo,
|
|
@@ -27,6 +38,18 @@ import type {
|
|
|
27
38
|
TypeAliasInfo,
|
|
28
39
|
EnumDeclarationInfo,
|
|
29
40
|
DecoratorInfo,
|
|
41
|
+
ArrayMutationInfo,
|
|
42
|
+
ObjectMutationInfo,
|
|
43
|
+
VariableReassignmentInfo,
|
|
44
|
+
ReturnStatementInfo,
|
|
45
|
+
ObjectLiteralInfo,
|
|
46
|
+
ObjectPropertyInfo,
|
|
47
|
+
ArrayLiteralInfo,
|
|
48
|
+
TryBlockInfo,
|
|
49
|
+
CatchBlockInfo,
|
|
50
|
+
FinallyBlockInfo,
|
|
51
|
+
UpdateExpressionInfo,
|
|
52
|
+
PromiseResolutionInfo,
|
|
30
53
|
ASTCollections,
|
|
31
54
|
GraphNode,
|
|
32
55
|
GraphEdge,
|
|
@@ -91,11 +114,21 @@ export class GraphBuilder {
|
|
|
91
114
|
functions,
|
|
92
115
|
parameters = [],
|
|
93
116
|
scopes,
|
|
117
|
+
// Branching
|
|
118
|
+
branches = [],
|
|
119
|
+
cases = [],
|
|
120
|
+
// Control flow (loops)
|
|
121
|
+
loops = [],
|
|
122
|
+
// Control flow (try/catch/finally) - Phase 4
|
|
123
|
+
tryBlocks = [],
|
|
124
|
+
catchBlocks = [],
|
|
125
|
+
finallyBlocks = [],
|
|
94
126
|
variableDeclarations,
|
|
95
127
|
callSites,
|
|
96
128
|
methodCalls = [],
|
|
97
129
|
eventListeners = [],
|
|
98
130
|
classInstantiations = [],
|
|
131
|
+
constructorCalls = [],
|
|
99
132
|
classDeclarations = [],
|
|
100
133
|
methodCallbacks = [],
|
|
101
134
|
callArguments = [],
|
|
@@ -108,7 +141,23 @@ export class GraphBuilder {
|
|
|
108
141
|
interfaces = [],
|
|
109
142
|
typeAliases = [],
|
|
110
143
|
enums = [],
|
|
111
|
-
decorators = []
|
|
144
|
+
decorators = [],
|
|
145
|
+
// Array mutation tracking for FLOWS_INTO edges
|
|
146
|
+
arrayMutations = [],
|
|
147
|
+
// Object mutation tracking for FLOWS_INTO edges
|
|
148
|
+
objectMutations = [],
|
|
149
|
+
// Variable reassignment tracking for FLOWS_INTO edges (REG-290)
|
|
150
|
+
variableReassignments = [],
|
|
151
|
+
// Return statement tracking for RETURNS edges
|
|
152
|
+
returnStatements = [],
|
|
153
|
+
// Update expression tracking for MODIFIES edges (REG-288, REG-312)
|
|
154
|
+
updateExpressions = [],
|
|
155
|
+
// Promise resolution tracking for RESOLVES_TO edges (REG-334)
|
|
156
|
+
promiseResolutions = [],
|
|
157
|
+
// Object/Array literal tracking
|
|
158
|
+
objectLiterals = [],
|
|
159
|
+
objectProperties = [],
|
|
160
|
+
arrayLiterals = []
|
|
112
161
|
} = data;
|
|
113
162
|
|
|
114
163
|
// Reset buffers for this build
|
|
@@ -127,10 +176,50 @@ export class GraphBuilder {
|
|
|
127
176
|
this._bufferNode(scopeData as GraphNode);
|
|
128
177
|
}
|
|
129
178
|
|
|
130
|
-
//
|
|
179
|
+
// 2.5. Buffer BRANCH nodes
|
|
180
|
+
// Note: parentScopeId is kept on node for query support (REG-275 test requirement)
|
|
181
|
+
for (const branch of branches) {
|
|
182
|
+
const { discriminantExpressionId, discriminantExpressionType, discriminantLine, discriminantColumn, ...branchData } = branch;
|
|
183
|
+
this._bufferNode(branchData as GraphNode);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2.6. Buffer CASE nodes
|
|
187
|
+
for (const caseInfo of cases) {
|
|
188
|
+
const { parentBranchId, ...caseData } = caseInfo;
|
|
189
|
+
this._bufferNode(caseData as GraphNode);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 2.7. Buffer LOOP nodes
|
|
193
|
+
for (const loop of loops) {
|
|
194
|
+
// Exclude metadata used for edge creation (not stored on node)
|
|
195
|
+
const {
|
|
196
|
+
iteratesOverName, iteratesOverLine, iteratesOverColumn,
|
|
197
|
+
conditionExpressionId, conditionExpressionType, conditionLine, conditionColumn,
|
|
198
|
+
...loopData
|
|
199
|
+
} = loop;
|
|
200
|
+
this._bufferNode(loopData as GraphNode);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 2.8. Buffer TRY_BLOCK nodes (Phase 4)
|
|
204
|
+
for (const tryBlock of tryBlocks) {
|
|
205
|
+
this._bufferNode(tryBlock as GraphNode);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 2.9. Buffer CATCH_BLOCK nodes (Phase 4)
|
|
209
|
+
for (const catchBlock of catchBlocks) {
|
|
210
|
+
const { parentTryBlockId, ...catchData } = catchBlock;
|
|
211
|
+
this._bufferNode(catchData as GraphNode);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 2.10. Buffer FINALLY_BLOCK nodes (Phase 4)
|
|
215
|
+
for (const finallyBlock of finallyBlocks) {
|
|
216
|
+
const { parentTryBlockId, ...finallyData } = finallyBlock;
|
|
217
|
+
this._bufferNode(finallyData as GraphNode);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 3. Buffer variables (keep parentScopeId on node for queries)
|
|
131
221
|
for (const varDecl of variableDeclarations) {
|
|
132
|
-
|
|
133
|
-
this._bufferNode(varData as GraphNode);
|
|
222
|
+
this._bufferNode(varDecl as unknown as GraphNode);
|
|
134
223
|
}
|
|
135
224
|
|
|
136
225
|
// 3.5. Buffer PARAMETER nodes and HAS_PARAMETER edges
|
|
@@ -149,26 +238,62 @@ export class GraphBuilder {
|
|
|
149
238
|
}
|
|
150
239
|
}
|
|
151
240
|
|
|
152
|
-
// 4. Buffer CALL_SITE
|
|
241
|
+
// 4. Buffer CALL_SITE (keep parentScopeId on node for queries)
|
|
153
242
|
for (const callSite of callSites) {
|
|
154
|
-
const {
|
|
243
|
+
const { targetFunctionName, ...callData } = callSite;
|
|
155
244
|
this._bufferNode(callData as GraphNode);
|
|
156
245
|
}
|
|
157
246
|
|
|
247
|
+
// 4.5 Buffer CONSTRUCTOR_CALL nodes
|
|
248
|
+
for (const constructorCall of constructorCalls) {
|
|
249
|
+
this._bufferNode({
|
|
250
|
+
id: constructorCall.id,
|
|
251
|
+
type: constructorCall.type,
|
|
252
|
+
name: `new ${constructorCall.className}()`,
|
|
253
|
+
className: constructorCall.className,
|
|
254
|
+
isBuiltin: constructorCall.isBuiltin,
|
|
255
|
+
file: constructorCall.file,
|
|
256
|
+
line: constructorCall.line,
|
|
257
|
+
column: constructorCall.column
|
|
258
|
+
} as GraphNode);
|
|
259
|
+
}
|
|
260
|
+
|
|
158
261
|
// 5. Buffer edges for functions
|
|
159
262
|
this.bufferFunctionEdges(module, functions);
|
|
160
263
|
|
|
161
264
|
// 6. Buffer edges for SCOPE
|
|
162
265
|
this.bufferScopeEdges(scopes, variableDeclarations);
|
|
163
266
|
|
|
267
|
+
// 6.3. Buffer edges for LOOP (HAS_BODY, ITERATES_OVER, CONTAINS)
|
|
268
|
+
this.bufferLoopEdges(loops, scopes, variableDeclarations, parameters);
|
|
269
|
+
|
|
270
|
+
// 6.35. Buffer HAS_CONDITION edges for LOOP (REG-280)
|
|
271
|
+
this.bufferLoopConditionEdges(loops, callSites);
|
|
272
|
+
|
|
273
|
+
// 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
|
|
274
|
+
this.bufferLoopConditionExpressions(loops);
|
|
275
|
+
|
|
276
|
+
// 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
|
|
277
|
+
// Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
|
|
278
|
+
this.bufferBranchEdges(branches, callSites, scopes);
|
|
279
|
+
|
|
280
|
+
// 6.6. Buffer edges for CASE
|
|
281
|
+
this.bufferCaseEdges(cases);
|
|
282
|
+
|
|
283
|
+
// 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
|
|
284
|
+
this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
|
|
285
|
+
|
|
286
|
+
// 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
|
|
287
|
+
this.bufferDiscriminantExpressions(branches, callSites);
|
|
288
|
+
|
|
164
289
|
// 7. Buffer edges for variables
|
|
165
290
|
this.bufferVariableEdges(variableDeclarations);
|
|
166
291
|
|
|
167
292
|
// 8. Buffer edges for CALL_SITE
|
|
168
293
|
this.bufferCallSiteEdges(callSites, functions);
|
|
169
294
|
|
|
170
|
-
// 9. Buffer METHOD_CALL nodes and
|
|
171
|
-
this.bufferMethodCalls(methodCalls);
|
|
295
|
+
// 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
|
|
296
|
+
this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
|
|
172
297
|
|
|
173
298
|
// 10. Buffer net:stdio and WRITES_TO edges for console.log/error
|
|
174
299
|
this.bufferStdioNodes(methodCalls);
|
|
@@ -197,6 +322,16 @@ export class GraphBuilder {
|
|
|
197
322
|
// 18. Buffer LITERAL nodes
|
|
198
323
|
this.bufferLiterals(literals);
|
|
199
324
|
|
|
325
|
+
// 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
|
|
326
|
+
this.bufferObjectLiteralNodes(objectLiterals);
|
|
327
|
+
|
|
328
|
+
// 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
|
|
329
|
+
this.bufferArrayLiteralNodes(arrayLiterals);
|
|
330
|
+
|
|
331
|
+
// 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
|
|
332
|
+
// REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
|
|
333
|
+
this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
|
|
334
|
+
|
|
200
335
|
// 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
|
|
201
336
|
this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
|
|
202
337
|
|
|
@@ -218,17 +353,33 @@ export class GraphBuilder {
|
|
|
218
353
|
// 25. Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
|
|
219
354
|
this.bufferImplementsEdges(classDeclarations, interfaces);
|
|
220
355
|
|
|
356
|
+
// 26. Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
357
|
+
this.bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters);
|
|
358
|
+
|
|
359
|
+
// 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
360
|
+
// REG-152: Now includes classDeclarations for this.prop = value patterns
|
|
361
|
+
this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
|
|
362
|
+
|
|
363
|
+
// 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
|
|
364
|
+
this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
|
|
365
|
+
|
|
366
|
+
// 29. Buffer RETURNS edges for return statements
|
|
367
|
+
this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
|
|
368
|
+
|
|
369
|
+
// 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
|
|
370
|
+
this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
|
|
371
|
+
|
|
372
|
+
// 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
|
|
373
|
+
this.bufferPromiseResolutionEdges(promiseResolutions);
|
|
374
|
+
|
|
221
375
|
// FLUSH: Write all nodes first, then edges in single batch calls
|
|
222
376
|
const nodesCreated = await this._flushNodes(graph);
|
|
223
377
|
const edgesCreated = await this._flushEdges(graph);
|
|
224
378
|
|
|
225
|
-
// Handle async operations that need graph queries (IMPORTS_FROM edges)
|
|
226
|
-
const importExportEdges = await this.createImportExportEdges(module, imports, exports, graph, projectPath);
|
|
227
|
-
|
|
228
379
|
// Handle async operations for ASSIGNED_FROM with CLASS lookups
|
|
229
380
|
const classAssignmentEdges = await this.createClassAssignmentEdges(variableAssignments, graph);
|
|
230
381
|
|
|
231
|
-
return { nodes: nodesCreated, edges: edgesCreated +
|
|
382
|
+
return { nodes: nodesCreated, edges: edgesCreated + classAssignmentEdges };
|
|
232
383
|
}
|
|
233
384
|
|
|
234
385
|
// ============= BUFFERED METHODS (synchronous, no awaits) =============
|
|
@@ -302,6 +453,428 @@ export class GraphBuilder {
|
|
|
302
453
|
}
|
|
303
454
|
}
|
|
304
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Buffer LOOP edges (CONTAINS, HAS_BODY, ITERATES_OVER)
|
|
458
|
+
*
|
|
459
|
+
* Creates edges for:
|
|
460
|
+
* - Parent -> CONTAINS -> LOOP
|
|
461
|
+
* - LOOP -> HAS_BODY -> body SCOPE
|
|
462
|
+
* - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
|
|
463
|
+
*
|
|
464
|
+
* Scope-aware variable lookup for ITERATES_OVER:
|
|
465
|
+
* For for-of/for-in, finds the iterated variable preferring:
|
|
466
|
+
* 1. Variables declared before the loop on same or earlier line (closest first)
|
|
467
|
+
* 2. Parameters (function arguments)
|
|
468
|
+
*/
|
|
469
|
+
private bufferLoopEdges(
|
|
470
|
+
loops: LoopInfo[],
|
|
471
|
+
scopes: ScopeInfo[],
|
|
472
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
473
|
+
parameters: ParameterInfo[]
|
|
474
|
+
): void {
|
|
475
|
+
for (const loop of loops) {
|
|
476
|
+
// Parent -> CONTAINS -> LOOP
|
|
477
|
+
if (loop.parentScopeId) {
|
|
478
|
+
this._bufferEdge({
|
|
479
|
+
type: 'CONTAINS',
|
|
480
|
+
src: loop.parentScopeId,
|
|
481
|
+
dst: loop.id
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// LOOP -> HAS_BODY -> body SCOPE
|
|
486
|
+
// Find the body scope by matching parentScopeId to loop.id
|
|
487
|
+
const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
|
|
488
|
+
if (bodyScope) {
|
|
489
|
+
this._bufferEdge({
|
|
490
|
+
type: 'HAS_BODY',
|
|
491
|
+
src: loop.id,
|
|
492
|
+
dst: bodyScope.id
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
|
|
497
|
+
if (loop.iteratesOverName && (loop.loopType === 'for-in' || loop.loopType === 'for-of')) {
|
|
498
|
+
// For MemberExpression iterables (obj.items), extract base object
|
|
499
|
+
const iterableName = loop.iteratesOverName.includes('.')
|
|
500
|
+
? loop.iteratesOverName.split('.')[0]
|
|
501
|
+
: loop.iteratesOverName;
|
|
502
|
+
|
|
503
|
+
// Scope-aware lookup: prefer parameters over variables
|
|
504
|
+
// Parameters are function-local and shadow outer variables
|
|
505
|
+
const param = parameters.find(p =>
|
|
506
|
+
p.name === iterableName && p.file === loop.file
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Determine iteration type: for-in iterates keys, for-of iterates values
|
|
510
|
+
const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
|
|
511
|
+
|
|
512
|
+
if (param) {
|
|
513
|
+
// Parameter found - most local binding
|
|
514
|
+
this._bufferEdge({
|
|
515
|
+
type: 'ITERATES_OVER',
|
|
516
|
+
src: loop.id,
|
|
517
|
+
dst: param.id,
|
|
518
|
+
metadata: { iterates }
|
|
519
|
+
});
|
|
520
|
+
} else {
|
|
521
|
+
// Find variable by name and line proximity (scope-aware heuristic)
|
|
522
|
+
// Prefer variables declared before the loop in the same file
|
|
523
|
+
const candidateVars = variableDeclarations.filter(v =>
|
|
524
|
+
v.name === iterableName &&
|
|
525
|
+
v.file === loop.file &&
|
|
526
|
+
(v.line ?? 0) <= loop.line // Declared before or on loop line
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// Sort by line descending to find closest declaration
|
|
530
|
+
candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
|
|
531
|
+
|
|
532
|
+
if (candidateVars.length > 0) {
|
|
533
|
+
this._bufferEdge({
|
|
534
|
+
type: 'ITERATES_OVER',
|
|
535
|
+
src: loop.id,
|
|
536
|
+
dst: candidateVars[0].id,
|
|
537
|
+
metadata: { iterates }
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
|
|
544
|
+
if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
|
|
545
|
+
// Find the variable declared in the init on this line
|
|
546
|
+
const initVar = variableDeclarations.find(v =>
|
|
547
|
+
v.name === loop.initVariableName &&
|
|
548
|
+
v.file === loop.file &&
|
|
549
|
+
v.line === loop.initLine
|
|
550
|
+
);
|
|
551
|
+
if (initVar) {
|
|
552
|
+
this._bufferEdge({
|
|
553
|
+
type: 'HAS_INIT',
|
|
554
|
+
src: loop.id,
|
|
555
|
+
dst: initVar.id
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// REG-282: LOOP -> HAS_CONDITION -> EXPRESSION (i < 10 or condition for while/do-while)
|
|
561
|
+
if (loop.testExpressionId && loop.testExpressionType) {
|
|
562
|
+
// Create EXPRESSION node for the test
|
|
563
|
+
this._bufferNode({
|
|
564
|
+
id: loop.testExpressionId,
|
|
565
|
+
type: 'EXPRESSION',
|
|
566
|
+
name: loop.testExpressionType,
|
|
567
|
+
file: loop.file,
|
|
568
|
+
line: loop.testLine,
|
|
569
|
+
column: loop.testColumn,
|
|
570
|
+
expressionType: loop.testExpressionType
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
this._bufferEdge({
|
|
574
|
+
type: 'HAS_CONDITION',
|
|
575
|
+
src: loop.id,
|
|
576
|
+
dst: loop.testExpressionId
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// REG-282: LOOP (for) -> HAS_UPDATE -> EXPRESSION (i++)
|
|
581
|
+
if (loop.loopType === 'for' && loop.updateExpressionId && loop.updateExpressionType) {
|
|
582
|
+
// Create EXPRESSION node for the update
|
|
583
|
+
this._bufferNode({
|
|
584
|
+
id: loop.updateExpressionId,
|
|
585
|
+
type: 'EXPRESSION',
|
|
586
|
+
name: loop.updateExpressionType,
|
|
587
|
+
file: loop.file,
|
|
588
|
+
line: loop.updateLine,
|
|
589
|
+
column: loop.updateColumn,
|
|
590
|
+
expressionType: loop.updateExpressionType
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
this._bufferEdge({
|
|
594
|
+
type: 'HAS_UPDATE',
|
|
595
|
+
src: loop.id,
|
|
596
|
+
dst: loop.updateExpressionId
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Buffer HAS_CONDITION edges from LOOP to condition EXPRESSION/CALL nodes.
|
|
604
|
+
* Also creates EXPRESSION nodes for non-CallExpression conditions.
|
|
605
|
+
*
|
|
606
|
+
* REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
|
|
607
|
+
* condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
|
|
608
|
+
*
|
|
609
|
+
* For CallExpression conditions, links to existing CALL_SITE node by coordinates.
|
|
610
|
+
*/
|
|
611
|
+
private bufferLoopConditionEdges(loops: LoopInfo[], callSites: CallSiteInfo[]): void {
|
|
612
|
+
for (const loop of loops) {
|
|
613
|
+
// Skip for-in/for-of loops - they don't have test expressions
|
|
614
|
+
if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Skip if no condition (e.g., infinite for loop: for(;;))
|
|
619
|
+
if (!loop.conditionExpressionId) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// LOOP -> HAS_CONDITION -> EXPRESSION/CALL
|
|
624
|
+
let targetId = loop.conditionExpressionId;
|
|
625
|
+
|
|
626
|
+
// For CallExpression conditions, look up the actual CALL_SITE by coordinates
|
|
627
|
+
// because CALL_SITE uses semantic IDs that don't match the generated ID
|
|
628
|
+
if (loop.conditionExpressionType === 'CallExpression' && loop.conditionLine && loop.conditionColumn !== undefined) {
|
|
629
|
+
const callSite = callSites.find(cs =>
|
|
630
|
+
cs.file === loop.file &&
|
|
631
|
+
cs.line === loop.conditionLine &&
|
|
632
|
+
cs.column === loop.conditionColumn
|
|
633
|
+
);
|
|
634
|
+
if (callSite) {
|
|
635
|
+
targetId = callSite.id;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
this._bufferEdge({
|
|
640
|
+
type: 'HAS_CONDITION',
|
|
641
|
+
src: loop.id,
|
|
642
|
+
dst: targetId
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
|
|
649
|
+
* Similar to bufferDiscriminantExpressions but for loops.
|
|
650
|
+
*
|
|
651
|
+
* REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
|
|
652
|
+
* CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
|
|
653
|
+
*/
|
|
654
|
+
private bufferLoopConditionExpressions(loops: LoopInfo[]): void {
|
|
655
|
+
for (const loop of loops) {
|
|
656
|
+
// Skip for-in/for-of loops - they don't have test expressions
|
|
657
|
+
if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (loop.conditionExpressionId && loop.conditionExpressionType) {
|
|
662
|
+
// Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
|
|
663
|
+
if (loop.conditionExpressionType === 'CallExpression') {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Only create if it looks like an EXPRESSION ID
|
|
668
|
+
if (loop.conditionExpressionId.includes(':EXPRESSION:')) {
|
|
669
|
+
this._bufferNode({
|
|
670
|
+
id: loop.conditionExpressionId,
|
|
671
|
+
type: 'EXPRESSION',
|
|
672
|
+
name: loop.conditionExpressionType,
|
|
673
|
+
file: loop.file,
|
|
674
|
+
line: loop.conditionLine,
|
|
675
|
+
column: loop.conditionColumn,
|
|
676
|
+
expressionType: loop.conditionExpressionType
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Buffer BRANCH edges (CONTAINS, HAS_CONDITION, HAS_CONSEQUENT, HAS_ALTERNATE)
|
|
685
|
+
*
|
|
686
|
+
* REG-275: For CallExpression discriminants (switch(getType())), looks up the
|
|
687
|
+
* actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
|
|
688
|
+
*
|
|
689
|
+
* Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
690
|
+
* pointing to the if-body and else-body SCOPEs.
|
|
691
|
+
*/
|
|
692
|
+
private bufferBranchEdges(branches: BranchInfo[], callSites: CallSiteInfo[], scopes: ScopeInfo[]): void {
|
|
693
|
+
for (const branch of branches) {
|
|
694
|
+
// Parent SCOPE -> CONTAINS -> BRANCH
|
|
695
|
+
if (branch.parentScopeId) {
|
|
696
|
+
this._bufferEdge({
|
|
697
|
+
type: 'CONTAINS',
|
|
698
|
+
src: branch.parentScopeId,
|
|
699
|
+
dst: branch.id
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
|
|
704
|
+
if (branch.discriminantExpressionId) {
|
|
705
|
+
let targetId = branch.discriminantExpressionId;
|
|
706
|
+
|
|
707
|
+
// For CallExpression discriminants, look up the actual CALL_SITE by coordinates
|
|
708
|
+
// because CALL_SITE uses semantic IDs that don't match the generated ID
|
|
709
|
+
if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
|
|
710
|
+
const callSite = callSites.find(cs =>
|
|
711
|
+
cs.file === branch.file &&
|
|
712
|
+
cs.line === branch.discriminantLine &&
|
|
713
|
+
cs.column === branch.discriminantColumn
|
|
714
|
+
);
|
|
715
|
+
if (callSite) {
|
|
716
|
+
targetId = callSite.id;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
this._bufferEdge({
|
|
721
|
+
type: 'HAS_CONDITION',
|
|
722
|
+
src: branch.id,
|
|
723
|
+
dst: targetId
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
728
|
+
if (branch.branchType === 'if') {
|
|
729
|
+
// Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
|
|
730
|
+
const consequentScope = scopes.find(s =>
|
|
731
|
+
s.parentScopeId === branch.id && s.scopeType === 'if_statement'
|
|
732
|
+
);
|
|
733
|
+
if (consequentScope) {
|
|
734
|
+
this._bufferEdge({
|
|
735
|
+
type: 'HAS_CONSEQUENT',
|
|
736
|
+
src: branch.id,
|
|
737
|
+
dst: consequentScope.id
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
|
|
742
|
+
const alternateScope = scopes.find(s =>
|
|
743
|
+
s.parentScopeId === branch.id && s.scopeType === 'else_statement'
|
|
744
|
+
);
|
|
745
|
+
if (alternateScope) {
|
|
746
|
+
this._bufferEdge({
|
|
747
|
+
type: 'HAS_ALTERNATE',
|
|
748
|
+
src: branch.id,
|
|
749
|
+
dst: alternateScope.id
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// For else-if chains: if this branch is the alternate of another branch
|
|
754
|
+
// This is handled differently - see below
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// REG-287: For ternary branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges to expressions
|
|
758
|
+
if (branch.branchType === 'ternary') {
|
|
759
|
+
if (branch.consequentExpressionId) {
|
|
760
|
+
this._bufferEdge({
|
|
761
|
+
type: 'HAS_CONSEQUENT',
|
|
762
|
+
src: branch.id,
|
|
763
|
+
dst: branch.consequentExpressionId
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
if (branch.alternateExpressionId) {
|
|
767
|
+
this._bufferEdge({
|
|
768
|
+
type: 'HAS_ALTERNATE',
|
|
769
|
+
src: branch.id,
|
|
770
|
+
dst: branch.alternateExpressionId
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Phase 3: For else-if chains, create HAS_ALTERNATE from parent branch to this branch
|
|
776
|
+
if (branch.isAlternateOfBranchId) {
|
|
777
|
+
this._bufferEdge({
|
|
778
|
+
type: 'HAS_ALTERNATE',
|
|
779
|
+
src: branch.isAlternateOfBranchId,
|
|
780
|
+
dst: branch.id
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Buffer CASE edges (HAS_CASE, HAS_DEFAULT)
|
|
788
|
+
*/
|
|
789
|
+
private bufferCaseEdges(cases: CaseInfo[]): void {
|
|
790
|
+
for (const caseInfo of cases) {
|
|
791
|
+
// BRANCH -> HAS_CASE or HAS_DEFAULT -> CASE
|
|
792
|
+
const edgeType = caseInfo.isDefault ? 'HAS_DEFAULT' : 'HAS_CASE';
|
|
793
|
+
this._bufferEdge({
|
|
794
|
+
type: edgeType,
|
|
795
|
+
src: caseInfo.parentBranchId,
|
|
796
|
+
dst: caseInfo.id
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK nodes (Phase 4)
|
|
803
|
+
*
|
|
804
|
+
* Creates edges for:
|
|
805
|
+
* - Parent -> CONTAINS -> TRY_BLOCK
|
|
806
|
+
* - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
807
|
+
* - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
|
|
808
|
+
*/
|
|
809
|
+
private bufferTryCatchFinallyEdges(
|
|
810
|
+
tryBlocks: TryBlockInfo[],
|
|
811
|
+
catchBlocks: CatchBlockInfo[],
|
|
812
|
+
finallyBlocks: FinallyBlockInfo[]
|
|
813
|
+
): void {
|
|
814
|
+
// Buffer TRY_BLOCK edges
|
|
815
|
+
for (const tryBlock of tryBlocks) {
|
|
816
|
+
// Parent -> CONTAINS -> TRY_BLOCK
|
|
817
|
+
if (tryBlock.parentScopeId) {
|
|
818
|
+
this._bufferEdge({
|
|
819
|
+
type: 'CONTAINS',
|
|
820
|
+
src: tryBlock.parentScopeId,
|
|
821
|
+
dst: tryBlock.id
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Buffer CATCH_BLOCK edges (HAS_CATCH from TRY_BLOCK)
|
|
827
|
+
for (const catchBlock of catchBlocks) {
|
|
828
|
+
// TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
829
|
+
this._bufferEdge({
|
|
830
|
+
type: 'HAS_CATCH',
|
|
831
|
+
src: catchBlock.parentTryBlockId,
|
|
832
|
+
dst: catchBlock.id
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Buffer FINALLY_BLOCK edges (HAS_FINALLY from TRY_BLOCK)
|
|
837
|
+
for (const finallyBlock of finallyBlocks) {
|
|
838
|
+
// TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
|
|
839
|
+
this._bufferEdge({
|
|
840
|
+
type: 'HAS_FINALLY',
|
|
841
|
+
src: finallyBlock.parentTryBlockId,
|
|
842
|
+
dst: finallyBlock.id
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Buffer EXPRESSION nodes for switch discriminants
|
|
849
|
+
* Uses stored metadata directly instead of parsing from ID (Linus improvement)
|
|
850
|
+
*
|
|
851
|
+
* REG-275: For CallExpression discriminants, we don't create nodes here since
|
|
852
|
+
* bufferBranchEdges links to the existing CALL_SITE node by coordinates.
|
|
853
|
+
*/
|
|
854
|
+
private bufferDiscriminantExpressions(branches: BranchInfo[], callSites: CallSiteInfo[]): void {
|
|
855
|
+
for (const branch of branches) {
|
|
856
|
+
if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
|
|
857
|
+
// Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
|
|
858
|
+
if (branch.discriminantExpressionType === 'CallExpression') {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Only create if it looks like an EXPRESSION ID
|
|
863
|
+
if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
|
|
864
|
+
this._bufferNode({
|
|
865
|
+
id: branch.discriminantExpressionId,
|
|
866
|
+
type: 'EXPRESSION',
|
|
867
|
+
name: branch.discriminantExpressionType,
|
|
868
|
+
file: branch.file,
|
|
869
|
+
line: branch.discriminantLine,
|
|
870
|
+
column: branch.discriminantColumn,
|
|
871
|
+
expressionType: branch.discriminantExpressionType
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
305
878
|
private bufferVariableEdges(variableDeclarations: VariableDeclarationInfo[]): void {
|
|
306
879
|
for (const varDecl of variableDeclarations) {
|
|
307
880
|
const { parentScopeId, ...varData } = varDecl;
|
|
@@ -338,19 +911,56 @@ export class GraphBuilder {
|
|
|
338
911
|
}
|
|
339
912
|
}
|
|
340
913
|
|
|
341
|
-
private bufferMethodCalls(
|
|
914
|
+
private bufferMethodCalls(
|
|
915
|
+
methodCalls: MethodCallInfo[],
|
|
916
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
917
|
+
parameters: ParameterInfo[]
|
|
918
|
+
): void {
|
|
342
919
|
for (const methodCall of methodCalls) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Buffer METHOD_CALL node
|
|
346
|
-
this._bufferNode(methodData as GraphNode);
|
|
920
|
+
// Keep parentScopeId on node for queries
|
|
921
|
+
this._bufferNode(methodCall as unknown as GraphNode);
|
|
347
922
|
|
|
348
923
|
// SCOPE -> CONTAINS -> METHOD_CALL
|
|
349
924
|
this._bufferEdge({
|
|
350
925
|
type: 'CONTAINS',
|
|
351
|
-
src: parentScopeId as string,
|
|
352
|
-
dst:
|
|
926
|
+
src: methodCall.parentScopeId as string,
|
|
927
|
+
dst: methodCall.id
|
|
353
928
|
});
|
|
929
|
+
|
|
930
|
+
// REG-262: Create USES edge from METHOD_CALL to receiver variable
|
|
931
|
+
// Skip 'this' - it's not a variable node
|
|
932
|
+
if (methodCall.object && methodCall.object !== 'this') {
|
|
933
|
+
// Handle nested member expressions: obj.nested.method() -> use base 'obj'
|
|
934
|
+
const receiverName = methodCall.object.includes('.')
|
|
935
|
+
? methodCall.object.split('.')[0]
|
|
936
|
+
: methodCall.object;
|
|
937
|
+
|
|
938
|
+
// Find receiver variable in current file
|
|
939
|
+
const receiverVar = variableDeclarations.find(v =>
|
|
940
|
+
v.name === receiverName && v.file === methodCall.file
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
if (receiverVar) {
|
|
944
|
+
this._bufferEdge({
|
|
945
|
+
type: 'USES',
|
|
946
|
+
src: methodCall.id,
|
|
947
|
+
dst: receiverVar.id
|
|
948
|
+
});
|
|
949
|
+
} else {
|
|
950
|
+
// Check parameters (function arguments)
|
|
951
|
+
const receiverParam = parameters.find(p =>
|
|
952
|
+
p.name === receiverName && p.file === methodCall.file
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
if (receiverParam) {
|
|
956
|
+
this._bufferEdge({
|
|
957
|
+
type: 'USES',
|
|
958
|
+
src: methodCall.id,
|
|
959
|
+
dst: receiverParam.id
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
354
964
|
}
|
|
355
965
|
}
|
|
356
966
|
|
|
@@ -360,16 +970,12 @@ export class GraphBuilder {
|
|
|
360
970
|
);
|
|
361
971
|
|
|
362
972
|
if (consoleIOMethods.length > 0) {
|
|
363
|
-
const
|
|
973
|
+
const stdioNode = NodeFactory.createExternalStdio();
|
|
974
|
+
|
|
364
975
|
// Buffer net:stdio node only once (singleton)
|
|
365
|
-
if (!this._createdSingletons.has(
|
|
366
|
-
this._bufferNode(
|
|
367
|
-
|
|
368
|
-
type: 'net:stdio',
|
|
369
|
-
name: '__stdio__',
|
|
370
|
-
description: 'Standard input/output stream'
|
|
371
|
-
});
|
|
372
|
-
this._createdSingletons.add(stdioId);
|
|
976
|
+
if (!this._createdSingletons.has(stdioNode.id)) {
|
|
977
|
+
this._bufferNode(stdioNode as unknown as GraphNode);
|
|
978
|
+
this._createdSingletons.add(stdioNode.id);
|
|
373
979
|
}
|
|
374
980
|
|
|
375
981
|
// Buffer WRITES_TO edges for console.log/error
|
|
@@ -377,7 +983,7 @@ export class GraphBuilder {
|
|
|
377
983
|
this._bufferEdge({
|
|
378
984
|
type: 'WRITES_TO',
|
|
379
985
|
src: methodCall.id,
|
|
380
|
-
dst:
|
|
986
|
+
dst: stdioNode.id
|
|
381
987
|
});
|
|
382
988
|
}
|
|
383
989
|
}
|
|
@@ -407,9 +1013,14 @@ export class GraphBuilder {
|
|
|
407
1013
|
});
|
|
408
1014
|
}
|
|
409
1015
|
|
|
410
|
-
// If superClass, buffer DERIVES_FROM edge
|
|
1016
|
+
// If superClass, buffer DERIVES_FROM edge with computed ID
|
|
411
1017
|
if (superClass) {
|
|
412
|
-
|
|
1018
|
+
// Compute superclass ID using semantic ID format
|
|
1019
|
+
// Assume superclass is in same file at global scope (most common case)
|
|
1020
|
+
// When superclass is in different file, edge will be dangling until that file analyzed
|
|
1021
|
+
const globalContext = { file, scopePath: [] as string[] };
|
|
1022
|
+
const superClassId = computeSemanticId('CLASS', superClass, globalContext);
|
|
1023
|
+
|
|
413
1024
|
this._bufferEdge({
|
|
414
1025
|
type: 'DERIVES_FROM',
|
|
415
1026
|
src: id,
|
|
@@ -421,9 +1032,11 @@ export class GraphBuilder {
|
|
|
421
1032
|
|
|
422
1033
|
private bufferClassNodes(module: ModuleNode, classInstantiations: ClassInstantiationInfo[], classDeclarations: ClassDeclarationInfo[]): void {
|
|
423
1034
|
// Create lookup map: className → declaration ID
|
|
1035
|
+
// Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
|
|
1036
|
+
const moduleBasename = basename(module.file);
|
|
424
1037
|
const declarationMap = new Map<string, string>();
|
|
425
1038
|
for (const decl of classDeclarations) {
|
|
426
|
-
if (decl.file ===
|
|
1039
|
+
if (decl.file === moduleBasename) {
|
|
427
1040
|
declarationMap.set(decl.name, decl.id);
|
|
428
1041
|
}
|
|
429
1042
|
}
|
|
@@ -434,16 +1047,13 @@ export class GraphBuilder {
|
|
|
434
1047
|
let classId = declarationMap.get(className);
|
|
435
1048
|
|
|
436
1049
|
if (!classId) {
|
|
437
|
-
// External class -
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
line,
|
|
445
|
-
isInstantiationRef: true
|
|
446
|
-
});
|
|
1050
|
+
// External class - compute semantic ID
|
|
1051
|
+
// Use basename to match CLASS node format (scopeTracker uses basename)
|
|
1052
|
+
// When class is in different file, edge will be dangling until that file analyzed
|
|
1053
|
+
const globalContext = { file: moduleBasename, scopePath: [] as string[] };
|
|
1054
|
+
classId = computeSemanticId('CLASS', className, globalContext);
|
|
1055
|
+
|
|
1056
|
+
// NO node creation - node will exist when class file analyzed
|
|
447
1057
|
}
|
|
448
1058
|
|
|
449
1059
|
// Buffer INSTANCE_OF edge
|
|
@@ -475,50 +1085,98 @@ export class GraphBuilder {
|
|
|
475
1085
|
|
|
476
1086
|
private bufferImportNodes(module: ModuleNode, imports: ImportInfo[]): void {
|
|
477
1087
|
for (const imp of imports) {
|
|
478
|
-
const { source, specifiers, line } = imp;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
1088
|
+
const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
|
|
1089
|
+
|
|
1090
|
+
// REG-273: Handle side-effect-only imports (no specifiers)
|
|
1091
|
+
if (specifiers.length === 0) {
|
|
1092
|
+
// Side-effect import: import './polyfill.js'
|
|
1093
|
+
const importNode = ImportNode.create(
|
|
1094
|
+
source, // name = source (no local binding)
|
|
1095
|
+
module.file, // file
|
|
1096
|
+
line, // line (stored as field, not in ID)
|
|
1097
|
+
column || 0, // column
|
|
1098
|
+
source, // source module
|
|
1099
|
+
{
|
|
1100
|
+
imported: '*', // no specific export
|
|
1101
|
+
local: source, // source becomes local
|
|
1102
|
+
sideEffect: true // mark as side-effect import
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
485
1105
|
|
|
486
|
-
this._bufferNode(
|
|
487
|
-
id: importId,
|
|
488
|
-
type: 'IMPORT',
|
|
489
|
-
source: source,
|
|
490
|
-
importType: importType,
|
|
491
|
-
imported: spec.imported,
|
|
492
|
-
local: spec.local,
|
|
493
|
-
file: module.file,
|
|
494
|
-
line: line
|
|
495
|
-
});
|
|
1106
|
+
this._bufferNode(importNode as unknown as GraphNode);
|
|
496
1107
|
|
|
497
1108
|
// MODULE -> CONTAINS -> IMPORT
|
|
498
1109
|
this._bufferEdge({
|
|
499
1110
|
type: 'CONTAINS',
|
|
500
1111
|
src: module.id,
|
|
501
|
-
dst:
|
|
1112
|
+
dst: importNode.id
|
|
502
1113
|
});
|
|
503
1114
|
|
|
504
1115
|
// Create EXTERNAL_MODULE node for external modules
|
|
505
1116
|
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
506
1117
|
if (!isRelative) {
|
|
507
|
-
const
|
|
1118
|
+
const externalModule = NodeFactory.createExternalModule(source);
|
|
508
1119
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
line: line
|
|
515
|
-
});
|
|
1120
|
+
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
1121
|
+
if (!this._createdSingletons.has(externalModule.id)) {
|
|
1122
|
+
this._bufferNode(externalModule as unknown as GraphNode);
|
|
1123
|
+
this._createdSingletons.add(externalModule.id);
|
|
1124
|
+
}
|
|
516
1125
|
|
|
517
1126
|
this._bufferEdge({
|
|
518
1127
|
type: 'IMPORTS',
|
|
519
1128
|
src: module.id,
|
|
520
|
-
dst:
|
|
1129
|
+
dst: externalModule.id
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
// Regular imports with specifiers
|
|
1134
|
+
for (const spec of specifiers) {
|
|
1135
|
+
// Use ImportNode factory for proper semantic IDs and field population
|
|
1136
|
+
const importNode = ImportNode.create(
|
|
1137
|
+
spec.local, // name = local binding
|
|
1138
|
+
module.file, // file
|
|
1139
|
+
line, // line (stored as field, not in ID)
|
|
1140
|
+
column || 0, // column
|
|
1141
|
+
source, // source module
|
|
1142
|
+
{
|
|
1143
|
+
imported: spec.imported,
|
|
1144
|
+
local: spec.local,
|
|
1145
|
+
sideEffect: false, // regular imports are not side-effects
|
|
1146
|
+
// importType is auto-detected from imported field
|
|
1147
|
+
// Dynamic import fields
|
|
1148
|
+
isDynamic,
|
|
1149
|
+
isResolvable,
|
|
1150
|
+
dynamicPath
|
|
1151
|
+
}
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
this._bufferNode(importNode as unknown as GraphNode);
|
|
1155
|
+
|
|
1156
|
+
// MODULE -> CONTAINS -> IMPORT
|
|
1157
|
+
this._bufferEdge({
|
|
1158
|
+
type: 'CONTAINS',
|
|
1159
|
+
src: module.id,
|
|
1160
|
+
dst: importNode.id
|
|
521
1161
|
});
|
|
1162
|
+
|
|
1163
|
+
// Create EXTERNAL_MODULE node for external modules
|
|
1164
|
+
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
1165
|
+
if (!isRelative) {
|
|
1166
|
+
const externalModule = NodeFactory.createExternalModule(source);
|
|
1167
|
+
|
|
1168
|
+
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
1169
|
+
if (!this._createdSingletons.has(externalModule.id)) {
|
|
1170
|
+
this._bufferNode(externalModule as unknown as GraphNode);
|
|
1171
|
+
this._createdSingletons.add(externalModule.id);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
this._bufferEdge({
|
|
1175
|
+
type: 'IMPORTS',
|
|
1176
|
+
src: module.id,
|
|
1177
|
+
dst: externalModule.id
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
522
1180
|
}
|
|
523
1181
|
}
|
|
524
1182
|
}
|
|
@@ -529,79 +1187,79 @@ export class GraphBuilder {
|
|
|
529
1187
|
const { type, line, name, specifiers, source } = exp;
|
|
530
1188
|
|
|
531
1189
|
if (type === 'default') {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
exportType: 'default'
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
});
|
|
1190
|
+
const exportNode = NodeFactory.createExport(
|
|
1191
|
+
'default',
|
|
1192
|
+
module.file,
|
|
1193
|
+
line,
|
|
1194
|
+
0,
|
|
1195
|
+
{ default: true, exportType: 'default' }
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
this._bufferNode(exportNode as unknown as GraphNode);
|
|
542
1199
|
|
|
543
1200
|
this._bufferEdge({
|
|
544
1201
|
type: 'CONTAINS',
|
|
545
1202
|
src: module.id,
|
|
546
|
-
dst:
|
|
1203
|
+
dst: exportNode.id
|
|
547
1204
|
});
|
|
548
1205
|
} else if (type === 'named') {
|
|
549
1206
|
if (specifiers) {
|
|
550
1207
|
for (const spec of specifiers) {
|
|
551
|
-
const
|
|
1208
|
+
const exportNode = NodeFactory.createExport(
|
|
1209
|
+
spec.exported,
|
|
1210
|
+
module.file,
|
|
1211
|
+
line,
|
|
1212
|
+
0,
|
|
1213
|
+
{
|
|
1214
|
+
local: spec.local,
|
|
1215
|
+
source: source,
|
|
1216
|
+
exportType: 'named'
|
|
1217
|
+
}
|
|
1218
|
+
);
|
|
552
1219
|
|
|
553
|
-
this._bufferNode(
|
|
554
|
-
id: exportId,
|
|
555
|
-
type: 'EXPORT',
|
|
556
|
-
exportType: 'named',
|
|
557
|
-
name: spec.exported,
|
|
558
|
-
local: spec.local,
|
|
559
|
-
file: module.file,
|
|
560
|
-
line: line,
|
|
561
|
-
source: source
|
|
562
|
-
});
|
|
1220
|
+
this._bufferNode(exportNode as unknown as GraphNode);
|
|
563
1221
|
|
|
564
1222
|
this._bufferEdge({
|
|
565
1223
|
type: 'CONTAINS',
|
|
566
1224
|
src: module.id,
|
|
567
|
-
dst:
|
|
1225
|
+
dst: exportNode.id
|
|
568
1226
|
});
|
|
569
1227
|
}
|
|
570
1228
|
} else if (name) {
|
|
571
|
-
const
|
|
1229
|
+
const exportNode = NodeFactory.createExport(
|
|
1230
|
+
name,
|
|
1231
|
+
module.file,
|
|
1232
|
+
line,
|
|
1233
|
+
0,
|
|
1234
|
+
{ exportType: 'named' }
|
|
1235
|
+
);
|
|
572
1236
|
|
|
573
|
-
this._bufferNode(
|
|
574
|
-
id: exportId,
|
|
575
|
-
type: 'EXPORT',
|
|
576
|
-
exportType: 'named',
|
|
577
|
-
name: name,
|
|
578
|
-
file: module.file,
|
|
579
|
-
line: line
|
|
580
|
-
});
|
|
1237
|
+
this._bufferNode(exportNode as unknown as GraphNode);
|
|
581
1238
|
|
|
582
1239
|
this._bufferEdge({
|
|
583
1240
|
type: 'CONTAINS',
|
|
584
1241
|
src: module.id,
|
|
585
|
-
dst:
|
|
1242
|
+
dst: exportNode.id
|
|
586
1243
|
});
|
|
587
1244
|
}
|
|
588
1245
|
} else if (type === 'all') {
|
|
589
|
-
const
|
|
1246
|
+
const exportNode = NodeFactory.createExport(
|
|
1247
|
+
'*',
|
|
1248
|
+
module.file,
|
|
1249
|
+
line,
|
|
1250
|
+
0,
|
|
1251
|
+
{
|
|
1252
|
+
source: source,
|
|
1253
|
+
exportType: 'all'
|
|
1254
|
+
}
|
|
1255
|
+
);
|
|
590
1256
|
|
|
591
|
-
this._bufferNode(
|
|
592
|
-
id: exportId,
|
|
593
|
-
type: 'EXPORT',
|
|
594
|
-
exportType: 'all',
|
|
595
|
-
name: '*',
|
|
596
|
-
file: module.file,
|
|
597
|
-
line: line,
|
|
598
|
-
source: source
|
|
599
|
-
});
|
|
1257
|
+
this._bufferNode(exportNode as unknown as GraphNode);
|
|
600
1258
|
|
|
601
1259
|
this._bufferEdge({
|
|
602
1260
|
type: 'CONTAINS',
|
|
603
1261
|
src: module.id,
|
|
604
|
-
dst:
|
|
1262
|
+
dst: exportNode.id
|
|
605
1263
|
});
|
|
606
1264
|
}
|
|
607
1265
|
}
|
|
@@ -638,15 +1296,12 @@ export class GraphBuilder {
|
|
|
638
1296
|
|
|
639
1297
|
private bufferHttpRequests(httpRequests: HttpRequestInfo[], functions: FunctionInfo[]): void {
|
|
640
1298
|
if (httpRequests.length > 0) {
|
|
641
|
-
|
|
1299
|
+
// Create net:request singleton using factory
|
|
1300
|
+
const networkNode = NetworkRequestNode.create();
|
|
642
1301
|
|
|
643
|
-
if (!this._createdSingletons.has(
|
|
644
|
-
this._bufferNode(
|
|
645
|
-
|
|
646
|
-
type: 'net:request',
|
|
647
|
-
name: '__network__'
|
|
648
|
-
});
|
|
649
|
-
this._createdSingletons.add(networkId);
|
|
1302
|
+
if (!this._createdSingletons.has(networkNode.id)) {
|
|
1303
|
+
this._bufferNode(networkNode as unknown as GraphNode);
|
|
1304
|
+
this._createdSingletons.add(networkNode.id);
|
|
650
1305
|
}
|
|
651
1306
|
|
|
652
1307
|
for (const request of httpRequests) {
|
|
@@ -657,7 +1312,7 @@ export class GraphBuilder {
|
|
|
657
1312
|
this._bufferEdge({
|
|
658
1313
|
type: 'CALLS',
|
|
659
1314
|
src: request.id,
|
|
660
|
-
dst:
|
|
1315
|
+
dst: networkNode.id
|
|
661
1316
|
});
|
|
662
1317
|
|
|
663
1318
|
if (parentScopeId) {
|
|
@@ -710,6 +1365,7 @@ export class GraphBuilder {
|
|
|
710
1365
|
sourceFile,
|
|
711
1366
|
functionName,
|
|
712
1367
|
line,
|
|
1368
|
+
column,
|
|
713
1369
|
className
|
|
714
1370
|
} = assignment;
|
|
715
1371
|
|
|
@@ -718,6 +1374,29 @@ export class GraphBuilder {
|
|
|
718
1374
|
continue;
|
|
719
1375
|
}
|
|
720
1376
|
|
|
1377
|
+
// CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
|
|
1378
|
+
// Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
|
|
1379
|
+
if (sourceType === 'CONSTRUCTOR_CALL' && className) {
|
|
1380
|
+
const constructorLine = line ?? 0;
|
|
1381
|
+
const constructorColumn = column ?? 0;
|
|
1382
|
+
const constructorFile = assignment.file ?? '';
|
|
1383
|
+
|
|
1384
|
+
// Generate ID matching the one created in NewExpression visitor
|
|
1385
|
+
const constructorCallId = NodeFactory.generateConstructorCallId(
|
|
1386
|
+
className,
|
|
1387
|
+
constructorFile,
|
|
1388
|
+
constructorLine,
|
|
1389
|
+
constructorColumn
|
|
1390
|
+
);
|
|
1391
|
+
|
|
1392
|
+
this._bufferEdge({
|
|
1393
|
+
type: 'ASSIGNED_FROM',
|
|
1394
|
+
src: variableId,
|
|
1395
|
+
dst: constructorCallId
|
|
1396
|
+
});
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
721
1400
|
// Direct LITERAL assignment
|
|
722
1401
|
if (sourceId && sourceType !== 'EXPRESSION') {
|
|
723
1402
|
this._bufferEdge({
|
|
@@ -766,8 +1445,10 @@ export class GraphBuilder {
|
|
|
766
1445
|
}
|
|
767
1446
|
// VARIABLE by name
|
|
768
1447
|
else if (sourceType === 'VARIABLE' && sourceName) {
|
|
769
|
-
|
|
770
|
-
|
|
1448
|
+
// Find the current variable's file by looking it up in variableDeclarations
|
|
1449
|
+
// (semantic IDs don't have predictable file positions like old hash-based IDs)
|
|
1450
|
+
const currentVar = variableDeclarations.find(v => v.id === variableId);
|
|
1451
|
+
const varFile = currentVar?.file ?? null;
|
|
771
1452
|
const sourceVariable = variableDeclarations.find(v =>
|
|
772
1453
|
v.name === sourceName && v.file === varFile
|
|
773
1454
|
);
|
|
@@ -806,7 +1487,7 @@ export class GraphBuilder {
|
|
|
806
1487
|
});
|
|
807
1488
|
}
|
|
808
1489
|
}
|
|
809
|
-
// EXPRESSION node creation
|
|
1490
|
+
// EXPRESSION node creation using NodeFactory
|
|
810
1491
|
else if (sourceType === 'EXPRESSION' && sourceId) {
|
|
811
1492
|
const {
|
|
812
1493
|
expressionType,
|
|
@@ -821,33 +1502,35 @@ export class GraphBuilder {
|
|
|
821
1502
|
consequentSourceName,
|
|
822
1503
|
alternateSourceName,
|
|
823
1504
|
file: exprFile,
|
|
824
|
-
line: exprLine
|
|
1505
|
+
line: exprLine,
|
|
1506
|
+
column: exprColumn,
|
|
1507
|
+
// Destructuring support (REG-201)
|
|
1508
|
+
path,
|
|
1509
|
+
baseName,
|
|
1510
|
+
propertyPath,
|
|
1511
|
+
arrayIndex
|
|
825
1512
|
} = assignment;
|
|
826
1513
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1514
|
+
// Create node from upstream metadata using factory
|
|
1515
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
1516
|
+
expressionType || 'Unknown',
|
|
1517
|
+
exprFile || '',
|
|
1518
|
+
exprLine || 0,
|
|
1519
|
+
exprColumn || 0,
|
|
1520
|
+
{
|
|
1521
|
+
id: sourceId, // ID from JSASTAnalyzer
|
|
1522
|
+
object,
|
|
1523
|
+
property,
|
|
1524
|
+
computed,
|
|
1525
|
+
computedPropertyVar: computedPropertyVar ?? undefined,
|
|
1526
|
+
operator,
|
|
1527
|
+
// Destructuring support (REG-201)
|
|
1528
|
+
path,
|
|
1529
|
+
baseName,
|
|
1530
|
+
propertyPath,
|
|
1531
|
+
arrayIndex
|
|
841
1532
|
}
|
|
842
|
-
|
|
843
|
-
} else if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
844
|
-
expressionNode.operator = operator;
|
|
845
|
-
expressionNode.name = `<${expressionType}>`;
|
|
846
|
-
} else if (expressionType === 'ConditionalExpression') {
|
|
847
|
-
expressionNode.name = '<ternary>';
|
|
848
|
-
} else if (expressionType === 'TemplateLiteral') {
|
|
849
|
-
expressionNode.name = '<template>';
|
|
850
|
-
}
|
|
1533
|
+
);
|
|
851
1534
|
|
|
852
1535
|
this._bufferNode(expressionNode);
|
|
853
1536
|
|
|
@@ -873,6 +1556,50 @@ export class GraphBuilder {
|
|
|
873
1556
|
});
|
|
874
1557
|
}
|
|
875
1558
|
}
|
|
1559
|
+
// Call-based source lookup (REG-223)
|
|
1560
|
+
else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
|
|
1561
|
+
const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
|
|
1562
|
+
|
|
1563
|
+
// Try CALL_SITE first (direct function calls)
|
|
1564
|
+
const callSite = callSites.find(cs =>
|
|
1565
|
+
cs.line === callSourceLine &&
|
|
1566
|
+
cs.column === callSourceColumn &&
|
|
1567
|
+
(callSourceName ? cs.name === callSourceName : true)
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
if (callSite) {
|
|
1571
|
+
this._bufferEdge({
|
|
1572
|
+
type: 'DERIVES_FROM',
|
|
1573
|
+
src: sourceId,
|
|
1574
|
+
dst: callSite.id
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
// Fall back to methodCalls (arr.map(), obj.getConfig())
|
|
1578
|
+
else {
|
|
1579
|
+
const methodCall = methodCalls.find(mc =>
|
|
1580
|
+
mc.line === callSourceLine &&
|
|
1581
|
+
mc.column === callSourceColumn &&
|
|
1582
|
+
(callSourceName ? mc.name === callSourceName : true)
|
|
1583
|
+
);
|
|
1584
|
+
|
|
1585
|
+
if (methodCall) {
|
|
1586
|
+
this._bufferEdge({
|
|
1587
|
+
type: 'DERIVES_FROM',
|
|
1588
|
+
src: sourceId,
|
|
1589
|
+
dst: methodCall.id
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
// Log warning when lookup fails (per Linus review - no silent failures)
|
|
1593
|
+
else {
|
|
1594
|
+
console.warn(
|
|
1595
|
+
`[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
|
|
1596
|
+
`at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
|
|
1597
|
+
`Expected CALL_SITE or methodCall for "${callSourceName}". ` +
|
|
1598
|
+
`This indicates a coordinate mismatch or missing call node.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
876
1603
|
|
|
877
1604
|
if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
|
|
878
1605
|
if (leftSourceName) {
|
|
@@ -1029,6 +1756,12 @@ export class GraphBuilder {
|
|
|
1029
1756
|
targetNodeId = nestedCall.id;
|
|
1030
1757
|
}
|
|
1031
1758
|
}
|
|
1759
|
+
else if (targetType === 'LITERAL' ||
|
|
1760
|
+
targetType === 'OBJECT_LITERAL' ||
|
|
1761
|
+
targetType === 'ARRAY_LITERAL') {
|
|
1762
|
+
// targetId is already set by CallExpressionVisitor
|
|
1763
|
+
targetNodeId = targetId;
|
|
1764
|
+
}
|
|
1032
1765
|
|
|
1033
1766
|
if (targetNodeId) {
|
|
1034
1767
|
const edgeData: GraphEdge = {
|
|
@@ -1051,54 +1784,66 @@ export class GraphBuilder {
|
|
|
1051
1784
|
|
|
1052
1785
|
/**
|
|
1053
1786
|
* Buffer INTERFACE nodes and EXTENDS edges
|
|
1787
|
+
*
|
|
1788
|
+
* Uses two-pass approach:
|
|
1789
|
+
* 1. First pass: create all interface nodes, store in Map
|
|
1790
|
+
* 2. Second pass: create EXTENDS edges using stored node IDs
|
|
1054
1791
|
*/
|
|
1055
1792
|
private bufferInterfaceNodes(module: ModuleNode, interfaces: InterfaceDeclarationInfo[]): void {
|
|
1793
|
+
// First pass: create all interface nodes and store them
|
|
1794
|
+
const interfaceNodes = new Map<string, InterfaceNodeRecord>();
|
|
1795
|
+
|
|
1056
1796
|
for (const iface of interfaces) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1797
|
+
const interfaceNode = InterfaceNode.create(
|
|
1798
|
+
iface.name,
|
|
1799
|
+
iface.file,
|
|
1800
|
+
iface.line,
|
|
1801
|
+
iface.column || 0,
|
|
1802
|
+
{
|
|
1803
|
+
extends: iface.extends,
|
|
1804
|
+
properties: iface.properties
|
|
1805
|
+
}
|
|
1806
|
+
);
|
|
1807
|
+
interfaceNodes.set(iface.name, interfaceNode);
|
|
1808
|
+
this._bufferNode(interfaceNode as unknown as GraphNode);
|
|
1068
1809
|
|
|
1069
1810
|
// MODULE -> CONTAINS -> INTERFACE
|
|
1070
1811
|
this._bufferEdge({
|
|
1071
1812
|
type: 'CONTAINS',
|
|
1072
1813
|
src: module.id,
|
|
1073
|
-
dst:
|
|
1814
|
+
dst: interfaceNode.id
|
|
1074
1815
|
});
|
|
1816
|
+
}
|
|
1075
1817
|
|
|
1076
|
-
|
|
1818
|
+
// Second pass: create EXTENDS edges
|
|
1819
|
+
for (const iface of interfaces) {
|
|
1077
1820
|
if (iface.extends && iface.extends.length > 0) {
|
|
1821
|
+
const srcNode = interfaceNodes.get(iface.name)!;
|
|
1822
|
+
|
|
1078
1823
|
for (const parentName of iface.extends) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
if (
|
|
1824
|
+
const parentNode = interfaceNodes.get(parentName);
|
|
1825
|
+
|
|
1826
|
+
if (parentNode) {
|
|
1827
|
+
// Same-file interface
|
|
1082
1828
|
this._bufferEdge({
|
|
1083
1829
|
type: 'EXTENDS',
|
|
1084
|
-
src:
|
|
1085
|
-
dst:
|
|
1830
|
+
src: srcNode.id,
|
|
1831
|
+
dst: parentNode.id
|
|
1086
1832
|
});
|
|
1087
1833
|
} else {
|
|
1088
1834
|
// External interface - create a reference node
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
});
|
|
1835
|
+
const externalInterface = NodeFactory.createInterface(
|
|
1836
|
+
parentName,
|
|
1837
|
+
iface.file,
|
|
1838
|
+
iface.line,
|
|
1839
|
+
0,
|
|
1840
|
+
{ isExternal: true }
|
|
1841
|
+
);
|
|
1842
|
+
this._bufferNode(externalInterface as unknown as GraphNode);
|
|
1098
1843
|
this._bufferEdge({
|
|
1099
1844
|
type: 'EXTENDS',
|
|
1100
|
-
src:
|
|
1101
|
-
dst:
|
|
1845
|
+
src: srcNode.id,
|
|
1846
|
+
dst: externalInterface.id
|
|
1102
1847
|
});
|
|
1103
1848
|
}
|
|
1104
1849
|
}
|
|
@@ -1111,48 +1856,51 @@ export class GraphBuilder {
|
|
|
1111
1856
|
*/
|
|
1112
1857
|
private bufferTypeAliasNodes(module: ModuleNode, typeAliases: TypeAliasInfo[]): void {
|
|
1113
1858
|
for (const typeAlias of typeAliases) {
|
|
1114
|
-
//
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
});
|
|
1859
|
+
// Create TYPE node using factory
|
|
1860
|
+
const typeNode = NodeFactory.createType(
|
|
1861
|
+
typeAlias.name,
|
|
1862
|
+
typeAlias.file,
|
|
1863
|
+
typeAlias.line,
|
|
1864
|
+
typeAlias.column || 0,
|
|
1865
|
+
{ aliasOf: typeAlias.aliasOf }
|
|
1866
|
+
);
|
|
1867
|
+
this._bufferNode(typeNode as unknown as GraphNode);
|
|
1124
1868
|
|
|
1125
1869
|
// MODULE -> CONTAINS -> TYPE
|
|
1126
1870
|
this._bufferEdge({
|
|
1127
1871
|
type: 'CONTAINS',
|
|
1128
1872
|
src: module.id,
|
|
1129
|
-
dst:
|
|
1873
|
+
dst: typeNode.id
|
|
1130
1874
|
});
|
|
1131
1875
|
}
|
|
1132
1876
|
}
|
|
1133
1877
|
|
|
1134
1878
|
/**
|
|
1135
1879
|
* Buffer ENUM nodes
|
|
1880
|
+
* Uses EnumNode.create() to ensure consistent ID format (colon separator)
|
|
1136
1881
|
*/
|
|
1137
1882
|
private bufferEnumNodes(module: ModuleNode, enums: EnumDeclarationInfo[]): void {
|
|
1138
1883
|
for (const enumDecl of enums) {
|
|
1139
|
-
//
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1884
|
+
// Use EnumNode.create() to generate proper ID (colon format)
|
|
1885
|
+
// Do NOT use enumDecl.id which has legacy # format from TypeScriptVisitor
|
|
1886
|
+
const enumNode = EnumNode.create(
|
|
1887
|
+
enumDecl.name,
|
|
1888
|
+
enumDecl.file,
|
|
1889
|
+
enumDecl.line,
|
|
1890
|
+
enumDecl.column || 0,
|
|
1891
|
+
{
|
|
1892
|
+
isConst: enumDecl.isConst || false,
|
|
1893
|
+
members: enumDecl.members || []
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
|
|
1897
|
+
this._bufferNode(enumNode as unknown as GraphNode);
|
|
1150
1898
|
|
|
1151
1899
|
// MODULE -> CONTAINS -> ENUM
|
|
1152
1900
|
this._bufferEdge({
|
|
1153
1901
|
type: 'CONTAINS',
|
|
1154
1902
|
src: module.id,
|
|
1155
|
-
dst:
|
|
1903
|
+
dst: enumNode.id // Use factory-generated ID (colon format)
|
|
1156
1904
|
});
|
|
1157
1905
|
}
|
|
1158
1906
|
}
|
|
@@ -1162,23 +1910,24 @@ export class GraphBuilder {
|
|
|
1162
1910
|
*/
|
|
1163
1911
|
private bufferDecoratorNodes(decorators: DecoratorInfo[]): void {
|
|
1164
1912
|
for (const decorator of decorators) {
|
|
1165
|
-
//
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
arguments: decorator.arguments
|
|
1174
|
-
|
|
1175
|
-
|
|
1913
|
+
// Create DECORATOR node using factory (generates colon-format ID)
|
|
1914
|
+
const decoratorNode = DecoratorNode.create(
|
|
1915
|
+
decorator.name,
|
|
1916
|
+
decorator.file,
|
|
1917
|
+
decorator.line,
|
|
1918
|
+
decorator.column || 0,
|
|
1919
|
+
decorator.targetId, // Now included in the node!
|
|
1920
|
+
decorator.targetType,
|
|
1921
|
+
{ arguments: decorator.arguments }
|
|
1922
|
+
);
|
|
1923
|
+
|
|
1924
|
+
this._bufferNode(decoratorNode as unknown as GraphNode);
|
|
1176
1925
|
|
|
1177
1926
|
// TARGET -> DECORATED_BY -> DECORATOR
|
|
1178
1927
|
this._bufferEdge({
|
|
1179
1928
|
type: 'DECORATED_BY',
|
|
1180
1929
|
src: decorator.targetId,
|
|
1181
|
-
dst:
|
|
1930
|
+
dst: decoratorNode.id // Use factory-generated ID (colon format)
|
|
1182
1931
|
});
|
|
1183
1932
|
}
|
|
1184
1933
|
}
|
|
@@ -1193,26 +1942,28 @@ export class GraphBuilder {
|
|
|
1193
1942
|
// Try to find the interface in the same file
|
|
1194
1943
|
const iface = interfaces.find(i => i.name === ifaceName);
|
|
1195
1944
|
if (iface) {
|
|
1945
|
+
// Compute interface ID using same formula as InterfaceNode.create()
|
|
1946
|
+
// Format: {file}:INTERFACE:{name}:{line}
|
|
1947
|
+
const interfaceId = `${iface.file}:INTERFACE:${iface.name}:${iface.line}`;
|
|
1196
1948
|
this._bufferEdge({
|
|
1197
1949
|
type: 'IMPLEMENTS',
|
|
1198
1950
|
src: classDecl.id,
|
|
1199
|
-
dst:
|
|
1951
|
+
dst: interfaceId
|
|
1200
1952
|
});
|
|
1201
1953
|
} else {
|
|
1202
1954
|
// External interface - create a reference node
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
});
|
|
1955
|
+
const externalInterface = NodeFactory.createInterface(
|
|
1956
|
+
ifaceName,
|
|
1957
|
+
classDecl.file,
|
|
1958
|
+
classDecl.line,
|
|
1959
|
+
0,
|
|
1960
|
+
{ isExternal: true }
|
|
1961
|
+
);
|
|
1962
|
+
this._bufferNode(externalInterface as unknown as GraphNode);
|
|
1212
1963
|
this._bufferEdge({
|
|
1213
1964
|
type: 'IMPLEMENTS',
|
|
1214
1965
|
src: classDecl.id,
|
|
1215
|
-
dst:
|
|
1966
|
+
dst: externalInterface.id
|
|
1216
1967
|
});
|
|
1217
1968
|
}
|
|
1218
1969
|
}
|
|
@@ -1221,148 +1972,1017 @@ export class GraphBuilder {
|
|
|
1221
1972
|
}
|
|
1222
1973
|
|
|
1223
1974
|
/**
|
|
1224
|
-
*
|
|
1975
|
+
* Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
1976
|
+
* Creates edges from inserted values to the array variable
|
|
1977
|
+
*
|
|
1978
|
+
* REG-117: Now handles nested mutations like obj.arr.push(item):
|
|
1979
|
+
* - For nested mutations, falls back to base object if array property not found
|
|
1980
|
+
* - Adds nestedProperty metadata for tracking
|
|
1981
|
+
*
|
|
1982
|
+
* OPTIMIZED: Uses Map-based lookup cache for O(1) variable lookups instead of O(n) find()
|
|
1225
1983
|
*/
|
|
1226
|
-
private
|
|
1227
|
-
|
|
1984
|
+
private bufferArrayMutationEdges(
|
|
1985
|
+
arrayMutations: ArrayMutationInfo[],
|
|
1986
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
1987
|
+
parameters: ParameterInfo[]
|
|
1988
|
+
): void {
|
|
1989
|
+
// Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
|
|
1228
1990
|
|
|
1229
|
-
for (const
|
|
1230
|
-
const {
|
|
1991
|
+
for (const mutation of arrayMutations) {
|
|
1992
|
+
const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
|
|
1231
1993
|
|
|
1232
|
-
|
|
1233
|
-
const parts = variableId.split('#');
|
|
1234
|
-
const file = parts.length >= 3 ? parts[2] : null;
|
|
1994
|
+
const scopePath = mutationScopePath ?? [];
|
|
1235
1995
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1996
|
+
// REG-117: For nested mutations (obj.arr.push), resolve target node
|
|
1997
|
+
let targetNodeId: string | null = null;
|
|
1998
|
+
let nestedProperty: string | undefined;
|
|
1999
|
+
|
|
2000
|
+
if (isNested && baseObjectName) {
|
|
2001
|
+
// Skip 'this.items.push' - 'this' is not a variable node
|
|
2002
|
+
if (baseObjectName === 'this') continue;
|
|
2003
|
+
|
|
2004
|
+
// Nested mutation: try base object lookup with scope chain (REG-309)
|
|
2005
|
+
const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
|
|
2006
|
+
const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
|
|
2007
|
+
targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
|
|
2008
|
+
nestedProperty = propertyName;
|
|
2009
|
+
} else {
|
|
2010
|
+
// Direct mutation: arr.push() (REG-309)
|
|
2011
|
+
const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
|
|
2012
|
+
const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
|
|
2013
|
+
targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (!targetNodeId) continue;
|
|
2017
|
+
|
|
2018
|
+
// Create FLOWS_INTO edges for each inserted value
|
|
2019
|
+
for (const arg of insertedValues) {
|
|
2020
|
+
if (arg.valueType === 'VARIABLE' && arg.valueName) {
|
|
2021
|
+
// Scope-aware lookup for source variable (REG-309)
|
|
2022
|
+
const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
|
|
2023
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
|
|
2024
|
+
const sourceNodeId = sourceVar?.id ?? sourceParam?.id;
|
|
2025
|
+
|
|
2026
|
+
if (sourceNodeId) {
|
|
2027
|
+
const edgeData: GraphEdge = {
|
|
2028
|
+
type: 'FLOWS_INTO',
|
|
2029
|
+
src: sourceNodeId,
|
|
2030
|
+
dst: targetNodeId,
|
|
2031
|
+
mutationMethod,
|
|
2032
|
+
argIndex: arg.argIndex
|
|
2033
|
+
};
|
|
2034
|
+
if (arg.isSpread) {
|
|
2035
|
+
edgeData.isSpread = true;
|
|
2036
|
+
}
|
|
2037
|
+
// REG-117: Add nested property metadata
|
|
2038
|
+
if (nestedProperty) {
|
|
2039
|
+
edgeData.nestedProperty = nestedProperty;
|
|
2040
|
+
}
|
|
2041
|
+
this._bufferEdge(edgeData);
|
|
1241
2042
|
}
|
|
1242
2043
|
}
|
|
2044
|
+
// For literals, object literals, etc. - we could create edges from LITERAL nodes
|
|
2045
|
+
// but for now we just track variable -> array flows
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
1243
2049
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
2050
|
+
/**
|
|
2051
|
+
* Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
2052
|
+
* Creates edges from source values to the object variable being mutated.
|
|
2053
|
+
*
|
|
2054
|
+
* REG-152: For 'this.prop = value' patterns inside classes, creates edges
|
|
2055
|
+
* to the CLASS node with mutationType: 'this_property'.
|
|
2056
|
+
*/
|
|
2057
|
+
private bufferObjectMutationEdges(
|
|
2058
|
+
objectMutations: ObjectMutationInfo[],
|
|
2059
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
2060
|
+
parameters: ParameterInfo[],
|
|
2061
|
+
functions: FunctionInfo[],
|
|
2062
|
+
classDeclarations: ClassDeclarationInfo[]
|
|
2063
|
+
): void {
|
|
2064
|
+
for (const mutation of objectMutations) {
|
|
2065
|
+
const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
|
|
2066
|
+
|
|
2067
|
+
const scopePath = mutationScopePath ?? [];
|
|
2068
|
+
|
|
2069
|
+
// Find the target node (object variable, parameter, or class for 'this')
|
|
2070
|
+
let objectNodeId: string | null = null;
|
|
2071
|
+
let effectiveMutationType: 'property' | 'computed' | 'assign' | 'spread' | 'this_property' = mutationType;
|
|
2072
|
+
|
|
2073
|
+
if (objectName !== 'this') {
|
|
2074
|
+
// Regular object - find variable, parameter, or function using scope chain (REG-309)
|
|
2075
|
+
const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
|
|
2076
|
+
const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
|
|
2077
|
+
const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
|
|
2078
|
+
objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
|
|
2079
|
+
if (!objectNodeId) continue;
|
|
2080
|
+
} else {
|
|
2081
|
+
// REG-152: 'this' mutations - find the CLASS node
|
|
2082
|
+
if (!enclosingClassName) continue; // Skip if no class context (e.g., standalone function)
|
|
2083
|
+
|
|
2084
|
+
// Compare using basename since classes use scopeTracker.file (basename)
|
|
2085
|
+
// but mutations use module.file (full path)
|
|
2086
|
+
const fileBasename = basename(file);
|
|
2087
|
+
const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
|
|
2088
|
+
objectNodeId = classDecl?.id ?? null;
|
|
2089
|
+
|
|
2090
|
+
if (!objectNodeId) continue; // Skip if class not found
|
|
2091
|
+
|
|
2092
|
+
// Use special mutation type to distinguish from regular property mutations
|
|
2093
|
+
effectiveMutationType = 'this_property';
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// Create FLOWS_INTO edge for VARIABLE value type
|
|
2097
|
+
if (value.valueType === 'VARIABLE' && value.valueName) {
|
|
2098
|
+
// Find the source: can be variable, parameter, or function using scope chain (REG-309)
|
|
2099
|
+
const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
|
|
2100
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
|
|
2101
|
+
const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
|
|
2102
|
+
const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
|
|
2103
|
+
|
|
2104
|
+
if (sourceNodeId && objectNodeId) {
|
|
2105
|
+
const edgeData: GraphEdge = {
|
|
2106
|
+
type: 'FLOWS_INTO',
|
|
2107
|
+
src: sourceNodeId,
|
|
2108
|
+
dst: objectNodeId,
|
|
2109
|
+
mutationType: effectiveMutationType,
|
|
2110
|
+
propertyName,
|
|
2111
|
+
computedPropertyVar // For enrichment phase resolution
|
|
2112
|
+
};
|
|
2113
|
+
if (value.argIndex !== undefined) {
|
|
2114
|
+
edgeData.argIndex = value.argIndex;
|
|
2115
|
+
}
|
|
2116
|
+
if (value.isSpread) {
|
|
2117
|
+
edgeData.isSpread = true;
|
|
2118
|
+
}
|
|
2119
|
+
this._bufferEdge(edgeData);
|
|
1251
2120
|
}
|
|
1252
2121
|
}
|
|
2122
|
+
// For literals, object literals, etc. - we just track variable -> object flows for now
|
|
1253
2123
|
}
|
|
2124
|
+
}
|
|
1254
2125
|
|
|
1255
|
-
|
|
2126
|
+
/**
|
|
2127
|
+
* Resolve variable by name using scope chain lookup (REG-309).
|
|
2128
|
+
* Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
|
|
2129
|
+
*
|
|
2130
|
+
* @param name - Variable name
|
|
2131
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
2132
|
+
* @param file - File path
|
|
2133
|
+
* @param variables - All variable declarations
|
|
2134
|
+
* @returns Variable declaration or null if not found
|
|
2135
|
+
*/
|
|
2136
|
+
private resolveVariableInScope(
|
|
2137
|
+
name: string,
|
|
2138
|
+
scopePath: string[],
|
|
2139
|
+
file: string,
|
|
2140
|
+
variables: VariableDeclarationInfo[]
|
|
2141
|
+
): VariableDeclarationInfo | null {
|
|
2142
|
+
// Try current scope, then parent, then grandparent, etc.
|
|
2143
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
2144
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
2145
|
+
|
|
2146
|
+
const matchingVar = variables.find(v => {
|
|
2147
|
+
if (v.name !== name || v.file !== file) return false;
|
|
2148
|
+
|
|
2149
|
+
// Variable ID IS the semantic ID (when scopeTracker was available during analysis)
|
|
2150
|
+
// Format: file->scope1->scope2->TYPE->name
|
|
2151
|
+
// Legacy format: VARIABLE#name#file#line:column:counter
|
|
2152
|
+
|
|
2153
|
+
// Try parsing as semantic ID
|
|
2154
|
+
const parsed = parseSemanticId(v.id);
|
|
2155
|
+
// REG-329: Check for both VARIABLE and CONSTANT (const declarations)
|
|
2156
|
+
if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
|
|
2157
|
+
// FIXED (REG-309): Handle module-level scope matching
|
|
2158
|
+
// Empty search scope [] should match semantic ID scope ['global']
|
|
2159
|
+
if (searchScopePath.length === 0) {
|
|
2160
|
+
return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
|
|
2161
|
+
}
|
|
2162
|
+
// Non-empty scope: exact match
|
|
2163
|
+
return this.scopePathsMatch(parsed.scopePath, searchScopePath);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// Legacy ID - assume module-level if no semantic ID
|
|
2167
|
+
return searchScopePath.length === 0;
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
if (matchingVar) return matchingVar;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
return null;
|
|
1256
2174
|
}
|
|
1257
2175
|
|
|
1258
2176
|
/**
|
|
1259
|
-
*
|
|
2177
|
+
* Resolve parameter by name using scope chain lookup (REG-309).
|
|
2178
|
+
* Same semantics as resolveVariableInScope but for parameters.
|
|
2179
|
+
*
|
|
2180
|
+
* @param name - Parameter name
|
|
2181
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
2182
|
+
* @param file - File path
|
|
2183
|
+
* @param parameters - All parameter declarations
|
|
2184
|
+
* @returns Parameter declaration or null if not found
|
|
1260
2185
|
*/
|
|
1261
|
-
private
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
2186
|
+
private resolveParameterInScope(
|
|
2187
|
+
name: string,
|
|
2188
|
+
scopePath: string[],
|
|
2189
|
+
file: string,
|
|
2190
|
+
parameters: ParameterInfo[]
|
|
2191
|
+
): ParameterInfo | null {
|
|
2192
|
+
// Parameters have semanticId field populated (unlike variables which use id field)
|
|
2193
|
+
return parameters.find(p => {
|
|
2194
|
+
if (p.name !== name || p.file !== file) return false;
|
|
2195
|
+
|
|
2196
|
+
if (p.semanticId) {
|
|
2197
|
+
const parsed = parseSemanticId(p.semanticId);
|
|
2198
|
+
if (parsed && parsed.type === 'PARAMETER') {
|
|
2199
|
+
// Check if parameter's scope matches any scope in the chain
|
|
2200
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
2201
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
2202
|
+
|
|
2203
|
+
// FIXED (REG-309): Handle module-level scope matching for parameters
|
|
2204
|
+
if (searchScopePath.length === 0) {
|
|
2205
|
+
if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
|
|
2206
|
+
return true;
|
|
2207
|
+
}
|
|
2208
|
+
} else {
|
|
2209
|
+
if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
|
|
2210
|
+
return true;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return false;
|
|
2217
|
+
}) ?? null;
|
|
2218
|
+
}
|
|
1269
2219
|
|
|
1270
|
-
|
|
1271
|
-
|
|
2220
|
+
/**
|
|
2221
|
+
* Check if two scope paths match (REG-309).
|
|
2222
|
+
* Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
|
|
2223
|
+
*/
|
|
2224
|
+
private scopePathsMatch(a: string[], b: string[]): boolean {
|
|
2225
|
+
if (a.length !== b.length) return false;
|
|
2226
|
+
return a.every((item, idx) => item === b[idx]);
|
|
2227
|
+
}
|
|
1272
2228
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
2229
|
+
/**
|
|
2230
|
+
* Buffer FLOWS_INTO edges for variable reassignments.
|
|
2231
|
+
* Handles: x = y, x += y (when x is already declared, not initialization)
|
|
2232
|
+
*
|
|
2233
|
+
* Edge patterns:
|
|
2234
|
+
* - Simple assignment (=): source --FLOWS_INTO--> variable
|
|
2235
|
+
* - Compound operators (+=, -=, etc.):
|
|
2236
|
+
* - source --FLOWS_INTO--> variable (write new value)
|
|
2237
|
+
* - variable --READS_FROM--> variable (self-loop: reads current value before write)
|
|
2238
|
+
*
|
|
2239
|
+
* REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
|
|
2240
|
+
*
|
|
2241
|
+
* REG-290: Complete implementation with inline node creation (no continue statements).
|
|
2242
|
+
*/
|
|
2243
|
+
private bufferVariableReassignmentEdges(
|
|
2244
|
+
variableReassignments: VariableReassignmentInfo[],
|
|
2245
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
2246
|
+
callSites: CallSiteInfo[],
|
|
2247
|
+
methodCalls: MethodCallInfo[],
|
|
2248
|
+
parameters: ParameterInfo[]
|
|
2249
|
+
): void {
|
|
2250
|
+
// Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
|
|
2251
|
+
// Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
|
|
2252
|
+
|
|
2253
|
+
for (const reassignment of variableReassignments) {
|
|
2254
|
+
const {
|
|
2255
|
+
variableName,
|
|
2256
|
+
mutationScopePath,
|
|
2257
|
+
valueType,
|
|
2258
|
+
valueName,
|
|
2259
|
+
valueId,
|
|
2260
|
+
callLine,
|
|
2261
|
+
callColumn,
|
|
2262
|
+
operator,
|
|
2263
|
+
literalValue,
|
|
2264
|
+
expressionType,
|
|
2265
|
+
expressionMetadata,
|
|
2266
|
+
file,
|
|
2267
|
+
line,
|
|
2268
|
+
column
|
|
2269
|
+
} = reassignment;
|
|
2270
|
+
|
|
2271
|
+
// Find target variable node using scope chain resolution (REG-309)
|
|
2272
|
+
const scopePath = mutationScopePath ?? [];
|
|
2273
|
+
const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
|
|
2274
|
+
const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
|
|
2275
|
+
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
2276
|
+
|
|
2277
|
+
if (!targetNodeId) {
|
|
2278
|
+
// Variable not found - could be external reference
|
|
1276
2279
|
continue;
|
|
1277
2280
|
}
|
|
1278
2281
|
|
|
1279
|
-
//
|
|
1280
|
-
|
|
1281
|
-
let targetPath = resolve(currentDir, source);
|
|
2282
|
+
// Resolve source node based on value type
|
|
2283
|
+
let sourceNodeId: string | null = null;
|
|
1282
2284
|
|
|
1283
|
-
//
|
|
1284
|
-
|
|
1285
|
-
|
|
2285
|
+
// LITERAL: Create node inline (NO CONTINUE STATEMENT)
|
|
2286
|
+
if (valueType === 'LITERAL' && valueId) {
|
|
2287
|
+
// Create LITERAL node
|
|
2288
|
+
this._bufferNode({
|
|
2289
|
+
type: 'LITERAL',
|
|
2290
|
+
id: valueId,
|
|
2291
|
+
value: literalValue,
|
|
2292
|
+
file,
|
|
2293
|
+
line,
|
|
2294
|
+
column
|
|
2295
|
+
});
|
|
2296
|
+
sourceNodeId = valueId;
|
|
2297
|
+
}
|
|
2298
|
+
// VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
|
|
2299
|
+
else if (valueType === 'VARIABLE' && valueName) {
|
|
2300
|
+
const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
|
|
2301
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
|
|
2302
|
+
sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
|
|
2303
|
+
}
|
|
2304
|
+
// CALL_SITE: Look up existing call node
|
|
2305
|
+
else if (valueType === 'CALL_SITE' && callLine && callColumn) {
|
|
2306
|
+
const callSite = callSites.find(cs =>
|
|
2307
|
+
cs.line === callLine && cs.column === callColumn && cs.file === file
|
|
2308
|
+
);
|
|
2309
|
+
sourceNodeId = callSite?.id ?? null;
|
|
2310
|
+
}
|
|
2311
|
+
// METHOD_CALL: Look up existing method call node
|
|
2312
|
+
else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
|
|
2313
|
+
const methodCall = methodCalls.find(mc =>
|
|
2314
|
+
mc.line === callLine && mc.column === callColumn && mc.file === file
|
|
2315
|
+
);
|
|
2316
|
+
sourceNodeId = methodCall?.id ?? null;
|
|
2317
|
+
}
|
|
2318
|
+
// EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
|
|
2319
|
+
else if (valueType === 'EXPRESSION' && valueId && expressionType) {
|
|
2320
|
+
// Create EXPRESSION node using NodeFactory
|
|
2321
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
2322
|
+
expressionType,
|
|
2323
|
+
file,
|
|
2324
|
+
line,
|
|
2325
|
+
column,
|
|
2326
|
+
{
|
|
2327
|
+
id: valueId, // ID from JSASTAnalyzer
|
|
2328
|
+
object: expressionMetadata?.object,
|
|
2329
|
+
property: expressionMetadata?.property,
|
|
2330
|
+
computed: expressionMetadata?.computed,
|
|
2331
|
+
computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
|
|
2332
|
+
operator: expressionMetadata?.operator
|
|
2333
|
+
}
|
|
2334
|
+
);
|
|
1286
2335
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
2336
|
+
this._bufferNode(expressionNode);
|
|
2337
|
+
sourceNodeId = valueId;
|
|
2338
|
+
}
|
|
1290
2339
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
2340
|
+
// Create edges if source found
|
|
2341
|
+
if (sourceNodeId && targetNodeId) {
|
|
2342
|
+
// For compound operators (operator !== '='), LHS reads its own current value
|
|
2343
|
+
// Create READS_FROM self-loop (Linus requirement)
|
|
2344
|
+
if (operator !== '=') {
|
|
2345
|
+
this._bufferEdge({
|
|
2346
|
+
type: 'READS_FROM',
|
|
2347
|
+
src: targetNodeId, // Variable reads from...
|
|
2348
|
+
dst: targetNodeId // ...itself (self-loop)
|
|
2349
|
+
});
|
|
1298
2350
|
}
|
|
1299
|
-
|
|
2351
|
+
|
|
2352
|
+
// RHS flows into LHS (write side)
|
|
2353
|
+
this._bufferEdge({
|
|
2354
|
+
type: 'FLOWS_INTO',
|
|
2355
|
+
src: sourceNodeId,
|
|
2356
|
+
dst: targetNodeId
|
|
2357
|
+
});
|
|
1300
2358
|
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* Buffer RETURNS edges connecting return expressions to their containing functions.
|
|
2364
|
+
*
|
|
2365
|
+
* Edge direction: returnExpression --RETURNS--> function
|
|
2366
|
+
*
|
|
2367
|
+
* This enables tracing data flow through function calls:
|
|
2368
|
+
* - Query: "What does formatDate return?"
|
|
2369
|
+
* - Answer: Follow RETURNS edges from function to see all possible return values
|
|
2370
|
+
*/
|
|
2371
|
+
private bufferReturnEdges(
|
|
2372
|
+
returnStatements: ReturnStatementInfo[],
|
|
2373
|
+
callSites: CallSiteInfo[],
|
|
2374
|
+
methodCalls: MethodCallInfo[],
|
|
2375
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
2376
|
+
parameters: ParameterInfo[]
|
|
2377
|
+
): void {
|
|
2378
|
+
for (const ret of returnStatements) {
|
|
2379
|
+
const { parentFunctionId, returnValueType, file } = ret;
|
|
1301
2380
|
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
2381
|
+
// Skip if no value returned (bare return;)
|
|
2382
|
+
if (returnValueType === 'NONE') {
|
|
1304
2383
|
continue;
|
|
1305
2384
|
}
|
|
1306
2385
|
|
|
1307
|
-
|
|
1308
|
-
await graph.addEdge({
|
|
1309
|
-
type: 'IMPORTS',
|
|
1310
|
-
src: module.id,
|
|
1311
|
-
dst: targetModule.id
|
|
1312
|
-
});
|
|
1313
|
-
edgesCreated++;
|
|
2386
|
+
let sourceNodeId: string | null = null;
|
|
1314
2387
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
2388
|
+
switch (returnValueType) {
|
|
2389
|
+
case 'LITERAL':
|
|
2390
|
+
// Direct reference to literal node
|
|
2391
|
+
sourceNodeId = ret.returnValueId ?? null;
|
|
2392
|
+
break;
|
|
1320
2393
|
|
|
1321
|
-
|
|
1322
|
-
//
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
2394
|
+
case 'VARIABLE': {
|
|
2395
|
+
// Find variable declaration by name in same file
|
|
2396
|
+
const varName = ret.returnValueName;
|
|
2397
|
+
if (varName) {
|
|
2398
|
+
const sourceVar = variableDeclarations.find(v =>
|
|
2399
|
+
v.name === varName && v.file === file
|
|
2400
|
+
);
|
|
2401
|
+
if (sourceVar) {
|
|
2402
|
+
sourceNodeId = sourceVar.id;
|
|
2403
|
+
} else {
|
|
2404
|
+
// Check parameters
|
|
2405
|
+
const sourceParam = parameters.find(p =>
|
|
2406
|
+
p.name === varName && p.file === file
|
|
2407
|
+
);
|
|
2408
|
+
if (sourceParam) {
|
|
2409
|
+
sourceNodeId = sourceParam.id;
|
|
2410
|
+
}
|
|
1336
2411
|
}
|
|
1337
2412
|
}
|
|
2413
|
+
break;
|
|
2414
|
+
}
|
|
1338
2415
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
2416
|
+
case 'CALL_SITE': {
|
|
2417
|
+
// Find call site by coordinates
|
|
2418
|
+
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
2419
|
+
if (returnValueLine && returnValueColumn) {
|
|
2420
|
+
const callSite = callSites.find(cs =>
|
|
2421
|
+
cs.line === returnValueLine &&
|
|
2422
|
+
cs.column === returnValueColumn &&
|
|
2423
|
+
(returnValueCallName ? cs.name === returnValueCallName : true)
|
|
2424
|
+
);
|
|
2425
|
+
if (callSite) {
|
|
2426
|
+
sourceNodeId = callSite.id;
|
|
2427
|
+
}
|
|
1346
2428
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
case 'METHOD_CALL': {
|
|
2433
|
+
// Find method call by coordinates and method name
|
|
2434
|
+
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
2435
|
+
if (returnValueLine && returnValueColumn) {
|
|
2436
|
+
const methodCall = methodCalls.find(mc =>
|
|
2437
|
+
mc.line === returnValueLine &&
|
|
2438
|
+
mc.column === returnValueColumn &&
|
|
2439
|
+
mc.file === file &&
|
|
2440
|
+
(returnValueCallName ? mc.method === returnValueCallName : true)
|
|
2441
|
+
);
|
|
2442
|
+
if (methodCall) {
|
|
2443
|
+
sourceNodeId = methodCall.id;
|
|
1354
2444
|
}
|
|
1355
2445
|
}
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
1356
2448
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
2449
|
+
case 'EXPRESSION': {
|
|
2450
|
+
// REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
|
|
2451
|
+
const {
|
|
2452
|
+
expressionType,
|
|
2453
|
+
returnValueId,
|
|
2454
|
+
returnValueLine,
|
|
2455
|
+
returnValueColumn,
|
|
2456
|
+
operator,
|
|
2457
|
+
object,
|
|
2458
|
+
property,
|
|
2459
|
+
computed,
|
|
2460
|
+
objectSourceName,
|
|
2461
|
+
leftSourceName,
|
|
2462
|
+
rightSourceName,
|
|
2463
|
+
consequentSourceName,
|
|
2464
|
+
alternateSourceName,
|
|
2465
|
+
expressionSourceNames,
|
|
2466
|
+
unaryArgSourceName
|
|
2467
|
+
} = ret;
|
|
2468
|
+
|
|
2469
|
+
// Skip if no expression ID was generated
|
|
2470
|
+
if (!returnValueId) {
|
|
2471
|
+
break;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// Create EXPRESSION node using NodeFactory
|
|
2475
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(
|
|
2476
|
+
expressionType || 'Unknown',
|
|
2477
|
+
file,
|
|
2478
|
+
returnValueLine || ret.line,
|
|
2479
|
+
returnValueColumn || ret.column,
|
|
2480
|
+
{
|
|
2481
|
+
id: returnValueId,
|
|
2482
|
+
object,
|
|
2483
|
+
property,
|
|
2484
|
+
computed,
|
|
2485
|
+
operator
|
|
2486
|
+
}
|
|
2487
|
+
);
|
|
2488
|
+
|
|
2489
|
+
this._bufferNode(expressionNode);
|
|
2490
|
+
sourceNodeId = returnValueId;
|
|
2491
|
+
|
|
2492
|
+
// Buffer DERIVES_FROM edges based on expression type
|
|
2493
|
+
// Helper function to find source variable or parameter
|
|
2494
|
+
const findSource = (name: string): string | null => {
|
|
2495
|
+
const variable = variableDeclarations.find(v =>
|
|
2496
|
+
v.name === name && v.file === file
|
|
2497
|
+
);
|
|
2498
|
+
if (variable) return variable.id;
|
|
2499
|
+
|
|
2500
|
+
const param = parameters.find(p =>
|
|
2501
|
+
p.name === name && p.file === file
|
|
2502
|
+
);
|
|
2503
|
+
if (param) return param.id;
|
|
2504
|
+
|
|
2505
|
+
return null;
|
|
2506
|
+
};
|
|
2507
|
+
|
|
2508
|
+
// MemberExpression: derives from the object
|
|
2509
|
+
if (expressionType === 'MemberExpression' && objectSourceName) {
|
|
2510
|
+
const sourceId = findSource(objectSourceName);
|
|
2511
|
+
if (sourceId) {
|
|
2512
|
+
this._bufferEdge({
|
|
2513
|
+
type: 'DERIVES_FROM',
|
|
2514
|
+
src: returnValueId,
|
|
2515
|
+
dst: sourceId
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
// BinaryExpression / LogicalExpression: derives from left and right operands
|
|
2521
|
+
if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
2522
|
+
if (leftSourceName) {
|
|
2523
|
+
const sourceId = findSource(leftSourceName);
|
|
2524
|
+
if (sourceId) {
|
|
2525
|
+
this._bufferEdge({
|
|
2526
|
+
type: 'DERIVES_FROM',
|
|
2527
|
+
src: returnValueId,
|
|
2528
|
+
dst: sourceId
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
if (rightSourceName) {
|
|
2533
|
+
const sourceId = findSource(rightSourceName);
|
|
2534
|
+
if (sourceId) {
|
|
2535
|
+
this._bufferEdge({
|
|
2536
|
+
type: 'DERIVES_FROM',
|
|
2537
|
+
src: returnValueId,
|
|
2538
|
+
dst: sourceId
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// ConditionalExpression: derives from consequent and alternate
|
|
2545
|
+
if (expressionType === 'ConditionalExpression') {
|
|
2546
|
+
if (consequentSourceName) {
|
|
2547
|
+
const sourceId = findSource(consequentSourceName);
|
|
2548
|
+
if (sourceId) {
|
|
2549
|
+
this._bufferEdge({
|
|
2550
|
+
type: 'DERIVES_FROM',
|
|
2551
|
+
src: returnValueId,
|
|
2552
|
+
dst: sourceId
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (alternateSourceName) {
|
|
2557
|
+
const sourceId = findSource(alternateSourceName);
|
|
2558
|
+
if (sourceId) {
|
|
2559
|
+
this._bufferEdge({
|
|
2560
|
+
type: 'DERIVES_FROM',
|
|
2561
|
+
src: returnValueId,
|
|
2562
|
+
dst: sourceId
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// UnaryExpression: derives from the argument
|
|
2569
|
+
if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
|
|
2570
|
+
const sourceId = findSource(unaryArgSourceName);
|
|
2571
|
+
if (sourceId) {
|
|
2572
|
+
this._bufferEdge({
|
|
2573
|
+
type: 'DERIVES_FROM',
|
|
2574
|
+
src: returnValueId,
|
|
2575
|
+
dst: sourceId
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// TemplateLiteral: derives from all embedded expressions
|
|
2581
|
+
if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
|
|
2582
|
+
for (const sourceName of expressionSourceNames) {
|
|
2583
|
+
const sourceId = findSource(sourceName);
|
|
2584
|
+
if (sourceId) {
|
|
2585
|
+
this._bufferEdge({
|
|
2586
|
+
type: 'DERIVES_FROM',
|
|
2587
|
+
src: returnValueId,
|
|
2588
|
+
dst: sourceId
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
break;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// Create RETURNS edge if we found a source node
|
|
2599
|
+
if (sourceNodeId && parentFunctionId) {
|
|
2600
|
+
this._bufferEdge({
|
|
2601
|
+
type: 'RETURNS',
|
|
2602
|
+
src: sourceNodeId,
|
|
2603
|
+
dst: parentFunctionId
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
/**
|
|
2610
|
+
* Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
|
|
2611
|
+
*
|
|
2612
|
+
* Handles two target types:
|
|
2613
|
+
* - IDENTIFIER: Simple variable (i++, --count)
|
|
2614
|
+
* - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
|
|
2615
|
+
*
|
|
2616
|
+
* Creates:
|
|
2617
|
+
* - UPDATE_EXPRESSION node with operator and target metadata
|
|
2618
|
+
* - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
|
|
2619
|
+
* - READS_FROM self-loop: target -> target (reads current value before update)
|
|
2620
|
+
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
2621
|
+
*
|
|
2622
|
+
* REG-288: Initial implementation for IDENTIFIER targets
|
|
2623
|
+
* REG-312: Extended for MEMBER_EXPRESSION targets
|
|
2624
|
+
*/
|
|
2625
|
+
private bufferUpdateExpressionEdges(
|
|
2626
|
+
updateExpressions: UpdateExpressionInfo[],
|
|
2627
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
2628
|
+
parameters: ParameterInfo[],
|
|
2629
|
+
classDeclarations: ClassDeclarationInfo[]
|
|
2630
|
+
): void {
|
|
2631
|
+
// Build lookup caches: O(n) instead of O(n*m)
|
|
2632
|
+
const varLookup = new Map<string, VariableDeclarationInfo>();
|
|
2633
|
+
for (const v of variableDeclarations) {
|
|
2634
|
+
varLookup.set(`${v.file}:${v.name}`, v);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
const paramLookup = new Map<string, ParameterInfo>();
|
|
2638
|
+
for (const p of parameters) {
|
|
2639
|
+
paramLookup.set(`${p.file}:${p.name}`, p);
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
for (const update of updateExpressions) {
|
|
2643
|
+
if (update.targetType === 'IDENTIFIER') {
|
|
2644
|
+
// REG-288: Simple identifier (i++, --count)
|
|
2645
|
+
this.bufferIdentifierUpdate(update, varLookup, paramLookup);
|
|
2646
|
+
} else if (update.targetType === 'MEMBER_EXPRESSION') {
|
|
2647
|
+
// REG-312: Member expression (obj.prop++, arr[i]++)
|
|
2648
|
+
this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
/**
|
|
2654
|
+
* Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
|
|
2655
|
+
* REG-288: Original implementation extracted for clarity
|
|
2656
|
+
*/
|
|
2657
|
+
private bufferIdentifierUpdate(
|
|
2658
|
+
update: UpdateExpressionInfo,
|
|
2659
|
+
varLookup: Map<string, VariableDeclarationInfo>,
|
|
2660
|
+
paramLookup: Map<string, ParameterInfo>
|
|
2661
|
+
): void {
|
|
2662
|
+
const {
|
|
2663
|
+
variableName,
|
|
2664
|
+
operator,
|
|
2665
|
+
prefix,
|
|
2666
|
+
file,
|
|
2667
|
+
line,
|
|
2668
|
+
column,
|
|
2669
|
+
parentScopeId
|
|
2670
|
+
} = update;
|
|
2671
|
+
|
|
2672
|
+
if (!variableName) return;
|
|
2673
|
+
|
|
2674
|
+
// Find target variable node
|
|
2675
|
+
const targetVar = varLookup.get(`${file}:${variableName}`);
|
|
2676
|
+
const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
|
|
2677
|
+
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
2678
|
+
|
|
2679
|
+
if (!targetNodeId) {
|
|
2680
|
+
// Variable not found - could be module-level or external reference
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
// Create UPDATE_EXPRESSION node
|
|
2685
|
+
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
2686
|
+
|
|
2687
|
+
this._bufferNode({
|
|
2688
|
+
type: 'UPDATE_EXPRESSION',
|
|
2689
|
+
id: updateId,
|
|
2690
|
+
name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
|
|
2691
|
+
targetType: 'IDENTIFIER',
|
|
2692
|
+
operator,
|
|
2693
|
+
prefix,
|
|
2694
|
+
variableName,
|
|
2695
|
+
file,
|
|
2696
|
+
line,
|
|
2697
|
+
column
|
|
2698
|
+
} as GraphNode);
|
|
2699
|
+
|
|
2700
|
+
// Create READS_FROM self-loop
|
|
2701
|
+
this._bufferEdge({
|
|
2702
|
+
type: 'READS_FROM',
|
|
2703
|
+
src: targetNodeId,
|
|
2704
|
+
dst: targetNodeId
|
|
2705
|
+
});
|
|
2706
|
+
|
|
2707
|
+
// Create MODIFIES edge
|
|
2708
|
+
this._bufferEdge({
|
|
2709
|
+
type: 'MODIFIES',
|
|
2710
|
+
src: updateId,
|
|
2711
|
+
dst: targetNodeId
|
|
2712
|
+
});
|
|
2713
|
+
|
|
2714
|
+
// Create CONTAINS edge
|
|
2715
|
+
if (parentScopeId) {
|
|
2716
|
+
this._bufferEdge({
|
|
2717
|
+
type: 'CONTAINS',
|
|
2718
|
+
src: parentScopeId,
|
|
2719
|
+
dst: updateId
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/**
|
|
2725
|
+
* Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
|
|
2726
|
+
* REG-312: New implementation for member expression targets
|
|
2727
|
+
*
|
|
2728
|
+
* Creates:
|
|
2729
|
+
* - UPDATE_EXPRESSION node with member expression metadata
|
|
2730
|
+
* - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
|
|
2731
|
+
* - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
|
|
2732
|
+
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
2733
|
+
*/
|
|
2734
|
+
private bufferMemberExpressionUpdate(
|
|
2735
|
+
update: UpdateExpressionInfo,
|
|
2736
|
+
varLookup: Map<string, VariableDeclarationInfo>,
|
|
2737
|
+
paramLookup: Map<string, ParameterInfo>,
|
|
2738
|
+
classDeclarations: ClassDeclarationInfo[]
|
|
2739
|
+
): void {
|
|
2740
|
+
const {
|
|
2741
|
+
objectName,
|
|
2742
|
+
propertyName,
|
|
2743
|
+
mutationType,
|
|
2744
|
+
computedPropertyVar,
|
|
2745
|
+
enclosingClassName,
|
|
2746
|
+
operator,
|
|
2747
|
+
prefix,
|
|
2748
|
+
file,
|
|
2749
|
+
line,
|
|
2750
|
+
column,
|
|
2751
|
+
parentScopeId
|
|
2752
|
+
} = update;
|
|
2753
|
+
|
|
2754
|
+
if (!objectName || !propertyName) return;
|
|
2755
|
+
|
|
2756
|
+
// Find target object node
|
|
2757
|
+
let objectNodeId: string | null = null;
|
|
2758
|
+
|
|
2759
|
+
if (objectName !== 'this') {
|
|
2760
|
+
// Regular object: obj.prop++, arr[i]++
|
|
2761
|
+
const targetVar = varLookup.get(`${file}:${objectName}`);
|
|
2762
|
+
const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
|
|
2763
|
+
objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
|
|
2764
|
+
} else {
|
|
2765
|
+
// this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
|
|
2766
|
+
if (!enclosingClassName) return;
|
|
2767
|
+
|
|
2768
|
+
const fileBasename = basename(file);
|
|
2769
|
+
const classDecl = classDeclarations.find(c =>
|
|
2770
|
+
c.name === enclosingClassName && c.file === fileBasename
|
|
2771
|
+
);
|
|
2772
|
+
objectNodeId = classDecl?.id ?? null;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
if (!objectNodeId) {
|
|
2776
|
+
// Object not found - external reference or scope issue
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// Create UPDATE_EXPRESSION node
|
|
2781
|
+
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
2782
|
+
|
|
2783
|
+
// Display name: "obj.prop++" or "this.count++" or "arr[i]++"
|
|
2784
|
+
const displayName = (() => {
|
|
2785
|
+
const opStr = prefix ? operator : '';
|
|
2786
|
+
const postOpStr = prefix ? '' : operator;
|
|
2787
|
+
|
|
2788
|
+
if (objectName === 'this') {
|
|
2789
|
+
return `${opStr}this.${propertyName}${postOpStr}`;
|
|
2790
|
+
}
|
|
2791
|
+
if (mutationType === 'computed') {
|
|
2792
|
+
const computedPart = computedPropertyVar || '?';
|
|
2793
|
+
return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
|
|
2794
|
+
}
|
|
2795
|
+
return `${opStr}${objectName}.${propertyName}${postOpStr}`;
|
|
2796
|
+
})();
|
|
2797
|
+
|
|
2798
|
+
this._bufferNode({
|
|
2799
|
+
type: 'UPDATE_EXPRESSION',
|
|
2800
|
+
id: updateId,
|
|
2801
|
+
name: displayName,
|
|
2802
|
+
targetType: 'MEMBER_EXPRESSION',
|
|
2803
|
+
operator,
|
|
2804
|
+
prefix,
|
|
2805
|
+
objectName,
|
|
2806
|
+
propertyName,
|
|
2807
|
+
mutationType,
|
|
2808
|
+
computedPropertyVar,
|
|
2809
|
+
enclosingClassName,
|
|
2810
|
+
file,
|
|
2811
|
+
line,
|
|
2812
|
+
column
|
|
2813
|
+
} as GraphNode);
|
|
2814
|
+
|
|
2815
|
+
// Create READS_FROM self-loop (object reads from itself)
|
|
2816
|
+
this._bufferEdge({
|
|
2817
|
+
type: 'READS_FROM',
|
|
2818
|
+
src: objectNodeId,
|
|
2819
|
+
dst: objectNodeId
|
|
2820
|
+
});
|
|
2821
|
+
|
|
2822
|
+
// Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
|
|
2823
|
+
this._bufferEdge({
|
|
2824
|
+
type: 'MODIFIES',
|
|
2825
|
+
src: updateId,
|
|
2826
|
+
dst: objectNodeId
|
|
2827
|
+
});
|
|
2828
|
+
|
|
2829
|
+
// Create CONTAINS edge
|
|
2830
|
+
if (parentScopeId) {
|
|
2831
|
+
this._bufferEdge({
|
|
2832
|
+
type: 'CONTAINS',
|
|
2833
|
+
src: parentScopeId,
|
|
2834
|
+
dst: updateId
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
/**
|
|
2840
|
+
* Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
|
|
2841
|
+
*
|
|
2842
|
+
* Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
|
|
2843
|
+
* This enables traceValues to follow Promise data flow:
|
|
2844
|
+
*
|
|
2845
|
+
* Example:
|
|
2846
|
+
* ```
|
|
2847
|
+
* const result = new Promise((resolve) => {
|
|
2848
|
+
* resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
|
|
2849
|
+
* });
|
|
2850
|
+
* ```
|
|
2851
|
+
*
|
|
2852
|
+
* The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
|
|
2853
|
+
* data flows FROM resolve(value) TO the Promise result.
|
|
2854
|
+
*/
|
|
2855
|
+
private bufferPromiseResolutionEdges(promiseResolutions: PromiseResolutionInfo[]): void {
|
|
2856
|
+
for (const resolution of promiseResolutions) {
|
|
2857
|
+
this._bufferEdge({
|
|
2858
|
+
type: 'RESOLVES_TO',
|
|
2859
|
+
src: resolution.callId,
|
|
2860
|
+
dst: resolution.constructorCallId,
|
|
2861
|
+
metadata: {
|
|
2862
|
+
isReject: resolution.isReject
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/**
|
|
2869
|
+
* Buffer OBJECT_LITERAL nodes to the graph.
|
|
2870
|
+
* These are object literals passed as function arguments or nested in other literals.
|
|
2871
|
+
*/
|
|
2872
|
+
private bufferObjectLiteralNodes(objectLiterals: ObjectLiteralInfo[]): void {
|
|
2873
|
+
for (const obj of objectLiterals) {
|
|
2874
|
+
this._bufferNode({
|
|
2875
|
+
id: obj.id,
|
|
2876
|
+
type: obj.type,
|
|
2877
|
+
name: '<object>',
|
|
2878
|
+
file: obj.file,
|
|
2879
|
+
line: obj.line,
|
|
2880
|
+
column: obj.column,
|
|
2881
|
+
parentCallId: obj.parentCallId,
|
|
2882
|
+
argIndex: obj.argIndex
|
|
2883
|
+
} as GraphNode);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
/**
|
|
2888
|
+
* Buffer ARRAY_LITERAL nodes to the graph.
|
|
2889
|
+
* These are array literals passed as function arguments or nested in other literals.
|
|
2890
|
+
*/
|
|
2891
|
+
private bufferArrayLiteralNodes(arrayLiterals: ArrayLiteralInfo[]): void {
|
|
2892
|
+
for (const arr of arrayLiterals) {
|
|
2893
|
+
this._bufferNode({
|
|
2894
|
+
id: arr.id,
|
|
2895
|
+
type: arr.type,
|
|
2896
|
+
name: '<array>',
|
|
2897
|
+
file: arr.file,
|
|
2898
|
+
line: arr.line,
|
|
2899
|
+
column: arr.column,
|
|
2900
|
+
parentCallId: arr.parentCallId,
|
|
2901
|
+
argIndex: arr.argIndex
|
|
2902
|
+
} as GraphNode);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
/**
|
|
2907
|
+
* Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
|
|
2908
|
+
* Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
|
|
2909
|
+
*
|
|
2910
|
+
* REG-329: Adds scope-aware variable resolution for VARIABLE property values.
|
|
2911
|
+
* Uses the same resolveVariableInScope infrastructure as mutation handlers.
|
|
2912
|
+
*/
|
|
2913
|
+
private bufferObjectPropertyEdges(
|
|
2914
|
+
objectProperties: ObjectPropertyInfo[],
|
|
2915
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
2916
|
+
parameters: ParameterInfo[]
|
|
2917
|
+
): void {
|
|
2918
|
+
for (const prop of objectProperties) {
|
|
2919
|
+
// REG-329: Handle VARIABLE value types with scope resolution
|
|
2920
|
+
if (prop.valueType === 'VARIABLE' && prop.valueName) {
|
|
2921
|
+
const scopePath = prop.valueScopePath ?? [];
|
|
2922
|
+
const file = prop.file;
|
|
2923
|
+
|
|
2924
|
+
// Resolve variable using scope chain
|
|
2925
|
+
const resolvedVar = this.resolveVariableInScope(
|
|
2926
|
+
prop.valueName, scopePath, file, variableDeclarations
|
|
2927
|
+
);
|
|
2928
|
+
const resolvedParam = !resolvedVar
|
|
2929
|
+
? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
|
|
2930
|
+
: null;
|
|
2931
|
+
|
|
2932
|
+
const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
|
|
2933
|
+
|
|
2934
|
+
if (resolvedNodeId) {
|
|
2935
|
+
this._bufferEdge({
|
|
2936
|
+
type: 'HAS_PROPERTY',
|
|
2937
|
+
src: prop.objectId,
|
|
2938
|
+
dst: resolvedNodeId,
|
|
2939
|
+
propertyName: prop.propertyName
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// Existing logic for non-VARIABLE types
|
|
2946
|
+
if (prop.valueNodeId) {
|
|
2947
|
+
this._bufferEdge({
|
|
2948
|
+
type: 'HAS_PROPERTY',
|
|
2949
|
+
src: prop.objectId,
|
|
2950
|
+
dst: prop.valueNodeId,
|
|
2951
|
+
propertyName: prop.propertyName
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
/**
|
|
2958
|
+
* Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
|
|
2959
|
+
*/
|
|
2960
|
+
private async createClassAssignmentEdges(variableAssignments: VariableAssignmentInfo[], graph: GraphBackend): Promise<number> {
|
|
2961
|
+
let edgesCreated = 0;
|
|
2962
|
+
|
|
2963
|
+
for (const assignment of variableAssignments) {
|
|
2964
|
+
const { variableId, sourceType, className } = assignment;
|
|
2965
|
+
|
|
2966
|
+
if (sourceType === 'CLASS' && className) {
|
|
2967
|
+
const parts = variableId.split('#');
|
|
2968
|
+
const file = parts.length >= 3 ? parts[2] : null;
|
|
2969
|
+
|
|
2970
|
+
let classNode: { id: string; name: string; file?: string } | null = null;
|
|
2971
|
+
for await (const node of graph.queryNodes({ type: 'CLASS' })) {
|
|
2972
|
+
if (node.name === className && (!file || node.file === file)) {
|
|
2973
|
+
classNode = node as { id: string; name: string; file?: string };
|
|
2974
|
+
break;
|
|
1364
2975
|
}
|
|
1365
2976
|
}
|
|
2977
|
+
|
|
2978
|
+
if (classNode) {
|
|
2979
|
+
await graph.addEdge({
|
|
2980
|
+
type: 'ASSIGNED_FROM',
|
|
2981
|
+
src: variableId,
|
|
2982
|
+
dst: classNode.id
|
|
2983
|
+
});
|
|
2984
|
+
edgesCreated++;
|
|
2985
|
+
}
|
|
1366
2986
|
}
|
|
1367
2987
|
}
|
|
1368
2988
|
|