@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
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* GraphBuilder - создание узлов и рёбер графа из собранных AST данных
|
|
3
3
|
* OPTIMIZED: Uses batched writes to reduce FFI overhead
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { basename } from 'path';
|
|
6
|
+
import { ImportNode } from '../../../core/nodes/ImportNode.js';
|
|
7
|
+
import { InterfaceNode } from '../../../core/nodes/InterfaceNode.js';
|
|
8
|
+
import { EnumNode } from '../../../core/nodes/EnumNode.js';
|
|
9
|
+
import { DecoratorNode } from '../../../core/nodes/DecoratorNode.js';
|
|
10
|
+
import { NetworkRequestNode } from '../../../core/nodes/NetworkRequestNode.js';
|
|
11
|
+
import { NodeFactory } from '../../../core/NodeFactory.js';
|
|
12
|
+
import { computeSemanticId, parseSemanticId } from '../../../core/SemanticId.js';
|
|
6
13
|
export class GraphBuilder {
|
|
7
14
|
// Track singleton nodes to avoid duplicates (net:stdio, net:request, etc.)
|
|
8
15
|
_createdSingletons = new Set();
|
|
@@ -51,9 +58,29 @@ export class GraphBuilder {
|
|
|
51
58
|
* Создаёт ноды и рёбра в графе (BATCHED VERSION)
|
|
52
59
|
*/
|
|
53
60
|
async build(module, graph, projectPath, data) {
|
|
54
|
-
const { functions, parameters = [], scopes,
|
|
61
|
+
const { functions, parameters = [], scopes,
|
|
62
|
+
// Branching
|
|
63
|
+
branches = [], cases = [],
|
|
64
|
+
// Control flow (loops)
|
|
65
|
+
loops = [],
|
|
66
|
+
// Control flow (try/catch/finally) - Phase 4
|
|
67
|
+
tryBlocks = [], catchBlocks = [], finallyBlocks = [], variableDeclarations, callSites, methodCalls = [], eventListeners = [], classInstantiations = [], constructorCalls = [], classDeclarations = [], methodCallbacks = [], callArguments = [], imports = [], exports = [], httpRequests = [], literals = [], variableAssignments = [],
|
|
55
68
|
// TypeScript-specific collections
|
|
56
|
-
interfaces = [], typeAliases = [], enums = [], decorators = []
|
|
69
|
+
interfaces = [], typeAliases = [], enums = [], decorators = [],
|
|
70
|
+
// Array mutation tracking for FLOWS_INTO edges
|
|
71
|
+
arrayMutations = [],
|
|
72
|
+
// Object mutation tracking for FLOWS_INTO edges
|
|
73
|
+
objectMutations = [],
|
|
74
|
+
// Variable reassignment tracking for FLOWS_INTO edges (REG-290)
|
|
75
|
+
variableReassignments = [],
|
|
76
|
+
// Return statement tracking for RETURNS edges
|
|
77
|
+
returnStatements = [],
|
|
78
|
+
// Update expression tracking for MODIFIES edges (REG-288, REG-312)
|
|
79
|
+
updateExpressions = [],
|
|
80
|
+
// Promise resolution tracking for RESOLVES_TO edges (REG-334)
|
|
81
|
+
promiseResolutions = [],
|
|
82
|
+
// Object/Array literal tracking
|
|
83
|
+
objectLiterals = [], objectProperties = [], arrayLiterals = [] } = data;
|
|
57
84
|
// Reset buffers for this build
|
|
58
85
|
this._nodeBuffer = [];
|
|
59
86
|
this._edgeBuffer = [];
|
|
@@ -67,10 +94,40 @@ export class GraphBuilder {
|
|
|
67
94
|
const { parentFunctionId, parentScopeId, capturesFrom, modifies, ...scopeData } = scope;
|
|
68
95
|
this._bufferNode(scopeData);
|
|
69
96
|
}
|
|
70
|
-
//
|
|
97
|
+
// 2.5. Buffer BRANCH nodes
|
|
98
|
+
// Note: parentScopeId is kept on node for query support (REG-275 test requirement)
|
|
99
|
+
for (const branch of branches) {
|
|
100
|
+
const { discriminantExpressionId, discriminantExpressionType, discriminantLine, discriminantColumn, ...branchData } = branch;
|
|
101
|
+
this._bufferNode(branchData);
|
|
102
|
+
}
|
|
103
|
+
// 2.6. Buffer CASE nodes
|
|
104
|
+
for (const caseInfo of cases) {
|
|
105
|
+
const { parentBranchId, ...caseData } = caseInfo;
|
|
106
|
+
this._bufferNode(caseData);
|
|
107
|
+
}
|
|
108
|
+
// 2.7. Buffer LOOP nodes
|
|
109
|
+
for (const loop of loops) {
|
|
110
|
+
// Exclude metadata used for edge creation (not stored on node)
|
|
111
|
+
const { iteratesOverName, iteratesOverLine, iteratesOverColumn, conditionExpressionId, conditionExpressionType, conditionLine, conditionColumn, ...loopData } = loop;
|
|
112
|
+
this._bufferNode(loopData);
|
|
113
|
+
}
|
|
114
|
+
// 2.8. Buffer TRY_BLOCK nodes (Phase 4)
|
|
115
|
+
for (const tryBlock of tryBlocks) {
|
|
116
|
+
this._bufferNode(tryBlock);
|
|
117
|
+
}
|
|
118
|
+
// 2.9. Buffer CATCH_BLOCK nodes (Phase 4)
|
|
119
|
+
for (const catchBlock of catchBlocks) {
|
|
120
|
+
const { parentTryBlockId, ...catchData } = catchBlock;
|
|
121
|
+
this._bufferNode(catchData);
|
|
122
|
+
}
|
|
123
|
+
// 2.10. Buffer FINALLY_BLOCK nodes (Phase 4)
|
|
124
|
+
for (const finallyBlock of finallyBlocks) {
|
|
125
|
+
const { parentTryBlockId, ...finallyData } = finallyBlock;
|
|
126
|
+
this._bufferNode(finallyData);
|
|
127
|
+
}
|
|
128
|
+
// 3. Buffer variables (keep parentScopeId on node for queries)
|
|
71
129
|
for (const varDecl of variableDeclarations) {
|
|
72
|
-
|
|
73
|
-
this._bufferNode(varData);
|
|
130
|
+
this._bufferNode(varDecl);
|
|
74
131
|
}
|
|
75
132
|
// 3.5. Buffer PARAMETER nodes and HAS_PARAMETER edges
|
|
76
133
|
for (const param of parameters) {
|
|
@@ -86,21 +143,49 @@ export class GraphBuilder {
|
|
|
86
143
|
});
|
|
87
144
|
}
|
|
88
145
|
}
|
|
89
|
-
// 4. Buffer CALL_SITE
|
|
146
|
+
// 4. Buffer CALL_SITE (keep parentScopeId on node for queries)
|
|
90
147
|
for (const callSite of callSites) {
|
|
91
|
-
const {
|
|
148
|
+
const { targetFunctionName, ...callData } = callSite;
|
|
92
149
|
this._bufferNode(callData);
|
|
93
150
|
}
|
|
151
|
+
// 4.5 Buffer CONSTRUCTOR_CALL nodes
|
|
152
|
+
for (const constructorCall of constructorCalls) {
|
|
153
|
+
this._bufferNode({
|
|
154
|
+
id: constructorCall.id,
|
|
155
|
+
type: constructorCall.type,
|
|
156
|
+
name: `new ${constructorCall.className}()`,
|
|
157
|
+
className: constructorCall.className,
|
|
158
|
+
isBuiltin: constructorCall.isBuiltin,
|
|
159
|
+
file: constructorCall.file,
|
|
160
|
+
line: constructorCall.line,
|
|
161
|
+
column: constructorCall.column
|
|
162
|
+
});
|
|
163
|
+
}
|
|
94
164
|
// 5. Buffer edges for functions
|
|
95
165
|
this.bufferFunctionEdges(module, functions);
|
|
96
166
|
// 6. Buffer edges for SCOPE
|
|
97
167
|
this.bufferScopeEdges(scopes, variableDeclarations);
|
|
168
|
+
// 6.3. Buffer edges for LOOP (HAS_BODY, ITERATES_OVER, CONTAINS)
|
|
169
|
+
this.bufferLoopEdges(loops, scopes, variableDeclarations, parameters);
|
|
170
|
+
// 6.35. Buffer HAS_CONDITION edges for LOOP (REG-280)
|
|
171
|
+
this.bufferLoopConditionEdges(loops, callSites);
|
|
172
|
+
// 6.37. Buffer EXPRESSION nodes for loop conditions (REG-280)
|
|
173
|
+
this.bufferLoopConditionExpressions(loops);
|
|
174
|
+
// 6.5. Buffer edges for BRANCH (needs callSites for CallExpression discriminant lookup)
|
|
175
|
+
// Phase 3 (REG-267): Now includes scopes for if-branches HAS_CONSEQUENT/HAS_ALTERNATE
|
|
176
|
+
this.bufferBranchEdges(branches, callSites, scopes);
|
|
177
|
+
// 6.6. Buffer edges for CASE
|
|
178
|
+
this.bufferCaseEdges(cases);
|
|
179
|
+
// 6.65. Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK (Phase 4)
|
|
180
|
+
this.bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks);
|
|
181
|
+
// 6.7. Buffer EXPRESSION nodes for switch discriminants (needs callSites for CallExpression)
|
|
182
|
+
this.bufferDiscriminantExpressions(branches, callSites);
|
|
98
183
|
// 7. Buffer edges for variables
|
|
99
184
|
this.bufferVariableEdges(variableDeclarations);
|
|
100
185
|
// 8. Buffer edges for CALL_SITE
|
|
101
186
|
this.bufferCallSiteEdges(callSites, functions);
|
|
102
|
-
// 9. Buffer METHOD_CALL nodes and
|
|
103
|
-
this.bufferMethodCalls(methodCalls);
|
|
187
|
+
// 9. Buffer METHOD_CALL nodes, CONTAINS edges, and USES edges (REG-262)
|
|
188
|
+
this.bufferMethodCalls(methodCalls, variableDeclarations, parameters);
|
|
104
189
|
// 10. Buffer net:stdio and WRITES_TO edges for console.log/error
|
|
105
190
|
this.bufferStdioNodes(methodCalls);
|
|
106
191
|
// 11. Buffer CLASS nodes for class declarations and CONTAINS edges
|
|
@@ -119,6 +204,13 @@ export class GraphBuilder {
|
|
|
119
204
|
this.bufferHttpRequests(httpRequests, functions);
|
|
120
205
|
// 18. Buffer LITERAL nodes
|
|
121
206
|
this.bufferLiterals(literals);
|
|
207
|
+
// 18.5. Buffer OBJECT_LITERAL nodes (moved before bufferArgumentEdges)
|
|
208
|
+
this.bufferObjectLiteralNodes(objectLiterals);
|
|
209
|
+
// 18.6. Buffer ARRAY_LITERAL nodes (moved before bufferArgumentEdges)
|
|
210
|
+
this.bufferArrayLiteralNodes(arrayLiterals);
|
|
211
|
+
// 18.7. Buffer HAS_PROPERTY edges (OBJECT_LITERAL -> property values)
|
|
212
|
+
// REG-329: Pass variableDeclarations and parameters for scope-aware variable resolution
|
|
213
|
+
this.bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters);
|
|
122
214
|
// 19. Buffer ASSIGNED_FROM edges for data flow (some need to create EXPRESSION nodes)
|
|
123
215
|
this.bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters);
|
|
124
216
|
// 20. Buffer PASSES_ARGUMENT edges (CALL -> argument)
|
|
@@ -133,14 +225,25 @@ export class GraphBuilder {
|
|
|
133
225
|
this.bufferDecoratorNodes(decorators);
|
|
134
226
|
// 25. Buffer IMPLEMENTS edges (CLASS -> INTERFACE)
|
|
135
227
|
this.bufferImplementsEdges(classDeclarations, interfaces);
|
|
228
|
+
// 26. Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
229
|
+
this.bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters);
|
|
230
|
+
// 27. Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
231
|
+
// REG-152: Now includes classDeclarations for this.prop = value patterns
|
|
232
|
+
this.bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations);
|
|
233
|
+
// 28. Buffer FLOWS_INTO edges for variable reassignments (REG-290)
|
|
234
|
+
this.bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters);
|
|
235
|
+
// 29. Buffer RETURNS edges for return statements
|
|
236
|
+
this.bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters);
|
|
237
|
+
// 30. Buffer UPDATE_EXPRESSION nodes and MODIFIES edges (REG-288, REG-312)
|
|
238
|
+
this.bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations);
|
|
239
|
+
// 31. Buffer RESOLVES_TO edges for Promise data flow (REG-334)
|
|
240
|
+
this.bufferPromiseResolutionEdges(promiseResolutions);
|
|
136
241
|
// FLUSH: Write all nodes first, then edges in single batch calls
|
|
137
242
|
const nodesCreated = await this._flushNodes(graph);
|
|
138
243
|
const edgesCreated = await this._flushEdges(graph);
|
|
139
|
-
// Handle async operations that need graph queries (IMPORTS_FROM edges)
|
|
140
|
-
const importExportEdges = await this.createImportExportEdges(module, imports, exports, graph, projectPath);
|
|
141
244
|
// Handle async operations for ASSIGNED_FROM with CLASS lookups
|
|
142
245
|
const classAssignmentEdges = await this.createClassAssignmentEdges(variableAssignments, graph);
|
|
143
|
-
return { nodes: nodesCreated, edges: edgesCreated +
|
|
246
|
+
return { nodes: nodesCreated, edges: edgesCreated + classAssignmentEdges };
|
|
144
247
|
}
|
|
145
248
|
// ============= BUFFERED METHODS (synchronous, no awaits) =============
|
|
146
249
|
bufferFunctionEdges(module, functions) {
|
|
@@ -206,6 +309,371 @@ export class GraphBuilder {
|
|
|
206
309
|
}
|
|
207
310
|
}
|
|
208
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Buffer LOOP edges (CONTAINS, HAS_BODY, ITERATES_OVER)
|
|
314
|
+
*
|
|
315
|
+
* Creates edges for:
|
|
316
|
+
* - Parent -> CONTAINS -> LOOP
|
|
317
|
+
* - LOOP -> HAS_BODY -> body SCOPE
|
|
318
|
+
* - LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
|
|
319
|
+
*
|
|
320
|
+
* Scope-aware variable lookup for ITERATES_OVER:
|
|
321
|
+
* For for-of/for-in, finds the iterated variable preferring:
|
|
322
|
+
* 1. Variables declared before the loop on same or earlier line (closest first)
|
|
323
|
+
* 2. Parameters (function arguments)
|
|
324
|
+
*/
|
|
325
|
+
bufferLoopEdges(loops, scopes, variableDeclarations, parameters) {
|
|
326
|
+
for (const loop of loops) {
|
|
327
|
+
// Parent -> CONTAINS -> LOOP
|
|
328
|
+
if (loop.parentScopeId) {
|
|
329
|
+
this._bufferEdge({
|
|
330
|
+
type: 'CONTAINS',
|
|
331
|
+
src: loop.parentScopeId,
|
|
332
|
+
dst: loop.id
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// LOOP -> HAS_BODY -> body SCOPE
|
|
336
|
+
// Find the body scope by matching parentScopeId to loop.id
|
|
337
|
+
const bodyScope = scopes.find(s => s.parentScopeId === loop.id);
|
|
338
|
+
if (bodyScope) {
|
|
339
|
+
this._bufferEdge({
|
|
340
|
+
type: 'HAS_BODY',
|
|
341
|
+
src: loop.id,
|
|
342
|
+
dst: bodyScope.id
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
// LOOP -> ITERATES_OVER -> collection VARIABLE/PARAMETER (for for-in/for-of)
|
|
346
|
+
if (loop.iteratesOverName && (loop.loopType === 'for-in' || loop.loopType === 'for-of')) {
|
|
347
|
+
// For MemberExpression iterables (obj.items), extract base object
|
|
348
|
+
const iterableName = loop.iteratesOverName.includes('.')
|
|
349
|
+
? loop.iteratesOverName.split('.')[0]
|
|
350
|
+
: loop.iteratesOverName;
|
|
351
|
+
// Scope-aware lookup: prefer parameters over variables
|
|
352
|
+
// Parameters are function-local and shadow outer variables
|
|
353
|
+
const param = parameters.find(p => p.name === iterableName && p.file === loop.file);
|
|
354
|
+
// Determine iteration type: for-in iterates keys, for-of iterates values
|
|
355
|
+
const iterates = loop.loopType === 'for-in' ? 'keys' : 'values';
|
|
356
|
+
if (param) {
|
|
357
|
+
// Parameter found - most local binding
|
|
358
|
+
this._bufferEdge({
|
|
359
|
+
type: 'ITERATES_OVER',
|
|
360
|
+
src: loop.id,
|
|
361
|
+
dst: param.id,
|
|
362
|
+
metadata: { iterates }
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// Find variable by name and line proximity (scope-aware heuristic)
|
|
367
|
+
// Prefer variables declared before the loop in the same file
|
|
368
|
+
const candidateVars = variableDeclarations.filter(v => v.name === iterableName &&
|
|
369
|
+
v.file === loop.file &&
|
|
370
|
+
(v.line ?? 0) <= loop.line // Declared before or on loop line
|
|
371
|
+
);
|
|
372
|
+
// Sort by line descending to find closest declaration
|
|
373
|
+
candidateVars.sort((a, b) => (b.line ?? 0) - (a.line ?? 0));
|
|
374
|
+
if (candidateVars.length > 0) {
|
|
375
|
+
this._bufferEdge({
|
|
376
|
+
type: 'ITERATES_OVER',
|
|
377
|
+
src: loop.id,
|
|
378
|
+
dst: candidateVars[0].id,
|
|
379
|
+
metadata: { iterates }
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// REG-282: LOOP (for) -> HAS_INIT -> VARIABLE (let i = 0)
|
|
385
|
+
if (loop.loopType === 'for' && loop.initVariableName && loop.initLine) {
|
|
386
|
+
// Find the variable declared in the init on this line
|
|
387
|
+
const initVar = variableDeclarations.find(v => v.name === loop.initVariableName &&
|
|
388
|
+
v.file === loop.file &&
|
|
389
|
+
v.line === loop.initLine);
|
|
390
|
+
if (initVar) {
|
|
391
|
+
this._bufferEdge({
|
|
392
|
+
type: 'HAS_INIT',
|
|
393
|
+
src: loop.id,
|
|
394
|
+
dst: initVar.id
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// REG-282: LOOP -> HAS_CONDITION -> EXPRESSION (i < 10 or condition for while/do-while)
|
|
399
|
+
if (loop.testExpressionId && loop.testExpressionType) {
|
|
400
|
+
// Create EXPRESSION node for the test
|
|
401
|
+
this._bufferNode({
|
|
402
|
+
id: loop.testExpressionId,
|
|
403
|
+
type: 'EXPRESSION',
|
|
404
|
+
name: loop.testExpressionType,
|
|
405
|
+
file: loop.file,
|
|
406
|
+
line: loop.testLine,
|
|
407
|
+
column: loop.testColumn,
|
|
408
|
+
expressionType: loop.testExpressionType
|
|
409
|
+
});
|
|
410
|
+
this._bufferEdge({
|
|
411
|
+
type: 'HAS_CONDITION',
|
|
412
|
+
src: loop.id,
|
|
413
|
+
dst: loop.testExpressionId
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// REG-282: LOOP (for) -> HAS_UPDATE -> EXPRESSION (i++)
|
|
417
|
+
if (loop.loopType === 'for' && loop.updateExpressionId && loop.updateExpressionType) {
|
|
418
|
+
// Create EXPRESSION node for the update
|
|
419
|
+
this._bufferNode({
|
|
420
|
+
id: loop.updateExpressionId,
|
|
421
|
+
type: 'EXPRESSION',
|
|
422
|
+
name: loop.updateExpressionType,
|
|
423
|
+
file: loop.file,
|
|
424
|
+
line: loop.updateLine,
|
|
425
|
+
column: loop.updateColumn,
|
|
426
|
+
expressionType: loop.updateExpressionType
|
|
427
|
+
});
|
|
428
|
+
this._bufferEdge({
|
|
429
|
+
type: 'HAS_UPDATE',
|
|
430
|
+
src: loop.id,
|
|
431
|
+
dst: loop.updateExpressionId
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Buffer HAS_CONDITION edges from LOOP to condition EXPRESSION/CALL nodes.
|
|
438
|
+
* Also creates EXPRESSION nodes for non-CallExpression conditions.
|
|
439
|
+
*
|
|
440
|
+
* REG-280: For while/do-while/for loops, creates HAS_CONDITION edge to the
|
|
441
|
+
* condition expression. For-in/for-of loops don't have conditions (use ITERATES_OVER).
|
|
442
|
+
*
|
|
443
|
+
* For CallExpression conditions, links to existing CALL_SITE node by coordinates.
|
|
444
|
+
*/
|
|
445
|
+
bufferLoopConditionEdges(loops, callSites) {
|
|
446
|
+
for (const loop of loops) {
|
|
447
|
+
// Skip for-in/for-of loops - they don't have test expressions
|
|
448
|
+
if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
// Skip if no condition (e.g., infinite for loop: for(;;))
|
|
452
|
+
if (!loop.conditionExpressionId) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
// LOOP -> HAS_CONDITION -> EXPRESSION/CALL
|
|
456
|
+
let targetId = loop.conditionExpressionId;
|
|
457
|
+
// For CallExpression conditions, look up the actual CALL_SITE by coordinates
|
|
458
|
+
// because CALL_SITE uses semantic IDs that don't match the generated ID
|
|
459
|
+
if (loop.conditionExpressionType === 'CallExpression' && loop.conditionLine && loop.conditionColumn !== undefined) {
|
|
460
|
+
const callSite = callSites.find(cs => cs.file === loop.file &&
|
|
461
|
+
cs.line === loop.conditionLine &&
|
|
462
|
+
cs.column === loop.conditionColumn);
|
|
463
|
+
if (callSite) {
|
|
464
|
+
targetId = callSite.id;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
this._bufferEdge({
|
|
468
|
+
type: 'HAS_CONDITION',
|
|
469
|
+
src: loop.id,
|
|
470
|
+
dst: targetId
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Buffer EXPRESSION nodes for loop condition expressions (non-CallExpression).
|
|
476
|
+
* Similar to bufferDiscriminantExpressions but for loops.
|
|
477
|
+
*
|
|
478
|
+
* REG-280: Creates EXPRESSION nodes for while/do-while/for loop conditions.
|
|
479
|
+
* CallExpression conditions use existing CALL_SITE nodes (no EXPRESSION created).
|
|
480
|
+
*/
|
|
481
|
+
bufferLoopConditionExpressions(loops) {
|
|
482
|
+
for (const loop of loops) {
|
|
483
|
+
// Skip for-in/for-of loops - they don't have test expressions
|
|
484
|
+
if (loop.loopType === 'for-in' || loop.loopType === 'for-of') {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (loop.conditionExpressionId && loop.conditionExpressionType) {
|
|
488
|
+
// Skip CallExpression - we link to existing CALL_SITE in bufferLoopConditionEdges
|
|
489
|
+
if (loop.conditionExpressionType === 'CallExpression') {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
// Only create if it looks like an EXPRESSION ID
|
|
493
|
+
if (loop.conditionExpressionId.includes(':EXPRESSION:')) {
|
|
494
|
+
this._bufferNode({
|
|
495
|
+
id: loop.conditionExpressionId,
|
|
496
|
+
type: 'EXPRESSION',
|
|
497
|
+
name: loop.conditionExpressionType,
|
|
498
|
+
file: loop.file,
|
|
499
|
+
line: loop.conditionLine,
|
|
500
|
+
column: loop.conditionColumn,
|
|
501
|
+
expressionType: loop.conditionExpressionType
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Buffer BRANCH edges (CONTAINS, HAS_CONDITION, HAS_CONSEQUENT, HAS_ALTERNATE)
|
|
509
|
+
*
|
|
510
|
+
* REG-275: For CallExpression discriminants (switch(getType())), looks up the
|
|
511
|
+
* actual CALL_SITE node by coordinates since the CALL_SITE uses semantic IDs.
|
|
512
|
+
*
|
|
513
|
+
* Phase 3 (REG-267): For if-branches, creates HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
514
|
+
* pointing to the if-body and else-body SCOPEs.
|
|
515
|
+
*/
|
|
516
|
+
bufferBranchEdges(branches, callSites, scopes) {
|
|
517
|
+
for (const branch of branches) {
|
|
518
|
+
// Parent SCOPE -> CONTAINS -> BRANCH
|
|
519
|
+
if (branch.parentScopeId) {
|
|
520
|
+
this._bufferEdge({
|
|
521
|
+
type: 'CONTAINS',
|
|
522
|
+
src: branch.parentScopeId,
|
|
523
|
+
dst: branch.id
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// BRANCH -> HAS_CONDITION -> EXPRESSION/CALL (discriminant)
|
|
527
|
+
if (branch.discriminantExpressionId) {
|
|
528
|
+
let targetId = branch.discriminantExpressionId;
|
|
529
|
+
// For CallExpression discriminants, look up the actual CALL_SITE by coordinates
|
|
530
|
+
// because CALL_SITE uses semantic IDs that don't match the generated ID
|
|
531
|
+
if (branch.discriminantExpressionType === 'CallExpression' && branch.discriminantLine && branch.discriminantColumn !== undefined) {
|
|
532
|
+
const callSite = callSites.find(cs => cs.file === branch.file &&
|
|
533
|
+
cs.line === branch.discriminantLine &&
|
|
534
|
+
cs.column === branch.discriminantColumn);
|
|
535
|
+
if (callSite) {
|
|
536
|
+
targetId = callSite.id;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
this._bufferEdge({
|
|
540
|
+
type: 'HAS_CONDITION',
|
|
541
|
+
src: branch.id,
|
|
542
|
+
dst: targetId
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
// Phase 3: For if-branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges
|
|
546
|
+
if (branch.branchType === 'if') {
|
|
547
|
+
// Find consequent (if-body) scope - parentScopeId matches branch.id, scopeType is 'if_statement'
|
|
548
|
+
const consequentScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'if_statement');
|
|
549
|
+
if (consequentScope) {
|
|
550
|
+
this._bufferEdge({
|
|
551
|
+
type: 'HAS_CONSEQUENT',
|
|
552
|
+
src: branch.id,
|
|
553
|
+
dst: consequentScope.id
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
// Find alternate (else-body) scope - parentScopeId matches branch.id, scopeType is 'else_statement'
|
|
557
|
+
const alternateScope = scopes.find(s => s.parentScopeId === branch.id && s.scopeType === 'else_statement');
|
|
558
|
+
if (alternateScope) {
|
|
559
|
+
this._bufferEdge({
|
|
560
|
+
type: 'HAS_ALTERNATE',
|
|
561
|
+
src: branch.id,
|
|
562
|
+
dst: alternateScope.id
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
// For else-if chains: if this branch is the alternate of another branch
|
|
566
|
+
// This is handled differently - see below
|
|
567
|
+
}
|
|
568
|
+
// REG-287: For ternary branches, create HAS_CONSEQUENT and HAS_ALTERNATE edges to expressions
|
|
569
|
+
if (branch.branchType === 'ternary') {
|
|
570
|
+
if (branch.consequentExpressionId) {
|
|
571
|
+
this._bufferEdge({
|
|
572
|
+
type: 'HAS_CONSEQUENT',
|
|
573
|
+
src: branch.id,
|
|
574
|
+
dst: branch.consequentExpressionId
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
if (branch.alternateExpressionId) {
|
|
578
|
+
this._bufferEdge({
|
|
579
|
+
type: 'HAS_ALTERNATE',
|
|
580
|
+
src: branch.id,
|
|
581
|
+
dst: branch.alternateExpressionId
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// Phase 3: For else-if chains, create HAS_ALTERNATE from parent branch to this branch
|
|
586
|
+
if (branch.isAlternateOfBranchId) {
|
|
587
|
+
this._bufferEdge({
|
|
588
|
+
type: 'HAS_ALTERNATE',
|
|
589
|
+
src: branch.isAlternateOfBranchId,
|
|
590
|
+
dst: branch.id
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Buffer CASE edges (HAS_CASE, HAS_DEFAULT)
|
|
597
|
+
*/
|
|
598
|
+
bufferCaseEdges(cases) {
|
|
599
|
+
for (const caseInfo of cases) {
|
|
600
|
+
// BRANCH -> HAS_CASE or HAS_DEFAULT -> CASE
|
|
601
|
+
const edgeType = caseInfo.isDefault ? 'HAS_DEFAULT' : 'HAS_CASE';
|
|
602
|
+
this._bufferEdge({
|
|
603
|
+
type: edgeType,
|
|
604
|
+
src: caseInfo.parentBranchId,
|
|
605
|
+
dst: caseInfo.id
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Buffer edges for TRY_BLOCK, CATCH_BLOCK, FINALLY_BLOCK nodes (Phase 4)
|
|
611
|
+
*
|
|
612
|
+
* Creates edges for:
|
|
613
|
+
* - Parent -> CONTAINS -> TRY_BLOCK
|
|
614
|
+
* - TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
615
|
+
* - TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
|
|
616
|
+
*/
|
|
617
|
+
bufferTryCatchFinallyEdges(tryBlocks, catchBlocks, finallyBlocks) {
|
|
618
|
+
// Buffer TRY_BLOCK edges
|
|
619
|
+
for (const tryBlock of tryBlocks) {
|
|
620
|
+
// Parent -> CONTAINS -> TRY_BLOCK
|
|
621
|
+
if (tryBlock.parentScopeId) {
|
|
622
|
+
this._bufferEdge({
|
|
623
|
+
type: 'CONTAINS',
|
|
624
|
+
src: tryBlock.parentScopeId,
|
|
625
|
+
dst: tryBlock.id
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Buffer CATCH_BLOCK edges (HAS_CATCH from TRY_BLOCK)
|
|
630
|
+
for (const catchBlock of catchBlocks) {
|
|
631
|
+
// TRY_BLOCK -> HAS_CATCH -> CATCH_BLOCK
|
|
632
|
+
this._bufferEdge({
|
|
633
|
+
type: 'HAS_CATCH',
|
|
634
|
+
src: catchBlock.parentTryBlockId,
|
|
635
|
+
dst: catchBlock.id
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
// Buffer FINALLY_BLOCK edges (HAS_FINALLY from TRY_BLOCK)
|
|
639
|
+
for (const finallyBlock of finallyBlocks) {
|
|
640
|
+
// TRY_BLOCK -> HAS_FINALLY -> FINALLY_BLOCK
|
|
641
|
+
this._bufferEdge({
|
|
642
|
+
type: 'HAS_FINALLY',
|
|
643
|
+
src: finallyBlock.parentTryBlockId,
|
|
644
|
+
dst: finallyBlock.id
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Buffer EXPRESSION nodes for switch discriminants
|
|
650
|
+
* Uses stored metadata directly instead of parsing from ID (Linus improvement)
|
|
651
|
+
*
|
|
652
|
+
* REG-275: For CallExpression discriminants, we don't create nodes here since
|
|
653
|
+
* bufferBranchEdges links to the existing CALL_SITE node by coordinates.
|
|
654
|
+
*/
|
|
655
|
+
bufferDiscriminantExpressions(branches, callSites) {
|
|
656
|
+
for (const branch of branches) {
|
|
657
|
+
if (branch.discriminantExpressionId && branch.discriminantExpressionType) {
|
|
658
|
+
// Skip CallExpression - we link to existing CALL_SITE in bufferBranchEdges
|
|
659
|
+
if (branch.discriminantExpressionType === 'CallExpression') {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
// Only create if it looks like an EXPRESSION ID
|
|
663
|
+
if (branch.discriminantExpressionId.includes(':EXPRESSION:')) {
|
|
664
|
+
this._bufferNode({
|
|
665
|
+
id: branch.discriminantExpressionId,
|
|
666
|
+
type: 'EXPRESSION',
|
|
667
|
+
name: branch.discriminantExpressionType,
|
|
668
|
+
file: branch.file,
|
|
669
|
+
line: branch.discriminantLine,
|
|
670
|
+
column: branch.discriminantColumn,
|
|
671
|
+
expressionType: branch.discriminantExpressionType
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
209
677
|
bufferVariableEdges(variableDeclarations) {
|
|
210
678
|
for (const varDecl of variableDeclarations) {
|
|
211
679
|
const { parentScopeId, ...varData } = varDecl;
|
|
@@ -237,39 +705,61 @@ export class GraphBuilder {
|
|
|
237
705
|
}
|
|
238
706
|
}
|
|
239
707
|
}
|
|
240
|
-
bufferMethodCalls(methodCalls) {
|
|
708
|
+
bufferMethodCalls(methodCalls, variableDeclarations, parameters) {
|
|
241
709
|
for (const methodCall of methodCalls) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this._bufferNode(methodData);
|
|
710
|
+
// Keep parentScopeId on node for queries
|
|
711
|
+
this._bufferNode(methodCall);
|
|
245
712
|
// SCOPE -> CONTAINS -> METHOD_CALL
|
|
246
713
|
this._bufferEdge({
|
|
247
714
|
type: 'CONTAINS',
|
|
248
|
-
src: parentScopeId,
|
|
249
|
-
dst:
|
|
715
|
+
src: methodCall.parentScopeId,
|
|
716
|
+
dst: methodCall.id
|
|
250
717
|
});
|
|
718
|
+
// REG-262: Create USES edge from METHOD_CALL to receiver variable
|
|
719
|
+
// Skip 'this' - it's not a variable node
|
|
720
|
+
if (methodCall.object && methodCall.object !== 'this') {
|
|
721
|
+
// Handle nested member expressions: obj.nested.method() -> use base 'obj'
|
|
722
|
+
const receiverName = methodCall.object.includes('.')
|
|
723
|
+
? methodCall.object.split('.')[0]
|
|
724
|
+
: methodCall.object;
|
|
725
|
+
// Find receiver variable in current file
|
|
726
|
+
const receiverVar = variableDeclarations.find(v => v.name === receiverName && v.file === methodCall.file);
|
|
727
|
+
if (receiverVar) {
|
|
728
|
+
this._bufferEdge({
|
|
729
|
+
type: 'USES',
|
|
730
|
+
src: methodCall.id,
|
|
731
|
+
dst: receiverVar.id
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
// Check parameters (function arguments)
|
|
736
|
+
const receiverParam = parameters.find(p => p.name === receiverName && p.file === methodCall.file);
|
|
737
|
+
if (receiverParam) {
|
|
738
|
+
this._bufferEdge({
|
|
739
|
+
type: 'USES',
|
|
740
|
+
src: methodCall.id,
|
|
741
|
+
dst: receiverParam.id
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
251
746
|
}
|
|
252
747
|
}
|
|
253
748
|
bufferStdioNodes(methodCalls) {
|
|
254
749
|
const consoleIOMethods = methodCalls.filter(mc => (mc.object === 'console' && (mc.method === 'log' || mc.method === 'error')));
|
|
255
750
|
if (consoleIOMethods.length > 0) {
|
|
256
|
-
const
|
|
751
|
+
const stdioNode = NodeFactory.createExternalStdio();
|
|
257
752
|
// Buffer net:stdio node only once (singleton)
|
|
258
|
-
if (!this._createdSingletons.has(
|
|
259
|
-
this._bufferNode(
|
|
260
|
-
|
|
261
|
-
type: 'net:stdio',
|
|
262
|
-
name: '__stdio__',
|
|
263
|
-
description: 'Standard input/output stream'
|
|
264
|
-
});
|
|
265
|
-
this._createdSingletons.add(stdioId);
|
|
753
|
+
if (!this._createdSingletons.has(stdioNode.id)) {
|
|
754
|
+
this._bufferNode(stdioNode);
|
|
755
|
+
this._createdSingletons.add(stdioNode.id);
|
|
266
756
|
}
|
|
267
757
|
// Buffer WRITES_TO edges for console.log/error
|
|
268
758
|
for (const methodCall of consoleIOMethods) {
|
|
269
759
|
this._bufferEdge({
|
|
270
760
|
type: 'WRITES_TO',
|
|
271
761
|
src: methodCall.id,
|
|
272
|
-
dst:
|
|
762
|
+
dst: stdioNode.id
|
|
273
763
|
});
|
|
274
764
|
}
|
|
275
765
|
}
|
|
@@ -295,9 +785,13 @@ export class GraphBuilder {
|
|
|
295
785
|
dst: methodId
|
|
296
786
|
});
|
|
297
787
|
}
|
|
298
|
-
// If superClass, buffer DERIVES_FROM edge
|
|
788
|
+
// If superClass, buffer DERIVES_FROM edge with computed ID
|
|
299
789
|
if (superClass) {
|
|
300
|
-
|
|
790
|
+
// Compute superclass ID using semantic ID format
|
|
791
|
+
// Assume superclass is in same file at global scope (most common case)
|
|
792
|
+
// When superclass is in different file, edge will be dangling until that file analyzed
|
|
793
|
+
const globalContext = { file, scopePath: [] };
|
|
794
|
+
const superClassId = computeSemanticId('CLASS', superClass, globalContext);
|
|
301
795
|
this._bufferEdge({
|
|
302
796
|
type: 'DERIVES_FROM',
|
|
303
797
|
src: id,
|
|
@@ -308,9 +802,11 @@ export class GraphBuilder {
|
|
|
308
802
|
}
|
|
309
803
|
bufferClassNodes(module, classInstantiations, classDeclarations) {
|
|
310
804
|
// Create lookup map: className → declaration ID
|
|
805
|
+
// Use basename for comparison because CLASS nodes use scopeTracker.file (basename)
|
|
806
|
+
const moduleBasename = basename(module.file);
|
|
311
807
|
const declarationMap = new Map();
|
|
312
808
|
for (const decl of classDeclarations) {
|
|
313
|
-
if (decl.file ===
|
|
809
|
+
if (decl.file === moduleBasename) {
|
|
314
810
|
declarationMap.set(decl.name, decl.id);
|
|
315
811
|
}
|
|
316
812
|
}
|
|
@@ -318,16 +814,12 @@ export class GraphBuilder {
|
|
|
318
814
|
const { variableId, className, line } = instantiation;
|
|
319
815
|
let classId = declarationMap.get(className);
|
|
320
816
|
if (!classId) {
|
|
321
|
-
// External class -
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
file: module.file,
|
|
328
|
-
line,
|
|
329
|
-
isInstantiationRef: true
|
|
330
|
-
});
|
|
817
|
+
// External class - compute semantic ID
|
|
818
|
+
// Use basename to match CLASS node format (scopeTracker uses basename)
|
|
819
|
+
// When class is in different file, edge will be dangling until that file analyzed
|
|
820
|
+
const globalContext = { file: moduleBasename, scopePath: [] };
|
|
821
|
+
classId = computeSemanticId('CLASS', className, globalContext);
|
|
822
|
+
// NO node creation - node will exist when class file analyzed
|
|
331
823
|
}
|
|
332
824
|
// Buffer INSTANCE_OF edge
|
|
333
825
|
this._bufferEdge({
|
|
@@ -352,119 +844,136 @@ export class GraphBuilder {
|
|
|
352
844
|
}
|
|
353
845
|
bufferImportNodes(module, imports) {
|
|
354
846
|
for (const imp of imports) {
|
|
355
|
-
const { source, specifiers, line } = imp;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
imported:
|
|
366
|
-
local:
|
|
367
|
-
|
|
368
|
-
line: line
|
|
847
|
+
const { source, specifiers, line, column, isDynamic, isResolvable, dynamicPath } = imp;
|
|
848
|
+
// REG-273: Handle side-effect-only imports (no specifiers)
|
|
849
|
+
if (specifiers.length === 0) {
|
|
850
|
+
// Side-effect import: import './polyfill.js'
|
|
851
|
+
const importNode = ImportNode.create(source, // name = source (no local binding)
|
|
852
|
+
module.file, // file
|
|
853
|
+
line, // line (stored as field, not in ID)
|
|
854
|
+
column || 0, // column
|
|
855
|
+
source, // source module
|
|
856
|
+
{
|
|
857
|
+
imported: '*', // no specific export
|
|
858
|
+
local: source, // source becomes local
|
|
859
|
+
sideEffect: true // mark as side-effect import
|
|
369
860
|
});
|
|
861
|
+
this._bufferNode(importNode);
|
|
370
862
|
// MODULE -> CONTAINS -> IMPORT
|
|
371
863
|
this._bufferEdge({
|
|
372
864
|
type: 'CONTAINS',
|
|
373
865
|
src: module.id,
|
|
374
|
-
dst:
|
|
866
|
+
dst: importNode.id
|
|
375
867
|
});
|
|
376
868
|
// Create EXTERNAL_MODULE node for external modules
|
|
377
869
|
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
378
870
|
if (!isRelative) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
line: line
|
|
386
|
-
});
|
|
871
|
+
const externalModule = NodeFactory.createExternalModule(source);
|
|
872
|
+
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
873
|
+
if (!this._createdSingletons.has(externalModule.id)) {
|
|
874
|
+
this._bufferNode(externalModule);
|
|
875
|
+
this._createdSingletons.add(externalModule.id);
|
|
876
|
+
}
|
|
387
877
|
this._bufferEdge({
|
|
388
878
|
type: 'IMPORTS',
|
|
389
879
|
src: module.id,
|
|
390
|
-
dst:
|
|
880
|
+
dst: externalModule.id
|
|
391
881
|
});
|
|
392
882
|
}
|
|
393
883
|
}
|
|
884
|
+
else {
|
|
885
|
+
// Regular imports with specifiers
|
|
886
|
+
for (const spec of specifiers) {
|
|
887
|
+
// Use ImportNode factory for proper semantic IDs and field population
|
|
888
|
+
const importNode = ImportNode.create(spec.local, // name = local binding
|
|
889
|
+
module.file, // file
|
|
890
|
+
line, // line (stored as field, not in ID)
|
|
891
|
+
column || 0, // column
|
|
892
|
+
source, // source module
|
|
893
|
+
{
|
|
894
|
+
imported: spec.imported,
|
|
895
|
+
local: spec.local,
|
|
896
|
+
sideEffect: false, // regular imports are not side-effects
|
|
897
|
+
// importType is auto-detected from imported field
|
|
898
|
+
// Dynamic import fields
|
|
899
|
+
isDynamic,
|
|
900
|
+
isResolvable,
|
|
901
|
+
dynamicPath
|
|
902
|
+
});
|
|
903
|
+
this._bufferNode(importNode);
|
|
904
|
+
// MODULE -> CONTAINS -> IMPORT
|
|
905
|
+
this._bufferEdge({
|
|
906
|
+
type: 'CONTAINS',
|
|
907
|
+
src: module.id,
|
|
908
|
+
dst: importNode.id
|
|
909
|
+
});
|
|
910
|
+
// Create EXTERNAL_MODULE node for external modules
|
|
911
|
+
const isRelative = source.startsWith('./') || source.startsWith('../');
|
|
912
|
+
if (!isRelative) {
|
|
913
|
+
const externalModule = NodeFactory.createExternalModule(source);
|
|
914
|
+
// Avoid duplicate EXTERNAL_MODULE nodes
|
|
915
|
+
if (!this._createdSingletons.has(externalModule.id)) {
|
|
916
|
+
this._bufferNode(externalModule);
|
|
917
|
+
this._createdSingletons.add(externalModule.id);
|
|
918
|
+
}
|
|
919
|
+
this._bufferEdge({
|
|
920
|
+
type: 'IMPORTS',
|
|
921
|
+
src: module.id,
|
|
922
|
+
dst: externalModule.id
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
394
927
|
}
|
|
395
928
|
}
|
|
396
929
|
bufferExportNodes(module, exports) {
|
|
397
930
|
for (const exp of exports) {
|
|
398
931
|
const { type, line, name, specifiers, source } = exp;
|
|
399
932
|
if (type === 'default') {
|
|
400
|
-
const
|
|
401
|
-
this._bufferNode(
|
|
402
|
-
id: exportId,
|
|
403
|
-
type: 'EXPORT',
|
|
404
|
-
exportType: 'default',
|
|
405
|
-
name: 'default',
|
|
406
|
-
file: module.file,
|
|
407
|
-
line: line
|
|
408
|
-
});
|
|
933
|
+
const exportNode = NodeFactory.createExport('default', module.file, line, 0, { default: true, exportType: 'default' });
|
|
934
|
+
this._bufferNode(exportNode);
|
|
409
935
|
this._bufferEdge({
|
|
410
936
|
type: 'CONTAINS',
|
|
411
937
|
src: module.id,
|
|
412
|
-
dst:
|
|
938
|
+
dst: exportNode.id
|
|
413
939
|
});
|
|
414
940
|
}
|
|
415
941
|
else if (type === 'named') {
|
|
416
942
|
if (specifiers) {
|
|
417
943
|
for (const spec of specifiers) {
|
|
418
|
-
const
|
|
419
|
-
this._bufferNode({
|
|
420
|
-
id: exportId,
|
|
421
|
-
type: 'EXPORT',
|
|
422
|
-
exportType: 'named',
|
|
423
|
-
name: spec.exported,
|
|
944
|
+
const exportNode = NodeFactory.createExport(spec.exported, module.file, line, 0, {
|
|
424
945
|
local: spec.local,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
source: source
|
|
946
|
+
source: source,
|
|
947
|
+
exportType: 'named'
|
|
428
948
|
});
|
|
949
|
+
this._bufferNode(exportNode);
|
|
429
950
|
this._bufferEdge({
|
|
430
951
|
type: 'CONTAINS',
|
|
431
952
|
src: module.id,
|
|
432
|
-
dst:
|
|
953
|
+
dst: exportNode.id
|
|
433
954
|
});
|
|
434
955
|
}
|
|
435
956
|
}
|
|
436
957
|
else if (name) {
|
|
437
|
-
const
|
|
438
|
-
this._bufferNode(
|
|
439
|
-
id: exportId,
|
|
440
|
-
type: 'EXPORT',
|
|
441
|
-
exportType: 'named',
|
|
442
|
-
name: name,
|
|
443
|
-
file: module.file,
|
|
444
|
-
line: line
|
|
445
|
-
});
|
|
958
|
+
const exportNode = NodeFactory.createExport(name, module.file, line, 0, { exportType: 'named' });
|
|
959
|
+
this._bufferNode(exportNode);
|
|
446
960
|
this._bufferEdge({
|
|
447
961
|
type: 'CONTAINS',
|
|
448
962
|
src: module.id,
|
|
449
|
-
dst:
|
|
963
|
+
dst: exportNode.id
|
|
450
964
|
});
|
|
451
965
|
}
|
|
452
966
|
}
|
|
453
967
|
else if (type === 'all') {
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
type: 'EXPORT',
|
|
458
|
-
exportType: 'all',
|
|
459
|
-
name: '*',
|
|
460
|
-
file: module.file,
|
|
461
|
-
line: line,
|
|
462
|
-
source: source
|
|
968
|
+
const exportNode = NodeFactory.createExport('*', module.file, line, 0, {
|
|
969
|
+
source: source,
|
|
970
|
+
exportType: 'all'
|
|
463
971
|
});
|
|
972
|
+
this._bufferNode(exportNode);
|
|
464
973
|
this._bufferEdge({
|
|
465
974
|
type: 'CONTAINS',
|
|
466
975
|
src: module.id,
|
|
467
|
-
dst:
|
|
976
|
+
dst: exportNode.id
|
|
468
977
|
});
|
|
469
978
|
}
|
|
470
979
|
}
|
|
@@ -493,14 +1002,11 @@ export class GraphBuilder {
|
|
|
493
1002
|
}
|
|
494
1003
|
bufferHttpRequests(httpRequests, functions) {
|
|
495
1004
|
if (httpRequests.length > 0) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
name: '__network__'
|
|
502
|
-
});
|
|
503
|
-
this._createdSingletons.add(networkId);
|
|
1005
|
+
// Create net:request singleton using factory
|
|
1006
|
+
const networkNode = NetworkRequestNode.create();
|
|
1007
|
+
if (!this._createdSingletons.has(networkNode.id)) {
|
|
1008
|
+
this._bufferNode(networkNode);
|
|
1009
|
+
this._createdSingletons.add(networkNode.id);
|
|
504
1010
|
}
|
|
505
1011
|
for (const request of httpRequests) {
|
|
506
1012
|
const { parentScopeId, ...requestData } = request;
|
|
@@ -508,7 +1014,7 @@ export class GraphBuilder {
|
|
|
508
1014
|
this._bufferEdge({
|
|
509
1015
|
type: 'CALLS',
|
|
510
1016
|
src: request.id,
|
|
511
|
-
dst:
|
|
1017
|
+
dst: networkNode.id
|
|
512
1018
|
});
|
|
513
1019
|
if (parentScopeId) {
|
|
514
1020
|
const scopeParts = parentScopeId.split(':');
|
|
@@ -536,11 +1042,26 @@ export class GraphBuilder {
|
|
|
536
1042
|
}
|
|
537
1043
|
bufferAssignmentEdges(variableAssignments, variableDeclarations, callSites, methodCalls, functions, classInstantiations, parameters) {
|
|
538
1044
|
for (const assignment of variableAssignments) {
|
|
539
|
-
const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, className } = assignment;
|
|
1045
|
+
const { variableId, sourceId, sourceType, sourceName, sourceLine, sourceColumn, sourceFile, functionName, line, column, className } = assignment;
|
|
540
1046
|
// Skip CLASS sourceType - handled async in createClassAssignmentEdges
|
|
541
1047
|
if (sourceType === 'CLASS') {
|
|
542
1048
|
continue;
|
|
543
1049
|
}
|
|
1050
|
+
// CONSTRUCTOR_CALL: create ASSIGNED_FROM edge to existing node
|
|
1051
|
+
// Note: CONSTRUCTOR_CALL nodes are already created from constructorCalls collection in step 4.5
|
|
1052
|
+
if (sourceType === 'CONSTRUCTOR_CALL' && className) {
|
|
1053
|
+
const constructorLine = line ?? 0;
|
|
1054
|
+
const constructorColumn = column ?? 0;
|
|
1055
|
+
const constructorFile = assignment.file ?? '';
|
|
1056
|
+
// Generate ID matching the one created in NewExpression visitor
|
|
1057
|
+
const constructorCallId = NodeFactory.generateConstructorCallId(className, constructorFile, constructorLine, constructorColumn);
|
|
1058
|
+
this._bufferEdge({
|
|
1059
|
+
type: 'ASSIGNED_FROM',
|
|
1060
|
+
src: variableId,
|
|
1061
|
+
dst: constructorCallId
|
|
1062
|
+
});
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
544
1065
|
// Direct LITERAL assignment
|
|
545
1066
|
if (sourceId && sourceType !== 'EXPRESSION') {
|
|
546
1067
|
this._bufferEdge({
|
|
@@ -582,8 +1103,10 @@ export class GraphBuilder {
|
|
|
582
1103
|
}
|
|
583
1104
|
// VARIABLE by name
|
|
584
1105
|
else if (sourceType === 'VARIABLE' && sourceName) {
|
|
585
|
-
|
|
586
|
-
|
|
1106
|
+
// Find the current variable's file by looking it up in variableDeclarations
|
|
1107
|
+
// (semantic IDs don't have predictable file positions like old hash-based IDs)
|
|
1108
|
+
const currentVar = variableDeclarations.find(v => v.id === variableId);
|
|
1109
|
+
const varFile = currentVar?.file ?? null;
|
|
587
1110
|
const sourceVariable = variableDeclarations.find(v => v.name === sourceName && v.file === varFile);
|
|
588
1111
|
if (sourceVariable) {
|
|
589
1112
|
this._bufferEdge({
|
|
@@ -614,35 +1137,25 @@ export class GraphBuilder {
|
|
|
614
1137
|
});
|
|
615
1138
|
}
|
|
616
1139
|
}
|
|
617
|
-
// EXPRESSION node creation
|
|
1140
|
+
// EXPRESSION node creation using NodeFactory
|
|
618
1141
|
else if (sourceType === 'EXPRESSION' && sourceId) {
|
|
619
|
-
const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
else if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
637
|
-
expressionNode.operator = operator;
|
|
638
|
-
expressionNode.name = `<${expressionType}>`;
|
|
639
|
-
}
|
|
640
|
-
else if (expressionType === 'ConditionalExpression') {
|
|
641
|
-
expressionNode.name = '<ternary>';
|
|
642
|
-
}
|
|
643
|
-
else if (expressionType === 'TemplateLiteral') {
|
|
644
|
-
expressionNode.name = '<template>';
|
|
645
|
-
}
|
|
1142
|
+
const { expressionType, object, property, computed, computedPropertyVar, operator, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, file: exprFile, line: exprLine, column: exprColumn,
|
|
1143
|
+
// Destructuring support (REG-201)
|
|
1144
|
+
path, baseName, propertyPath, arrayIndex } = assignment;
|
|
1145
|
+
// Create node from upstream metadata using factory
|
|
1146
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', exprFile || '', exprLine || 0, exprColumn || 0, {
|
|
1147
|
+
id: sourceId, // ID from JSASTAnalyzer
|
|
1148
|
+
object,
|
|
1149
|
+
property,
|
|
1150
|
+
computed,
|
|
1151
|
+
computedPropertyVar: computedPropertyVar ?? undefined,
|
|
1152
|
+
operator,
|
|
1153
|
+
// Destructuring support (REG-201)
|
|
1154
|
+
path,
|
|
1155
|
+
baseName,
|
|
1156
|
+
propertyPath,
|
|
1157
|
+
arrayIndex
|
|
1158
|
+
});
|
|
646
1159
|
this._bufferNode(expressionNode);
|
|
647
1160
|
this._bufferEdge({
|
|
648
1161
|
type: 'ASSIGNED_FROM',
|
|
@@ -662,6 +1175,41 @@ export class GraphBuilder {
|
|
|
662
1175
|
});
|
|
663
1176
|
}
|
|
664
1177
|
}
|
|
1178
|
+
// Call-based source lookup (REG-223)
|
|
1179
|
+
else if (expressionType === 'MemberExpression' && assignment.callSourceLine !== undefined) {
|
|
1180
|
+
const { callSourceLine, callSourceColumn, callSourceName, callSourceFile } = assignment;
|
|
1181
|
+
// Try CALL_SITE first (direct function calls)
|
|
1182
|
+
const callSite = callSites.find(cs => cs.line === callSourceLine &&
|
|
1183
|
+
cs.column === callSourceColumn &&
|
|
1184
|
+
(callSourceName ? cs.name === callSourceName : true));
|
|
1185
|
+
if (callSite) {
|
|
1186
|
+
this._bufferEdge({
|
|
1187
|
+
type: 'DERIVES_FROM',
|
|
1188
|
+
src: sourceId,
|
|
1189
|
+
dst: callSite.id
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
// Fall back to methodCalls (arr.map(), obj.getConfig())
|
|
1193
|
+
else {
|
|
1194
|
+
const methodCall = methodCalls.find(mc => mc.line === callSourceLine &&
|
|
1195
|
+
mc.column === callSourceColumn &&
|
|
1196
|
+
(callSourceName ? mc.name === callSourceName : true));
|
|
1197
|
+
if (methodCall) {
|
|
1198
|
+
this._bufferEdge({
|
|
1199
|
+
type: 'DERIVES_FROM',
|
|
1200
|
+
src: sourceId,
|
|
1201
|
+
dst: methodCall.id
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
// Log warning when lookup fails (per Linus review - no silent failures)
|
|
1205
|
+
else {
|
|
1206
|
+
console.warn(`[REG-223] DERIVES_FROM lookup failed for EXPRESSION(${assignment.object}.${assignment.property}) ` +
|
|
1207
|
+
`at ${callSourceFile}:${callSourceLine}:${callSourceColumn}. ` +
|
|
1208
|
+
`Expected CALL_SITE or methodCall for "${callSourceName}". ` +
|
|
1209
|
+
`This indicates a coordinate mismatch or missing call node.`);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
665
1213
|
if ((expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression')) {
|
|
666
1214
|
if (leftSourceName) {
|
|
667
1215
|
const leftVar = variableDeclarations.find(v => v.name === leftSourceName && (!varFile || v.file === varFile));
|
|
@@ -770,6 +1318,12 @@ export class GraphBuilder {
|
|
|
770
1318
|
targetNodeId = nestedCall.id;
|
|
771
1319
|
}
|
|
772
1320
|
}
|
|
1321
|
+
else if (targetType === 'LITERAL' ||
|
|
1322
|
+
targetType === 'OBJECT_LITERAL' ||
|
|
1323
|
+
targetType === 'ARRAY_LITERAL') {
|
|
1324
|
+
// targetId is already set by CallExpressionVisitor
|
|
1325
|
+
targetNodeId = targetId;
|
|
1326
|
+
}
|
|
773
1327
|
if (targetNodeId) {
|
|
774
1328
|
const edgeData = {
|
|
775
1329
|
type: 'PASSES_ARGUMENT',
|
|
@@ -787,53 +1341,50 @@ export class GraphBuilder {
|
|
|
787
1341
|
// ============= TypeScript-specific buffer methods =============
|
|
788
1342
|
/**
|
|
789
1343
|
* Buffer INTERFACE nodes and EXTENDS edges
|
|
1344
|
+
*
|
|
1345
|
+
* Uses two-pass approach:
|
|
1346
|
+
* 1. First pass: create all interface nodes, store in Map
|
|
1347
|
+
* 2. Second pass: create EXTENDS edges using stored node IDs
|
|
790
1348
|
*/
|
|
791
1349
|
bufferInterfaceNodes(module, interfaces) {
|
|
1350
|
+
// First pass: create all interface nodes and store them
|
|
1351
|
+
const interfaceNodes = new Map();
|
|
792
1352
|
for (const iface of interfaces) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
type: 'INTERFACE',
|
|
797
|
-
name: iface.name,
|
|
798
|
-
file: iface.file,
|
|
799
|
-
line: iface.line,
|
|
800
|
-
column: iface.column,
|
|
801
|
-
properties: iface.properties,
|
|
802
|
-
extends: iface.extends
|
|
1353
|
+
const interfaceNode = InterfaceNode.create(iface.name, iface.file, iface.line, iface.column || 0, {
|
|
1354
|
+
extends: iface.extends,
|
|
1355
|
+
properties: iface.properties
|
|
803
1356
|
});
|
|
1357
|
+
interfaceNodes.set(iface.name, interfaceNode);
|
|
1358
|
+
this._bufferNode(interfaceNode);
|
|
804
1359
|
// MODULE -> CONTAINS -> INTERFACE
|
|
805
1360
|
this._bufferEdge({
|
|
806
1361
|
type: 'CONTAINS',
|
|
807
1362
|
src: module.id,
|
|
808
|
-
dst:
|
|
1363
|
+
dst: interfaceNode.id
|
|
809
1364
|
});
|
|
810
|
-
|
|
1365
|
+
}
|
|
1366
|
+
// Second pass: create EXTENDS edges
|
|
1367
|
+
for (const iface of interfaces) {
|
|
811
1368
|
if (iface.extends && iface.extends.length > 0) {
|
|
1369
|
+
const srcNode = interfaceNodes.get(iface.name);
|
|
812
1370
|
for (const parentName of iface.extends) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1371
|
+
const parentNode = interfaceNodes.get(parentName);
|
|
1372
|
+
if (parentNode) {
|
|
1373
|
+
// Same-file interface
|
|
816
1374
|
this._bufferEdge({
|
|
817
1375
|
type: 'EXTENDS',
|
|
818
|
-
src:
|
|
819
|
-
dst:
|
|
1376
|
+
src: srcNode.id,
|
|
1377
|
+
dst: parentNode.id
|
|
820
1378
|
});
|
|
821
1379
|
}
|
|
822
1380
|
else {
|
|
823
1381
|
// External interface - create a reference node
|
|
824
|
-
const
|
|
825
|
-
this._bufferNode(
|
|
826
|
-
id: externalId,
|
|
827
|
-
type: 'INTERFACE',
|
|
828
|
-
name: parentName,
|
|
829
|
-
file: iface.file,
|
|
830
|
-
line: iface.line,
|
|
831
|
-
isExternal: true
|
|
832
|
-
});
|
|
1382
|
+
const externalInterface = NodeFactory.createInterface(parentName, iface.file, iface.line, 0, { isExternal: true });
|
|
1383
|
+
this._bufferNode(externalInterface);
|
|
833
1384
|
this._bufferEdge({
|
|
834
1385
|
type: 'EXTENDS',
|
|
835
|
-
src:
|
|
836
|
-
dst:
|
|
1386
|
+
src: srcNode.id,
|
|
1387
|
+
dst: externalInterface.id
|
|
837
1388
|
});
|
|
838
1389
|
}
|
|
839
1390
|
}
|
|
@@ -845,45 +1396,35 @@ export class GraphBuilder {
|
|
|
845
1396
|
*/
|
|
846
1397
|
bufferTypeAliasNodes(module, typeAliases) {
|
|
847
1398
|
for (const typeAlias of typeAliases) {
|
|
848
|
-
//
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
type: 'TYPE',
|
|
852
|
-
name: typeAlias.name,
|
|
853
|
-
file: typeAlias.file,
|
|
854
|
-
line: typeAlias.line,
|
|
855
|
-
column: typeAlias.column,
|
|
856
|
-
aliasOf: typeAlias.aliasOf
|
|
857
|
-
});
|
|
1399
|
+
// Create TYPE node using factory
|
|
1400
|
+
const typeNode = NodeFactory.createType(typeAlias.name, typeAlias.file, typeAlias.line, typeAlias.column || 0, { aliasOf: typeAlias.aliasOf });
|
|
1401
|
+
this._bufferNode(typeNode);
|
|
858
1402
|
// MODULE -> CONTAINS -> TYPE
|
|
859
1403
|
this._bufferEdge({
|
|
860
1404
|
type: 'CONTAINS',
|
|
861
1405
|
src: module.id,
|
|
862
|
-
dst:
|
|
1406
|
+
dst: typeNode.id
|
|
863
1407
|
});
|
|
864
1408
|
}
|
|
865
1409
|
}
|
|
866
1410
|
/**
|
|
867
1411
|
* Buffer ENUM nodes
|
|
1412
|
+
* Uses EnumNode.create() to ensure consistent ID format (colon separator)
|
|
868
1413
|
*/
|
|
869
1414
|
bufferEnumNodes(module, enums) {
|
|
870
1415
|
for (const enumDecl of enums) {
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
file: enumDecl.file,
|
|
877
|
-
line: enumDecl.line,
|
|
878
|
-
column: enumDecl.column,
|
|
879
|
-
isConst: enumDecl.isConst,
|
|
880
|
-
members: enumDecl.members
|
|
1416
|
+
// Use EnumNode.create() to generate proper ID (colon format)
|
|
1417
|
+
// Do NOT use enumDecl.id which has legacy # format from TypeScriptVisitor
|
|
1418
|
+
const enumNode = EnumNode.create(enumDecl.name, enumDecl.file, enumDecl.line, enumDecl.column || 0, {
|
|
1419
|
+
isConst: enumDecl.isConst || false,
|
|
1420
|
+
members: enumDecl.members || []
|
|
881
1421
|
});
|
|
1422
|
+
this._bufferNode(enumNode);
|
|
882
1423
|
// MODULE -> CONTAINS -> ENUM
|
|
883
1424
|
this._bufferEdge({
|
|
884
1425
|
type: 'CONTAINS',
|
|
885
1426
|
src: module.id,
|
|
886
|
-
dst:
|
|
1427
|
+
dst: enumNode.id // Use factory-generated ID (colon format)
|
|
887
1428
|
});
|
|
888
1429
|
}
|
|
889
1430
|
}
|
|
@@ -892,22 +1433,15 @@ export class GraphBuilder {
|
|
|
892
1433
|
*/
|
|
893
1434
|
bufferDecoratorNodes(decorators) {
|
|
894
1435
|
for (const decorator of decorators) {
|
|
895
|
-
//
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
name: decorator.name,
|
|
900
|
-
file: decorator.file,
|
|
901
|
-
line: decorator.line,
|
|
902
|
-
column: decorator.column,
|
|
903
|
-
arguments: decorator.arguments,
|
|
904
|
-
targetType: decorator.targetType
|
|
905
|
-
});
|
|
1436
|
+
// Create DECORATOR node using factory (generates colon-format ID)
|
|
1437
|
+
const decoratorNode = DecoratorNode.create(decorator.name, decorator.file, decorator.line, decorator.column || 0, decorator.targetId, // Now included in the node!
|
|
1438
|
+
decorator.targetType, { arguments: decorator.arguments });
|
|
1439
|
+
this._bufferNode(decoratorNode);
|
|
906
1440
|
// TARGET -> DECORATED_BY -> DECORATOR
|
|
907
1441
|
this._bufferEdge({
|
|
908
1442
|
type: 'DECORATED_BY',
|
|
909
1443
|
src: decorator.targetId,
|
|
910
|
-
dst:
|
|
1444
|
+
dst: decoratorNode.id // Use factory-generated ID (colon format)
|
|
911
1445
|
});
|
|
912
1446
|
}
|
|
913
1447
|
}
|
|
@@ -921,27 +1455,23 @@ export class GraphBuilder {
|
|
|
921
1455
|
// Try to find the interface in the same file
|
|
922
1456
|
const iface = interfaces.find(i => i.name === ifaceName);
|
|
923
1457
|
if (iface) {
|
|
1458
|
+
// Compute interface ID using same formula as InterfaceNode.create()
|
|
1459
|
+
// Format: {file}:INTERFACE:{name}:{line}
|
|
1460
|
+
const interfaceId = `${iface.file}:INTERFACE:${iface.name}:${iface.line}`;
|
|
924
1461
|
this._bufferEdge({
|
|
925
1462
|
type: 'IMPLEMENTS',
|
|
926
1463
|
src: classDecl.id,
|
|
927
|
-
dst:
|
|
1464
|
+
dst: interfaceId
|
|
928
1465
|
});
|
|
929
1466
|
}
|
|
930
1467
|
else {
|
|
931
1468
|
// External interface - create a reference node
|
|
932
|
-
const
|
|
933
|
-
this._bufferNode(
|
|
934
|
-
id: externalId,
|
|
935
|
-
type: 'INTERFACE',
|
|
936
|
-
name: ifaceName,
|
|
937
|
-
file: classDecl.file,
|
|
938
|
-
line: classDecl.line,
|
|
939
|
-
isExternal: true
|
|
940
|
-
});
|
|
1469
|
+
const externalInterface = NodeFactory.createInterface(ifaceName, classDecl.file, classDecl.line, 0, { isExternal: true });
|
|
1470
|
+
this._bufferNode(externalInterface);
|
|
941
1471
|
this._bufferEdge({
|
|
942
1472
|
type: 'IMPLEMENTS',
|
|
943
1473
|
src: classDecl.id,
|
|
944
|
-
dst:
|
|
1474
|
+
dst: externalInterface.id
|
|
945
1475
|
});
|
|
946
1476
|
}
|
|
947
1477
|
}
|
|
@@ -949,127 +1479,808 @@ export class GraphBuilder {
|
|
|
949
1479
|
}
|
|
950
1480
|
}
|
|
951
1481
|
/**
|
|
952
|
-
*
|
|
1482
|
+
* Buffer FLOWS_INTO edges for array mutations (push, unshift, splice, indexed assignment)
|
|
1483
|
+
* Creates edges from inserted values to the array variable
|
|
1484
|
+
*
|
|
1485
|
+
* REG-117: Now handles nested mutations like obj.arr.push(item):
|
|
1486
|
+
* - For nested mutations, falls back to base object if array property not found
|
|
1487
|
+
* - Adds nestedProperty metadata for tracking
|
|
1488
|
+
*
|
|
1489
|
+
* OPTIMIZED: Uses Map-based lookup cache for O(1) variable lookups instead of O(n) find()
|
|
953
1490
|
*/
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
for (const
|
|
957
|
-
const {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1491
|
+
bufferArrayMutationEdges(arrayMutations, variableDeclarations, parameters) {
|
|
1492
|
+
// Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
|
|
1493
|
+
for (const mutation of arrayMutations) {
|
|
1494
|
+
const { arrayName, mutationScopePath, mutationMethod, insertedValues, file, isNested, baseObjectName, propertyName } = mutation;
|
|
1495
|
+
const scopePath = mutationScopePath ?? [];
|
|
1496
|
+
// REG-117: For nested mutations (obj.arr.push), resolve target node
|
|
1497
|
+
let targetNodeId = null;
|
|
1498
|
+
let nestedProperty;
|
|
1499
|
+
if (isNested && baseObjectName) {
|
|
1500
|
+
// Skip 'this.items.push' - 'this' is not a variable node
|
|
1501
|
+
if (baseObjectName === 'this')
|
|
1502
|
+
continue;
|
|
1503
|
+
// Nested mutation: try base object lookup with scope chain (REG-309)
|
|
1504
|
+
const baseVar = this.resolveVariableInScope(baseObjectName, scopePath, file, variableDeclarations);
|
|
1505
|
+
const baseParam = !baseVar ? this.resolveParameterInScope(baseObjectName, scopePath, file, parameters) : null;
|
|
1506
|
+
targetNodeId = baseVar?.id ?? baseParam?.id ?? null;
|
|
1507
|
+
nestedProperty = propertyName;
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
// Direct mutation: arr.push() (REG-309)
|
|
1511
|
+
const arrayVar = this.resolveVariableInScope(arrayName, scopePath, file, variableDeclarations);
|
|
1512
|
+
const arrayParam = !arrayVar ? this.resolveParameterInScope(arrayName, scopePath, file, parameters) : null;
|
|
1513
|
+
targetNodeId = arrayVar?.id ?? arrayParam?.id ?? null;
|
|
1514
|
+
}
|
|
1515
|
+
if (!targetNodeId)
|
|
1516
|
+
continue;
|
|
1517
|
+
// Create FLOWS_INTO edges for each inserted value
|
|
1518
|
+
for (const arg of insertedValues) {
|
|
1519
|
+
if (arg.valueType === 'VARIABLE' && arg.valueName) {
|
|
1520
|
+
// Scope-aware lookup for source variable (REG-309)
|
|
1521
|
+
const sourceVar = this.resolveVariableInScope(arg.valueName, scopePath, file, variableDeclarations);
|
|
1522
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(arg.valueName, scopePath, file, parameters) : null;
|
|
1523
|
+
const sourceNodeId = sourceVar?.id ?? sourceParam?.id;
|
|
1524
|
+
if (sourceNodeId) {
|
|
1525
|
+
const edgeData = {
|
|
1526
|
+
type: 'FLOWS_INTO',
|
|
1527
|
+
src: sourceNodeId,
|
|
1528
|
+
dst: targetNodeId,
|
|
1529
|
+
mutationMethod,
|
|
1530
|
+
argIndex: arg.argIndex
|
|
1531
|
+
};
|
|
1532
|
+
if (arg.isSpread) {
|
|
1533
|
+
edgeData.isSpread = true;
|
|
1534
|
+
}
|
|
1535
|
+
// REG-117: Add nested property metadata
|
|
1536
|
+
if (nestedProperty) {
|
|
1537
|
+
edgeData.nestedProperty = nestedProperty;
|
|
1538
|
+
}
|
|
1539
|
+
this._bufferEdge(edgeData);
|
|
966
1540
|
}
|
|
967
1541
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
type: 'ASSIGNED_FROM',
|
|
971
|
-
src: variableId,
|
|
972
|
-
dst: classNode.id
|
|
973
|
-
});
|
|
974
|
-
edgesCreated++;
|
|
975
|
-
}
|
|
1542
|
+
// For literals, object literals, etc. - we could create edges from LITERAL nodes
|
|
1543
|
+
// but for now we just track variable -> array flows
|
|
976
1544
|
}
|
|
977
1545
|
}
|
|
978
|
-
return edgesCreated;
|
|
979
1546
|
}
|
|
980
1547
|
/**
|
|
981
|
-
*
|
|
1548
|
+
* Buffer FLOWS_INTO edges for object mutations (property assignment, Object.assign)
|
|
1549
|
+
* Creates edges from source values to the object variable being mutated.
|
|
1550
|
+
*
|
|
1551
|
+
* REG-152: For 'this.prop = value' patterns inside classes, creates edges
|
|
1552
|
+
* to the CLASS node with mutationType: 'this_property'.
|
|
982
1553
|
*/
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
//
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1554
|
+
bufferObjectMutationEdges(objectMutations, variableDeclarations, parameters, functions, classDeclarations) {
|
|
1555
|
+
for (const mutation of objectMutations) {
|
|
1556
|
+
const { objectName, mutationScopePath, propertyName, mutationType, computedPropertyVar, value, file, enclosingClassName } = mutation;
|
|
1557
|
+
const scopePath = mutationScopePath ?? [];
|
|
1558
|
+
// Find the target node (object variable, parameter, or class for 'this')
|
|
1559
|
+
let objectNodeId = null;
|
|
1560
|
+
let effectiveMutationType = mutationType;
|
|
1561
|
+
if (objectName !== 'this') {
|
|
1562
|
+
// Regular object - find variable, parameter, or function using scope chain (REG-309)
|
|
1563
|
+
const objectVar = this.resolveVariableInScope(objectName, scopePath, file, variableDeclarations);
|
|
1564
|
+
const objectParam = !objectVar ? this.resolveParameterInScope(objectName, scopePath, file, parameters) : null;
|
|
1565
|
+
const objectFunc = !objectVar && !objectParam ? functions.find(f => f.name === objectName && f.file === file) : null;
|
|
1566
|
+
objectNodeId = objectVar?.id ?? objectParam?.id ?? objectFunc?.id ?? null;
|
|
1567
|
+
if (!objectNodeId)
|
|
1568
|
+
continue;
|
|
991
1569
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1570
|
+
else {
|
|
1571
|
+
// REG-152: 'this' mutations - find the CLASS node
|
|
1572
|
+
if (!enclosingClassName)
|
|
1573
|
+
continue; // Skip if no class context (e.g., standalone function)
|
|
1574
|
+
// Compare using basename since classes use scopeTracker.file (basename)
|
|
1575
|
+
// but mutations use module.file (full path)
|
|
1576
|
+
const fileBasename = basename(file);
|
|
1577
|
+
const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
|
|
1578
|
+
objectNodeId = classDecl?.id ?? null;
|
|
1579
|
+
if (!objectNodeId)
|
|
1580
|
+
continue; // Skip if class not found
|
|
1581
|
+
// Use special mutation type to distinguish from regular property mutations
|
|
1582
|
+
effectiveMutationType = 'this_property';
|
|
1583
|
+
}
|
|
1584
|
+
// Create FLOWS_INTO edge for VARIABLE value type
|
|
1585
|
+
if (value.valueType === 'VARIABLE' && value.valueName) {
|
|
1586
|
+
// Find the source: can be variable, parameter, or function using scope chain (REG-309)
|
|
1587
|
+
const sourceVar = this.resolveVariableInScope(value.valueName, scopePath, file, variableDeclarations);
|
|
1588
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(value.valueName, scopePath, file, parameters) : null;
|
|
1589
|
+
const sourceFunc = !sourceVar && !sourceParam ? functions.find(f => f.name === value.valueName && f.file === file) : null;
|
|
1590
|
+
const sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? sourceFunc?.id;
|
|
1591
|
+
if (sourceNodeId && objectNodeId) {
|
|
1592
|
+
const edgeData = {
|
|
1593
|
+
type: 'FLOWS_INTO',
|
|
1594
|
+
src: sourceNodeId,
|
|
1595
|
+
dst: objectNodeId,
|
|
1596
|
+
mutationType: effectiveMutationType,
|
|
1597
|
+
propertyName,
|
|
1598
|
+
computedPropertyVar // For enrichment phase resolution
|
|
1599
|
+
};
|
|
1600
|
+
if (value.argIndex !== undefined) {
|
|
1601
|
+
edgeData.argIndex = value.argIndex;
|
|
1007
1602
|
}
|
|
1603
|
+
if (value.isSpread) {
|
|
1604
|
+
edgeData.isSpread = true;
|
|
1605
|
+
}
|
|
1606
|
+
this._bufferEdge(edgeData);
|
|
1008
1607
|
}
|
|
1009
|
-
if (targetModule)
|
|
1010
|
-
break;
|
|
1011
1608
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1609
|
+
// For literals, object literals, etc. - we just track variable -> object flows for now
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Resolve variable by name using scope chain lookup (REG-309).
|
|
1614
|
+
* Mirrors JavaScript lexical scoping: search current scope, then parent, then grandparent, etc.
|
|
1615
|
+
*
|
|
1616
|
+
* @param name - Variable name
|
|
1617
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
1618
|
+
* @param file - File path
|
|
1619
|
+
* @param variables - All variable declarations
|
|
1620
|
+
* @returns Variable declaration or null if not found
|
|
1621
|
+
*/
|
|
1622
|
+
resolveVariableInScope(name, scopePath, file, variables) {
|
|
1623
|
+
// Try current scope, then parent, then grandparent, etc.
|
|
1624
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
1625
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
1626
|
+
const matchingVar = variables.find(v => {
|
|
1627
|
+
if (v.name !== name || v.file !== file)
|
|
1628
|
+
return false;
|
|
1629
|
+
// Variable ID IS the semantic ID (when scopeTracker was available during analysis)
|
|
1630
|
+
// Format: file->scope1->scope2->TYPE->name
|
|
1631
|
+
// Legacy format: VARIABLE#name#file#line:column:counter
|
|
1632
|
+
// Try parsing as semantic ID
|
|
1633
|
+
const parsed = parseSemanticId(v.id);
|
|
1634
|
+
// REG-329: Check for both VARIABLE and CONSTANT (const declarations)
|
|
1635
|
+
if (parsed && (parsed.type === 'VARIABLE' || parsed.type === 'CONSTANT')) {
|
|
1636
|
+
// FIXED (REG-309): Handle module-level scope matching
|
|
1637
|
+
// Empty search scope [] should match semantic ID scope ['global']
|
|
1638
|
+
if (searchScopePath.length === 0) {
|
|
1639
|
+
return parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global';
|
|
1640
|
+
}
|
|
1641
|
+
// Non-empty scope: exact match
|
|
1642
|
+
return this.scopePathsMatch(parsed.scopePath, searchScopePath);
|
|
1643
|
+
}
|
|
1644
|
+
// Legacy ID - assume module-level if no semantic ID
|
|
1645
|
+
return searchScopePath.length === 0;
|
|
1646
|
+
});
|
|
1647
|
+
if (matchingVar)
|
|
1648
|
+
return matchingVar;
|
|
1649
|
+
}
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Resolve parameter by name using scope chain lookup (REG-309).
|
|
1654
|
+
* Same semantics as resolveVariableInScope but for parameters.
|
|
1655
|
+
*
|
|
1656
|
+
* @param name - Parameter name
|
|
1657
|
+
* @param scopePath - Scope path where reference occurs (from ScopeTracker)
|
|
1658
|
+
* @param file - File path
|
|
1659
|
+
* @param parameters - All parameter declarations
|
|
1660
|
+
* @returns Parameter declaration or null if not found
|
|
1661
|
+
*/
|
|
1662
|
+
resolveParameterInScope(name, scopePath, file, parameters) {
|
|
1663
|
+
// Parameters have semanticId field populated (unlike variables which use id field)
|
|
1664
|
+
return parameters.find(p => {
|
|
1665
|
+
if (p.name !== name || p.file !== file)
|
|
1666
|
+
return false;
|
|
1667
|
+
if (p.semanticId) {
|
|
1668
|
+
const parsed = parseSemanticId(p.semanticId);
|
|
1669
|
+
if (parsed && parsed.type === 'PARAMETER') {
|
|
1670
|
+
// Check if parameter's scope matches any scope in the chain
|
|
1671
|
+
for (let i = scopePath.length; i >= 0; i--) {
|
|
1672
|
+
const searchScopePath = scopePath.slice(0, i);
|
|
1673
|
+
// FIXED (REG-309): Handle module-level scope matching for parameters
|
|
1674
|
+
if (searchScopePath.length === 0) {
|
|
1675
|
+
if (parsed.scopePath.length === 1 && parsed.scopePath[0] === 'global') {
|
|
1676
|
+
return true;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
if (this.scopePathsMatch(parsed.scopePath, searchScopePath)) {
|
|
1681
|
+
return true;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return false;
|
|
1688
|
+
}) ?? null;
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Check if two scope paths match (REG-309).
|
|
1692
|
+
* Handles: ['foo', 'if#0'] vs ['foo', 'if#0']
|
|
1693
|
+
*/
|
|
1694
|
+
scopePathsMatch(a, b) {
|
|
1695
|
+
if (a.length !== b.length)
|
|
1696
|
+
return false;
|
|
1697
|
+
return a.every((item, idx) => item === b[idx]);
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Buffer FLOWS_INTO edges for variable reassignments.
|
|
1701
|
+
* Handles: x = y, x += y (when x is already declared, not initialization)
|
|
1702
|
+
*
|
|
1703
|
+
* Edge patterns:
|
|
1704
|
+
* - Simple assignment (=): source --FLOWS_INTO--> variable
|
|
1705
|
+
* - Compound operators (+=, -=, etc.):
|
|
1706
|
+
* - source --FLOWS_INTO--> variable (write new value)
|
|
1707
|
+
* - variable --READS_FROM--> variable (self-loop: reads current value before write)
|
|
1708
|
+
*
|
|
1709
|
+
* REG-309: Uses scope-aware variable lookup via resolveVariableInScope().
|
|
1710
|
+
*
|
|
1711
|
+
* REG-290: Complete implementation with inline node creation (no continue statements).
|
|
1712
|
+
*/
|
|
1713
|
+
bufferVariableReassignmentEdges(variableReassignments, variableDeclarations, callSites, methodCalls, parameters) {
|
|
1714
|
+
// Note: No longer using Map-based cache - scope-aware lookup requires scope chain walk
|
|
1715
|
+
// Performance: O(n*m*s) where s = scope depth (typically 2-3), acceptable for correctness
|
|
1716
|
+
for (const reassignment of variableReassignments) {
|
|
1717
|
+
const { variableName, mutationScopePath, valueType, valueName, valueId, callLine, callColumn, operator, literalValue, expressionType, expressionMetadata, file, line, column } = reassignment;
|
|
1718
|
+
// Find target variable node using scope chain resolution (REG-309)
|
|
1719
|
+
const scopePath = mutationScopePath ?? [];
|
|
1720
|
+
const targetVar = this.resolveVariableInScope(variableName, scopePath, file, variableDeclarations);
|
|
1721
|
+
const targetParam = !targetVar ? this.resolveParameterInScope(variableName, scopePath, file, parameters) : null;
|
|
1722
|
+
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
1723
|
+
if (!targetNodeId) {
|
|
1724
|
+
// Variable not found - could be external reference
|
|
1014
1725
|
continue;
|
|
1015
1726
|
}
|
|
1016
|
-
//
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1727
|
+
// Resolve source node based on value type
|
|
1728
|
+
let sourceNodeId = null;
|
|
1729
|
+
// LITERAL: Create node inline (NO CONTINUE STATEMENT)
|
|
1730
|
+
if (valueType === 'LITERAL' && valueId) {
|
|
1731
|
+
// Create LITERAL node
|
|
1732
|
+
this._bufferNode({
|
|
1733
|
+
type: 'LITERAL',
|
|
1734
|
+
id: valueId,
|
|
1735
|
+
value: literalValue,
|
|
1736
|
+
file,
|
|
1737
|
+
line,
|
|
1738
|
+
column
|
|
1739
|
+
});
|
|
1740
|
+
sourceNodeId = valueId;
|
|
1741
|
+
}
|
|
1742
|
+
// VARIABLE: Look up existing variable/parameter node using scope chain (REG-309)
|
|
1743
|
+
else if (valueType === 'VARIABLE' && valueName) {
|
|
1744
|
+
const sourceVar = this.resolveVariableInScope(valueName, scopePath, file, variableDeclarations);
|
|
1745
|
+
const sourceParam = !sourceVar ? this.resolveParameterInScope(valueName, scopePath, file, parameters) : null;
|
|
1746
|
+
sourceNodeId = sourceVar?.id ?? sourceParam?.id ?? null;
|
|
1747
|
+
}
|
|
1748
|
+
// CALL_SITE: Look up existing call node
|
|
1749
|
+
else if (valueType === 'CALL_SITE' && callLine && callColumn) {
|
|
1750
|
+
const callSite = callSites.find(cs => cs.line === callLine && cs.column === callColumn && cs.file === file);
|
|
1751
|
+
sourceNodeId = callSite?.id ?? null;
|
|
1752
|
+
}
|
|
1753
|
+
// METHOD_CALL: Look up existing method call node
|
|
1754
|
+
else if (valueType === 'METHOD_CALL' && callLine && callColumn) {
|
|
1755
|
+
const methodCall = methodCalls.find(mc => mc.line === callLine && mc.column === callColumn && mc.file === file);
|
|
1756
|
+
sourceNodeId = methodCall?.id ?? null;
|
|
1757
|
+
}
|
|
1758
|
+
// EXPRESSION: Create node inline (NO CONTINUE STATEMENT)
|
|
1759
|
+
else if (valueType === 'EXPRESSION' && valueId && expressionType) {
|
|
1760
|
+
// Create EXPRESSION node using NodeFactory
|
|
1761
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType, file, line, column, {
|
|
1762
|
+
id: valueId, // ID from JSASTAnalyzer
|
|
1763
|
+
object: expressionMetadata?.object,
|
|
1764
|
+
property: expressionMetadata?.property,
|
|
1765
|
+
computed: expressionMetadata?.computed,
|
|
1766
|
+
computedPropertyVar: expressionMetadata?.computedPropertyVar ?? undefined,
|
|
1767
|
+
operator: expressionMetadata?.operator
|
|
1768
|
+
});
|
|
1769
|
+
this._bufferNode(expressionNode);
|
|
1770
|
+
sourceNodeId = valueId;
|
|
1771
|
+
}
|
|
1772
|
+
// Create edges if source found
|
|
1773
|
+
if (sourceNodeId && targetNodeId) {
|
|
1774
|
+
// For compound operators (operator !== '='), LHS reads its own current value
|
|
1775
|
+
// Create READS_FROM self-loop (Linus requirement)
|
|
1776
|
+
if (operator !== '=') {
|
|
1777
|
+
this._bufferEdge({
|
|
1778
|
+
type: 'READS_FROM',
|
|
1779
|
+
src: targetNodeId, // Variable reads from...
|
|
1780
|
+
dst: targetNodeId // ...itself (self-loop)
|
|
1034
1781
|
});
|
|
1035
|
-
edgesCreated++;
|
|
1036
1782
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1783
|
+
// RHS flows into LHS (write side)
|
|
1784
|
+
this._bufferEdge({
|
|
1785
|
+
type: 'FLOWS_INTO',
|
|
1786
|
+
src: sourceNodeId,
|
|
1787
|
+
dst: targetNodeId
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Buffer RETURNS edges connecting return expressions to their containing functions.
|
|
1794
|
+
*
|
|
1795
|
+
* Edge direction: returnExpression --RETURNS--> function
|
|
1796
|
+
*
|
|
1797
|
+
* This enables tracing data flow through function calls:
|
|
1798
|
+
* - Query: "What does formatDate return?"
|
|
1799
|
+
* - Answer: Follow RETURNS edges from function to see all possible return values
|
|
1800
|
+
*/
|
|
1801
|
+
bufferReturnEdges(returnStatements, callSites, methodCalls, variableDeclarations, parameters) {
|
|
1802
|
+
for (const ret of returnStatements) {
|
|
1803
|
+
const { parentFunctionId, returnValueType, file } = ret;
|
|
1804
|
+
// Skip if no value returned (bare return;)
|
|
1805
|
+
if (returnValueType === 'NONE') {
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
let sourceNodeId = null;
|
|
1809
|
+
switch (returnValueType) {
|
|
1810
|
+
case 'LITERAL':
|
|
1811
|
+
// Direct reference to literal node
|
|
1812
|
+
sourceNodeId = ret.returnValueId ?? null;
|
|
1813
|
+
break;
|
|
1814
|
+
case 'VARIABLE': {
|
|
1815
|
+
// Find variable declaration by name in same file
|
|
1816
|
+
const varName = ret.returnValueName;
|
|
1817
|
+
if (varName) {
|
|
1818
|
+
const sourceVar = variableDeclarations.find(v => v.name === varName && v.file === file);
|
|
1819
|
+
if (sourceVar) {
|
|
1820
|
+
sourceNodeId = sourceVar.id;
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
// Check parameters
|
|
1824
|
+
const sourceParam = parameters.find(p => p.name === varName && p.file === file);
|
|
1825
|
+
if (sourceParam) {
|
|
1826
|
+
sourceNodeId = sourceParam.id;
|
|
1827
|
+
}
|
|
1044
1828
|
}
|
|
1045
1829
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
case 'CALL_SITE': {
|
|
1833
|
+
// Find call site by coordinates
|
|
1834
|
+
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
1835
|
+
if (returnValueLine && returnValueColumn) {
|
|
1836
|
+
const callSite = callSites.find(cs => cs.line === returnValueLine &&
|
|
1837
|
+
cs.column === returnValueColumn &&
|
|
1838
|
+
(returnValueCallName ? cs.name === returnValueCallName : true));
|
|
1839
|
+
if (callSite) {
|
|
1840
|
+
sourceNodeId = callSite.id;
|
|
1841
|
+
}
|
|
1053
1842
|
}
|
|
1843
|
+
break;
|
|
1054
1844
|
}
|
|
1055
|
-
|
|
1056
|
-
//
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1845
|
+
case 'METHOD_CALL': {
|
|
1846
|
+
// Find method call by coordinates and method name
|
|
1847
|
+
const { returnValueLine, returnValueColumn, returnValueCallName } = ret;
|
|
1848
|
+
if (returnValueLine && returnValueColumn) {
|
|
1849
|
+
const methodCall = methodCalls.find(mc => mc.line === returnValueLine &&
|
|
1850
|
+
mc.column === returnValueColumn &&
|
|
1851
|
+
mc.file === file &&
|
|
1852
|
+
(returnValueCallName ? mc.method === returnValueCallName : true));
|
|
1853
|
+
if (methodCall) {
|
|
1854
|
+
sourceNodeId = methodCall.id;
|
|
1062
1855
|
}
|
|
1063
1856
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1857
|
+
break;
|
|
1858
|
+
}
|
|
1859
|
+
case 'EXPRESSION': {
|
|
1860
|
+
// REG-276: Create EXPRESSION node and DERIVES_FROM edges for return expressions
|
|
1861
|
+
const { expressionType, returnValueId, returnValueLine, returnValueColumn, operator, object, property, computed, objectSourceName, leftSourceName, rightSourceName, consequentSourceName, alternateSourceName, expressionSourceNames, unaryArgSourceName } = ret;
|
|
1862
|
+
// Skip if no expression ID was generated
|
|
1863
|
+
if (!returnValueId) {
|
|
1864
|
+
break;
|
|
1865
|
+
}
|
|
1866
|
+
// Create EXPRESSION node using NodeFactory
|
|
1867
|
+
const expressionNode = NodeFactory.createExpressionFromMetadata(expressionType || 'Unknown', file, returnValueLine || ret.line, returnValueColumn || ret.column, {
|
|
1868
|
+
id: returnValueId,
|
|
1869
|
+
object,
|
|
1870
|
+
property,
|
|
1871
|
+
computed,
|
|
1872
|
+
operator
|
|
1873
|
+
});
|
|
1874
|
+
this._bufferNode(expressionNode);
|
|
1875
|
+
sourceNodeId = returnValueId;
|
|
1876
|
+
// Buffer DERIVES_FROM edges based on expression type
|
|
1877
|
+
// Helper function to find source variable or parameter
|
|
1878
|
+
const findSource = (name) => {
|
|
1879
|
+
const variable = variableDeclarations.find(v => v.name === name && v.file === file);
|
|
1880
|
+
if (variable)
|
|
1881
|
+
return variable.id;
|
|
1882
|
+
const param = parameters.find(p => p.name === name && p.file === file);
|
|
1883
|
+
if (param)
|
|
1884
|
+
return param.id;
|
|
1885
|
+
return null;
|
|
1886
|
+
};
|
|
1887
|
+
// MemberExpression: derives from the object
|
|
1888
|
+
if (expressionType === 'MemberExpression' && objectSourceName) {
|
|
1889
|
+
const sourceId = findSource(objectSourceName);
|
|
1890
|
+
if (sourceId) {
|
|
1891
|
+
this._bufferEdge({
|
|
1892
|
+
type: 'DERIVES_FROM',
|
|
1893
|
+
src: returnValueId,
|
|
1894
|
+
dst: sourceId
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
// BinaryExpression / LogicalExpression: derives from left and right operands
|
|
1899
|
+
if (expressionType === 'BinaryExpression' || expressionType === 'LogicalExpression') {
|
|
1900
|
+
if (leftSourceName) {
|
|
1901
|
+
const sourceId = findSource(leftSourceName);
|
|
1902
|
+
if (sourceId) {
|
|
1903
|
+
this._bufferEdge({
|
|
1904
|
+
type: 'DERIVES_FROM',
|
|
1905
|
+
src: returnValueId,
|
|
1906
|
+
dst: sourceId
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (rightSourceName) {
|
|
1911
|
+
const sourceId = findSource(rightSourceName);
|
|
1912
|
+
if (sourceId) {
|
|
1913
|
+
this._bufferEdge({
|
|
1914
|
+
type: 'DERIVES_FROM',
|
|
1915
|
+
src: returnValueId,
|
|
1916
|
+
dst: sourceId
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
// ConditionalExpression: derives from consequent and alternate
|
|
1922
|
+
if (expressionType === 'ConditionalExpression') {
|
|
1923
|
+
if (consequentSourceName) {
|
|
1924
|
+
const sourceId = findSource(consequentSourceName);
|
|
1925
|
+
if (sourceId) {
|
|
1926
|
+
this._bufferEdge({
|
|
1927
|
+
type: 'DERIVES_FROM',
|
|
1928
|
+
src: returnValueId,
|
|
1929
|
+
dst: sourceId
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (alternateSourceName) {
|
|
1934
|
+
const sourceId = findSource(alternateSourceName);
|
|
1935
|
+
if (sourceId) {
|
|
1936
|
+
this._bufferEdge({
|
|
1937
|
+
type: 'DERIVES_FROM',
|
|
1938
|
+
src: returnValueId,
|
|
1939
|
+
dst: sourceId
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
// UnaryExpression: derives from the argument
|
|
1945
|
+
if (expressionType === 'UnaryExpression' && unaryArgSourceName) {
|
|
1946
|
+
const sourceId = findSource(unaryArgSourceName);
|
|
1947
|
+
if (sourceId) {
|
|
1948
|
+
this._bufferEdge({
|
|
1949
|
+
type: 'DERIVES_FROM',
|
|
1950
|
+
src: returnValueId,
|
|
1951
|
+
dst: sourceId
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
// TemplateLiteral: derives from all embedded expressions
|
|
1956
|
+
if (expressionType === 'TemplateLiteral' && expressionSourceNames && expressionSourceNames.length > 0) {
|
|
1957
|
+
for (const sourceName of expressionSourceNames) {
|
|
1958
|
+
const sourceId = findSource(sourceName);
|
|
1959
|
+
if (sourceId) {
|
|
1960
|
+
this._bufferEdge({
|
|
1961
|
+
type: 'DERIVES_FROM',
|
|
1962
|
+
src: returnValueId,
|
|
1963
|
+
dst: sourceId
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
// Create RETURNS edge if we found a source node
|
|
1972
|
+
if (sourceNodeId && parentFunctionId) {
|
|
1973
|
+
this._bufferEdge({
|
|
1974
|
+
type: 'RETURNS',
|
|
1975
|
+
src: sourceNodeId,
|
|
1976
|
+
dst: parentFunctionId
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Buffer UPDATE_EXPRESSION nodes and edges for increment/decrement operations.
|
|
1983
|
+
*
|
|
1984
|
+
* Handles two target types:
|
|
1985
|
+
* - IDENTIFIER: Simple variable (i++, --count)
|
|
1986
|
+
* - MEMBER_EXPRESSION: Object property (obj.prop++, arr[i]++, this.count++)
|
|
1987
|
+
*
|
|
1988
|
+
* Creates:
|
|
1989
|
+
* - UPDATE_EXPRESSION node with operator and target metadata
|
|
1990
|
+
* - MODIFIES edge: UPDATE_EXPRESSION -> target (VARIABLE, PARAMETER, or CLASS)
|
|
1991
|
+
* - READS_FROM self-loop: target -> target (reads current value before update)
|
|
1992
|
+
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
1993
|
+
*
|
|
1994
|
+
* REG-288: Initial implementation for IDENTIFIER targets
|
|
1995
|
+
* REG-312: Extended for MEMBER_EXPRESSION targets
|
|
1996
|
+
*/
|
|
1997
|
+
bufferUpdateExpressionEdges(updateExpressions, variableDeclarations, parameters, classDeclarations) {
|
|
1998
|
+
// Build lookup caches: O(n) instead of O(n*m)
|
|
1999
|
+
const varLookup = new Map();
|
|
2000
|
+
for (const v of variableDeclarations) {
|
|
2001
|
+
varLookup.set(`${v.file}:${v.name}`, v);
|
|
2002
|
+
}
|
|
2003
|
+
const paramLookup = new Map();
|
|
2004
|
+
for (const p of parameters) {
|
|
2005
|
+
paramLookup.set(`${p.file}:${p.name}`, p);
|
|
2006
|
+
}
|
|
2007
|
+
for (const update of updateExpressions) {
|
|
2008
|
+
if (update.targetType === 'IDENTIFIER') {
|
|
2009
|
+
// REG-288: Simple identifier (i++, --count)
|
|
2010
|
+
this.bufferIdentifierUpdate(update, varLookup, paramLookup);
|
|
2011
|
+
}
|
|
2012
|
+
else if (update.targetType === 'MEMBER_EXPRESSION') {
|
|
2013
|
+
// REG-312: Member expression (obj.prop++, arr[i]++)
|
|
2014
|
+
this.bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Buffer UPDATE_EXPRESSION node and edges for simple identifier updates (i++, --count)
|
|
2020
|
+
* REG-288: Original implementation extracted for clarity
|
|
2021
|
+
*/
|
|
2022
|
+
bufferIdentifierUpdate(update, varLookup, paramLookup) {
|
|
2023
|
+
const { variableName, operator, prefix, file, line, column, parentScopeId } = update;
|
|
2024
|
+
if (!variableName)
|
|
2025
|
+
return;
|
|
2026
|
+
// Find target variable node
|
|
2027
|
+
const targetVar = varLookup.get(`${file}:${variableName}`);
|
|
2028
|
+
const targetParam = !targetVar ? paramLookup.get(`${file}:${variableName}`) : null;
|
|
2029
|
+
const targetNodeId = targetVar?.id ?? targetParam?.id;
|
|
2030
|
+
if (!targetNodeId) {
|
|
2031
|
+
// Variable not found - could be module-level or external reference
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
// Create UPDATE_EXPRESSION node
|
|
2035
|
+
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
2036
|
+
this._bufferNode({
|
|
2037
|
+
type: 'UPDATE_EXPRESSION',
|
|
2038
|
+
id: updateId,
|
|
2039
|
+
name: `${prefix ? operator : ''}${variableName}${prefix ? '' : operator}`,
|
|
2040
|
+
targetType: 'IDENTIFIER',
|
|
2041
|
+
operator,
|
|
2042
|
+
prefix,
|
|
2043
|
+
variableName,
|
|
2044
|
+
file,
|
|
2045
|
+
line,
|
|
2046
|
+
column
|
|
2047
|
+
});
|
|
2048
|
+
// Create READS_FROM self-loop
|
|
2049
|
+
this._bufferEdge({
|
|
2050
|
+
type: 'READS_FROM',
|
|
2051
|
+
src: targetNodeId,
|
|
2052
|
+
dst: targetNodeId
|
|
2053
|
+
});
|
|
2054
|
+
// Create MODIFIES edge
|
|
2055
|
+
this._bufferEdge({
|
|
2056
|
+
type: 'MODIFIES',
|
|
2057
|
+
src: updateId,
|
|
2058
|
+
dst: targetNodeId
|
|
2059
|
+
});
|
|
2060
|
+
// Create CONTAINS edge
|
|
2061
|
+
if (parentScopeId) {
|
|
2062
|
+
this._bufferEdge({
|
|
2063
|
+
type: 'CONTAINS',
|
|
2064
|
+
src: parentScopeId,
|
|
2065
|
+
dst: updateId
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Buffer UPDATE_EXPRESSION node and edges for member expression updates (obj.prop++, arr[i]++)
|
|
2071
|
+
* REG-312: New implementation for member expression targets
|
|
2072
|
+
*
|
|
2073
|
+
* Creates:
|
|
2074
|
+
* - UPDATE_EXPRESSION node with member expression metadata
|
|
2075
|
+
* - MODIFIES edge: UPDATE_EXPRESSION -> VARIABLE(object) or CLASS (for this.prop++)
|
|
2076
|
+
* - READS_FROM self-loop: VARIABLE(object) -> VARIABLE(object)
|
|
2077
|
+
* - CONTAINS edge: SCOPE -> UPDATE_EXPRESSION
|
|
2078
|
+
*/
|
|
2079
|
+
bufferMemberExpressionUpdate(update, varLookup, paramLookup, classDeclarations) {
|
|
2080
|
+
const { objectName, propertyName, mutationType, computedPropertyVar, enclosingClassName, operator, prefix, file, line, column, parentScopeId } = update;
|
|
2081
|
+
if (!objectName || !propertyName)
|
|
2082
|
+
return;
|
|
2083
|
+
// Find target object node
|
|
2084
|
+
let objectNodeId = null;
|
|
2085
|
+
if (objectName !== 'this') {
|
|
2086
|
+
// Regular object: obj.prop++, arr[i]++
|
|
2087
|
+
const targetVar = varLookup.get(`${file}:${objectName}`);
|
|
2088
|
+
const targetParam = !targetVar ? paramLookup.get(`${file}:${objectName}`) : null;
|
|
2089
|
+
objectNodeId = targetVar?.id ?? targetParam?.id ?? null;
|
|
2090
|
+
}
|
|
2091
|
+
else {
|
|
2092
|
+
// this.prop++ - follow REG-152 pattern from bufferObjectMutationEdges
|
|
2093
|
+
if (!enclosingClassName)
|
|
2094
|
+
return;
|
|
2095
|
+
const fileBasename = basename(file);
|
|
2096
|
+
const classDecl = classDeclarations.find(c => c.name === enclosingClassName && c.file === fileBasename);
|
|
2097
|
+
objectNodeId = classDecl?.id ?? null;
|
|
2098
|
+
}
|
|
2099
|
+
if (!objectNodeId) {
|
|
2100
|
+
// Object not found - external reference or scope issue
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
// Create UPDATE_EXPRESSION node
|
|
2104
|
+
const updateId = `${file}:UPDATE_EXPRESSION:${operator}:${line}:${column}`;
|
|
2105
|
+
// Display name: "obj.prop++" or "this.count++" or "arr[i]++"
|
|
2106
|
+
const displayName = (() => {
|
|
2107
|
+
const opStr = prefix ? operator : '';
|
|
2108
|
+
const postOpStr = prefix ? '' : operator;
|
|
2109
|
+
if (objectName === 'this') {
|
|
2110
|
+
return `${opStr}this.${propertyName}${postOpStr}`;
|
|
2111
|
+
}
|
|
2112
|
+
if (mutationType === 'computed') {
|
|
2113
|
+
const computedPart = computedPropertyVar || '?';
|
|
2114
|
+
return `${opStr}${objectName}[${computedPart}]${postOpStr}`;
|
|
2115
|
+
}
|
|
2116
|
+
return `${opStr}${objectName}.${propertyName}${postOpStr}`;
|
|
2117
|
+
})();
|
|
2118
|
+
this._bufferNode({
|
|
2119
|
+
type: 'UPDATE_EXPRESSION',
|
|
2120
|
+
id: updateId,
|
|
2121
|
+
name: displayName,
|
|
2122
|
+
targetType: 'MEMBER_EXPRESSION',
|
|
2123
|
+
operator,
|
|
2124
|
+
prefix,
|
|
2125
|
+
objectName,
|
|
2126
|
+
propertyName,
|
|
2127
|
+
mutationType,
|
|
2128
|
+
computedPropertyVar,
|
|
2129
|
+
enclosingClassName,
|
|
2130
|
+
file,
|
|
2131
|
+
line,
|
|
2132
|
+
column
|
|
2133
|
+
});
|
|
2134
|
+
// Create READS_FROM self-loop (object reads from itself)
|
|
2135
|
+
this._bufferEdge({
|
|
2136
|
+
type: 'READS_FROM',
|
|
2137
|
+
src: objectNodeId,
|
|
2138
|
+
dst: objectNodeId
|
|
2139
|
+
});
|
|
2140
|
+
// Create MODIFIES edge (UPDATE_EXPRESSION modifies object)
|
|
2141
|
+
this._bufferEdge({
|
|
2142
|
+
type: 'MODIFIES',
|
|
2143
|
+
src: updateId,
|
|
2144
|
+
dst: objectNodeId
|
|
2145
|
+
});
|
|
2146
|
+
// Create CONTAINS edge
|
|
2147
|
+
if (parentScopeId) {
|
|
2148
|
+
this._bufferEdge({
|
|
2149
|
+
type: 'CONTAINS',
|
|
2150
|
+
src: parentScopeId,
|
|
2151
|
+
dst: updateId
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Buffer RESOLVES_TO edges for Promise resolution data flow (REG-334).
|
|
2157
|
+
*
|
|
2158
|
+
* Links resolve/reject CALL nodes to their parent Promise CONSTRUCTOR_CALL.
|
|
2159
|
+
* This enables traceValues to follow Promise data flow:
|
|
2160
|
+
*
|
|
2161
|
+
* Example:
|
|
2162
|
+
* ```
|
|
2163
|
+
* const result = new Promise((resolve) => {
|
|
2164
|
+
* resolve(42); // CALL[resolve] --RESOLVES_TO--> CONSTRUCTOR_CALL[Promise]
|
|
2165
|
+
* });
|
|
2166
|
+
* ```
|
|
2167
|
+
*
|
|
2168
|
+
* The edge direction (CALL -> CONSTRUCTOR_CALL) matches data flow semantics:
|
|
2169
|
+
* data flows FROM resolve(value) TO the Promise result.
|
|
2170
|
+
*/
|
|
2171
|
+
bufferPromiseResolutionEdges(promiseResolutions) {
|
|
2172
|
+
for (const resolution of promiseResolutions) {
|
|
2173
|
+
this._bufferEdge({
|
|
2174
|
+
type: 'RESOLVES_TO',
|
|
2175
|
+
src: resolution.callId,
|
|
2176
|
+
dst: resolution.constructorCallId,
|
|
2177
|
+
metadata: {
|
|
2178
|
+
isReject: resolution.isReject
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Buffer OBJECT_LITERAL nodes to the graph.
|
|
2185
|
+
* These are object literals passed as function arguments or nested in other literals.
|
|
2186
|
+
*/
|
|
2187
|
+
bufferObjectLiteralNodes(objectLiterals) {
|
|
2188
|
+
for (const obj of objectLiterals) {
|
|
2189
|
+
this._bufferNode({
|
|
2190
|
+
id: obj.id,
|
|
2191
|
+
type: obj.type,
|
|
2192
|
+
name: '<object>',
|
|
2193
|
+
file: obj.file,
|
|
2194
|
+
line: obj.line,
|
|
2195
|
+
column: obj.column,
|
|
2196
|
+
parentCallId: obj.parentCallId,
|
|
2197
|
+
argIndex: obj.argIndex
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Buffer ARRAY_LITERAL nodes to the graph.
|
|
2203
|
+
* These are array literals passed as function arguments or nested in other literals.
|
|
2204
|
+
*/
|
|
2205
|
+
bufferArrayLiteralNodes(arrayLiterals) {
|
|
2206
|
+
for (const arr of arrayLiterals) {
|
|
2207
|
+
this._bufferNode({
|
|
2208
|
+
id: arr.id,
|
|
2209
|
+
type: arr.type,
|
|
2210
|
+
name: '<array>',
|
|
2211
|
+
file: arr.file,
|
|
2212
|
+
line: arr.line,
|
|
2213
|
+
column: arr.column,
|
|
2214
|
+
parentCallId: arr.parentCallId,
|
|
2215
|
+
argIndex: arr.argIndex
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Buffer HAS_PROPERTY edges connecting OBJECT_LITERAL nodes to their property values.
|
|
2221
|
+
* Creates edges from object literal to its property value nodes (LITERAL, nested OBJECT_LITERAL, ARRAY_LITERAL, etc.)
|
|
2222
|
+
*
|
|
2223
|
+
* REG-329: Adds scope-aware variable resolution for VARIABLE property values.
|
|
2224
|
+
* Uses the same resolveVariableInScope infrastructure as mutation handlers.
|
|
2225
|
+
*/
|
|
2226
|
+
bufferObjectPropertyEdges(objectProperties, variableDeclarations, parameters) {
|
|
2227
|
+
for (const prop of objectProperties) {
|
|
2228
|
+
// REG-329: Handle VARIABLE value types with scope resolution
|
|
2229
|
+
if (prop.valueType === 'VARIABLE' && prop.valueName) {
|
|
2230
|
+
const scopePath = prop.valueScopePath ?? [];
|
|
2231
|
+
const file = prop.file;
|
|
2232
|
+
// Resolve variable using scope chain
|
|
2233
|
+
const resolvedVar = this.resolveVariableInScope(prop.valueName, scopePath, file, variableDeclarations);
|
|
2234
|
+
const resolvedParam = !resolvedVar
|
|
2235
|
+
? this.resolveParameterInScope(prop.valueName, scopePath, file, parameters)
|
|
2236
|
+
: null;
|
|
2237
|
+
const resolvedNodeId = resolvedVar?.id ?? resolvedParam?.semanticId ?? resolvedParam?.id;
|
|
2238
|
+
if (resolvedNodeId) {
|
|
2239
|
+
this._bufferEdge({
|
|
2240
|
+
type: 'HAS_PROPERTY',
|
|
2241
|
+
src: prop.objectId,
|
|
2242
|
+
dst: resolvedNodeId,
|
|
2243
|
+
propertyName: prop.propertyName
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
// Existing logic for non-VARIABLE types
|
|
2249
|
+
if (prop.valueNodeId) {
|
|
2250
|
+
this._bufferEdge({
|
|
2251
|
+
type: 'HAS_PROPERTY',
|
|
2252
|
+
src: prop.objectId,
|
|
2253
|
+
dst: prop.valueNodeId,
|
|
2254
|
+
propertyName: prop.propertyName
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Handle CLASS ASSIGNED_FROM edges asynchronously (needs graph queries)
|
|
2261
|
+
*/
|
|
2262
|
+
async createClassAssignmentEdges(variableAssignments, graph) {
|
|
2263
|
+
let edgesCreated = 0;
|
|
2264
|
+
for (const assignment of variableAssignments) {
|
|
2265
|
+
const { variableId, sourceType, className } = assignment;
|
|
2266
|
+
if (sourceType === 'CLASS' && className) {
|
|
2267
|
+
const parts = variableId.split('#');
|
|
2268
|
+
const file = parts.length >= 3 ? parts[2] : null;
|
|
2269
|
+
let classNode = null;
|
|
2270
|
+
for await (const node of graph.queryNodes({ type: 'CLASS' })) {
|
|
2271
|
+
if (node.name === className && (!file || node.file === file)) {
|
|
2272
|
+
classNode = node;
|
|
2273
|
+
break;
|
|
1071
2274
|
}
|
|
1072
2275
|
}
|
|
2276
|
+
if (classNode) {
|
|
2277
|
+
await graph.addEdge({
|
|
2278
|
+
type: 'ASSIGNED_FROM',
|
|
2279
|
+
src: variableId,
|
|
2280
|
+
dst: classNode.id
|
|
2281
|
+
});
|
|
2282
|
+
edgesCreated++;
|
|
2283
|
+
}
|
|
1073
2284
|
}
|
|
1074
2285
|
}
|
|
1075
2286
|
return edgesCreated;
|