@grafema/core 0.1.0-alpha.5 → 0.1.1-alpha
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 +24 -2
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +197 -24
- package/dist/config/ConfigLoader.d.ts +72 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +187 -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/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 +225 -17
- package/dist/core/NodeFactory.d.ts.map +1 -1
- package/dist/core/NodeFactory.js +208 -18
- 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 +41 -0
- package/dist/core/nodes/CallSiteNode.d.ts +28 -0
- package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
- package/dist/core/nodes/CallSiteNode.js +46 -0
- package/dist/core/nodes/ClassNode.d.ts +33 -1
- package/dist/core/nodes/ClassNode.d.ts.map +1 -1
- package/dist/core/nodes/ClassNode.js +46 -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 +62 -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 +54 -0
- package/dist/core/nodes/ExportNode.d.ts +37 -1
- package/dist/core/nodes/ExportNode.d.ts.map +1 -1
- package/dist/core/nodes/ExportNode.js +48 -2
- 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 +178 -0
- package/dist/core/nodes/ExternalModuleNode.d.ts +28 -0
- package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -0
- package/dist/core/nodes/ExternalModuleNode.js +41 -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/ImportNode.d.ts +19 -5
- package/dist/core/nodes/ImportNode.d.ts.map +1 -1
- package/dist/core/nodes/ImportNode.js +23 -5
- 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 +55 -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/MethodCallNode.d.ts +30 -0
- package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodCallNode.js +49 -0
- package/dist/core/nodes/MethodNode.d.ts +32 -0
- package/dist/core/nodes/MethodNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodNode.js +48 -0
- 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 +41 -0
- 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 +53 -0
- package/dist/core/nodes/VariableDeclarationNode.d.ts +27 -0
- package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
- package/dist/core/nodes/VariableDeclarationNode.js +40 -0
- package/dist/core/nodes/index.d.ts +12 -1
- package/dist/core/nodes/index.d.ts.map +1 -1
- package/dist/core/nodes/index.js +14 -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 +77 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +159 -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 +118 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +131 -0
- package/dist/index.d.ts +57 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -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 +13 -6
- package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressAnalyzer.js +27 -19
- package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressRouteAnalyzer.js +21 -14
- package/dist/plugins/analysis/FetchAnalyzer.d.ts +1 -0
- package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/FetchAnalyzer.js +34 -14
- 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 +180 -17
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.js +1171 -471
- 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 +15 -10
- package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SQLiteAnalyzer.js +9 -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.map +1 -1
- package/dist/plugins/analysis/SocketIOAnalyzer.js +27 -15
- 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 +34 -4
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.js +318 -298
- 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 +176 -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 +9 -4
- 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 +99 -9
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +663 -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 +4 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +128 -63
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +11 -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 +4 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +17 -13
- 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 +136 -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 +14 -8
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.js +14 -7
- 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 +18 -12
- package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MountPointResolver.js +8 -3
- 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 +17 -0
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
- package/dist/plugins/enrichment/ValueDomainAnalyzer.js +129 -10
- 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.map +1 -1
- package/dist/plugins/indexing/JSModuleIndexer.js +63 -31
- package/dist/plugins/indexing/RustModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.js +5 -4
- 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/CallResolverValidator.d.ts.map +1 -1
- package/dist/plugins/validation/CallResolverValidator.js +8 -7
- package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
- package/dist/plugins/validation/DataFlowValidator.js +17 -12
- 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 +19 -23
- 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 +59 -16
- 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/storage/backends/RFDBServerBackend.d.ts +10 -17
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.js +31 -10
- 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 +237 -24
- package/src/config/ConfigLoader.ts +263 -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/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 +401 -18
- 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 +65 -0
- package/src/core/nodes/CallSiteNode.ts +58 -0
- package/src/core/nodes/ClassNode.ts +63 -2
- package/src/core/nodes/DecoratorNode.ts +91 -0
- package/src/core/nodes/EnumNode.ts +86 -0
- package/src/core/nodes/ExportNode.ts +70 -2
- package/src/core/nodes/ExpressionNode.ts +231 -0
- package/src/core/nodes/ExternalModuleNode.ts +56 -0
- package/src/core/nodes/ExternalStdioNode.ts +17 -9
- package/src/core/nodes/FunctionNode.ts +101 -1
- package/src/core/nodes/ImportNode.ts +32 -10
- package/src/core/nodes/InterfaceNode.ts +91 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/MethodCallNode.ts +64 -0
- package/src/core/nodes/MethodNode.ts +63 -0
- package/src/core/nodes/ModuleNode.ts +50 -0
- package/src/core/nodes/NetworkRequestNode.ts +77 -0
- package/src/core/nodes/ObjectLiteralNode.ts +65 -0
- package/src/core/nodes/ScopeNode.ts +65 -0
- package/src/core/nodes/TypeNode.ts +78 -0
- package/src/core/nodes/VariableDeclarationNode.ts +52 -0
- package/src/core/nodes/index.ts +18 -1
- package/src/diagnostics/DiagnosticCollector.ts +163 -0
- package/src/diagnostics/DiagnosticReporter.ts +204 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/index.ts +16 -0
- package/src/errors/GrafemaError.ts +174 -0
- package/src/index.ts +148 -1
- package/src/logging/Logger.ts +152 -0
- package/src/plugins/Plugin.ts +42 -0
- package/src/plugins/analysis/DatabaseAnalyzer.ts +14 -8
- package/src/plugins/analysis/ExpressAnalyzer.ts +29 -19
- package/src/plugins/analysis/ExpressRouteAnalyzer.ts +22 -21
- package/src/plugins/analysis/FetchAnalyzer.ts +39 -16
- package/src/plugins/analysis/IncrementalAnalysisPlugin.ts +84 -101
- package/src/plugins/analysis/JSASTAnalyzer.ts +1483 -503
- package/src/plugins/analysis/ReactAnalyzer.ts +57 -57
- package/src/plugins/analysis/RustAnalyzer.ts +15 -10
- package/src/plugins/analysis/SQLiteAnalyzer.ts +10 -7
- package/src/plugins/analysis/ServiceLayerAnalyzer.ts +22 -16
- package/src/plugins/analysis/SocketIOAnalyzer.ts +31 -22
- package/src/plugins/analysis/SystemDbAnalyzer.ts +16 -11
- package/src/plugins/analysis/ast/GraphBuilder.ts +439 -327
- package/src/plugins/analysis/ast/IdGenerator.ts +177 -0
- package/src/plugins/analysis/ast/types.ts +209 -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 +11 -8
- package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +909 -83
- package/src/plugins/analysis/ast/visitors/ClassVisitor.ts +97 -44
- package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +159 -93
- package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +12 -8
- package/src/plugins/analysis/ast/visitors/TypeScriptVisitor.ts +41 -14
- package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +37 -17
- package/src/plugins/discovery/MonorepoServiceDiscovery.ts +3 -2
- package/src/plugins/discovery/SimpleProjectDiscovery.ts +6 -1
- package/src/plugins/discovery/WorkspaceDiscovery.ts +177 -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 +14 -8
- package/src/plugins/enrichment/HTTPConnectionEnricher.ts +14 -7
- package/src/plugins/enrichment/ImportExportLinker.ts +24 -6
- package/src/plugins/enrichment/MethodCallResolver.ts +18 -12
- package/src/plugins/enrichment/MountPointResolver.ts +8 -3
- package/src/plugins/enrichment/PrefixEvaluator.ts +16 -7
- package/src/plugins/enrichment/RustFFIEnricher.ts +6 -5
- package/src/plugins/enrichment/ValueDomainAnalyzer.ts +149 -12
- package/src/plugins/indexing/IncrementalModuleIndexer.ts +23 -14
- package/src/plugins/indexing/JSModuleIndexer.ts +74 -34
- package/src/plugins/indexing/RustModuleIndexer.ts +5 -4
- package/src/plugins/validation/CallResolverValidator.ts +8 -7
- package/src/plugins/validation/DataFlowValidator.ts +16 -12
- package/src/plugins/validation/EvalBanValidator.ts +17 -16
- package/src/plugins/validation/GraphConnectivityValidator.ts +19 -23
- package/src/plugins/validation/NodeCreationValidator.ts +554 -0
- package/src/plugins/validation/SQLInjectionValidator.ts +61 -15
- 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/storage/backends/RFDBServerBackend.ts +43 -29
- 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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'fs';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
|
+
import { basename } from 'path';
|
|
8
9
|
import { parse } from '@babel/parser';
|
|
9
10
|
import traverseModule from '@babel/traverse';
|
|
10
11
|
import type { NodePath, TraverseOptions } from '@babel/traverse';
|
|
@@ -46,8 +47,13 @@ import {
|
|
|
46
47
|
import { Task } from '../../core/Task.js';
|
|
47
48
|
import { PriorityQueue } from '../../core/PriorityQueue.js';
|
|
48
49
|
import { WorkerPool } from '../../core/WorkerPool.js';
|
|
50
|
+
import { ASTWorkerPool, type ModuleInfo as ASTModuleInfo, type ParseResult } from '../../core/ASTWorkerPool.js';
|
|
49
51
|
import { ConditionParser } from './ast/ConditionParser.js';
|
|
52
|
+
import { getLine, getColumn } from './ast/utils/location.js';
|
|
50
53
|
import { Profiler } from '../../core/Profiler.js';
|
|
54
|
+
import { ScopeTracker } from '../../core/ScopeTracker.js';
|
|
55
|
+
import { computeSemanticId } from '../../core/SemanticId.js';
|
|
56
|
+
import { ExpressionNode } from '../../core/nodes/ExpressionNode.js';
|
|
51
57
|
import type { PluginContext, PluginResult, PluginMetadata, GraphBackend } from '@grafema/types';
|
|
52
58
|
import type {
|
|
53
59
|
ModuleNode,
|
|
@@ -71,6 +77,14 @@ import type {
|
|
|
71
77
|
TypeAliasInfo,
|
|
72
78
|
EnumDeclarationInfo,
|
|
73
79
|
DecoratorInfo,
|
|
80
|
+
ObjectLiteralInfo,
|
|
81
|
+
ObjectPropertyInfo,
|
|
82
|
+
ArrayLiteralInfo,
|
|
83
|
+
ArrayElementInfo,
|
|
84
|
+
ArrayMutationInfo,
|
|
85
|
+
ArrayMutationArgument,
|
|
86
|
+
ObjectMutationInfo,
|
|
87
|
+
ObjectMutationValue,
|
|
74
88
|
CounterRef,
|
|
75
89
|
ProcessedNodes,
|
|
76
90
|
ASTCollections,
|
|
@@ -79,14 +93,8 @@ import type {
|
|
|
79
93
|
|
|
80
94
|
// === LOCAL TYPES ===
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* Used to generate stable, line-number-independent IDs for SCOPE nodes.
|
|
85
|
-
*/
|
|
86
|
-
interface ScopeContext {
|
|
87
|
-
semanticPath: string; // "ClassName.method" or "funcName"
|
|
88
|
-
siblingCounters: Map<string, number>; // scopeType → count for this level
|
|
89
|
-
}
|
|
96
|
+
// Note: Legacy ScopeContext interface removed in REG-141
|
|
97
|
+
// Semantic ID generation now uses ScopeTracker exclusively
|
|
90
98
|
|
|
91
99
|
// Internal Collections with required fields (ASTCollections has optional for GraphBuilder)
|
|
92
100
|
interface Collections {
|
|
@@ -111,6 +119,17 @@ interface Collections {
|
|
|
111
119
|
typeAliases: TypeAliasInfo[];
|
|
112
120
|
enums: EnumDeclarationInfo[];
|
|
113
121
|
decorators: DecoratorInfo[];
|
|
122
|
+
// Object/Array literal tracking
|
|
123
|
+
objectLiterals: ObjectLiteralInfo[];
|
|
124
|
+
objectProperties: ObjectPropertyInfo[];
|
|
125
|
+
arrayLiterals: ArrayLiteralInfo[];
|
|
126
|
+
arrayElements: ArrayElementInfo[];
|
|
127
|
+
// Array mutation tracking for FLOWS_INTO edges
|
|
128
|
+
arrayMutations: ArrayMutationInfo[];
|
|
129
|
+
// Object mutation tracking for FLOWS_INTO edges
|
|
130
|
+
objectMutations: ObjectMutationInfo[];
|
|
131
|
+
objectLiteralCounterRef: CounterRef;
|
|
132
|
+
arrayLiteralCounterRef: CounterRef;
|
|
114
133
|
ifScopeCounterRef: CounterRef;
|
|
115
134
|
scopeCounterRef: CounterRef;
|
|
116
135
|
varDeclCounterRef: CounterRef;
|
|
@@ -120,7 +139,6 @@ interface Collections {
|
|
|
120
139
|
literalCounterRef: CounterRef;
|
|
121
140
|
anonymousFunctionCounterRef: CounterRef;
|
|
122
141
|
processedNodes: ProcessedNodes;
|
|
123
|
-
moduleScopeCtx?: ScopeContext;
|
|
124
142
|
code?: string;
|
|
125
143
|
// VisitorCollections compatibility
|
|
126
144
|
classes: ClassDeclarationInfo[];
|
|
@@ -128,6 +146,8 @@ interface Collections {
|
|
|
128
146
|
variables: VariableDeclarationInfo[];
|
|
129
147
|
sideEffects: unknown[]; // TODO: define SideEffectInfo
|
|
130
148
|
variableCounterRef: CounterRef;
|
|
149
|
+
// ScopeTracker for semantic ID generation
|
|
150
|
+
scopeTracker?: ScopeTracker;
|
|
131
151
|
[key: string]: unknown;
|
|
132
152
|
}
|
|
133
153
|
|
|
@@ -140,6 +160,8 @@ interface AnalyzeContext extends PluginContext {
|
|
|
140
160
|
manifest?: AnalysisManifest;
|
|
141
161
|
forceAnalysis?: boolean;
|
|
142
162
|
workerCount?: number;
|
|
163
|
+
/** Enable parallel parsing using ASTWorkerPool (worker_threads) */
|
|
164
|
+
parallelParsing?: boolean;
|
|
143
165
|
// Use base onProgress type for compatibility
|
|
144
166
|
onProgress?: (info: Record<string, unknown>) => void;
|
|
145
167
|
}
|
|
@@ -148,14 +170,12 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
148
170
|
private graphBuilder: GraphBuilder;
|
|
149
171
|
private analyzedModules: Set<string>;
|
|
150
172
|
private profiler: Profiler;
|
|
151
|
-
private _cacheCleared: boolean;
|
|
152
173
|
|
|
153
174
|
constructor() {
|
|
154
175
|
super();
|
|
155
176
|
this.graphBuilder = new GraphBuilder();
|
|
156
177
|
this.analyzedModules = new Set();
|
|
157
178
|
this.profiler = new Profiler('JSASTAnalyzer');
|
|
158
|
-
this._cacheCleared = false;
|
|
159
179
|
}
|
|
160
180
|
|
|
161
181
|
get metadata(): PluginMetadata {
|
|
@@ -235,13 +255,14 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
async execute(context: AnalyzeContext): Promise<PluginResult> {
|
|
258
|
+
const logger = this.log(context);
|
|
259
|
+
|
|
238
260
|
try {
|
|
239
261
|
const { manifest, graph, forceAnalysis = false } = context;
|
|
240
262
|
const projectPath = manifest?.projectPath ?? '';
|
|
241
263
|
|
|
242
|
-
if (forceAnalysis
|
|
264
|
+
if (forceAnalysis) {
|
|
243
265
|
this.analyzedModules.clear();
|
|
244
|
-
this._cacheCleared = true;
|
|
245
266
|
}
|
|
246
267
|
|
|
247
268
|
const allModules = await this.getModuleNodes(graph);
|
|
@@ -262,13 +283,18 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
262
283
|
}
|
|
263
284
|
}
|
|
264
285
|
|
|
265
|
-
|
|
286
|
+
logger.info('Starting module analysis', { toAnalyze: modulesToAnalyze.length, cached: skippedCount });
|
|
266
287
|
|
|
267
288
|
if (modulesToAnalyze.length === 0) {
|
|
268
|
-
|
|
289
|
+
logger.info('All modules up-to-date, skipping analysis');
|
|
269
290
|
return createSuccessResult({ nodes: 0, edges: 0 });
|
|
270
291
|
}
|
|
271
292
|
|
|
293
|
+
// Use ASTWorkerPool for true parallel parsing with worker_threads if enabled
|
|
294
|
+
if (context.parallelParsing) {
|
|
295
|
+
return await this.executeParallel(modulesToAnalyze, graph, projectPath, context);
|
|
296
|
+
}
|
|
297
|
+
|
|
272
298
|
const queue = new PriorityQueue();
|
|
273
299
|
const pool = new WorkerPool(context.workerCount || 10);
|
|
274
300
|
|
|
@@ -311,7 +337,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
311
337
|
completed++;
|
|
312
338
|
|
|
313
339
|
if (completed % 10 === 0 || completed === modulesToAnalyze.length) {
|
|
314
|
-
|
|
340
|
+
logger.debug('Analysis progress', { completed, total: modulesToAnalyze.length });
|
|
315
341
|
}
|
|
316
342
|
});
|
|
317
343
|
|
|
@@ -339,8 +365,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
339
365
|
}
|
|
340
366
|
}
|
|
341
367
|
|
|
342
|
-
|
|
343
|
-
|
|
368
|
+
logger.info('Analysis complete', { modulesAnalyzed: modulesToAnalyze.length, nodesCreated });
|
|
369
|
+
logger.debug('Worker stats', { ...stats });
|
|
344
370
|
|
|
345
371
|
this.profiler.printSummary();
|
|
346
372
|
|
|
@@ -350,12 +376,105 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
350
376
|
);
|
|
351
377
|
|
|
352
378
|
} catch (error) {
|
|
353
|
-
|
|
379
|
+
logger.error('Analysis failed', { error: error instanceof Error ? error.message : String(error) });
|
|
354
380
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
355
381
|
return createErrorResult(err);
|
|
356
382
|
}
|
|
357
383
|
}
|
|
358
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Execute parallel analysis using ASTWorkerPool (worker_threads).
|
|
387
|
+
*
|
|
388
|
+
* This method uses actual OS threads for true parallel CPU-intensive parsing.
|
|
389
|
+
* Workers generate semantic IDs using ScopeTracker, matching sequential behavior.
|
|
390
|
+
*
|
|
391
|
+
* @param modules - Modules to analyze
|
|
392
|
+
* @param graph - Graph backend for writing results
|
|
393
|
+
* @param projectPath - Project root path
|
|
394
|
+
* @param context - Analysis context with options
|
|
395
|
+
* @returns Plugin result with node/edge counts
|
|
396
|
+
*/
|
|
397
|
+
private async executeParallel(
|
|
398
|
+
modules: ModuleNode[],
|
|
399
|
+
graph: GraphBackend,
|
|
400
|
+
projectPath: string,
|
|
401
|
+
context: AnalyzeContext
|
|
402
|
+
): Promise<PluginResult> {
|
|
403
|
+
const logger = this.log(context);
|
|
404
|
+
const workerCount = context.workerCount || 4;
|
|
405
|
+
const pool = new ASTWorkerPool(workerCount);
|
|
406
|
+
|
|
407
|
+
logger.debug('Starting parallel parsing', { workerCount });
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await pool.init();
|
|
411
|
+
|
|
412
|
+
// Convert ModuleNode to ASTModuleInfo format
|
|
413
|
+
const moduleInfos: ASTModuleInfo[] = modules.map(m => ({
|
|
414
|
+
id: m.id,
|
|
415
|
+
file: m.file,
|
|
416
|
+
name: m.name
|
|
417
|
+
}));
|
|
418
|
+
|
|
419
|
+
// Parse all modules in parallel using worker threads
|
|
420
|
+
const results: ParseResult[] = await pool.parseModules(moduleInfos);
|
|
421
|
+
|
|
422
|
+
let nodesCreated = 0;
|
|
423
|
+
let edgesCreated = 0;
|
|
424
|
+
let errors = 0;
|
|
425
|
+
|
|
426
|
+
// Process results - collections already have semantic IDs from workers
|
|
427
|
+
for (const result of results) {
|
|
428
|
+
if (result.error) {
|
|
429
|
+
logger.warn('Parse error', { file: result.module.file, error: result.error.message });
|
|
430
|
+
errors++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (result.collections) {
|
|
435
|
+
// Find original module for metadata
|
|
436
|
+
const module = modules.find(m => m.id === result.module.id);
|
|
437
|
+
if (!module) continue;
|
|
438
|
+
|
|
439
|
+
// Pass collections directly to GraphBuilder - IDs already semantic
|
|
440
|
+
// Cast is safe because ASTWorker.ASTCollections is structurally compatible
|
|
441
|
+
// with ast/types.ASTCollections (METHOD extends FUNCTION semantically)
|
|
442
|
+
const buildResult = await this.graphBuilder.build(
|
|
443
|
+
module,
|
|
444
|
+
graph,
|
|
445
|
+
projectPath,
|
|
446
|
+
result.collections as unknown as ASTCollections
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
if (typeof buildResult === 'object' && buildResult !== null) {
|
|
450
|
+
nodesCreated += (buildResult as { nodes: number }).nodes || 0;
|
|
451
|
+
edgesCreated += (buildResult as { edges: number }).edges || 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Report progress
|
|
456
|
+
if (context.onProgress) {
|
|
457
|
+
context.onProgress({
|
|
458
|
+
phase: 'analysis',
|
|
459
|
+
currentPlugin: 'JSASTAnalyzer',
|
|
460
|
+
message: `Processed ${result.module.file.replace(projectPath, '')}`,
|
|
461
|
+
totalFiles: modules.length,
|
|
462
|
+
processedFiles: results.indexOf(result) + 1
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
logger.info('Parallel parsing complete', { nodesCreated, edgesCreated, errors });
|
|
468
|
+
|
|
469
|
+
return createSuccessResult(
|
|
470
|
+
{ nodes: nodesCreated, edges: edgesCreated },
|
|
471
|
+
{ modulesAnalyzed: modules.length - errors, parallelParsing: true }
|
|
472
|
+
);
|
|
473
|
+
} finally {
|
|
474
|
+
await pool.terminate();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
359
478
|
/**
|
|
360
479
|
* Extract variable names from destructuring patterns
|
|
361
480
|
* Uses t.isX() type guards to avoid casts
|
|
@@ -473,8 +592,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
473
592
|
sourceId: null,
|
|
474
593
|
sourceType: 'CALL_SITE',
|
|
475
594
|
callName: initExpression.callee.name,
|
|
476
|
-
callLine: initExpression
|
|
477
|
-
callColumn: initExpression
|
|
595
|
+
callLine: getLine(initExpression),
|
|
596
|
+
callColumn: getColumn(initExpression)
|
|
478
597
|
});
|
|
479
598
|
return;
|
|
480
599
|
}
|
|
@@ -486,7 +605,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
486
605
|
const methodName = callee.property.type === 'Identifier' ? callee.property.name : 'unknown';
|
|
487
606
|
|
|
488
607
|
const fullName = `${objectName}.${methodName}`;
|
|
489
|
-
const methodCallId = `CALL#${fullName}#${module.file}#${initExpression
|
|
608
|
+
const methodCallId = `CALL#${fullName}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:inline`;
|
|
490
609
|
|
|
491
610
|
const existing = variableAssignments.find(a => a.sourceId === methodCallId);
|
|
492
611
|
if (!existing) {
|
|
@@ -495,15 +614,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
495
614
|
if (arg.type !== 'SpreadElement') {
|
|
496
615
|
const argLiteralValue = ExpressionEvaluator.extractLiteralValue(arg);
|
|
497
616
|
if (argLiteralValue !== null) {
|
|
498
|
-
const literalId = `LITERAL#arg${index}#${module.file}#${initExpression
|
|
617
|
+
const literalId = `LITERAL#arg${index}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:${literalCounterRef.value++}`;
|
|
499
618
|
literals.push({
|
|
500
619
|
id: literalId,
|
|
501
620
|
type: 'LITERAL',
|
|
502
621
|
value: argLiteralValue,
|
|
503
622
|
valueType: typeof argLiteralValue,
|
|
504
623
|
file: module.file,
|
|
505
|
-
line: arg.loc?.start.line || initExpression
|
|
506
|
-
column: arg.loc?.start.column || initExpression
|
|
624
|
+
line: arg.loc?.start.line || getLine(initExpression),
|
|
625
|
+
column: arg.loc?.start.column || getColumn(initExpression),
|
|
507
626
|
parentCallId: methodCallId,
|
|
508
627
|
argIndex: index
|
|
509
628
|
});
|
|
@@ -522,8 +641,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
522
641
|
method: methodName,
|
|
523
642
|
file: module.file,
|
|
524
643
|
arguments: extractedArgs,
|
|
525
|
-
line: initExpression
|
|
526
|
-
column: initExpression
|
|
644
|
+
line: getLine(initExpression),
|
|
645
|
+
column: getColumn(initExpression)
|
|
527
646
|
});
|
|
528
647
|
}
|
|
529
648
|
|
|
@@ -584,7 +703,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
584
703
|
? initExpression.property.name
|
|
585
704
|
: null;
|
|
586
705
|
|
|
587
|
-
const
|
|
706
|
+
const column = initExpression.start ?? 0;
|
|
707
|
+
const expressionId = ExpressionNode.generateId('MemberExpression', module.file, line, column);
|
|
588
708
|
|
|
589
709
|
variableAssignments.push({
|
|
590
710
|
variableId,
|
|
@@ -597,14 +717,16 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
597
717
|
computedPropertyVar,
|
|
598
718
|
objectSourceName: initExpression.object.type === 'Identifier' ? initExpression.object.name : null,
|
|
599
719
|
file: module.file,
|
|
600
|
-
line: line
|
|
720
|
+
line: line,
|
|
721
|
+
column: column
|
|
601
722
|
});
|
|
602
723
|
return;
|
|
603
724
|
}
|
|
604
725
|
|
|
605
726
|
// 8. BinaryExpression
|
|
606
727
|
if (initExpression.type === 'BinaryExpression') {
|
|
607
|
-
const
|
|
728
|
+
const column = initExpression.start ?? 0;
|
|
729
|
+
const expressionId = ExpressionNode.generateId('BinaryExpression', module.file, line, column);
|
|
608
730
|
|
|
609
731
|
variableAssignments.push({
|
|
610
732
|
variableId,
|
|
@@ -615,14 +737,16 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
615
737
|
leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
|
|
616
738
|
rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
|
|
617
739
|
file: module.file,
|
|
618
|
-
line: line
|
|
740
|
+
line: line,
|
|
741
|
+
column: column
|
|
619
742
|
});
|
|
620
743
|
return;
|
|
621
744
|
}
|
|
622
745
|
|
|
623
746
|
// 9. ConditionalExpression
|
|
624
747
|
if (initExpression.type === 'ConditionalExpression') {
|
|
625
|
-
const
|
|
748
|
+
const column = initExpression.start ?? 0;
|
|
749
|
+
const expressionId = ExpressionNode.generateId('ConditionalExpression', module.file, line, column);
|
|
626
750
|
|
|
627
751
|
variableAssignments.push({
|
|
628
752
|
variableId,
|
|
@@ -632,7 +756,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
632
756
|
consequentSourceName: initExpression.consequent.type === 'Identifier' ? initExpression.consequent.name : null,
|
|
633
757
|
alternateSourceName: initExpression.alternate.type === 'Identifier' ? initExpression.alternate.name : null,
|
|
634
758
|
file: module.file,
|
|
635
|
-
line: line
|
|
759
|
+
line: line,
|
|
760
|
+
column: column
|
|
636
761
|
});
|
|
637
762
|
|
|
638
763
|
this.trackVariableAssignment(initExpression.consequent, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
@@ -642,7 +767,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
642
767
|
|
|
643
768
|
// 10. LogicalExpression
|
|
644
769
|
if (initExpression.type === 'LogicalExpression') {
|
|
645
|
-
const
|
|
770
|
+
const column = initExpression.start ?? 0;
|
|
771
|
+
const expressionId = ExpressionNode.generateId('LogicalExpression', module.file, line, column);
|
|
646
772
|
|
|
647
773
|
variableAssignments.push({
|
|
648
774
|
variableId,
|
|
@@ -653,7 +779,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
653
779
|
leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
|
|
654
780
|
rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
|
|
655
781
|
file: module.file,
|
|
656
|
-
line: line
|
|
782
|
+
line: line,
|
|
783
|
+
column: column
|
|
657
784
|
});
|
|
658
785
|
|
|
659
786
|
this.trackVariableAssignment(initExpression.left, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
@@ -663,7 +790,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
663
790
|
|
|
664
791
|
// 11. TemplateLiteral
|
|
665
792
|
if (initExpression.type === 'TemplateLiteral' && initExpression.expressions.length > 0) {
|
|
666
|
-
const
|
|
793
|
+
const column = initExpression.start ?? 0;
|
|
794
|
+
const expressionId = ExpressionNode.generateId('TemplateLiteral', module.file, line, column);
|
|
667
795
|
|
|
668
796
|
const expressionSourceNames = initExpression.expressions
|
|
669
797
|
.filter((expr): expr is t.Identifier => expr.type === 'Identifier')
|
|
@@ -676,7 +804,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
676
804
|
expressionType: 'TemplateLiteral',
|
|
677
805
|
expressionSourceNames,
|
|
678
806
|
file: module.file,
|
|
679
|
-
line: line
|
|
807
|
+
line: line,
|
|
808
|
+
column: column
|
|
680
809
|
});
|
|
681
810
|
|
|
682
811
|
for (const expr of initExpression.expressions) {
|
|
@@ -719,6 +848,10 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
719
848
|
});
|
|
720
849
|
this.profiler.end('babel_parse');
|
|
721
850
|
|
|
851
|
+
// Create ScopeTracker for semantic ID generation
|
|
852
|
+
// Use basename for shorter, more readable semantic IDs
|
|
853
|
+
const scopeTracker = new ScopeTracker(basename(module.file));
|
|
854
|
+
|
|
722
855
|
const functions: FunctionInfo[] = [];
|
|
723
856
|
const parameters: ParameterInfo[] = [];
|
|
724
857
|
const scopes: ScopeInfo[] = [];
|
|
@@ -740,6 +873,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
740
873
|
const typeAliases: TypeAliasInfo[] = [];
|
|
741
874
|
const enums: EnumDeclarationInfo[] = [];
|
|
742
875
|
const decorators: DecoratorInfo[] = [];
|
|
876
|
+
// Object/Array literal tracking for data flow
|
|
877
|
+
const objectLiterals: ObjectLiteralInfo[] = [];
|
|
878
|
+
const objectProperties: ObjectPropertyInfo[] = [];
|
|
879
|
+
const arrayLiterals: ArrayLiteralInfo[] = [];
|
|
880
|
+
const arrayElements: ArrayElementInfo[] = [];
|
|
881
|
+
// Array mutation tracking for FLOWS_INTO edges
|
|
882
|
+
const arrayMutations: ArrayMutationInfo[] = [];
|
|
883
|
+
// Object mutation tracking for FLOWS_INTO edges
|
|
884
|
+
const objectMutations: ObjectMutationInfo[] = [];
|
|
743
885
|
|
|
744
886
|
const ifScopeCounterRef: CounterRef = { value: 0 };
|
|
745
887
|
const scopeCounterRef: CounterRef = { value: 0 };
|
|
@@ -749,6 +891,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
749
891
|
const httpRequestCounterRef: CounterRef = { value: 0 };
|
|
750
892
|
const literalCounterRef: CounterRef = { value: 0 };
|
|
751
893
|
const anonymousFunctionCounterRef: CounterRef = { value: 0 };
|
|
894
|
+
const objectLiteralCounterRef: CounterRef = { value: 0 };
|
|
895
|
+
const arrayLiteralCounterRef: CounterRef = { value: 0 };
|
|
752
896
|
|
|
753
897
|
const processedNodes: ProcessedNodes = {
|
|
754
898
|
functions: new Set(),
|
|
@@ -779,33 +923,37 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
779
923
|
module,
|
|
780
924
|
{ variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef },
|
|
781
925
|
this.extractVariableNamesFromPattern.bind(this),
|
|
782
|
-
this.trackVariableAssignment.bind(this) as TrackVariableAssignmentCallback
|
|
926
|
+
this.trackVariableAssignment.bind(this) as TrackVariableAssignmentCallback,
|
|
927
|
+
scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
783
928
|
);
|
|
784
929
|
traverse(ast, variableVisitor.getHandlers());
|
|
785
930
|
this.profiler.end('traverse_variables');
|
|
786
931
|
|
|
787
|
-
// Module-level scope context for consistent anonymous naming
|
|
788
|
-
const moduleScopeCtx: ScopeContext = {
|
|
789
|
-
semanticPath: module.name!,
|
|
790
|
-
siblingCounters: new Map()
|
|
791
|
-
};
|
|
792
|
-
|
|
793
932
|
const allCollections: Collections = {
|
|
794
933
|
functions, parameters, scopes, variableDeclarations, callSites, methodCalls,
|
|
795
934
|
eventListeners, methodCallbacks, callArguments, classInstantiations, classDeclarations,
|
|
796
935
|
httpRequests, literals, variableAssignments,
|
|
797
936
|
// TypeScript-specific collections
|
|
798
937
|
interfaces, typeAliases, enums, decorators,
|
|
938
|
+
// Object/Array literal tracking
|
|
939
|
+
objectLiterals, objectProperties, arrayLiterals, arrayElements,
|
|
940
|
+
// Array mutation tracking
|
|
941
|
+
arrayMutations,
|
|
942
|
+
// Object mutation tracking
|
|
943
|
+
objectMutations,
|
|
944
|
+
objectLiteralCounterRef, arrayLiteralCounterRef,
|
|
799
945
|
ifScopeCounterRef, scopeCounterRef, varDeclCounterRef,
|
|
800
946
|
callSiteCounterRef, functionCounterRef, httpRequestCounterRef,
|
|
801
947
|
literalCounterRef, anonymousFunctionCounterRef, processedNodes,
|
|
802
|
-
imports, exports,
|
|
948
|
+
imports, exports, code,
|
|
803
949
|
// VisitorCollections compatibility
|
|
804
950
|
classes: classDeclarations,
|
|
805
951
|
methods: [],
|
|
806
952
|
variables: variableDeclarations,
|
|
807
953
|
sideEffects: [],
|
|
808
|
-
variableCounterRef: varDeclCounterRef
|
|
954
|
+
variableCounterRef: varDeclCounterRef,
|
|
955
|
+
// ScopeTracker for semantic ID generation
|
|
956
|
+
scopeTracker
|
|
809
957
|
};
|
|
810
958
|
|
|
811
959
|
// Functions
|
|
@@ -813,7 +961,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
813
961
|
const functionVisitor = new FunctionVisitor(
|
|
814
962
|
module,
|
|
815
963
|
allCollections,
|
|
816
|
-
this.analyzeFunctionBody.bind(this)
|
|
964
|
+
this.analyzeFunctionBody.bind(this),
|
|
965
|
+
scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
817
966
|
);
|
|
818
967
|
traverse(ast, functionVisitor.getHandlers());
|
|
819
968
|
this.profiler.end('traverse_functions');
|
|
@@ -841,22 +990,22 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
841
990
|
}
|
|
842
991
|
|
|
843
992
|
const funcNode = assignNode.right;
|
|
844
|
-
|
|
993
|
+
// Use semantic ID as primary ID (matching FunctionVisitor pattern)
|
|
994
|
+
const functionId = computeSemanticId('FUNCTION', functionName, scopeTracker.getContext());
|
|
845
995
|
|
|
846
996
|
functions.push({
|
|
847
997
|
id: functionId,
|
|
848
|
-
stableId: functionId,
|
|
849
998
|
type: 'FUNCTION',
|
|
850
999
|
name: functionName,
|
|
851
1000
|
file: module.file,
|
|
852
|
-
line: assignNode
|
|
853
|
-
column: assignNode
|
|
1001
|
+
line: getLine(assignNode),
|
|
1002
|
+
column: getColumn(assignNode),
|
|
854
1003
|
async: funcNode.async || false,
|
|
855
1004
|
generator: funcNode.type === 'FunctionExpression' ? funcNode.generator : false,
|
|
856
1005
|
isAssignment: true
|
|
857
1006
|
});
|
|
858
1007
|
|
|
859
|
-
const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${assignNode
|
|
1008
|
+
const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${getLine(assignNode)}`;
|
|
860
1009
|
scopes.push({
|
|
861
1010
|
id: funcBodyScopeId,
|
|
862
1011
|
type: 'SCOPE',
|
|
@@ -865,18 +1014,22 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
865
1014
|
semanticId: `${functionName}:function_body[0]`,
|
|
866
1015
|
conditional: false,
|
|
867
1016
|
file: module.file,
|
|
868
|
-
line: assignNode
|
|
1017
|
+
line: getLine(assignNode),
|
|
869
1018
|
parentFunctionId: functionId
|
|
870
1019
|
});
|
|
871
1020
|
|
|
872
1021
|
const funcPath = assignPath.get('right') as NodePath<t.FunctionExpression | t.ArrowFunctionExpression>;
|
|
873
|
-
//
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
};
|
|
878
|
-
this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections, funcScopeCtx);
|
|
1022
|
+
// Enter function scope for semantic ID generation and analyze
|
|
1023
|
+
scopeTracker.enterScope(functionName, 'function');
|
|
1024
|
+
this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections);
|
|
1025
|
+
scopeTracker.exitScope();
|
|
879
1026
|
}
|
|
1027
|
+
|
|
1028
|
+
// Check for indexed array assignment at module level: arr[i] = value
|
|
1029
|
+
this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
|
|
1030
|
+
|
|
1031
|
+
// Check for object property assignment at module level: obj.prop = value
|
|
1032
|
+
this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
|
|
880
1033
|
}
|
|
881
1034
|
});
|
|
882
1035
|
this.profiler.end('traverse_assignments');
|
|
@@ -886,14 +1039,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
886
1039
|
const classVisitor = new ClassVisitor(
|
|
887
1040
|
module,
|
|
888
1041
|
allCollections,
|
|
889
|
-
this.analyzeFunctionBody.bind(this)
|
|
1042
|
+
this.analyzeFunctionBody.bind(this),
|
|
1043
|
+
scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
890
1044
|
);
|
|
891
1045
|
traverse(ast, classVisitor.getHandlers());
|
|
892
1046
|
this.profiler.end('traverse_classes');
|
|
893
1047
|
|
|
894
1048
|
// TypeScript-specific constructs (interfaces, type aliases, enums)
|
|
895
1049
|
this.profiler.start('traverse_typescript');
|
|
896
|
-
const typescriptVisitor = new TypeScriptVisitor(module, allCollections);
|
|
1050
|
+
const typescriptVisitor = new TypeScriptVisitor(module, allCollections, scopeTracker);
|
|
897
1051
|
traverse(ast, typescriptVisitor.getHandlers());
|
|
898
1052
|
this.profiler.end('traverse_typescript');
|
|
899
1053
|
|
|
@@ -906,24 +1060,24 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
906
1060
|
if (functionParent) return;
|
|
907
1061
|
|
|
908
1062
|
if (funcPath.parent && funcPath.parent.type === 'CallExpression') {
|
|
909
|
-
const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(
|
|
910
|
-
|
|
1063
|
+
const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(scopeTracker);
|
|
1064
|
+
// Use semantic ID as primary ID (matching FunctionVisitor pattern)
|
|
1065
|
+
const functionId = computeSemanticId('FUNCTION', funcName, scopeTracker.getContext());
|
|
911
1066
|
|
|
912
1067
|
functions.push({
|
|
913
1068
|
id: functionId,
|
|
914
|
-
stableId: functionId,
|
|
915
1069
|
type: 'FUNCTION',
|
|
916
1070
|
name: funcName,
|
|
917
1071
|
file: module.file,
|
|
918
|
-
line: funcNode
|
|
919
|
-
column: funcNode
|
|
1072
|
+
line: getLine(funcNode),
|
|
1073
|
+
column: getColumn(funcNode),
|
|
920
1074
|
async: funcNode.async || false,
|
|
921
1075
|
generator: funcNode.generator || false,
|
|
922
1076
|
isCallback: true,
|
|
923
1077
|
parentScopeId: module.id
|
|
924
1078
|
});
|
|
925
1079
|
|
|
926
|
-
const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${funcNode
|
|
1080
|
+
const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(funcNode)}`;
|
|
927
1081
|
scopes.push({
|
|
928
1082
|
id: callbackScopeId,
|
|
929
1083
|
type: 'SCOPE',
|
|
@@ -932,16 +1086,14 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
932
1086
|
semanticId: `${funcName}:callback_body[0]`,
|
|
933
1087
|
conditional: false,
|
|
934
1088
|
file: module.file,
|
|
935
|
-
line: funcNode
|
|
1089
|
+
line: getLine(funcNode),
|
|
936
1090
|
parentFunctionId: functionId
|
|
937
1091
|
});
|
|
938
1092
|
|
|
939
|
-
//
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
};
|
|
944
|
-
this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections, callbackScopeCtx);
|
|
1093
|
+
// Enter callback scope for semantic ID generation and analyze
|
|
1094
|
+
scopeTracker.enterScope(funcName, 'callback');
|
|
1095
|
+
this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections);
|
|
1096
|
+
scopeTracker.exitScope();
|
|
945
1097
|
funcPath.skip();
|
|
946
1098
|
}
|
|
947
1099
|
}
|
|
@@ -950,7 +1102,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
950
1102
|
|
|
951
1103
|
// Call expressions
|
|
952
1104
|
this.profiler.start('traverse_calls');
|
|
953
|
-
const callExpressionVisitor = new CallExpressionVisitor(module, allCollections);
|
|
1105
|
+
const callExpressionVisitor = new CallExpressionVisitor(module, allCollections, scopeTracker);
|
|
954
1106
|
traverse(ast, callExpressionVisitor.getHandlers());
|
|
955
1107
|
this.profiler.end('traverse_calls');
|
|
956
1108
|
|
|
@@ -964,42 +1116,42 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
964
1116
|
const ifNode = ifPath.node;
|
|
965
1117
|
const condition = code.substring(ifNode.test.start!, ifNode.test.end!) || 'condition';
|
|
966
1118
|
const counterId = ifScopeCounterRef.value++;
|
|
967
|
-
const ifScopeId = `SCOPE#if#${module.file}#${ifNode
|
|
1119
|
+
const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
|
|
968
1120
|
|
|
969
1121
|
const constraints = ConditionParser.parse(ifNode.test);
|
|
970
|
-
const ifSemanticId = this.generateSemanticId('if_statement',
|
|
1122
|
+
const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
|
|
971
1123
|
|
|
972
1124
|
scopes.push({
|
|
973
1125
|
id: ifScopeId,
|
|
974
1126
|
type: 'SCOPE',
|
|
975
1127
|
scopeType: 'if_statement',
|
|
976
|
-
name: `if:${ifNode
|
|
1128
|
+
name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
|
|
977
1129
|
semanticId: ifSemanticId,
|
|
978
1130
|
conditional: true,
|
|
979
1131
|
condition,
|
|
980
1132
|
constraints: constraints.length > 0 ? constraints : undefined,
|
|
981
1133
|
file: module.file,
|
|
982
|
-
line: ifNode
|
|
1134
|
+
line: getLine(ifNode),
|
|
983
1135
|
parentScopeId: module.id
|
|
984
1136
|
});
|
|
985
1137
|
|
|
986
1138
|
if (ifNode.alternate && ifNode.alternate.type !== 'IfStatement') {
|
|
987
1139
|
const elseCounterId = ifScopeCounterRef.value++;
|
|
988
|
-
const elseScopeId = `SCOPE#else#${module.file}#${ifNode.alternate
|
|
1140
|
+
const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
|
|
989
1141
|
|
|
990
1142
|
const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
|
|
991
|
-
const elseSemanticId = this.generateSemanticId('else_statement',
|
|
1143
|
+
const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
|
|
992
1144
|
|
|
993
1145
|
scopes.push({
|
|
994
1146
|
id: elseScopeId,
|
|
995
1147
|
type: 'SCOPE',
|
|
996
1148
|
scopeType: 'else_statement',
|
|
997
|
-
name: `else:${ifNode.alternate
|
|
1149
|
+
name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
|
|
998
1150
|
semanticId: elseSemanticId,
|
|
999
1151
|
conditional: true,
|
|
1000
1152
|
constraints: negatedConstraints,
|
|
1001
1153
|
file: module.file,
|
|
1002
|
-
line: ifNode.alternate
|
|
1154
|
+
line: getLine(ifNode.alternate),
|
|
1003
1155
|
parentScopeId: module.id
|
|
1004
1156
|
});
|
|
1005
1157
|
}
|
|
@@ -1030,67 +1182,569 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1030
1182
|
interfaces,
|
|
1031
1183
|
typeAliases,
|
|
1032
1184
|
enums,
|
|
1033
|
-
decorators
|
|
1185
|
+
decorators,
|
|
1186
|
+
// Array mutation tracking
|
|
1187
|
+
arrayMutations,
|
|
1188
|
+
// Object mutation tracking
|
|
1189
|
+
objectMutations,
|
|
1190
|
+
// Object/Array literal tracking - use allCollections refs as visitors may have created new arrays
|
|
1191
|
+
objectLiterals: allCollections.objectLiterals || objectLiterals,
|
|
1192
|
+
arrayLiterals: allCollections.arrayLiterals || arrayLiterals
|
|
1034
1193
|
});
|
|
1035
1194
|
this.profiler.end('graph_build');
|
|
1036
1195
|
|
|
1037
1196
|
nodesCreated = result.nodes;
|
|
1038
1197
|
edgesCreated = result.edges;
|
|
1039
1198
|
|
|
1040
|
-
} catch
|
|
1041
|
-
|
|
1042
|
-
console.error(`[JSASTAnalyzer] Error analyzing ${module.file}:`, err.message);
|
|
1043
|
-
console.error(err.stack);
|
|
1199
|
+
} catch {
|
|
1200
|
+
// Error analyzing module - silently skip, caller handles the result
|
|
1044
1201
|
}
|
|
1045
1202
|
|
|
1046
1203
|
return { nodes: nodesCreated, edges: edgesCreated };
|
|
1047
1204
|
}
|
|
1048
1205
|
|
|
1049
1206
|
/**
|
|
1050
|
-
* Helper to generate semantic ID for a scope
|
|
1207
|
+
* Helper to generate semantic ID for a scope using ScopeTracker.
|
|
1208
|
+
* Format: "scopePath:scopeType[index]" e.g. "MyClass->myMethod:if_statement[0]"
|
|
1051
1209
|
*/
|
|
1052
1210
|
private generateSemanticId(
|
|
1053
1211
|
scopeType: string,
|
|
1054
|
-
|
|
1212
|
+
scopeTracker: ScopeTracker | undefined
|
|
1055
1213
|
): string | undefined {
|
|
1056
|
-
if (!
|
|
1214
|
+
if (!scopeTracker) return undefined;
|
|
1215
|
+
|
|
1216
|
+
const scopePath = scopeTracker.getScopePath();
|
|
1217
|
+
const siblingIndex = scopeTracker.getItemCounter(`semanticId:${scopeType}`);
|
|
1218
|
+
return `${scopePath}:${scopeType}[${siblingIndex}]`;
|
|
1219
|
+
}
|
|
1057
1220
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1221
|
+
/**
|
|
1222
|
+
* Generate a unique anonymous function name within the current scope.
|
|
1223
|
+
* Uses ScopeTracker.getSiblingIndex() for stable naming.
|
|
1224
|
+
*/
|
|
1225
|
+
private generateAnonymousName(scopeTracker: ScopeTracker | undefined): string {
|
|
1226
|
+
if (!scopeTracker) return 'anonymous';
|
|
1227
|
+
const index = scopeTracker.getSiblingIndex('anonymous');
|
|
1228
|
+
return `anonymous[${index}]`;
|
|
1061
1229
|
}
|
|
1062
1230
|
|
|
1063
1231
|
/**
|
|
1064
|
-
*
|
|
1232
|
+
* Factory method to create loop scope handlers.
|
|
1233
|
+
* All loop statements (for, for-in, for-of, while, do-while) follow the same pattern:
|
|
1234
|
+
* 1. Create scope with SCOPE#<scopeType>#file#line:counter
|
|
1235
|
+
* 2. Generate semantic ID
|
|
1236
|
+
* 3. Push to scopes array
|
|
1237
|
+
* 4. Enter/exit scope tracker
|
|
1238
|
+
*
|
|
1239
|
+
* @param trackerScopeType - Scope type for ScopeTracker (e.g., 'for', 'for-in', 'while')
|
|
1240
|
+
* @param scopeType - Scope type for the graph node (e.g., 'for-loop', 'for-in-loop')
|
|
1241
|
+
* @param parentScopeId - Parent scope ID for the scope node
|
|
1242
|
+
* @param module - Module context
|
|
1243
|
+
* @param scopes - Collection to push scope nodes to
|
|
1244
|
+
* @param scopeCounterRef - Counter for unique scope IDs
|
|
1245
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1246
|
+
*/
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Handles VariableDeclaration nodes within function bodies.
|
|
1250
|
+
*
|
|
1251
|
+
* Extracts variable names from patterns (including destructuring), determines
|
|
1252
|
+
* if the variable should be CONSTANT or VARIABLE, generates semantic or legacy IDs,
|
|
1253
|
+
* and tracks class instantiations and variable assignments.
|
|
1254
|
+
*
|
|
1255
|
+
* @param varPath - The NodePath for the VariableDeclaration
|
|
1256
|
+
* @param parentScopeId - Parent scope ID for the variable
|
|
1257
|
+
* @param module - Module context with file info
|
|
1258
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
1259
|
+
* @param classInstantiations - Collection to push class instantiations to
|
|
1260
|
+
* @param literals - Collection for literal tracking
|
|
1261
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
1262
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
1263
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
1264
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1265
|
+
* @param parentScopeVariables - Set to track variables for closure analysis
|
|
1065
1266
|
*/
|
|
1066
|
-
private
|
|
1067
|
-
|
|
1267
|
+
private handleVariableDeclaration(
|
|
1268
|
+
varPath: NodePath<t.VariableDeclaration>,
|
|
1269
|
+
parentScopeId: string,
|
|
1270
|
+
module: VisitorModule,
|
|
1271
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
1272
|
+
classInstantiations: ClassInstantiationInfo[],
|
|
1273
|
+
literals: LiteralInfo[],
|
|
1274
|
+
variableAssignments: VariableAssignmentInfo[],
|
|
1275
|
+
varDeclCounterRef: CounterRef,
|
|
1276
|
+
literalCounterRef: CounterRef,
|
|
1277
|
+
scopeTracker: ScopeTracker | undefined,
|
|
1278
|
+
parentScopeVariables: Set<{ name: string; id: string; scopeId: string }>
|
|
1279
|
+
): void {
|
|
1280
|
+
const varNode = varPath.node;
|
|
1281
|
+
const isConst = varNode.kind === 'const';
|
|
1282
|
+
|
|
1283
|
+
varNode.declarations.forEach(declarator => {
|
|
1284
|
+
const variables = this.extractVariableNamesFromPattern(declarator.id);
|
|
1285
|
+
|
|
1286
|
+
variables.forEach(varInfo => {
|
|
1287
|
+
const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
|
|
1288
|
+
const isLiteral = literalValue !== null;
|
|
1289
|
+
const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
|
|
1290
|
+
|
|
1291
|
+
const shouldBeConstant = isConst && (isLiteral || isNewExpression);
|
|
1292
|
+
const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
|
|
1293
|
+
|
|
1294
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
1295
|
+
const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1296
|
+
|
|
1297
|
+
const varId = scopeTracker
|
|
1298
|
+
? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
|
|
1299
|
+
: legacyId;
|
|
1300
|
+
|
|
1301
|
+
parentScopeVariables.add({
|
|
1302
|
+
name: varInfo.name,
|
|
1303
|
+
id: varId,
|
|
1304
|
+
scopeId: parentScopeId
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
if (shouldBeConstant) {
|
|
1308
|
+
const constantData: VariableDeclarationInfo = {
|
|
1309
|
+
id: varId,
|
|
1310
|
+
type: 'CONSTANT',
|
|
1311
|
+
name: varInfo.name,
|
|
1312
|
+
file: module.file,
|
|
1313
|
+
line: varInfo.loc.start.line,
|
|
1314
|
+
parentScopeId
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
if (isLiteral) {
|
|
1318
|
+
constantData.value = literalValue;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
variableDeclarations.push(constantData);
|
|
1322
|
+
|
|
1323
|
+
const init = declarator.init;
|
|
1324
|
+
if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
|
|
1325
|
+
const className = init.callee.name;
|
|
1326
|
+
classInstantiations.push({
|
|
1327
|
+
variableId: varId,
|
|
1328
|
+
variableName: varInfo.name,
|
|
1329
|
+
className: className,
|
|
1330
|
+
line: varInfo.loc.start.line,
|
|
1331
|
+
parentScopeId
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
} else {
|
|
1335
|
+
variableDeclarations.push({
|
|
1336
|
+
id: varId,
|
|
1337
|
+
type: 'VARIABLE',
|
|
1338
|
+
name: varInfo.name,
|
|
1339
|
+
file: module.file,
|
|
1340
|
+
line: varInfo.loc.start.line,
|
|
1341
|
+
parentScopeId
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (declarator.init) {
|
|
1346
|
+
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
private createLoopScopeHandler(
|
|
1353
|
+
trackerScopeType: string,
|
|
1354
|
+
scopeType: string,
|
|
1355
|
+
parentScopeId: string,
|
|
1356
|
+
module: VisitorModule,
|
|
1357
|
+
scopes: ScopeInfo[],
|
|
1358
|
+
scopeCounterRef: CounterRef,
|
|
1359
|
+
scopeTracker: ScopeTracker | undefined
|
|
1360
|
+
): { enter: (path: NodePath<t.Loop>) => void; exit: () => void } {
|
|
1068
1361
|
return {
|
|
1069
|
-
|
|
1070
|
-
|
|
1362
|
+
enter: (path: NodePath<t.Loop>) => {
|
|
1363
|
+
const node = path.node;
|
|
1364
|
+
const scopeId = `SCOPE#${scopeType}#${module.file}#${getLine(node)}:${scopeCounterRef.value++}`;
|
|
1365
|
+
const semanticId = this.generateSemanticId(scopeType, scopeTracker);
|
|
1366
|
+
scopes.push({
|
|
1367
|
+
id: scopeId,
|
|
1368
|
+
type: 'SCOPE',
|
|
1369
|
+
scopeType,
|
|
1370
|
+
semanticId,
|
|
1371
|
+
file: module.file,
|
|
1372
|
+
line: getLine(node),
|
|
1373
|
+
parentScopeId
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
// Enter scope for semantic ID generation
|
|
1377
|
+
if (scopeTracker) {
|
|
1378
|
+
scopeTracker.enterCountedScope(trackerScopeType);
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
exit: () => {
|
|
1382
|
+
// Exit scope
|
|
1383
|
+
if (scopeTracker) {
|
|
1384
|
+
scopeTracker.exitScope();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1071
1387
|
};
|
|
1072
1388
|
}
|
|
1073
1389
|
|
|
1074
1390
|
/**
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1391
|
+
* Process VariableDeclarations within a try/catch/finally block.
|
|
1392
|
+
* This is a simplified version that doesn't track parentScopeVariables or class instantiations.
|
|
1393
|
+
*
|
|
1394
|
+
* @param blockPath - The NodePath for the block to process
|
|
1395
|
+
* @param blockScopeId - The scope ID for variables in this block
|
|
1396
|
+
* @param module - Module context
|
|
1397
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
1398
|
+
* @param literals - Collection for literal tracking
|
|
1399
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
1400
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
1401
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
1402
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1077
1403
|
*/
|
|
1078
|
-
private
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1404
|
+
private processBlockVariables(
|
|
1405
|
+
blockPath: NodePath,
|
|
1406
|
+
blockScopeId: string,
|
|
1407
|
+
module: VisitorModule,
|
|
1408
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
1409
|
+
literals: LiteralInfo[],
|
|
1410
|
+
variableAssignments: VariableAssignmentInfo[],
|
|
1411
|
+
varDeclCounterRef: CounterRef,
|
|
1412
|
+
literalCounterRef: CounterRef,
|
|
1413
|
+
scopeTracker: ScopeTracker | undefined
|
|
1414
|
+
): void {
|
|
1415
|
+
blockPath.traverse({
|
|
1416
|
+
VariableDeclaration: (varPath: NodePath<t.VariableDeclaration>) => {
|
|
1417
|
+
const varNode = varPath.node;
|
|
1418
|
+
const isConst = varNode.kind === 'const';
|
|
1419
|
+
|
|
1420
|
+
varNode.declarations.forEach(declarator => {
|
|
1421
|
+
const variables = this.extractVariableNamesFromPattern(declarator.id);
|
|
1422
|
+
|
|
1423
|
+
variables.forEach(varInfo => {
|
|
1424
|
+
const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
|
|
1425
|
+
const isLiteral = literalValue !== null;
|
|
1426
|
+
const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
|
|
1427
|
+
const shouldBeConstant = isConst && (isLiteral || isNewExpression);
|
|
1428
|
+
const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
|
|
1429
|
+
|
|
1430
|
+
const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1431
|
+
const varId = scopeTracker
|
|
1432
|
+
? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
|
|
1433
|
+
: legacyId;
|
|
1434
|
+
|
|
1435
|
+
variableDeclarations.push({
|
|
1436
|
+
id: varId,
|
|
1437
|
+
type: nodeType,
|
|
1438
|
+
name: varInfo.name,
|
|
1439
|
+
file: module.file,
|
|
1440
|
+
line: varInfo.loc.start.line,
|
|
1441
|
+
parentScopeId: blockScopeId
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
if (declarator.init) {
|
|
1445
|
+
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Handles TryStatement nodes within function bodies.
|
|
1455
|
+
* Creates try, catch (with optional error parameter), and finally scopes,
|
|
1456
|
+
* and processes variable declarations within each block.
|
|
1457
|
+
*
|
|
1458
|
+
* @param tryPath - The NodePath for the TryStatement
|
|
1459
|
+
* @param parentScopeId - Parent scope ID for the scope nodes
|
|
1460
|
+
* @param module - Module context
|
|
1461
|
+
* @param scopes - Collection to push scope nodes to
|
|
1462
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
1463
|
+
* @param literals - Collection for literal tracking
|
|
1464
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
1465
|
+
* @param scopeCounterRef - Counter for unique scope IDs
|
|
1466
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
1467
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
1468
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1469
|
+
*/
|
|
1470
|
+
private handleTryStatement(
|
|
1471
|
+
tryPath: NodePath<t.TryStatement>,
|
|
1472
|
+
parentScopeId: string,
|
|
1473
|
+
module: VisitorModule,
|
|
1474
|
+
scopes: ScopeInfo[],
|
|
1475
|
+
variableDeclarations: VariableDeclarationInfo[],
|
|
1476
|
+
literals: LiteralInfo[],
|
|
1477
|
+
variableAssignments: VariableAssignmentInfo[],
|
|
1478
|
+
scopeCounterRef: CounterRef,
|
|
1479
|
+
varDeclCounterRef: CounterRef,
|
|
1480
|
+
literalCounterRef: CounterRef,
|
|
1481
|
+
scopeTracker: ScopeTracker | undefined
|
|
1482
|
+
): void {
|
|
1483
|
+
const tryNode = tryPath.node;
|
|
1484
|
+
|
|
1485
|
+
// Create and process try block
|
|
1486
|
+
const tryScopeId = `SCOPE#try-block#${module.file}#${getLine(tryNode)}:${scopeCounterRef.value++}`;
|
|
1487
|
+
const trySemanticId = this.generateSemanticId('try-block', scopeTracker);
|
|
1488
|
+
scopes.push({
|
|
1489
|
+
id: tryScopeId,
|
|
1490
|
+
type: 'SCOPE',
|
|
1491
|
+
scopeType: 'try-block',
|
|
1492
|
+
semanticId: trySemanticId,
|
|
1493
|
+
file: module.file,
|
|
1494
|
+
line: getLine(tryNode),
|
|
1495
|
+
parentScopeId
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
if (scopeTracker) {
|
|
1499
|
+
scopeTracker.enterCountedScope('try');
|
|
1500
|
+
}
|
|
1501
|
+
this.processBlockVariables(
|
|
1502
|
+
tryPath.get('block'),
|
|
1503
|
+
tryScopeId,
|
|
1504
|
+
module,
|
|
1505
|
+
variableDeclarations,
|
|
1506
|
+
literals,
|
|
1507
|
+
variableAssignments,
|
|
1508
|
+
varDeclCounterRef,
|
|
1509
|
+
literalCounterRef,
|
|
1510
|
+
scopeTracker
|
|
1511
|
+
);
|
|
1512
|
+
if (scopeTracker) {
|
|
1513
|
+
scopeTracker.exitScope();
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Create and process catch block if present
|
|
1517
|
+
if (tryNode.handler) {
|
|
1518
|
+
const catchBlock = tryNode.handler;
|
|
1519
|
+
const catchScopeId = `SCOPE#catch-block#${module.file}#${getLine(catchBlock)}:${scopeCounterRef.value++}`;
|
|
1520
|
+
const catchSemanticId = this.generateSemanticId('catch-block', scopeTracker);
|
|
1521
|
+
|
|
1522
|
+
scopes.push({
|
|
1523
|
+
id: catchScopeId,
|
|
1524
|
+
type: 'SCOPE',
|
|
1525
|
+
scopeType: 'catch-block',
|
|
1526
|
+
semanticId: catchSemanticId,
|
|
1527
|
+
file: module.file,
|
|
1528
|
+
line: getLine(catchBlock),
|
|
1529
|
+
parentScopeId
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
if (scopeTracker) {
|
|
1533
|
+
scopeTracker.enterCountedScope('catch');
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Handle catch parameter (e.g., catch (e))
|
|
1537
|
+
if (catchBlock.param) {
|
|
1538
|
+
const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
|
|
1539
|
+
|
|
1540
|
+
errorVarInfo.forEach(varInfo => {
|
|
1541
|
+
const legacyId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1542
|
+
const varId = scopeTracker
|
|
1543
|
+
? computeSemanticId('VARIABLE', varInfo.name, scopeTracker.getContext())
|
|
1544
|
+
: legacyId;
|
|
1545
|
+
|
|
1546
|
+
variableDeclarations.push({
|
|
1547
|
+
id: varId,
|
|
1548
|
+
type: 'VARIABLE',
|
|
1549
|
+
name: varInfo.name,
|
|
1550
|
+
file: module.file,
|
|
1551
|
+
line: varInfo.loc.start.line,
|
|
1552
|
+
parentScopeId: catchScopeId
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
this.processBlockVariables(
|
|
1558
|
+
tryPath.get('handler.body'),
|
|
1559
|
+
catchScopeId,
|
|
1560
|
+
module,
|
|
1561
|
+
variableDeclarations,
|
|
1562
|
+
literals,
|
|
1563
|
+
variableAssignments,
|
|
1564
|
+
varDeclCounterRef,
|
|
1565
|
+
literalCounterRef,
|
|
1566
|
+
scopeTracker
|
|
1567
|
+
);
|
|
1568
|
+
|
|
1569
|
+
if (scopeTracker) {
|
|
1570
|
+
scopeTracker.exitScope();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Create and process finally block if present
|
|
1575
|
+
if (tryNode.finalizer) {
|
|
1576
|
+
const finallyScopeId = `SCOPE#finally-block#${module.file}#${getLine(tryNode.finalizer)}:${scopeCounterRef.value++}`;
|
|
1577
|
+
const finallySemanticId = this.generateSemanticId('finally-block', scopeTracker);
|
|
1578
|
+
|
|
1579
|
+
scopes.push({
|
|
1580
|
+
id: finallyScopeId,
|
|
1581
|
+
type: 'SCOPE',
|
|
1582
|
+
scopeType: 'finally-block',
|
|
1583
|
+
semanticId: finallySemanticId,
|
|
1584
|
+
file: module.file,
|
|
1585
|
+
line: getLine(tryNode.finalizer),
|
|
1586
|
+
parentScopeId
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
if (scopeTracker) {
|
|
1590
|
+
scopeTracker.enterCountedScope('finally');
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const finalizerPath = tryPath.get('finalizer');
|
|
1594
|
+
if (finalizerPath.node) {
|
|
1595
|
+
this.processBlockVariables(
|
|
1596
|
+
finalizerPath as NodePath,
|
|
1597
|
+
finallyScopeId,
|
|
1598
|
+
module,
|
|
1599
|
+
variableDeclarations,
|
|
1600
|
+
literals,
|
|
1601
|
+
variableAssignments,
|
|
1602
|
+
varDeclCounterRef,
|
|
1603
|
+
literalCounterRef,
|
|
1604
|
+
scopeTracker
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
if (scopeTracker) {
|
|
1609
|
+
scopeTracker.exitScope();
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
tryPath.skip();
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Factory method to create IfStatement handler.
|
|
1618
|
+
* Creates if scope with condition parsing and optional else scope.
|
|
1619
|
+
* Tracks if/else scope transitions via ifElseScopeMap.
|
|
1620
|
+
*
|
|
1621
|
+
* @param parentScopeId - Parent scope ID for the scope nodes
|
|
1622
|
+
* @param module - Module context
|
|
1623
|
+
* @param scopes - Collection to push scope nodes to
|
|
1624
|
+
* @param ifScopeCounterRef - Counter for unique if scope IDs
|
|
1625
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1626
|
+
* @param sourceCode - Source code for extracting condition text
|
|
1627
|
+
* @param ifElseScopeMap - Map to track if/else scope transitions
|
|
1628
|
+
*/
|
|
1629
|
+
private createIfStatementHandler(
|
|
1630
|
+
parentScopeId: string,
|
|
1631
|
+
module: VisitorModule,
|
|
1632
|
+
scopes: ScopeInfo[],
|
|
1633
|
+
ifScopeCounterRef: CounterRef,
|
|
1634
|
+
scopeTracker: ScopeTracker | undefined,
|
|
1635
|
+
sourceCode: string,
|
|
1636
|
+
ifElseScopeMap: Map<t.IfStatement, { inElse: boolean; hasElse: boolean }>
|
|
1637
|
+
): { enter: (ifPath: NodePath<t.IfStatement>) => void; exit: (ifPath: NodePath<t.IfStatement>) => void } {
|
|
1638
|
+
return {
|
|
1639
|
+
enter: (ifPath: NodePath<t.IfStatement>) => {
|
|
1640
|
+
const ifNode = ifPath.node;
|
|
1641
|
+
const condition = sourceCode.substring(ifNode.test.start!, ifNode.test.end!) || 'condition';
|
|
1642
|
+
const counterId = ifScopeCounterRef.value++;
|
|
1643
|
+
const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
|
|
1644
|
+
|
|
1645
|
+
// Parse condition to extract constraints
|
|
1646
|
+
const constraints = ConditionParser.parse(ifNode.test);
|
|
1647
|
+
const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
|
|
1648
|
+
|
|
1649
|
+
scopes.push({
|
|
1650
|
+
id: ifScopeId,
|
|
1651
|
+
type: 'SCOPE',
|
|
1652
|
+
scopeType: 'if_statement',
|
|
1653
|
+
name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
|
|
1654
|
+
semanticId: ifSemanticId,
|
|
1655
|
+
conditional: true,
|
|
1656
|
+
condition,
|
|
1657
|
+
constraints: constraints.length > 0 ? constraints : undefined,
|
|
1658
|
+
file: module.file,
|
|
1659
|
+
line: getLine(ifNode),
|
|
1660
|
+
parentScopeId
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
// Enter scope for semantic ID generation
|
|
1664
|
+
if (scopeTracker) {
|
|
1665
|
+
scopeTracker.enterCountedScope('if');
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Handle else branch if present
|
|
1669
|
+
if (ifNode.alternate && !t.isIfStatement(ifNode.alternate)) {
|
|
1670
|
+
// Only create else scope for actual else block, not else-if
|
|
1671
|
+
const elseCounterId = ifScopeCounterRef.value++;
|
|
1672
|
+
const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
|
|
1673
|
+
|
|
1674
|
+
const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
|
|
1675
|
+
const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
|
|
1676
|
+
|
|
1677
|
+
scopes.push({
|
|
1678
|
+
id: elseScopeId,
|
|
1679
|
+
type: 'SCOPE',
|
|
1680
|
+
scopeType: 'else_statement',
|
|
1681
|
+
name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
|
|
1682
|
+
semanticId: elseSemanticId,
|
|
1683
|
+
conditional: true,
|
|
1684
|
+
constraints: negatedConstraints,
|
|
1685
|
+
file: module.file,
|
|
1686
|
+
line: getLine(ifNode.alternate),
|
|
1687
|
+
parentScopeId
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
// Store info to switch to else scope when we enter alternate
|
|
1691
|
+
ifElseScopeMap.set(ifNode, { inElse: false, hasElse: true });
|
|
1692
|
+
} else {
|
|
1693
|
+
ifElseScopeMap.set(ifNode, { inElse: false, hasElse: false });
|
|
1694
|
+
}
|
|
1695
|
+
},
|
|
1696
|
+
exit: (ifPath: NodePath<t.IfStatement>) => {
|
|
1697
|
+
const ifNode = ifPath.node;
|
|
1698
|
+
|
|
1699
|
+
// Exit the current scope (either if or else)
|
|
1700
|
+
if (scopeTracker) {
|
|
1701
|
+
scopeTracker.exitScope();
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// If we were in else, we already exited else scope
|
|
1705
|
+
// If we only had if, we exit if scope (done above)
|
|
1706
|
+
ifElseScopeMap.delete(ifNode);
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Factory method to create BlockStatement handler for tracking if/else transitions.
|
|
1713
|
+
* When entering an else block, switches scope from if to else.
|
|
1714
|
+
*
|
|
1715
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1716
|
+
* @param ifElseScopeMap - Map to track if/else scope transitions
|
|
1717
|
+
*/
|
|
1718
|
+
private createIfElseBlockStatementHandler(
|
|
1719
|
+
scopeTracker: ScopeTracker | undefined,
|
|
1720
|
+
ifElseScopeMap: Map<t.IfStatement, { inElse: boolean; hasElse: boolean }>
|
|
1721
|
+
): { enter: (blockPath: NodePath<t.BlockStatement>) => void } {
|
|
1722
|
+
return {
|
|
1723
|
+
enter: (blockPath: NodePath<t.BlockStatement>) => {
|
|
1724
|
+
// Check if this block is the alternate of an IfStatement
|
|
1725
|
+
const parent = blockPath.parent;
|
|
1726
|
+
if (t.isIfStatement(parent) && parent.alternate === blockPath.node) {
|
|
1727
|
+
const scopeInfo = ifElseScopeMap.get(parent);
|
|
1728
|
+
if (scopeInfo && scopeInfo.hasElse && !scopeInfo.inElse && scopeTracker) {
|
|
1729
|
+
// Exit if scope, enter else scope
|
|
1730
|
+
scopeTracker.exitScope();
|
|
1731
|
+
scopeTracker.enterCountedScope('else');
|
|
1732
|
+
scopeInfo.inElse = true;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1083
1737
|
}
|
|
1084
1738
|
|
|
1085
1739
|
/**
|
|
1086
|
-
* Анализирует тело функции и извлекает переменные, вызовы, условные
|
|
1740
|
+
* Анализирует тело функции и извлекает переменные, вызовы, условные блоки.
|
|
1741
|
+
* Uses ScopeTracker from collections for semantic ID generation.
|
|
1087
1742
|
*/
|
|
1088
1743
|
analyzeFunctionBody(
|
|
1089
1744
|
funcPath: NodePath<t.Function>,
|
|
1090
1745
|
parentScopeId: string,
|
|
1091
1746
|
module: VisitorModule,
|
|
1092
|
-
collections: VisitorCollections
|
|
1093
|
-
scopeCtx?: ScopeContext
|
|
1747
|
+
collections: VisitorCollections
|
|
1094
1748
|
): void {
|
|
1095
1749
|
// Extract with defaults for optional properties
|
|
1096
1750
|
const functions = (collections.functions ?? []) as FunctionInfo[];
|
|
@@ -1112,6 +1766,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1112
1766
|
const httpRequestCounterRef = (collections.httpRequestCounterRef ?? { value: 0 }) as CounterRef;
|
|
1113
1767
|
const literalCounterRef = (collections.literalCounterRef ?? { value: 0 }) as CounterRef;
|
|
1114
1768
|
const anonymousFunctionCounterRef = (collections.anonymousFunctionCounterRef ?? { value: 0 }) as CounterRef;
|
|
1769
|
+
const scopeTracker = collections.scopeTracker as ScopeTracker | undefined;
|
|
1115
1770
|
const processedNodes = collections.processedNodes ?? {
|
|
1116
1771
|
functions: new Set<string>(),
|
|
1117
1772
|
classes: new Set<string>(),
|
|
@@ -1131,219 +1786,75 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1131
1786
|
const processedMethodCalls = processedNodes.methodCalls;
|
|
1132
1787
|
const processedEventListeners = processedNodes.eventListeners;
|
|
1133
1788
|
|
|
1789
|
+
// Track if/else scope transitions
|
|
1790
|
+
const ifElseScopeMap = new Map<t.IfStatement, { inElse: boolean; hasElse: boolean }>();
|
|
1791
|
+
|
|
1134
1792
|
funcPath.traverse({
|
|
1135
1793
|
VariableDeclaration: (varPath: NodePath<t.VariableDeclaration>) => {
|
|
1136
|
-
|
|
1137
|
-
|
|
1794
|
+
this.handleVariableDeclaration(
|
|
1795
|
+
varPath,
|
|
1796
|
+
parentScopeId,
|
|
1797
|
+
module,
|
|
1798
|
+
variableDeclarations,
|
|
1799
|
+
classInstantiations,
|
|
1800
|
+
literals,
|
|
1801
|
+
variableAssignments,
|
|
1802
|
+
varDeclCounterRef,
|
|
1803
|
+
literalCounterRef,
|
|
1804
|
+
scopeTracker,
|
|
1805
|
+
parentScopeVariables
|
|
1806
|
+
);
|
|
1807
|
+
},
|
|
1138
1808
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1809
|
+
// Detect indexed array assignments: arr[i] = value
|
|
1810
|
+
AssignmentExpression: (assignPath: NodePath<t.AssignmentExpression>) => {
|
|
1811
|
+
const assignNode = assignPath.node;
|
|
1141
1812
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1813
|
+
// Initialize collection if not exists
|
|
1814
|
+
if (!collections.arrayMutations) {
|
|
1815
|
+
collections.arrayMutations = [];
|
|
1816
|
+
}
|
|
1817
|
+
const arrayMutations = collections.arrayMutations as ArrayMutationInfo[];
|
|
1146
1818
|
|
|
1147
|
-
|
|
1819
|
+
// Check for indexed array assignment: arr[i] = value
|
|
1820
|
+
this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
|
|
1148
1821
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1822
|
+
// Initialize object mutations collection if not exists
|
|
1823
|
+
if (!collections.objectMutations) {
|
|
1824
|
+
collections.objectMutations = [];
|
|
1825
|
+
}
|
|
1826
|
+
const objectMutations = collections.objectMutations as ObjectMutationInfo[];
|
|
1152
1827
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
id: varId,
|
|
1156
|
-
scopeId: parentScopeId
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
if (shouldBeConstant) {
|
|
1160
|
-
const constantData: VariableDeclarationInfo = {
|
|
1161
|
-
id: varId,
|
|
1162
|
-
type: 'CONSTANT',
|
|
1163
|
-
name: varInfo.name,
|
|
1164
|
-
file: module.file,
|
|
1165
|
-
line: varInfo.loc.start.line,
|
|
1166
|
-
parentScopeId
|
|
1167
|
-
};
|
|
1168
|
-
|
|
1169
|
-
if (isLiteral) {
|
|
1170
|
-
constantData.value = literalValue;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
variableDeclarations.push(constantData);
|
|
1174
|
-
|
|
1175
|
-
const init = declarator.init;
|
|
1176
|
-
if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
|
|
1177
|
-
const className = init.callee.name;
|
|
1178
|
-
classInstantiations.push({
|
|
1179
|
-
variableId: varId,
|
|
1180
|
-
variableName: varInfo.name,
|
|
1181
|
-
className: className,
|
|
1182
|
-
line: varInfo.loc.start.line,
|
|
1183
|
-
parentScopeId
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
} else {
|
|
1187
|
-
variableDeclarations.push({
|
|
1188
|
-
id: varId,
|
|
1189
|
-
type: 'VARIABLE',
|
|
1190
|
-
name: varInfo.name,
|
|
1191
|
-
file: module.file,
|
|
1192
|
-
line: varInfo.loc.start.line,
|
|
1193
|
-
parentScopeId
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
if (declarator.init) {
|
|
1198
|
-
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
1201
|
-
});
|
|
1202
|
-
},
|
|
1203
|
-
|
|
1204
|
-
ForStatement: (forPath: NodePath<t.ForStatement>) => {
|
|
1205
|
-
const forNode = forPath.node;
|
|
1206
|
-
const scopeId = `SCOPE#for-loop#${module.file}#${forNode.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1207
|
-
const semanticId = this.generateSemanticId('for-loop', scopeCtx);
|
|
1208
|
-
scopes.push({
|
|
1209
|
-
id: scopeId,
|
|
1210
|
-
type: 'SCOPE',
|
|
1211
|
-
scopeType: 'for-loop',
|
|
1212
|
-
semanticId,
|
|
1213
|
-
file: module.file,
|
|
1214
|
-
line: forNode.loc!.start.line,
|
|
1215
|
-
parentScopeId
|
|
1216
|
-
});
|
|
1217
|
-
},
|
|
1218
|
-
|
|
1219
|
-
ForInStatement: (forPath: NodePath<t.ForInStatement>) => {
|
|
1220
|
-
const forNode = forPath.node;
|
|
1221
|
-
const scopeId = `SCOPE#for-in-loop#${module.file}#${forNode.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1222
|
-
const semanticId = this.generateSemanticId('for-in-loop', scopeCtx);
|
|
1223
|
-
scopes.push({
|
|
1224
|
-
id: scopeId,
|
|
1225
|
-
type: 'SCOPE',
|
|
1226
|
-
scopeType: 'for-in-loop',
|
|
1227
|
-
semanticId,
|
|
1228
|
-
file: module.file,
|
|
1229
|
-
line: forNode.loc!.start.line,
|
|
1230
|
-
parentScopeId
|
|
1231
|
-
});
|
|
1828
|
+
// Check for object property assignment: obj.prop = value
|
|
1829
|
+
this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
|
|
1232
1830
|
},
|
|
1233
1831
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
id: scopeId,
|
|
1240
|
-
type: 'SCOPE',
|
|
1241
|
-
scopeType: 'for-of-loop',
|
|
1242
|
-
semanticId,
|
|
1243
|
-
file: module.file,
|
|
1244
|
-
line: forNode.loc!.start.line,
|
|
1245
|
-
parentScopeId
|
|
1246
|
-
});
|
|
1247
|
-
},
|
|
1248
|
-
|
|
1249
|
-
WhileStatement: (whilePath: NodePath<t.WhileStatement>) => {
|
|
1250
|
-
const whileNode = whilePath.node;
|
|
1251
|
-
const scopeId = `SCOPE#while-loop#${module.file}#${whileNode.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1252
|
-
const semanticId = this.generateSemanticId('while-loop', scopeCtx);
|
|
1253
|
-
scopes.push({
|
|
1254
|
-
id: scopeId,
|
|
1255
|
-
type: 'SCOPE',
|
|
1256
|
-
scopeType: 'while-loop',
|
|
1257
|
-
semanticId,
|
|
1258
|
-
file: module.file,
|
|
1259
|
-
line: whileNode.loc!.start.line,
|
|
1260
|
-
parentScopeId
|
|
1261
|
-
});
|
|
1262
|
-
},
|
|
1263
|
-
|
|
1264
|
-
DoWhileStatement: (doPath: NodePath<t.DoWhileStatement>) => {
|
|
1265
|
-
const doNode = doPath.node;
|
|
1266
|
-
const scopeId = `SCOPE#do-while-loop#${module.file}#${doNode.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1267
|
-
const semanticId = this.generateSemanticId('do-while-loop', scopeCtx);
|
|
1268
|
-
scopes.push({
|
|
1269
|
-
id: scopeId,
|
|
1270
|
-
type: 'SCOPE',
|
|
1271
|
-
scopeType: 'do-while-loop',
|
|
1272
|
-
semanticId,
|
|
1273
|
-
file: module.file,
|
|
1274
|
-
line: doNode.loc!.start.line,
|
|
1275
|
-
parentScopeId
|
|
1276
|
-
});
|
|
1277
|
-
},
|
|
1832
|
+
ForStatement: this.createLoopScopeHandler('for', 'for-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1833
|
+
ForInStatement: this.createLoopScopeHandler('for-in', 'for-in-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1834
|
+
ForOfStatement: this.createLoopScopeHandler('for-of', 'for-of-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1835
|
+
WhileStatement: this.createLoopScopeHandler('while', 'while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1836
|
+
DoWhileStatement: this.createLoopScopeHandler('do-while', 'do-while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1278
1837
|
|
|
1279
1838
|
TryStatement: (tryPath: NodePath<t.TryStatement>) => {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
if (tryNode.handler) {
|
|
1295
|
-
const catchBlock = tryNode.handler;
|
|
1296
|
-
const catchScopeId = `SCOPE#catch-block#${module.file}#${catchBlock.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1297
|
-
const catchSemanticId = this.generateSemanticId('catch-block', scopeCtx);
|
|
1298
|
-
|
|
1299
|
-
scopes.push({
|
|
1300
|
-
id: catchScopeId,
|
|
1301
|
-
type: 'SCOPE',
|
|
1302
|
-
scopeType: 'catch-block',
|
|
1303
|
-
semanticId: catchSemanticId,
|
|
1304
|
-
file: module.file,
|
|
1305
|
-
line: catchBlock.loc!.start.line,
|
|
1306
|
-
parentScopeId
|
|
1307
|
-
});
|
|
1308
|
-
|
|
1309
|
-
if (catchBlock.param) {
|
|
1310
|
-
const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
|
|
1311
|
-
|
|
1312
|
-
errorVarInfo.forEach(varInfo => {
|
|
1313
|
-
const varId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1314
|
-
|
|
1315
|
-
variableDeclarations.push({
|
|
1316
|
-
id: varId,
|
|
1317
|
-
type: 'VARIABLE',
|
|
1318
|
-
name: varInfo.name,
|
|
1319
|
-
file: module.file,
|
|
1320
|
-
line: varInfo.loc.start.line,
|
|
1321
|
-
parentScopeId: catchScopeId
|
|
1322
|
-
});
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
if (tryNode.finalizer) {
|
|
1328
|
-
const finallyScopeId = `SCOPE#finally-block#${module.file}#${tryNode.finalizer.loc!.start.line}:${scopeCounterRef.value++}`;
|
|
1329
|
-
const finallySemanticId = this.generateSemanticId('finally-block', scopeCtx);
|
|
1330
|
-
|
|
1331
|
-
scopes.push({
|
|
1332
|
-
id: finallyScopeId,
|
|
1333
|
-
type: 'SCOPE',
|
|
1334
|
-
scopeType: 'finally-block',
|
|
1335
|
-
semanticId: finallySemanticId,
|
|
1336
|
-
file: module.file,
|
|
1337
|
-
line: tryNode.finalizer.loc!.start.line,
|
|
1338
|
-
parentScopeId
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1839
|
+
this.handleTryStatement(
|
|
1840
|
+
tryPath,
|
|
1841
|
+
parentScopeId,
|
|
1842
|
+
module,
|
|
1843
|
+
scopes,
|
|
1844
|
+
variableDeclarations,
|
|
1845
|
+
literals,
|
|
1846
|
+
variableAssignments,
|
|
1847
|
+
scopeCounterRef,
|
|
1848
|
+
varDeclCounterRef,
|
|
1849
|
+
literalCounterRef,
|
|
1850
|
+
scopeTracker
|
|
1851
|
+
);
|
|
1341
1852
|
},
|
|
1342
1853
|
|
|
1343
1854
|
SwitchStatement: (switchPath: NodePath<t.SwitchStatement>) => {
|
|
1344
1855
|
const switchNode = switchPath.node;
|
|
1345
|
-
const scopeId = `SCOPE#switch-case#${module.file}#${switchNode
|
|
1346
|
-
const semanticId = this.generateSemanticId('switch-case',
|
|
1856
|
+
const scopeId = `SCOPE#switch-case#${module.file}#${getLine(switchNode)}:${scopeCounterRef.value++}`;
|
|
1857
|
+
const semanticId = this.generateSemanticId('switch-case', scopeTracker);
|
|
1347
1858
|
|
|
1348
1859
|
scopes.push({
|
|
1349
1860
|
id: scopeId,
|
|
@@ -1351,31 +1862,34 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1351
1862
|
scopeType: 'switch-case',
|
|
1352
1863
|
semanticId,
|
|
1353
1864
|
file: module.file,
|
|
1354
|
-
line: switchNode
|
|
1865
|
+
line: getLine(switchNode),
|
|
1355
1866
|
parentScopeId
|
|
1356
1867
|
});
|
|
1357
1868
|
},
|
|
1358
1869
|
|
|
1359
1870
|
FunctionExpression: (funcPath: NodePath<t.FunctionExpression>) => {
|
|
1360
1871
|
const node = funcPath.node;
|
|
1361
|
-
const funcName = node.id ? node.id.name : this.generateAnonymousName(
|
|
1362
|
-
|
|
1872
|
+
const funcName = node.id ? node.id.name : this.generateAnonymousName(scopeTracker);
|
|
1873
|
+
// Use semantic ID as primary ID when scopeTracker available
|
|
1874
|
+
const legacyId = `FUNCTION#${funcName}#${module.file}#${getLine(node)}:${getColumn(node)}:${functionCounterRef.value++}`;
|
|
1875
|
+
const functionId = scopeTracker
|
|
1876
|
+
? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
|
|
1877
|
+
: legacyId;
|
|
1363
1878
|
|
|
1364
1879
|
functions.push({
|
|
1365
1880
|
id: functionId,
|
|
1366
|
-
stableId: functionId,
|
|
1367
1881
|
type: 'FUNCTION',
|
|
1368
1882
|
name: funcName,
|
|
1369
1883
|
file: module.file,
|
|
1370
|
-
line: node
|
|
1371
|
-
column: node
|
|
1884
|
+
line: getLine(node),
|
|
1885
|
+
column: getColumn(node),
|
|
1372
1886
|
async: node.async || false,
|
|
1373
1887
|
generator: node.generator || false,
|
|
1374
1888
|
parentScopeId
|
|
1375
1889
|
});
|
|
1376
1890
|
|
|
1377
|
-
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${node
|
|
1378
|
-
const closureSemanticId = this.generateSemanticId('closure',
|
|
1891
|
+
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(node)}`;
|
|
1892
|
+
const closureSemanticId = this.generateSemanticId('closure', scopeTracker);
|
|
1379
1893
|
scopes.push({
|
|
1380
1894
|
id: nestedScopeId,
|
|
1381
1895
|
type: 'SCOPE',
|
|
@@ -1384,24 +1898,26 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1384
1898
|
semanticId: closureSemanticId,
|
|
1385
1899
|
conditional: false,
|
|
1386
1900
|
file: module.file,
|
|
1387
|
-
line: node
|
|
1901
|
+
line: getLine(node),
|
|
1388
1902
|
parentFunctionId: functionId,
|
|
1389
1903
|
capturesFrom: parentScopeId
|
|
1390
1904
|
});
|
|
1391
1905
|
|
|
1392
|
-
//
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1906
|
+
// Enter nested function scope for semantic ID generation
|
|
1907
|
+
if (scopeTracker) {
|
|
1908
|
+
scopeTracker.enterScope(funcName, 'function');
|
|
1909
|
+
}
|
|
1910
|
+
this.analyzeFunctionBody(funcPath, nestedScopeId, module, collections);
|
|
1911
|
+
if (scopeTracker) {
|
|
1912
|
+
scopeTracker.exitScope();
|
|
1913
|
+
}
|
|
1398
1914
|
funcPath.skip();
|
|
1399
1915
|
},
|
|
1400
1916
|
|
|
1401
1917
|
ArrowFunctionExpression: (arrowPath: NodePath<t.ArrowFunctionExpression>) => {
|
|
1402
1918
|
const node = arrowPath.node;
|
|
1403
|
-
const line = node
|
|
1404
|
-
const column = node
|
|
1919
|
+
const line = getLine(node);
|
|
1920
|
+
const column = getColumn(node);
|
|
1405
1921
|
|
|
1406
1922
|
// Определяем имя (anonymous если не присвоено переменной)
|
|
1407
1923
|
const parent = arrowPath.parent;
|
|
@@ -1410,14 +1926,17 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1410
1926
|
funcName = parent.id.name;
|
|
1411
1927
|
} else {
|
|
1412
1928
|
// Используем scope-level счётчик для стабильного semanticId
|
|
1413
|
-
funcName = this.generateAnonymousName(
|
|
1929
|
+
funcName = this.generateAnonymousName(scopeTracker);
|
|
1414
1930
|
}
|
|
1415
1931
|
|
|
1416
|
-
|
|
1932
|
+
// Use semantic ID as primary ID when scopeTracker available
|
|
1933
|
+
const legacyId = `FUNCTION#${funcName}:${line}:${column}:${functionCounterRef.value++}`;
|
|
1934
|
+
const functionId = scopeTracker
|
|
1935
|
+
? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
|
|
1936
|
+
: legacyId;
|
|
1417
1937
|
|
|
1418
1938
|
functions.push({
|
|
1419
1939
|
id: functionId,
|
|
1420
|
-
stableId: functionId,
|
|
1421
1940
|
type: 'FUNCTION',
|
|
1422
1941
|
name: funcName,
|
|
1423
1942
|
file: module.file,
|
|
@@ -1430,7 +1949,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1430
1949
|
|
|
1431
1950
|
if (node.body.type === 'BlockStatement') {
|
|
1432
1951
|
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${line}`;
|
|
1433
|
-
const arrowSemanticId = this.generateSemanticId('arrow_body',
|
|
1952
|
+
const arrowSemanticId = this.generateSemanticId('arrow_body', scopeTracker);
|
|
1434
1953
|
scopes.push({
|
|
1435
1954
|
id: nestedScopeId,
|
|
1436
1955
|
type: 'SCOPE',
|
|
@@ -1444,12 +1963,14 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1444
1963
|
capturesFrom: parentScopeId
|
|
1445
1964
|
});
|
|
1446
1965
|
|
|
1447
|
-
//
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1966
|
+
// Enter arrow function scope for semantic ID generation
|
|
1967
|
+
if (scopeTracker) {
|
|
1968
|
+
scopeTracker.enterScope(funcName, 'arrow');
|
|
1969
|
+
}
|
|
1970
|
+
this.analyzeFunctionBody(arrowPath, nestedScopeId, module, collections);
|
|
1971
|
+
if (scopeTracker) {
|
|
1972
|
+
scopeTracker.exitScope();
|
|
1973
|
+
}
|
|
1453
1974
|
}
|
|
1454
1975
|
|
|
1455
1976
|
arrowPath.skip();
|
|
@@ -1472,7 +1993,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1472
1993
|
scope.modifies.push({
|
|
1473
1994
|
variableId: variable.id,
|
|
1474
1995
|
variableName: varName,
|
|
1475
|
-
line: updateNode
|
|
1996
|
+
line: getLine(updateNode)
|
|
1476
1997
|
});
|
|
1477
1998
|
}
|
|
1478
1999
|
}
|
|
@@ -1480,201 +2001,660 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1480
2001
|
},
|
|
1481
2002
|
|
|
1482
2003
|
// IF statements - создаём условные scope и обходим содержимое для CALL узлов
|
|
1483
|
-
IfStatement: (
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2004
|
+
IfStatement: this.createIfStatementHandler(
|
|
2005
|
+
parentScopeId,
|
|
2006
|
+
module,
|
|
2007
|
+
scopes,
|
|
2008
|
+
ifScopeCounterRef,
|
|
2009
|
+
scopeTracker,
|
|
2010
|
+
collections.code ?? '',
|
|
2011
|
+
ifElseScopeMap
|
|
2012
|
+
),
|
|
1489
2013
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const ifSemanticId = this.generateSemanticId('if_statement', scopeCtx);
|
|
2014
|
+
// Track when we enter the alternate (else) block of an IfStatement
|
|
2015
|
+
BlockStatement: this.createIfElseBlockStatementHandler(scopeTracker, ifElseScopeMap),
|
|
1493
2016
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
parentScopeId
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
ifPath.get('consequent').traverse({
|
|
1510
|
-
CallExpression: (callPath: NodePath<t.CallExpression>) => {
|
|
1511
|
-
const callNode = callPath.node;
|
|
1512
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1513
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1514
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
processedCallSites.add(nodeKey);
|
|
2017
|
+
// Function call expressions
|
|
2018
|
+
CallExpression: (callPath: NodePath<t.CallExpression>) => {
|
|
2019
|
+
this.handleCallExpression(
|
|
2020
|
+
callPath.node,
|
|
2021
|
+
processedCallSites,
|
|
2022
|
+
processedMethodCalls,
|
|
2023
|
+
callSites,
|
|
2024
|
+
methodCalls,
|
|
2025
|
+
module,
|
|
2026
|
+
callSiteCounterRef,
|
|
2027
|
+
scopeTracker,
|
|
2028
|
+
parentScopeId,
|
|
2029
|
+
collections
|
|
2030
|
+
);
|
|
2031
|
+
},
|
|
1518
2032
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
name: callNode.callee.name,
|
|
1523
|
-
file: module.file,
|
|
1524
|
-
line: callNode.loc!.start.line,
|
|
1525
|
-
parentScopeId: ifScopeId,
|
|
1526
|
-
targetFunctionName: callNode.callee.name
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
},
|
|
1530
|
-
NewExpression: (newPath: NodePath<t.NewExpression>) => {
|
|
1531
|
-
const newNode = newPath.node;
|
|
1532
|
-
if (newNode.callee.type === 'Identifier') {
|
|
1533
|
-
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1534
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
processedCallSites.add(nodeKey);
|
|
2033
|
+
// NewExpression (constructor calls)
|
|
2034
|
+
NewExpression: (newPath: NodePath<t.NewExpression>) => {
|
|
2035
|
+
const newNode = newPath.node;
|
|
1538
2036
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
line: newNode.loc!.start.line,
|
|
1545
|
-
parentScopeId: ifScopeId,
|
|
1546
|
-
targetFunctionName: newNode.callee.name,
|
|
1547
|
-
isNew: true
|
|
1548
|
-
});
|
|
1549
|
-
}
|
|
2037
|
+
// Handle simple constructor: new Foo()
|
|
2038
|
+
if (newNode.callee.type === 'Identifier') {
|
|
2039
|
+
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
2040
|
+
if (processedCallSites.has(nodeKey)) {
|
|
2041
|
+
return;
|
|
1550
2042
|
}
|
|
1551
|
-
|
|
2043
|
+
processedCallSites.add(nodeKey);
|
|
1552
2044
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
const
|
|
1556
|
-
const elseScopeId = `SCOPE#else#${module.file}#${ifNode.alternate.loc!.start.line}:${ifNode.alternate.loc!.start.column}:${elseCounterId}`;
|
|
2045
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
2046
|
+
const constructorName = newNode.callee.name;
|
|
2047
|
+
const legacyId = `CALL#new:${constructorName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
|
|
1557
2048
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
2049
|
+
let newCallId = legacyId;
|
|
2050
|
+
if (scopeTracker) {
|
|
2051
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:new:${constructorName}`);
|
|
2052
|
+
newCallId = computeSemanticId('CALL', `new:${constructorName}`, scopeTracker.getContext(), { discriminator });
|
|
2053
|
+
}
|
|
1561
2054
|
|
|
1562
|
-
|
|
1563
|
-
id:
|
|
1564
|
-
type: '
|
|
1565
|
-
|
|
1566
|
-
name: `else:${ifNode.alternate.loc!.start.line}:${ifNode.alternate.loc!.start.column}:${elseCounterId}`,
|
|
1567
|
-
semanticId: elseSemanticId,
|
|
1568
|
-
conditional: true,
|
|
1569
|
-
constraints: negatedConstraints,
|
|
2055
|
+
callSites.push({
|
|
2056
|
+
id: newCallId,
|
|
2057
|
+
type: 'CALL',
|
|
2058
|
+
name: constructorName,
|
|
1570
2059
|
file: module.file,
|
|
1571
|
-
line:
|
|
1572
|
-
parentScopeId
|
|
2060
|
+
line: getLine(newNode),
|
|
2061
|
+
parentScopeId,
|
|
2062
|
+
targetFunctionName: constructorName,
|
|
2063
|
+
isNew: true
|
|
1573
2064
|
});
|
|
2065
|
+
}
|
|
2066
|
+
// Handle namespaced constructor: new ns.Constructor()
|
|
2067
|
+
else if (newNode.callee.type === 'MemberExpression') {
|
|
2068
|
+
const memberCallee = newNode.callee;
|
|
2069
|
+
const object = memberCallee.object;
|
|
2070
|
+
const property = memberCallee.property;
|
|
1574
2071
|
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1580
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1581
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1582
|
-
return;
|
|
1583
|
-
}
|
|
1584
|
-
processedCallSites.add(nodeKey);
|
|
1585
|
-
|
|
1586
|
-
callSites.push({
|
|
1587
|
-
id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc!.start.line}:${callNode.loc!.start.column}:${callSiteCounterRef.value++}`,
|
|
1588
|
-
type: 'CALL',
|
|
1589
|
-
name: callNode.callee.name,
|
|
1590
|
-
file: module.file,
|
|
1591
|
-
line: callNode.loc!.start.line,
|
|
1592
|
-
parentScopeId: elseScopeId,
|
|
1593
|
-
targetFunctionName: callNode.callee.name
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
},
|
|
1597
|
-
NewExpression: (newPath: NodePath<t.NewExpression>) => {
|
|
1598
|
-
const newNode = newPath.node;
|
|
1599
|
-
if (newNode.callee.type === 'Identifier') {
|
|
1600
|
-
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1601
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
processedCallSites.add(nodeKey);
|
|
1605
|
-
|
|
1606
|
-
callSites.push({
|
|
1607
|
-
id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc!.start.line}:${newNode.loc!.start.column}:${callSiteCounterRef.value++}`,
|
|
1608
|
-
type: 'CALL',
|
|
1609
|
-
name: newNode.callee.name,
|
|
1610
|
-
file: module.file,
|
|
1611
|
-
line: newNode.loc!.start.line,
|
|
1612
|
-
parentScopeId: elseScopeId,
|
|
1613
|
-
targetFunctionName: newNode.callee.name,
|
|
1614
|
-
isNew: true
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
2072
|
+
if (object.type === 'Identifier' && property.type === 'Identifier') {
|
|
2073
|
+
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
2074
|
+
if (processedMethodCalls.has(nodeKey)) {
|
|
2075
|
+
return;
|
|
1617
2076
|
}
|
|
1618
|
-
|
|
1619
|
-
}
|
|
2077
|
+
processedMethodCalls.add(nodeKey);
|
|
1620
2078
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2079
|
+
const objectName = object.name;
|
|
2080
|
+
const constructorName = property.name;
|
|
2081
|
+
const fullName = `${objectName}.${constructorName}`;
|
|
1624
2082
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
// Обычные вызовы функций (greet(), main())
|
|
1633
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1634
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1635
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1636
|
-
return;
|
|
2083
|
+
// Generate semantic ID for method-style constructor call
|
|
2084
|
+
const legacyId = `CALL#new:${fullName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
|
|
2085
|
+
|
|
2086
|
+
let newMethodCallId = legacyId;
|
|
2087
|
+
if (scopeTracker) {
|
|
2088
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:new:${fullName}`);
|
|
2089
|
+
newMethodCallId = computeSemanticId('CALL', `new:${fullName}`, scopeTracker.getContext(), { discriminator });
|
|
1637
2090
|
}
|
|
1638
|
-
processedCallSites.add(nodeKey);
|
|
1639
2091
|
|
|
1640
|
-
|
|
1641
|
-
id:
|
|
2092
|
+
methodCalls.push({
|
|
2093
|
+
id: newMethodCallId,
|
|
1642
2094
|
type: 'CALL',
|
|
1643
|
-
name:
|
|
2095
|
+
name: fullName,
|
|
2096
|
+
object: objectName,
|
|
2097
|
+
method: constructorName,
|
|
1644
2098
|
file: module.file,
|
|
1645
|
-
line:
|
|
2099
|
+
line: getLine(newNode),
|
|
2100
|
+
column: getColumn(newNode),
|
|
1646
2101
|
parentScopeId,
|
|
1647
|
-
|
|
2102
|
+
isNew: true
|
|
1648
2103
|
});
|
|
1649
2104
|
}
|
|
1650
2105
|
}
|
|
1651
|
-
}
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
1652
2109
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
2110
|
+
/**
|
|
2111
|
+
* Handle CallExpression nodes: direct function calls (greet(), main())
|
|
2112
|
+
* and method calls (obj.method(), data.process()).
|
|
2113
|
+
*
|
|
2114
|
+
* Handles:
|
|
2115
|
+
* - Direct function calls (Identifier callee) → callSites collection
|
|
2116
|
+
* - Method calls (MemberExpression callee) → methodCalls collection
|
|
2117
|
+
* - Array mutation detection (push, unshift, splice)
|
|
2118
|
+
* - Object.assign() detection
|
|
2119
|
+
*
|
|
2120
|
+
* @param callNode - The call expression AST node
|
|
2121
|
+
* @param processedCallSites - Set of already processed call site keys to avoid duplicates
|
|
2122
|
+
* @param processedMethodCalls - Set of already processed method call keys to avoid duplicates
|
|
2123
|
+
* @param callSites - Collection for direct function calls
|
|
2124
|
+
* @param methodCalls - Collection for method calls
|
|
2125
|
+
* @param module - Current module being analyzed
|
|
2126
|
+
* @param callSiteCounterRef - Counter for legacy ID generation
|
|
2127
|
+
* @param scopeTracker - Optional scope tracker for semantic ID generation
|
|
2128
|
+
* @param parentScopeId - ID of the parent scope containing this call
|
|
2129
|
+
* @param collections - Full collections object for array/object mutations
|
|
2130
|
+
*/
|
|
2131
|
+
private handleCallExpression(
|
|
2132
|
+
callNode: t.CallExpression,
|
|
2133
|
+
processedCallSites: Set<string>,
|
|
2134
|
+
processedMethodCalls: Set<string>,
|
|
2135
|
+
callSites: CallSiteInfo[],
|
|
2136
|
+
methodCalls: MethodCallInfo[],
|
|
2137
|
+
module: VisitorModule,
|
|
2138
|
+
callSiteCounterRef: CounterRef,
|
|
2139
|
+
scopeTracker: ScopeTracker | undefined,
|
|
2140
|
+
parentScopeId: string,
|
|
2141
|
+
collections: VisitorCollections
|
|
2142
|
+
): void {
|
|
2143
|
+
// Handle direct function calls (greet(), main())
|
|
2144
|
+
if (callNode.callee.type === 'Identifier') {
|
|
2145
|
+
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
2146
|
+
if (processedCallSites.has(nodeKey)) {
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
processedCallSites.add(nodeKey);
|
|
1664
2150
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
2151
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
2152
|
+
const calleeName = callNode.callee.name;
|
|
2153
|
+
const legacyId = `CALL#${calleeName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
|
|
2154
|
+
|
|
2155
|
+
let callId = legacyId;
|
|
2156
|
+
if (scopeTracker) {
|
|
2157
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:${calleeName}`);
|
|
2158
|
+
callId = computeSemanticId('CALL', calleeName, scopeTracker.getContext(), { discriminator });
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
callSites.push({
|
|
2162
|
+
id: callId,
|
|
2163
|
+
type: 'CALL',
|
|
2164
|
+
name: calleeName,
|
|
2165
|
+
file: module.file,
|
|
2166
|
+
line: getLine(callNode),
|
|
2167
|
+
parentScopeId,
|
|
2168
|
+
targetFunctionName: calleeName
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
// Handle method calls (obj.method(), data.process())
|
|
2172
|
+
else if (callNode.callee.type === 'MemberExpression') {
|
|
2173
|
+
const memberCallee = callNode.callee;
|
|
2174
|
+
const object = memberCallee.object;
|
|
2175
|
+
const property = memberCallee.property;
|
|
2176
|
+
const isComputed = memberCallee.computed;
|
|
2177
|
+
|
|
2178
|
+
if ((object.type === 'Identifier' || object.type === 'ThisExpression') && property.type === 'Identifier') {
|
|
2179
|
+
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
2180
|
+
if (processedMethodCalls.has(nodeKey)) {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
processedMethodCalls.add(nodeKey);
|
|
2184
|
+
|
|
2185
|
+
const objectName = object.type === 'Identifier' ? object.name : 'this';
|
|
2186
|
+
const methodName = isComputed ? '<computed>' : property.name;
|
|
2187
|
+
const fullName = `${objectName}.${methodName}`;
|
|
2188
|
+
|
|
2189
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
2190
|
+
const legacyId = `CALL#${fullName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
|
|
2191
|
+
|
|
2192
|
+
let methodCallId = legacyId;
|
|
2193
|
+
if (scopeTracker) {
|
|
2194
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:${fullName}`);
|
|
2195
|
+
methodCallId = computeSemanticId('CALL', fullName, scopeTracker.getContext(), { discriminator });
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
methodCalls.push({
|
|
2199
|
+
id: methodCallId,
|
|
2200
|
+
type: 'CALL',
|
|
2201
|
+
name: fullName,
|
|
2202
|
+
object: objectName,
|
|
2203
|
+
method: methodName,
|
|
2204
|
+
computed: isComputed,
|
|
2205
|
+
computedPropertyVar: isComputed ? property.name : null,
|
|
2206
|
+
file: module.file,
|
|
2207
|
+
line: getLine(callNode),
|
|
2208
|
+
column: getColumn(callNode),
|
|
2209
|
+
parentScopeId
|
|
2210
|
+
});
|
|
2211
|
+
|
|
2212
|
+
// Check for array mutation methods (push, unshift, splice)
|
|
2213
|
+
const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
|
|
2214
|
+
if (ARRAY_MUTATION_METHODS.includes(methodName)) {
|
|
2215
|
+
// Initialize collection if not exists
|
|
2216
|
+
if (!collections.arrayMutations) {
|
|
2217
|
+
collections.arrayMutations = [];
|
|
1675
2218
|
}
|
|
2219
|
+
const arrayMutations = collections.arrayMutations as ArrayMutationInfo[];
|
|
2220
|
+
this.detectArrayMutationInFunction(
|
|
2221
|
+
callNode,
|
|
2222
|
+
objectName,
|
|
2223
|
+
methodName as 'push' | 'unshift' | 'splice',
|
|
2224
|
+
module,
|
|
2225
|
+
arrayMutations,
|
|
2226
|
+
scopeTracker
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Check for Object.assign() calls
|
|
2231
|
+
if (objectName === 'Object' && methodName === 'assign') {
|
|
2232
|
+
// Initialize collection if not exists
|
|
2233
|
+
if (!collections.objectMutations) {
|
|
2234
|
+
collections.objectMutations = [];
|
|
2235
|
+
}
|
|
2236
|
+
const objectMutations = collections.objectMutations as ObjectMutationInfo[];
|
|
2237
|
+
this.detectObjectAssignInFunction(
|
|
2238
|
+
callNode,
|
|
2239
|
+
module,
|
|
2240
|
+
objectMutations,
|
|
2241
|
+
scopeTracker
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
// REG-117: Nested array mutations like obj.arr.push(item)
|
|
2246
|
+
// object is MemberExpression, property is the method name
|
|
2247
|
+
else if (object.type === 'MemberExpression' && property.type === 'Identifier') {
|
|
2248
|
+
const nestedMember = object;
|
|
2249
|
+
const methodName = property.name;
|
|
2250
|
+
const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
|
|
2251
|
+
|
|
2252
|
+
if (ARRAY_MUTATION_METHODS.includes(methodName)) {
|
|
2253
|
+
// Extract base object and property from nested MemberExpression
|
|
2254
|
+
const base = nestedMember.object;
|
|
2255
|
+
const prop = nestedMember.property;
|
|
2256
|
+
|
|
2257
|
+
// Only handle single-level nesting: obj.arr.push() or this.items.push()
|
|
2258
|
+
if ((base.type === 'Identifier' || base.type === 'ThisExpression') &&
|
|
2259
|
+
!nestedMember.computed &&
|
|
2260
|
+
prop.type === 'Identifier') {
|
|
2261
|
+
const baseObjectName = base.type === 'Identifier' ? base.name : 'this';
|
|
2262
|
+
const propertyName = prop.name;
|
|
2263
|
+
|
|
2264
|
+
// Initialize collection if not exists
|
|
2265
|
+
if (!collections.arrayMutations) {
|
|
2266
|
+
collections.arrayMutations = [];
|
|
2267
|
+
}
|
|
2268
|
+
const arrayMutations = collections.arrayMutations as ArrayMutationInfo[];
|
|
2269
|
+
|
|
2270
|
+
this.detectArrayMutationInFunction(
|
|
2271
|
+
callNode,
|
|
2272
|
+
`${baseObjectName}.${propertyName}`, // arrayName for ID purposes
|
|
2273
|
+
methodName as 'push' | 'unshift' | 'splice',
|
|
2274
|
+
module,
|
|
2275
|
+
arrayMutations,
|
|
2276
|
+
scopeTracker,
|
|
2277
|
+
true, // isNested
|
|
2278
|
+
baseObjectName,
|
|
2279
|
+
propertyName
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
/**
|
|
2288
|
+
* Detect array mutation calls (push, unshift, splice) inside functions
|
|
2289
|
+
* and collect mutation info for FLOWS_INTO edge creation in GraphBuilder
|
|
2290
|
+
*
|
|
2291
|
+
* REG-117: Added isNested, baseObjectName, propertyName for nested mutations
|
|
2292
|
+
*
|
|
2293
|
+
* @param callNode - The call expression node
|
|
2294
|
+
* @param arrayName - Name of the array being mutated
|
|
2295
|
+
* @param method - The mutation method (push, unshift, splice)
|
|
2296
|
+
* @param module - Current module being analyzed
|
|
2297
|
+
* @param arrayMutations - Collection to push mutation info into
|
|
2298
|
+
* @param scopeTracker - Optional scope tracker for semantic IDs
|
|
2299
|
+
* @param isNested - REG-117: true if this is a nested mutation (obj.arr.push)
|
|
2300
|
+
* @param baseObjectName - REG-117: base object name for nested mutations
|
|
2301
|
+
* @param propertyName - REG-117: property name for nested mutations
|
|
2302
|
+
*/
|
|
2303
|
+
private detectArrayMutationInFunction(
|
|
2304
|
+
callNode: t.CallExpression,
|
|
2305
|
+
arrayName: string,
|
|
2306
|
+
method: 'push' | 'unshift' | 'splice',
|
|
2307
|
+
module: VisitorModule,
|
|
2308
|
+
arrayMutations: ArrayMutationInfo[],
|
|
2309
|
+
scopeTracker?: ScopeTracker,
|
|
2310
|
+
isNested?: boolean,
|
|
2311
|
+
baseObjectName?: string,
|
|
2312
|
+
propertyName?: string
|
|
2313
|
+
): void {
|
|
2314
|
+
const mutationArgs: ArrayMutationArgument[] = [];
|
|
2315
|
+
|
|
2316
|
+
// For splice, only arguments from index 2 onwards are insertions
|
|
2317
|
+
// splice(start, deleteCount, item1, item2, ...)
|
|
2318
|
+
callNode.arguments.forEach((arg, index) => {
|
|
2319
|
+
// Skip start and deleteCount for splice
|
|
2320
|
+
if (method === 'splice' && index < 2) return;
|
|
2321
|
+
|
|
2322
|
+
const argInfo: ArrayMutationArgument = {
|
|
2323
|
+
argIndex: method === 'splice' ? index - 2 : index,
|
|
2324
|
+
isSpread: arg.type === 'SpreadElement',
|
|
2325
|
+
valueType: 'EXPRESSION' // Default
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
let actualArg: t.Node = arg;
|
|
2329
|
+
if (arg.type === 'SpreadElement') {
|
|
2330
|
+
actualArg = arg.argument;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// Determine value type
|
|
2334
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(actualArg);
|
|
2335
|
+
if (literalValue !== null) {
|
|
2336
|
+
argInfo.valueType = 'LITERAL';
|
|
2337
|
+
argInfo.literalValue = literalValue;
|
|
2338
|
+
} else if (actualArg.type === 'Identifier') {
|
|
2339
|
+
argInfo.valueType = 'VARIABLE';
|
|
2340
|
+
argInfo.valueName = actualArg.name;
|
|
2341
|
+
} else if (actualArg.type === 'ObjectExpression') {
|
|
2342
|
+
argInfo.valueType = 'OBJECT_LITERAL';
|
|
2343
|
+
} else if (actualArg.type === 'ArrayExpression') {
|
|
2344
|
+
argInfo.valueType = 'ARRAY_LITERAL';
|
|
2345
|
+
} else if (actualArg.type === 'CallExpression') {
|
|
2346
|
+
argInfo.valueType = 'CALL';
|
|
2347
|
+
argInfo.callLine = actualArg.loc?.start.line;
|
|
2348
|
+
argInfo.callColumn = actualArg.loc?.start.column;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
mutationArgs.push(argInfo);
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
// Only record if there are actual insertions
|
|
2355
|
+
if (mutationArgs.length > 0) {
|
|
2356
|
+
const line = callNode.loc?.start.line ?? 0;
|
|
2357
|
+
const column = callNode.loc?.start.column ?? 0;
|
|
2358
|
+
|
|
2359
|
+
// Generate semantic ID for array mutation if scopeTracker available
|
|
2360
|
+
let mutationId: string | undefined;
|
|
2361
|
+
if (scopeTracker) {
|
|
2362
|
+
const discriminator = scopeTracker.getItemCounter(`ARRAY_MUTATION:${arrayName}.${method}`);
|
|
2363
|
+
mutationId = computeSemanticId('ARRAY_MUTATION', `${arrayName}.${method}`, scopeTracker.getContext(), { discriminator });
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
arrayMutations.push({
|
|
2367
|
+
id: mutationId,
|
|
2368
|
+
arrayName,
|
|
2369
|
+
mutationMethod: method,
|
|
2370
|
+
file: module.file,
|
|
2371
|
+
line,
|
|
2372
|
+
column,
|
|
2373
|
+
insertedValues: mutationArgs,
|
|
2374
|
+
// REG-117: Nested mutation fields
|
|
2375
|
+
isNested,
|
|
2376
|
+
baseObjectName,
|
|
2377
|
+
propertyName
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
/**
|
|
2383
|
+
* Detect indexed array assignment: arr[i] = value
|
|
2384
|
+
* Creates ArrayMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
2385
|
+
*
|
|
2386
|
+
* @param assignNode - The assignment expression node
|
|
2387
|
+
* @param module - Current module being analyzed
|
|
2388
|
+
* @param arrayMutations - Collection to push mutation info into
|
|
2389
|
+
*/
|
|
2390
|
+
private detectIndexedArrayAssignment(
|
|
2391
|
+
assignNode: t.AssignmentExpression,
|
|
2392
|
+
module: VisitorModule,
|
|
2393
|
+
arrayMutations: ArrayMutationInfo[]
|
|
2394
|
+
): void {
|
|
2395
|
+
// Check for indexed array assignment: arr[i] = value
|
|
2396
|
+
if (assignNode.left.type === 'MemberExpression' && assignNode.left.computed) {
|
|
2397
|
+
const memberExpr = assignNode.left;
|
|
2398
|
+
|
|
2399
|
+
// Only process NumericLiteral keys - those are clearly array indexed assignments
|
|
2400
|
+
// e.g., arr[0] = value, arr[1] = value
|
|
2401
|
+
// All other computed keys (StringLiteral, Identifier, expressions) are handled as object mutations
|
|
2402
|
+
// This avoids duplicate edge creation for ambiguous cases like obj[key] = value
|
|
2403
|
+
if (memberExpr.property.type !== 'NumericLiteral') {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// Get array name (only simple identifiers for now)
|
|
2408
|
+
if (memberExpr.object.type === 'Identifier') {
|
|
2409
|
+
const arrayName = memberExpr.object.name;
|
|
2410
|
+
const value = assignNode.right;
|
|
2411
|
+
|
|
2412
|
+
const argInfo: ArrayMutationArgument = {
|
|
2413
|
+
argIndex: 0,
|
|
2414
|
+
isSpread: false,
|
|
2415
|
+
valueType: 'EXPRESSION'
|
|
2416
|
+
};
|
|
2417
|
+
|
|
2418
|
+
// Determine value type
|
|
2419
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(value);
|
|
2420
|
+
if (literalValue !== null) {
|
|
2421
|
+
argInfo.valueType = 'LITERAL';
|
|
2422
|
+
argInfo.literalValue = literalValue;
|
|
2423
|
+
} else if (value.type === 'Identifier') {
|
|
2424
|
+
argInfo.valueType = 'VARIABLE';
|
|
2425
|
+
argInfo.valueName = value.name;
|
|
2426
|
+
} else if (value.type === 'ObjectExpression') {
|
|
2427
|
+
argInfo.valueType = 'OBJECT_LITERAL';
|
|
2428
|
+
} else if (value.type === 'ArrayExpression') {
|
|
2429
|
+
argInfo.valueType = 'ARRAY_LITERAL';
|
|
2430
|
+
} else if (value.type === 'CallExpression') {
|
|
2431
|
+
argInfo.valueType = 'CALL';
|
|
2432
|
+
argInfo.callLine = value.loc?.start.line;
|
|
2433
|
+
argInfo.callColumn = value.loc?.start.column;
|
|
1676
2434
|
}
|
|
2435
|
+
|
|
2436
|
+
// Use defensive loc checks instead of ! assertions
|
|
2437
|
+
const line = assignNode.loc?.start.line ?? 0;
|
|
2438
|
+
const column = assignNode.loc?.start.column ?? 0;
|
|
2439
|
+
|
|
2440
|
+
arrayMutations.push({
|
|
2441
|
+
arrayName,
|
|
2442
|
+
mutationMethod: 'indexed',
|
|
2443
|
+
file: module.file,
|
|
2444
|
+
line: line,
|
|
2445
|
+
column: column,
|
|
2446
|
+
insertedValues: [argInfo]
|
|
2447
|
+
});
|
|
1677
2448
|
}
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* Detect object property assignment: obj.prop = value, obj['prop'] = value
|
|
2454
|
+
* Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
2455
|
+
*
|
|
2456
|
+
* @param assignNode - The assignment expression node
|
|
2457
|
+
* @param module - Current module being analyzed
|
|
2458
|
+
* @param objectMutations - Collection to push mutation info into
|
|
2459
|
+
* @param scopeTracker - Optional scope tracker for semantic IDs
|
|
2460
|
+
*/
|
|
2461
|
+
private detectObjectPropertyAssignment(
|
|
2462
|
+
assignNode: t.AssignmentExpression,
|
|
2463
|
+
module: VisitorModule,
|
|
2464
|
+
objectMutations: ObjectMutationInfo[],
|
|
2465
|
+
scopeTracker?: ScopeTracker
|
|
2466
|
+
): void {
|
|
2467
|
+
// Check for property assignment: obj.prop = value or obj['prop'] = value
|
|
2468
|
+
if (assignNode.left.type !== 'MemberExpression') return;
|
|
2469
|
+
|
|
2470
|
+
const memberExpr = assignNode.left;
|
|
2471
|
+
|
|
2472
|
+
// Skip NumericLiteral indexed assignment (handled by array mutation handler)
|
|
2473
|
+
// Array mutation handler processes: arr[0] (numeric literal index)
|
|
2474
|
+
// Object mutation handler processes: obj.prop, obj['prop'], obj[key], obj[expr]
|
|
2475
|
+
if (memberExpr.computed && memberExpr.property.type === 'NumericLiteral') {
|
|
2476
|
+
return; // Let array mutation handler deal with this
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// Get object name and enclosing class context for 'this'
|
|
2480
|
+
let objectName: string;
|
|
2481
|
+
let enclosingClassName: string | undefined;
|
|
2482
|
+
|
|
2483
|
+
if (memberExpr.object.type === 'Identifier') {
|
|
2484
|
+
objectName = memberExpr.object.name;
|
|
2485
|
+
} else if (memberExpr.object.type === 'ThisExpression') {
|
|
2486
|
+
objectName = 'this';
|
|
2487
|
+
// REG-152: Extract enclosing class name from scope context
|
|
2488
|
+
if (scopeTracker) {
|
|
2489
|
+
enclosingClassName = scopeTracker.getEnclosingScope('CLASS');
|
|
2490
|
+
}
|
|
2491
|
+
} else {
|
|
2492
|
+
// Complex expressions like obj.nested.prop = value
|
|
2493
|
+
// For now, skip these (documented limitation)
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// Get property name
|
|
2498
|
+
let propertyName: string;
|
|
2499
|
+
let mutationType: 'property' | 'computed';
|
|
2500
|
+
let computedPropertyVar: string | undefined;
|
|
2501
|
+
|
|
2502
|
+
if (!memberExpr.computed) {
|
|
2503
|
+
// obj.prop
|
|
2504
|
+
if (memberExpr.property.type === 'Identifier') {
|
|
2505
|
+
propertyName = memberExpr.property.name;
|
|
2506
|
+
mutationType = 'property';
|
|
2507
|
+
} else {
|
|
2508
|
+
return; // Unexpected property type
|
|
2509
|
+
}
|
|
2510
|
+
} else {
|
|
2511
|
+
// obj['prop'] or obj[key]
|
|
2512
|
+
if (memberExpr.property.type === 'StringLiteral') {
|
|
2513
|
+
propertyName = memberExpr.property.value;
|
|
2514
|
+
mutationType = 'property'; // String literal is effectively a property name
|
|
2515
|
+
} else {
|
|
2516
|
+
propertyName = '<computed>';
|
|
2517
|
+
mutationType = 'computed';
|
|
2518
|
+
// Capture variable name for later resolution in enrichment phase
|
|
2519
|
+
if (memberExpr.property.type === 'Identifier') {
|
|
2520
|
+
computedPropertyVar = memberExpr.property.name;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// Extract value info
|
|
2526
|
+
const value = assignNode.right;
|
|
2527
|
+
const valueInfo = this.extractMutationValue(value);
|
|
2528
|
+
|
|
2529
|
+
// Use defensive loc checks
|
|
2530
|
+
const line = assignNode.loc?.start.line ?? 0;
|
|
2531
|
+
const column = assignNode.loc?.start.column ?? 0;
|
|
2532
|
+
|
|
2533
|
+
// Generate semantic ID if scopeTracker available
|
|
2534
|
+
let mutationId: string | undefined;
|
|
2535
|
+
if (scopeTracker) {
|
|
2536
|
+
const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:${objectName}.${propertyName}`);
|
|
2537
|
+
mutationId = computeSemanticId('OBJECT_MUTATION', `${objectName}.${propertyName}`, scopeTracker.getContext(), { discriminator });
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
objectMutations.push({
|
|
2541
|
+
id: mutationId,
|
|
2542
|
+
objectName,
|
|
2543
|
+
enclosingClassName, // REG-152: Class name for 'this' mutations
|
|
2544
|
+
propertyName,
|
|
2545
|
+
mutationType,
|
|
2546
|
+
computedPropertyVar,
|
|
2547
|
+
file: module.file,
|
|
2548
|
+
line,
|
|
2549
|
+
column,
|
|
2550
|
+
value: valueInfo
|
|
1678
2551
|
});
|
|
1679
2552
|
}
|
|
2553
|
+
|
|
2554
|
+
/**
|
|
2555
|
+
* Extract value information from an expression for mutation tracking
|
|
2556
|
+
*/
|
|
2557
|
+
private extractMutationValue(value: t.Expression): ObjectMutationValue {
|
|
2558
|
+
const valueInfo: ObjectMutationValue = {
|
|
2559
|
+
valueType: 'EXPRESSION' // Default
|
|
2560
|
+
};
|
|
2561
|
+
|
|
2562
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(value);
|
|
2563
|
+
if (literalValue !== null) {
|
|
2564
|
+
valueInfo.valueType = 'LITERAL';
|
|
2565
|
+
valueInfo.literalValue = literalValue;
|
|
2566
|
+
} else if (value.type === 'Identifier') {
|
|
2567
|
+
valueInfo.valueType = 'VARIABLE';
|
|
2568
|
+
valueInfo.valueName = value.name;
|
|
2569
|
+
} else if (value.type === 'ObjectExpression') {
|
|
2570
|
+
valueInfo.valueType = 'OBJECT_LITERAL';
|
|
2571
|
+
} else if (value.type === 'ArrayExpression') {
|
|
2572
|
+
valueInfo.valueType = 'ARRAY_LITERAL';
|
|
2573
|
+
} else if (value.type === 'CallExpression') {
|
|
2574
|
+
valueInfo.valueType = 'CALL';
|
|
2575
|
+
valueInfo.callLine = value.loc?.start.line;
|
|
2576
|
+
valueInfo.callColumn = value.loc?.start.column;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
return valueInfo;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
/**
|
|
2583
|
+
* Detect Object.assign() calls inside functions
|
|
2584
|
+
* Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
2585
|
+
*/
|
|
2586
|
+
private detectObjectAssignInFunction(
|
|
2587
|
+
callNode: t.CallExpression,
|
|
2588
|
+
module: VisitorModule,
|
|
2589
|
+
objectMutations: ObjectMutationInfo[],
|
|
2590
|
+
scopeTracker?: ScopeTracker
|
|
2591
|
+
): void {
|
|
2592
|
+
// Need at least 2 arguments: target and at least one source
|
|
2593
|
+
if (callNode.arguments.length < 2) return;
|
|
2594
|
+
|
|
2595
|
+
// First argument is target
|
|
2596
|
+
const targetArg = callNode.arguments[0];
|
|
2597
|
+
let targetName: string;
|
|
2598
|
+
|
|
2599
|
+
if (targetArg.type === 'Identifier') {
|
|
2600
|
+
targetName = targetArg.name;
|
|
2601
|
+
} else if (targetArg.type === 'ObjectExpression') {
|
|
2602
|
+
targetName = '<anonymous>';
|
|
2603
|
+
} else {
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
const line = callNode.loc?.start.line ?? 0;
|
|
2608
|
+
const column = callNode.loc?.start.column ?? 0;
|
|
2609
|
+
|
|
2610
|
+
for (let i = 1; i < callNode.arguments.length; i++) {
|
|
2611
|
+
let arg = callNode.arguments[i];
|
|
2612
|
+
let isSpread = false;
|
|
2613
|
+
|
|
2614
|
+
if (arg.type === 'SpreadElement') {
|
|
2615
|
+
isSpread = true;
|
|
2616
|
+
arg = arg.argument;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
const valueInfo: ObjectMutationValue = {
|
|
2620
|
+
valueType: 'EXPRESSION',
|
|
2621
|
+
argIndex: i - 1,
|
|
2622
|
+
isSpread
|
|
2623
|
+
};
|
|
2624
|
+
|
|
2625
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(arg);
|
|
2626
|
+
if (literalValue !== null) {
|
|
2627
|
+
valueInfo.valueType = 'LITERAL';
|
|
2628
|
+
valueInfo.literalValue = literalValue;
|
|
2629
|
+
} else if (arg.type === 'Identifier') {
|
|
2630
|
+
valueInfo.valueType = 'VARIABLE';
|
|
2631
|
+
valueInfo.valueName = arg.name;
|
|
2632
|
+
} else if (arg.type === 'ObjectExpression') {
|
|
2633
|
+
valueInfo.valueType = 'OBJECT_LITERAL';
|
|
2634
|
+
} else if (arg.type === 'ArrayExpression') {
|
|
2635
|
+
valueInfo.valueType = 'ARRAY_LITERAL';
|
|
2636
|
+
} else if (arg.type === 'CallExpression') {
|
|
2637
|
+
valueInfo.valueType = 'CALL';
|
|
2638
|
+
valueInfo.callLine = arg.loc?.start.line;
|
|
2639
|
+
valueInfo.callColumn = arg.loc?.start.column;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
let mutationId: string | undefined;
|
|
2643
|
+
if (scopeTracker) {
|
|
2644
|
+
const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:Object.assign:${targetName}`);
|
|
2645
|
+
mutationId = computeSemanticId('OBJECT_MUTATION', `Object.assign:${targetName}`, scopeTracker.getContext(), { discriminator });
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
objectMutations.push({
|
|
2649
|
+
id: mutationId,
|
|
2650
|
+
objectName: targetName,
|
|
2651
|
+
propertyName: '<assign>',
|
|
2652
|
+
mutationType: 'assign',
|
|
2653
|
+
file: module.file,
|
|
2654
|
+
line,
|
|
2655
|
+
column,
|
|
2656
|
+
value: valueInfo
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
1680
2660
|
}
|