@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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { readFileSync } from 'fs';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
|
+
import { basename } from 'path';
|
|
7
8
|
import { parse } from '@babel/parser';
|
|
8
9
|
import traverseModule from '@babel/traverse';
|
|
9
10
|
import * as t from '@babel/types';
|
|
@@ -25,19 +26,22 @@ import { ImportExportVisitor, VariableVisitor, FunctionVisitor, ClassVisitor, Ca
|
|
|
25
26
|
import { Task } from '../../core/Task.js';
|
|
26
27
|
import { PriorityQueue } from '../../core/PriorityQueue.js';
|
|
27
28
|
import { WorkerPool } from '../../core/WorkerPool.js';
|
|
29
|
+
import { ASTWorkerPool } from '../../core/ASTWorkerPool.js';
|
|
28
30
|
import { ConditionParser } from './ast/ConditionParser.js';
|
|
31
|
+
import { getLine, getColumn } from './ast/utils/location.js';
|
|
29
32
|
import { Profiler } from '../../core/Profiler.js';
|
|
33
|
+
import { ScopeTracker } from '../../core/ScopeTracker.js';
|
|
34
|
+
import { computeSemanticId } from '../../core/SemanticId.js';
|
|
35
|
+
import { ExpressionNode } from '../../core/nodes/ExpressionNode.js';
|
|
30
36
|
export class JSASTAnalyzer extends Plugin {
|
|
31
37
|
graphBuilder;
|
|
32
38
|
analyzedModules;
|
|
33
39
|
profiler;
|
|
34
|
-
_cacheCleared;
|
|
35
40
|
constructor() {
|
|
36
41
|
super();
|
|
37
42
|
this.graphBuilder = new GraphBuilder();
|
|
38
43
|
this.analyzedModules = new Set();
|
|
39
44
|
this.profiler = new Profiler('JSASTAnalyzer');
|
|
40
|
-
this._cacheCleared = false;
|
|
41
45
|
}
|
|
42
46
|
get metadata() {
|
|
43
47
|
return {
|
|
@@ -110,12 +114,12 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
110
114
|
return true;
|
|
111
115
|
}
|
|
112
116
|
async execute(context) {
|
|
117
|
+
const logger = this.log(context);
|
|
113
118
|
try {
|
|
114
119
|
const { manifest, graph, forceAnalysis = false } = context;
|
|
115
120
|
const projectPath = manifest?.projectPath ?? '';
|
|
116
|
-
if (forceAnalysis
|
|
121
|
+
if (forceAnalysis) {
|
|
117
122
|
this.analyzedModules.clear();
|
|
118
|
-
this._cacheCleared = true;
|
|
119
123
|
}
|
|
120
124
|
const allModules = await this.getModuleNodes(graph);
|
|
121
125
|
const modulesToAnalyze = [];
|
|
@@ -132,11 +136,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
132
136
|
skippedCount++;
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
|
-
|
|
139
|
+
logger.info('Starting module analysis', { toAnalyze: modulesToAnalyze.length, cached: skippedCount });
|
|
136
140
|
if (modulesToAnalyze.length === 0) {
|
|
137
|
-
|
|
141
|
+
logger.info('All modules up-to-date, skipping analysis');
|
|
138
142
|
return createSuccessResult({ nodes: 0, edges: 0 });
|
|
139
143
|
}
|
|
144
|
+
// Use ASTWorkerPool for true parallel parsing with worker_threads if enabled
|
|
145
|
+
if (context.parallelParsing) {
|
|
146
|
+
return await this.executeParallel(modulesToAnalyze, graph, projectPath, context);
|
|
147
|
+
}
|
|
140
148
|
const queue = new PriorityQueue();
|
|
141
149
|
const pool = new WorkerPool(context.workerCount || 10);
|
|
142
150
|
pool.registerHandler('ANALYZE_MODULE', async (task) => {
|
|
@@ -171,7 +179,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
171
179
|
pool.on('worker:task:completed', () => {
|
|
172
180
|
completed++;
|
|
173
181
|
if (completed % 10 === 0 || completed === modulesToAnalyze.length) {
|
|
174
|
-
|
|
182
|
+
logger.debug('Analysis progress', { completed, total: modulesToAnalyze.length });
|
|
175
183
|
}
|
|
176
184
|
});
|
|
177
185
|
await pool.processQueue(queue);
|
|
@@ -193,17 +201,86 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
193
201
|
edgesCreated += task.result.edges;
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
logger.info('Analysis complete', { modulesAnalyzed: modulesToAnalyze.length, nodesCreated });
|
|
205
|
+
logger.debug('Worker stats', { ...stats });
|
|
198
206
|
this.profiler.printSummary();
|
|
199
207
|
return createSuccessResult({ nodes: nodesCreated, edges: edgesCreated }, { modulesAnalyzed: modulesToAnalyze.length, workerStats: stats });
|
|
200
208
|
}
|
|
201
209
|
catch (error) {
|
|
202
|
-
|
|
210
|
+
logger.error('Analysis failed', { error: error instanceof Error ? error.message : String(error) });
|
|
203
211
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
204
212
|
return createErrorResult(err);
|
|
205
213
|
}
|
|
206
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Execute parallel analysis using ASTWorkerPool (worker_threads).
|
|
217
|
+
*
|
|
218
|
+
* This method uses actual OS threads for true parallel CPU-intensive parsing.
|
|
219
|
+
* Workers generate semantic IDs using ScopeTracker, matching sequential behavior.
|
|
220
|
+
*
|
|
221
|
+
* @param modules - Modules to analyze
|
|
222
|
+
* @param graph - Graph backend for writing results
|
|
223
|
+
* @param projectPath - Project root path
|
|
224
|
+
* @param context - Analysis context with options
|
|
225
|
+
* @returns Plugin result with node/edge counts
|
|
226
|
+
*/
|
|
227
|
+
async executeParallel(modules, graph, projectPath, context) {
|
|
228
|
+
const logger = this.log(context);
|
|
229
|
+
const workerCount = context.workerCount || 4;
|
|
230
|
+
const pool = new ASTWorkerPool(workerCount);
|
|
231
|
+
logger.debug('Starting parallel parsing', { workerCount });
|
|
232
|
+
try {
|
|
233
|
+
await pool.init();
|
|
234
|
+
// Convert ModuleNode to ASTModuleInfo format
|
|
235
|
+
const moduleInfos = modules.map(m => ({
|
|
236
|
+
id: m.id,
|
|
237
|
+
file: m.file,
|
|
238
|
+
name: m.name
|
|
239
|
+
}));
|
|
240
|
+
// Parse all modules in parallel using worker threads
|
|
241
|
+
const results = await pool.parseModules(moduleInfos);
|
|
242
|
+
let nodesCreated = 0;
|
|
243
|
+
let edgesCreated = 0;
|
|
244
|
+
let errors = 0;
|
|
245
|
+
// Process results - collections already have semantic IDs from workers
|
|
246
|
+
for (const result of results) {
|
|
247
|
+
if (result.error) {
|
|
248
|
+
logger.warn('Parse error', { file: result.module.file, error: result.error.message });
|
|
249
|
+
errors++;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (result.collections) {
|
|
253
|
+
// Find original module for metadata
|
|
254
|
+
const module = modules.find(m => m.id === result.module.id);
|
|
255
|
+
if (!module)
|
|
256
|
+
continue;
|
|
257
|
+
// Pass collections directly to GraphBuilder - IDs already semantic
|
|
258
|
+
// Cast is safe because ASTWorker.ASTCollections is structurally compatible
|
|
259
|
+
// with ast/types.ASTCollections (METHOD extends FUNCTION semantically)
|
|
260
|
+
const buildResult = await this.graphBuilder.build(module, graph, projectPath, result.collections);
|
|
261
|
+
if (typeof buildResult === 'object' && buildResult !== null) {
|
|
262
|
+
nodesCreated += buildResult.nodes || 0;
|
|
263
|
+
edgesCreated += buildResult.edges || 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Report progress
|
|
267
|
+
if (context.onProgress) {
|
|
268
|
+
context.onProgress({
|
|
269
|
+
phase: 'analysis',
|
|
270
|
+
currentPlugin: 'JSASTAnalyzer',
|
|
271
|
+
message: `Processed ${result.module.file.replace(projectPath, '')}`,
|
|
272
|
+
totalFiles: modules.length,
|
|
273
|
+
processedFiles: results.indexOf(result) + 1
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
logger.info('Parallel parsing complete', { nodesCreated, edgesCreated, errors });
|
|
278
|
+
return createSuccessResult({ nodes: nodesCreated, edges: edgesCreated }, { modulesAnalyzed: modules.length - errors, parallelParsing: true });
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await pool.terminate();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
207
284
|
/**
|
|
208
285
|
* Extract variable names from destructuring patterns
|
|
209
286
|
* Uses t.isX() type guards to avoid casts
|
|
@@ -313,8 +390,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
313
390
|
sourceId: null,
|
|
314
391
|
sourceType: 'CALL_SITE',
|
|
315
392
|
callName: initExpression.callee.name,
|
|
316
|
-
callLine: initExpression
|
|
317
|
-
callColumn: initExpression
|
|
393
|
+
callLine: getLine(initExpression),
|
|
394
|
+
callColumn: getColumn(initExpression)
|
|
318
395
|
});
|
|
319
396
|
return;
|
|
320
397
|
}
|
|
@@ -324,7 +401,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
324
401
|
const objectName = callee.object.type === 'Identifier' ? callee.object.name : (callee.object.type === 'ThisExpression' ? 'this' : 'unknown');
|
|
325
402
|
const methodName = callee.property.type === 'Identifier' ? callee.property.name : 'unknown';
|
|
326
403
|
const fullName = `${objectName}.${methodName}`;
|
|
327
|
-
const methodCallId = `CALL#${fullName}#${module.file}#${initExpression
|
|
404
|
+
const methodCallId = `CALL#${fullName}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:inline`;
|
|
328
405
|
const existing = variableAssignments.find(a => a.sourceId === methodCallId);
|
|
329
406
|
if (!existing) {
|
|
330
407
|
const extractedArgs = [];
|
|
@@ -332,15 +409,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
332
409
|
if (arg.type !== 'SpreadElement') {
|
|
333
410
|
const argLiteralValue = ExpressionEvaluator.extractLiteralValue(arg);
|
|
334
411
|
if (argLiteralValue !== null) {
|
|
335
|
-
const literalId = `LITERAL#arg${index}#${module.file}#${initExpression
|
|
412
|
+
const literalId = `LITERAL#arg${index}#${module.file}#${getLine(initExpression)}:${getColumn(initExpression)}:${literalCounterRef.value++}`;
|
|
336
413
|
literals.push({
|
|
337
414
|
id: literalId,
|
|
338
415
|
type: 'LITERAL',
|
|
339
416
|
value: argLiteralValue,
|
|
340
417
|
valueType: typeof argLiteralValue,
|
|
341
418
|
file: module.file,
|
|
342
|
-
line: arg.loc?.start.line || initExpression
|
|
343
|
-
column: arg.loc?.start.column || initExpression
|
|
419
|
+
line: arg.loc?.start.line || getLine(initExpression),
|
|
420
|
+
column: arg.loc?.start.column || getColumn(initExpression),
|
|
344
421
|
parentCallId: methodCallId,
|
|
345
422
|
argIndex: index
|
|
346
423
|
});
|
|
@@ -359,8 +436,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
359
436
|
method: methodName,
|
|
360
437
|
file: module.file,
|
|
361
438
|
arguments: extractedArgs,
|
|
362
|
-
line: initExpression
|
|
363
|
-
column: initExpression
|
|
439
|
+
line: getLine(initExpression),
|
|
440
|
+
column: getColumn(initExpression)
|
|
364
441
|
});
|
|
365
442
|
}
|
|
366
443
|
variableAssignments.push({
|
|
@@ -414,7 +491,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
414
491
|
const computedPropertyVar = initExpression.computed && initExpression.property.type === 'Identifier'
|
|
415
492
|
? initExpression.property.name
|
|
416
493
|
: null;
|
|
417
|
-
const
|
|
494
|
+
const column = initExpression.start ?? 0;
|
|
495
|
+
const expressionId = ExpressionNode.generateId('MemberExpression', module.file, line, column);
|
|
418
496
|
variableAssignments.push({
|
|
419
497
|
variableId,
|
|
420
498
|
sourceType: 'EXPRESSION',
|
|
@@ -426,13 +504,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
426
504
|
computedPropertyVar,
|
|
427
505
|
objectSourceName: initExpression.object.type === 'Identifier' ? initExpression.object.name : null,
|
|
428
506
|
file: module.file,
|
|
429
|
-
line: line
|
|
507
|
+
line: line,
|
|
508
|
+
column: column
|
|
430
509
|
});
|
|
431
510
|
return;
|
|
432
511
|
}
|
|
433
512
|
// 8. BinaryExpression
|
|
434
513
|
if (initExpression.type === 'BinaryExpression') {
|
|
435
|
-
const
|
|
514
|
+
const column = initExpression.start ?? 0;
|
|
515
|
+
const expressionId = ExpressionNode.generateId('BinaryExpression', module.file, line, column);
|
|
436
516
|
variableAssignments.push({
|
|
437
517
|
variableId,
|
|
438
518
|
sourceType: 'EXPRESSION',
|
|
@@ -442,13 +522,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
442
522
|
leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
|
|
443
523
|
rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
|
|
444
524
|
file: module.file,
|
|
445
|
-
line: line
|
|
525
|
+
line: line,
|
|
526
|
+
column: column
|
|
446
527
|
});
|
|
447
528
|
return;
|
|
448
529
|
}
|
|
449
530
|
// 9. ConditionalExpression
|
|
450
531
|
if (initExpression.type === 'ConditionalExpression') {
|
|
451
|
-
const
|
|
532
|
+
const column = initExpression.start ?? 0;
|
|
533
|
+
const expressionId = ExpressionNode.generateId('ConditionalExpression', module.file, line, column);
|
|
452
534
|
variableAssignments.push({
|
|
453
535
|
variableId,
|
|
454
536
|
sourceType: 'EXPRESSION',
|
|
@@ -457,7 +539,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
457
539
|
consequentSourceName: initExpression.consequent.type === 'Identifier' ? initExpression.consequent.name : null,
|
|
458
540
|
alternateSourceName: initExpression.alternate.type === 'Identifier' ? initExpression.alternate.name : null,
|
|
459
541
|
file: module.file,
|
|
460
|
-
line: line
|
|
542
|
+
line: line,
|
|
543
|
+
column: column
|
|
461
544
|
});
|
|
462
545
|
this.trackVariableAssignment(initExpression.consequent, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
463
546
|
this.trackVariableAssignment(initExpression.alternate, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
@@ -465,7 +548,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
465
548
|
}
|
|
466
549
|
// 10. LogicalExpression
|
|
467
550
|
if (initExpression.type === 'LogicalExpression') {
|
|
468
|
-
const
|
|
551
|
+
const column = initExpression.start ?? 0;
|
|
552
|
+
const expressionId = ExpressionNode.generateId('LogicalExpression', module.file, line, column);
|
|
469
553
|
variableAssignments.push({
|
|
470
554
|
variableId,
|
|
471
555
|
sourceType: 'EXPRESSION',
|
|
@@ -475,7 +559,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
475
559
|
leftSourceName: initExpression.left.type === 'Identifier' ? initExpression.left.name : null,
|
|
476
560
|
rightSourceName: initExpression.right.type === 'Identifier' ? initExpression.right.name : null,
|
|
477
561
|
file: module.file,
|
|
478
|
-
line: line
|
|
562
|
+
line: line,
|
|
563
|
+
column: column
|
|
479
564
|
});
|
|
480
565
|
this.trackVariableAssignment(initExpression.left, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
481
566
|
this.trackVariableAssignment(initExpression.right, variableId, variableName, module, line, literals, variableAssignments, literalCounterRef);
|
|
@@ -483,7 +568,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
483
568
|
}
|
|
484
569
|
// 11. TemplateLiteral
|
|
485
570
|
if (initExpression.type === 'TemplateLiteral' && initExpression.expressions.length > 0) {
|
|
486
|
-
const
|
|
571
|
+
const column = initExpression.start ?? 0;
|
|
572
|
+
const expressionId = ExpressionNode.generateId('TemplateLiteral', module.file, line, column);
|
|
487
573
|
const expressionSourceNames = initExpression.expressions
|
|
488
574
|
.filter((expr) => expr.type === 'Identifier')
|
|
489
575
|
.map(expr => expr.name);
|
|
@@ -494,7 +580,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
494
580
|
expressionType: 'TemplateLiteral',
|
|
495
581
|
expressionSourceNames,
|
|
496
582
|
file: module.file,
|
|
497
|
-
line: line
|
|
583
|
+
line: line,
|
|
584
|
+
column: column
|
|
498
585
|
});
|
|
499
586
|
for (const expr of initExpression.expressions) {
|
|
500
587
|
// Filter out TSType nodes (only in TypeScript code)
|
|
@@ -531,6 +618,9 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
531
618
|
plugins: ['jsx', 'typescript']
|
|
532
619
|
});
|
|
533
620
|
this.profiler.end('babel_parse');
|
|
621
|
+
// Create ScopeTracker for semantic ID generation
|
|
622
|
+
// Use basename for shorter, more readable semantic IDs
|
|
623
|
+
const scopeTracker = new ScopeTracker(basename(module.file));
|
|
534
624
|
const functions = [];
|
|
535
625
|
const parameters = [];
|
|
536
626
|
const scopes = [];
|
|
@@ -552,6 +642,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
552
642
|
const typeAliases = [];
|
|
553
643
|
const enums = [];
|
|
554
644
|
const decorators = [];
|
|
645
|
+
// Object/Array literal tracking for data flow
|
|
646
|
+
const objectLiterals = [];
|
|
647
|
+
const objectProperties = [];
|
|
648
|
+
const arrayLiterals = [];
|
|
649
|
+
const arrayElements = [];
|
|
650
|
+
// Array mutation tracking for FLOWS_INTO edges
|
|
651
|
+
const arrayMutations = [];
|
|
652
|
+
// Object mutation tracking for FLOWS_INTO edges
|
|
653
|
+
const objectMutations = [];
|
|
555
654
|
const ifScopeCounterRef = { value: 0 };
|
|
556
655
|
const scopeCounterRef = { value: 0 };
|
|
557
656
|
const varDeclCounterRef = { value: 0 };
|
|
@@ -560,6 +659,8 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
560
659
|
const httpRequestCounterRef = { value: 0 };
|
|
561
660
|
const literalCounterRef = { value: 0 };
|
|
562
661
|
const anonymousFunctionCounterRef = { value: 0 };
|
|
662
|
+
const objectLiteralCounterRef = { value: 0 };
|
|
663
|
+
const arrayLiteralCounterRef = { value: 0 };
|
|
563
664
|
const processedNodes = {
|
|
564
665
|
functions: new Set(),
|
|
565
666
|
classes: new Set(),
|
|
@@ -579,34 +680,40 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
579
680
|
this.profiler.end('traverse_imports');
|
|
580
681
|
// Variables
|
|
581
682
|
this.profiler.start('traverse_variables');
|
|
582
|
-
const variableVisitor = new VariableVisitor(module, { variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef }, this.extractVariableNamesFromPattern.bind(this), this.trackVariableAssignment.bind(this)
|
|
683
|
+
const variableVisitor = new VariableVisitor(module, { variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef }, this.extractVariableNamesFromPattern.bind(this), this.trackVariableAssignment.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
684
|
+
);
|
|
583
685
|
traverse(ast, variableVisitor.getHandlers());
|
|
584
686
|
this.profiler.end('traverse_variables');
|
|
585
|
-
// Module-level scope context for consistent anonymous naming
|
|
586
|
-
const moduleScopeCtx = {
|
|
587
|
-
semanticPath: module.name,
|
|
588
|
-
siblingCounters: new Map()
|
|
589
|
-
};
|
|
590
687
|
const allCollections = {
|
|
591
688
|
functions, parameters, scopes, variableDeclarations, callSites, methodCalls,
|
|
592
689
|
eventListeners, methodCallbacks, callArguments, classInstantiations, classDeclarations,
|
|
593
690
|
httpRequests, literals, variableAssignments,
|
|
594
691
|
// TypeScript-specific collections
|
|
595
692
|
interfaces, typeAliases, enums, decorators,
|
|
693
|
+
// Object/Array literal tracking
|
|
694
|
+
objectLiterals, objectProperties, arrayLiterals, arrayElements,
|
|
695
|
+
// Array mutation tracking
|
|
696
|
+
arrayMutations,
|
|
697
|
+
// Object mutation tracking
|
|
698
|
+
objectMutations,
|
|
699
|
+
objectLiteralCounterRef, arrayLiteralCounterRef,
|
|
596
700
|
ifScopeCounterRef, scopeCounterRef, varDeclCounterRef,
|
|
597
701
|
callSiteCounterRef, functionCounterRef, httpRequestCounterRef,
|
|
598
702
|
literalCounterRef, anonymousFunctionCounterRef, processedNodes,
|
|
599
|
-
imports, exports,
|
|
703
|
+
imports, exports, code,
|
|
600
704
|
// VisitorCollections compatibility
|
|
601
705
|
classes: classDeclarations,
|
|
602
706
|
methods: [],
|
|
603
707
|
variables: variableDeclarations,
|
|
604
708
|
sideEffects: [],
|
|
605
|
-
variableCounterRef: varDeclCounterRef
|
|
709
|
+
variableCounterRef: varDeclCounterRef,
|
|
710
|
+
// ScopeTracker for semantic ID generation
|
|
711
|
+
scopeTracker
|
|
606
712
|
};
|
|
607
713
|
// Functions
|
|
608
714
|
this.profiler.start('traverse_functions');
|
|
609
|
-
const functionVisitor = new FunctionVisitor(module, allCollections, this.analyzeFunctionBody.bind(this)
|
|
715
|
+
const functionVisitor = new FunctionVisitor(module, allCollections, this.analyzeFunctionBody.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
716
|
+
);
|
|
610
717
|
traverse(ast, functionVisitor.getHandlers());
|
|
611
718
|
this.profiler.end('traverse_functions');
|
|
612
719
|
// AssignmentExpression (module-level function assignments)
|
|
@@ -631,20 +738,20 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
631
738
|
functionName = assignNode.left.name;
|
|
632
739
|
}
|
|
633
740
|
const funcNode = assignNode.right;
|
|
634
|
-
|
|
741
|
+
// Use semantic ID as primary ID (matching FunctionVisitor pattern)
|
|
742
|
+
const functionId = computeSemanticId('FUNCTION', functionName, scopeTracker.getContext());
|
|
635
743
|
functions.push({
|
|
636
744
|
id: functionId,
|
|
637
|
-
stableId: functionId,
|
|
638
745
|
type: 'FUNCTION',
|
|
639
746
|
name: functionName,
|
|
640
747
|
file: module.file,
|
|
641
|
-
line: assignNode
|
|
642
|
-
column: assignNode
|
|
748
|
+
line: getLine(assignNode),
|
|
749
|
+
column: getColumn(assignNode),
|
|
643
750
|
async: funcNode.async || false,
|
|
644
751
|
generator: funcNode.type === 'FunctionExpression' ? funcNode.generator : false,
|
|
645
752
|
isAssignment: true
|
|
646
753
|
});
|
|
647
|
-
const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${assignNode
|
|
754
|
+
const funcBodyScopeId = `SCOPE#${functionName}:body#${module.file}#${getLine(assignNode)}`;
|
|
648
755
|
scopes.push({
|
|
649
756
|
id: funcBodyScopeId,
|
|
650
757
|
type: 'SCOPE',
|
|
@@ -653,28 +760,31 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
653
760
|
semanticId: `${functionName}:function_body[0]`,
|
|
654
761
|
conditional: false,
|
|
655
762
|
file: module.file,
|
|
656
|
-
line: assignNode
|
|
763
|
+
line: getLine(assignNode),
|
|
657
764
|
parentFunctionId: functionId
|
|
658
765
|
});
|
|
659
766
|
const funcPath = assignPath.get('right');
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
};
|
|
665
|
-
this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections, funcScopeCtx);
|
|
767
|
+
// Enter function scope for semantic ID generation and analyze
|
|
768
|
+
scopeTracker.enterScope(functionName, 'function');
|
|
769
|
+
this.analyzeFunctionBody(funcPath, funcBodyScopeId, module, allCollections);
|
|
770
|
+
scopeTracker.exitScope();
|
|
666
771
|
}
|
|
772
|
+
// Check for indexed array assignment at module level: arr[i] = value
|
|
773
|
+
this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
|
|
774
|
+
// Check for object property assignment at module level: obj.prop = value
|
|
775
|
+
this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
|
|
667
776
|
}
|
|
668
777
|
});
|
|
669
778
|
this.profiler.end('traverse_assignments');
|
|
670
779
|
// Classes
|
|
671
780
|
this.profiler.start('traverse_classes');
|
|
672
|
-
const classVisitor = new ClassVisitor(module, allCollections, this.analyzeFunctionBody.bind(this)
|
|
781
|
+
const classVisitor = new ClassVisitor(module, allCollections, this.analyzeFunctionBody.bind(this), scopeTracker // Pass ScopeTracker for semantic ID generation
|
|
782
|
+
);
|
|
673
783
|
traverse(ast, classVisitor.getHandlers());
|
|
674
784
|
this.profiler.end('traverse_classes');
|
|
675
785
|
// TypeScript-specific constructs (interfaces, type aliases, enums)
|
|
676
786
|
this.profiler.start('traverse_typescript');
|
|
677
|
-
const typescriptVisitor = new TypeScriptVisitor(module, allCollections);
|
|
787
|
+
const typescriptVisitor = new TypeScriptVisitor(module, allCollections, scopeTracker);
|
|
678
788
|
traverse(ast, typescriptVisitor.getHandlers());
|
|
679
789
|
this.profiler.end('traverse_typescript');
|
|
680
790
|
// Module-level callbacks
|
|
@@ -686,22 +796,22 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
686
796
|
if (functionParent)
|
|
687
797
|
return;
|
|
688
798
|
if (funcPath.parent && funcPath.parent.type === 'CallExpression') {
|
|
689
|
-
const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(
|
|
690
|
-
|
|
799
|
+
const funcName = funcNode.id ? funcNode.id.name : this.generateAnonymousName(scopeTracker);
|
|
800
|
+
// Use semantic ID as primary ID (matching FunctionVisitor pattern)
|
|
801
|
+
const functionId = computeSemanticId('FUNCTION', funcName, scopeTracker.getContext());
|
|
691
802
|
functions.push({
|
|
692
803
|
id: functionId,
|
|
693
|
-
stableId: functionId,
|
|
694
804
|
type: 'FUNCTION',
|
|
695
805
|
name: funcName,
|
|
696
806
|
file: module.file,
|
|
697
|
-
line: funcNode
|
|
698
|
-
column: funcNode
|
|
807
|
+
line: getLine(funcNode),
|
|
808
|
+
column: getColumn(funcNode),
|
|
699
809
|
async: funcNode.async || false,
|
|
700
810
|
generator: funcNode.generator || false,
|
|
701
811
|
isCallback: true,
|
|
702
812
|
parentScopeId: module.id
|
|
703
813
|
});
|
|
704
|
-
const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${funcNode
|
|
814
|
+
const callbackScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(funcNode)}`;
|
|
705
815
|
scopes.push({
|
|
706
816
|
id: callbackScopeId,
|
|
707
817
|
type: 'SCOPE',
|
|
@@ -710,15 +820,13 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
710
820
|
semanticId: `${funcName}:callback_body[0]`,
|
|
711
821
|
conditional: false,
|
|
712
822
|
file: module.file,
|
|
713
|
-
line: funcNode
|
|
823
|
+
line: getLine(funcNode),
|
|
714
824
|
parentFunctionId: functionId
|
|
715
825
|
});
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
};
|
|
721
|
-
this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections, callbackScopeCtx);
|
|
826
|
+
// Enter callback scope for semantic ID generation and analyze
|
|
827
|
+
scopeTracker.enterScope(funcName, 'callback');
|
|
828
|
+
this.analyzeFunctionBody(funcPath, callbackScopeId, module, allCollections);
|
|
829
|
+
scopeTracker.exitScope();
|
|
722
830
|
funcPath.skip();
|
|
723
831
|
}
|
|
724
832
|
}
|
|
@@ -726,7 +834,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
726
834
|
this.profiler.end('traverse_callbacks');
|
|
727
835
|
// Call expressions
|
|
728
836
|
this.profiler.start('traverse_calls');
|
|
729
|
-
const callExpressionVisitor = new CallExpressionVisitor(module, allCollections);
|
|
837
|
+
const callExpressionVisitor = new CallExpressionVisitor(module, allCollections, scopeTracker);
|
|
730
838
|
traverse(ast, callExpressionVisitor.getHandlers());
|
|
731
839
|
this.profiler.end('traverse_calls');
|
|
732
840
|
// Module-level IfStatements
|
|
@@ -739,37 +847,37 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
739
847
|
const ifNode = ifPath.node;
|
|
740
848
|
const condition = code.substring(ifNode.test.start, ifNode.test.end) || 'condition';
|
|
741
849
|
const counterId = ifScopeCounterRef.value++;
|
|
742
|
-
const ifScopeId = `SCOPE#if#${module.file}#${ifNode
|
|
850
|
+
const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
|
|
743
851
|
const constraints = ConditionParser.parse(ifNode.test);
|
|
744
|
-
const ifSemanticId = this.generateSemanticId('if_statement',
|
|
852
|
+
const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
|
|
745
853
|
scopes.push({
|
|
746
854
|
id: ifScopeId,
|
|
747
855
|
type: 'SCOPE',
|
|
748
856
|
scopeType: 'if_statement',
|
|
749
|
-
name: `if:${ifNode
|
|
857
|
+
name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
|
|
750
858
|
semanticId: ifSemanticId,
|
|
751
859
|
conditional: true,
|
|
752
860
|
condition,
|
|
753
861
|
constraints: constraints.length > 0 ? constraints : undefined,
|
|
754
862
|
file: module.file,
|
|
755
|
-
line: ifNode
|
|
863
|
+
line: getLine(ifNode),
|
|
756
864
|
parentScopeId: module.id
|
|
757
865
|
});
|
|
758
866
|
if (ifNode.alternate && ifNode.alternate.type !== 'IfStatement') {
|
|
759
867
|
const elseCounterId = ifScopeCounterRef.value++;
|
|
760
|
-
const elseScopeId = `SCOPE#else#${module.file}#${ifNode.alternate
|
|
868
|
+
const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
|
|
761
869
|
const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
|
|
762
|
-
const elseSemanticId = this.generateSemanticId('else_statement',
|
|
870
|
+
const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
|
|
763
871
|
scopes.push({
|
|
764
872
|
id: elseScopeId,
|
|
765
873
|
type: 'SCOPE',
|
|
766
874
|
scopeType: 'else_statement',
|
|
767
|
-
name: `else:${ifNode.alternate
|
|
875
|
+
name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
|
|
768
876
|
semanticId: elseSemanticId,
|
|
769
877
|
conditional: true,
|
|
770
878
|
constraints: negatedConstraints,
|
|
771
879
|
file: module.file,
|
|
772
|
-
line: ifNode.alternate
|
|
880
|
+
line: getLine(ifNode.alternate),
|
|
773
881
|
parentScopeId: module.id
|
|
774
882
|
});
|
|
775
883
|
}
|
|
@@ -799,55 +907,428 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
799
907
|
interfaces,
|
|
800
908
|
typeAliases,
|
|
801
909
|
enums,
|
|
802
|
-
decorators
|
|
910
|
+
decorators,
|
|
911
|
+
// Array mutation tracking
|
|
912
|
+
arrayMutations,
|
|
913
|
+
// Object mutation tracking
|
|
914
|
+
objectMutations,
|
|
915
|
+
// Object/Array literal tracking - use allCollections refs as visitors may have created new arrays
|
|
916
|
+
objectLiterals: allCollections.objectLiterals || objectLiterals,
|
|
917
|
+
arrayLiterals: allCollections.arrayLiterals || arrayLiterals
|
|
803
918
|
});
|
|
804
919
|
this.profiler.end('graph_build');
|
|
805
920
|
nodesCreated = result.nodes;
|
|
806
921
|
edgesCreated = result.edges;
|
|
807
922
|
}
|
|
808
|
-
catch
|
|
809
|
-
|
|
810
|
-
console.error(`[JSASTAnalyzer] Error analyzing ${module.file}:`, err.message);
|
|
811
|
-
console.error(err.stack);
|
|
923
|
+
catch {
|
|
924
|
+
// Error analyzing module - silently skip, caller handles the result
|
|
812
925
|
}
|
|
813
926
|
return { nodes: nodesCreated, edges: edgesCreated };
|
|
814
927
|
}
|
|
815
928
|
/**
|
|
816
|
-
* Helper to generate semantic ID for a scope
|
|
929
|
+
* Helper to generate semantic ID for a scope using ScopeTracker.
|
|
930
|
+
* Format: "scopePath:scopeType[index]" e.g. "MyClass->myMethod:if_statement[0]"
|
|
817
931
|
*/
|
|
818
|
-
generateSemanticId(scopeType,
|
|
819
|
-
if (!
|
|
932
|
+
generateSemanticId(scopeType, scopeTracker) {
|
|
933
|
+
if (!scopeTracker)
|
|
820
934
|
return undefined;
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
return `${
|
|
935
|
+
const scopePath = scopeTracker.getScopePath();
|
|
936
|
+
const siblingIndex = scopeTracker.getItemCounter(`semanticId:${scopeType}`);
|
|
937
|
+
return `${scopePath}:${scopeType}[${siblingIndex}]`;
|
|
824
938
|
}
|
|
825
939
|
/**
|
|
826
|
-
*
|
|
940
|
+
* Generate a unique anonymous function name within the current scope.
|
|
941
|
+
* Uses ScopeTracker.getSiblingIndex() for stable naming.
|
|
827
942
|
*/
|
|
828
|
-
|
|
829
|
-
if (!
|
|
830
|
-
return
|
|
943
|
+
generateAnonymousName(scopeTracker) {
|
|
944
|
+
if (!scopeTracker)
|
|
945
|
+
return 'anonymous';
|
|
946
|
+
const index = scopeTracker.getSiblingIndex('anonymous');
|
|
947
|
+
return `anonymous[${index}]`;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Factory method to create loop scope handlers.
|
|
951
|
+
* All loop statements (for, for-in, for-of, while, do-while) follow the same pattern:
|
|
952
|
+
* 1. Create scope with SCOPE#<scopeType>#file#line:counter
|
|
953
|
+
* 2. Generate semantic ID
|
|
954
|
+
* 3. Push to scopes array
|
|
955
|
+
* 4. Enter/exit scope tracker
|
|
956
|
+
*
|
|
957
|
+
* @param trackerScopeType - Scope type for ScopeTracker (e.g., 'for', 'for-in', 'while')
|
|
958
|
+
* @param scopeType - Scope type for the graph node (e.g., 'for-loop', 'for-in-loop')
|
|
959
|
+
* @param parentScopeId - Parent scope ID for the scope node
|
|
960
|
+
* @param module - Module context
|
|
961
|
+
* @param scopes - Collection to push scope nodes to
|
|
962
|
+
* @param scopeCounterRef - Counter for unique scope IDs
|
|
963
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
964
|
+
*/
|
|
965
|
+
/**
|
|
966
|
+
* Handles VariableDeclaration nodes within function bodies.
|
|
967
|
+
*
|
|
968
|
+
* Extracts variable names from patterns (including destructuring), determines
|
|
969
|
+
* if the variable should be CONSTANT or VARIABLE, generates semantic or legacy IDs,
|
|
970
|
+
* and tracks class instantiations and variable assignments.
|
|
971
|
+
*
|
|
972
|
+
* @param varPath - The NodePath for the VariableDeclaration
|
|
973
|
+
* @param parentScopeId - Parent scope ID for the variable
|
|
974
|
+
* @param module - Module context with file info
|
|
975
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
976
|
+
* @param classInstantiations - Collection to push class instantiations to
|
|
977
|
+
* @param literals - Collection for literal tracking
|
|
978
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
979
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
980
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
981
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
982
|
+
* @param parentScopeVariables - Set to track variables for closure analysis
|
|
983
|
+
*/
|
|
984
|
+
handleVariableDeclaration(varPath, parentScopeId, module, variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker, parentScopeVariables) {
|
|
985
|
+
const varNode = varPath.node;
|
|
986
|
+
const isConst = varNode.kind === 'const';
|
|
987
|
+
varNode.declarations.forEach(declarator => {
|
|
988
|
+
const variables = this.extractVariableNamesFromPattern(declarator.id);
|
|
989
|
+
variables.forEach(varInfo => {
|
|
990
|
+
const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
|
|
991
|
+
const isLiteral = literalValue !== null;
|
|
992
|
+
const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
|
|
993
|
+
const shouldBeConstant = isConst && (isLiteral || isNewExpression);
|
|
994
|
+
const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
|
|
995
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
996
|
+
const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
997
|
+
const varId = scopeTracker
|
|
998
|
+
? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
|
|
999
|
+
: legacyId;
|
|
1000
|
+
parentScopeVariables.add({
|
|
1001
|
+
name: varInfo.name,
|
|
1002
|
+
id: varId,
|
|
1003
|
+
scopeId: parentScopeId
|
|
1004
|
+
});
|
|
1005
|
+
if (shouldBeConstant) {
|
|
1006
|
+
const constantData = {
|
|
1007
|
+
id: varId,
|
|
1008
|
+
type: 'CONSTANT',
|
|
1009
|
+
name: varInfo.name,
|
|
1010
|
+
file: module.file,
|
|
1011
|
+
line: varInfo.loc.start.line,
|
|
1012
|
+
parentScopeId
|
|
1013
|
+
};
|
|
1014
|
+
if (isLiteral) {
|
|
1015
|
+
constantData.value = literalValue;
|
|
1016
|
+
}
|
|
1017
|
+
variableDeclarations.push(constantData);
|
|
1018
|
+
const init = declarator.init;
|
|
1019
|
+
if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
|
|
1020
|
+
const className = init.callee.name;
|
|
1021
|
+
classInstantiations.push({
|
|
1022
|
+
variableId: varId,
|
|
1023
|
+
variableName: varInfo.name,
|
|
1024
|
+
className: className,
|
|
1025
|
+
line: varInfo.loc.start.line,
|
|
1026
|
+
parentScopeId
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
variableDeclarations.push({
|
|
1032
|
+
id: varId,
|
|
1033
|
+
type: 'VARIABLE',
|
|
1034
|
+
name: varInfo.name,
|
|
1035
|
+
file: module.file,
|
|
1036
|
+
line: varInfo.loc.start.line,
|
|
1037
|
+
parentScopeId
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
if (declarator.init) {
|
|
1041
|
+
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
createLoopScopeHandler(trackerScopeType, scopeType, parentScopeId, module, scopes, scopeCounterRef, scopeTracker) {
|
|
831
1047
|
return {
|
|
832
|
-
|
|
833
|
-
|
|
1048
|
+
enter: (path) => {
|
|
1049
|
+
const node = path.node;
|
|
1050
|
+
const scopeId = `SCOPE#${scopeType}#${module.file}#${getLine(node)}:${scopeCounterRef.value++}`;
|
|
1051
|
+
const semanticId = this.generateSemanticId(scopeType, scopeTracker);
|
|
1052
|
+
scopes.push({
|
|
1053
|
+
id: scopeId,
|
|
1054
|
+
type: 'SCOPE',
|
|
1055
|
+
scopeType,
|
|
1056
|
+
semanticId,
|
|
1057
|
+
file: module.file,
|
|
1058
|
+
line: getLine(node),
|
|
1059
|
+
parentScopeId
|
|
1060
|
+
});
|
|
1061
|
+
// Enter scope for semantic ID generation
|
|
1062
|
+
if (scopeTracker) {
|
|
1063
|
+
scopeTracker.enterCountedScope(trackerScopeType);
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
exit: () => {
|
|
1067
|
+
// Exit scope
|
|
1068
|
+
if (scopeTracker) {
|
|
1069
|
+
scopeTracker.exitScope();
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
834
1072
|
};
|
|
835
1073
|
}
|
|
836
1074
|
/**
|
|
837
|
-
*
|
|
838
|
-
*
|
|
1075
|
+
* Process VariableDeclarations within a try/catch/finally block.
|
|
1076
|
+
* This is a simplified version that doesn't track parentScopeVariables or class instantiations.
|
|
1077
|
+
*
|
|
1078
|
+
* @param blockPath - The NodePath for the block to process
|
|
1079
|
+
* @param blockScopeId - The scope ID for variables in this block
|
|
1080
|
+
* @param module - Module context
|
|
1081
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
1082
|
+
* @param literals - Collection for literal tracking
|
|
1083
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
1084
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
1085
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
1086
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
839
1087
|
*/
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1088
|
+
processBlockVariables(blockPath, blockScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker) {
|
|
1089
|
+
blockPath.traverse({
|
|
1090
|
+
VariableDeclaration: (varPath) => {
|
|
1091
|
+
const varNode = varPath.node;
|
|
1092
|
+
const isConst = varNode.kind === 'const';
|
|
1093
|
+
varNode.declarations.forEach(declarator => {
|
|
1094
|
+
const variables = this.extractVariableNamesFromPattern(declarator.id);
|
|
1095
|
+
variables.forEach(varInfo => {
|
|
1096
|
+
const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
|
|
1097
|
+
const isLiteral = literalValue !== null;
|
|
1098
|
+
const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
|
|
1099
|
+
const shouldBeConstant = isConst && (isLiteral || isNewExpression);
|
|
1100
|
+
const nodeType = shouldBeConstant ? 'CONSTANT' : 'VARIABLE';
|
|
1101
|
+
const legacyId = `${nodeType}#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1102
|
+
const varId = scopeTracker
|
|
1103
|
+
? computeSemanticId(nodeType, varInfo.name, scopeTracker.getContext())
|
|
1104
|
+
: legacyId;
|
|
1105
|
+
variableDeclarations.push({
|
|
1106
|
+
id: varId,
|
|
1107
|
+
type: nodeType,
|
|
1108
|
+
name: varInfo.name,
|
|
1109
|
+
file: module.file,
|
|
1110
|
+
line: varInfo.loc.start.line,
|
|
1111
|
+
parentScopeId: blockScopeId
|
|
1112
|
+
});
|
|
1113
|
+
if (declarator.init) {
|
|
1114
|
+
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Handles TryStatement nodes within function bodies.
|
|
1123
|
+
* Creates try, catch (with optional error parameter), and finally scopes,
|
|
1124
|
+
* and processes variable declarations within each block.
|
|
1125
|
+
*
|
|
1126
|
+
* @param tryPath - The NodePath for the TryStatement
|
|
1127
|
+
* @param parentScopeId - Parent scope ID for the scope nodes
|
|
1128
|
+
* @param module - Module context
|
|
1129
|
+
* @param scopes - Collection to push scope nodes to
|
|
1130
|
+
* @param variableDeclarations - Collection to push variable declarations to
|
|
1131
|
+
* @param literals - Collection for literal tracking
|
|
1132
|
+
* @param variableAssignments - Collection for variable assignment tracking
|
|
1133
|
+
* @param scopeCounterRef - Counter for unique scope IDs
|
|
1134
|
+
* @param varDeclCounterRef - Counter for unique variable declaration IDs
|
|
1135
|
+
* @param literalCounterRef - Counter for unique literal IDs
|
|
1136
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1137
|
+
*/
|
|
1138
|
+
handleTryStatement(tryPath, parentScopeId, module, scopes, variableDeclarations, literals, variableAssignments, scopeCounterRef, varDeclCounterRef, literalCounterRef, scopeTracker) {
|
|
1139
|
+
const tryNode = tryPath.node;
|
|
1140
|
+
// Create and process try block
|
|
1141
|
+
const tryScopeId = `SCOPE#try-block#${module.file}#${getLine(tryNode)}:${scopeCounterRef.value++}`;
|
|
1142
|
+
const trySemanticId = this.generateSemanticId('try-block', scopeTracker);
|
|
1143
|
+
scopes.push({
|
|
1144
|
+
id: tryScopeId,
|
|
1145
|
+
type: 'SCOPE',
|
|
1146
|
+
scopeType: 'try-block',
|
|
1147
|
+
semanticId: trySemanticId,
|
|
1148
|
+
file: module.file,
|
|
1149
|
+
line: getLine(tryNode),
|
|
1150
|
+
parentScopeId
|
|
1151
|
+
});
|
|
1152
|
+
if (scopeTracker) {
|
|
1153
|
+
scopeTracker.enterCountedScope('try');
|
|
1154
|
+
}
|
|
1155
|
+
this.processBlockVariables(tryPath.get('block'), tryScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
|
|
1156
|
+
if (scopeTracker) {
|
|
1157
|
+
scopeTracker.exitScope();
|
|
1158
|
+
}
|
|
1159
|
+
// Create and process catch block if present
|
|
1160
|
+
if (tryNode.handler) {
|
|
1161
|
+
const catchBlock = tryNode.handler;
|
|
1162
|
+
const catchScopeId = `SCOPE#catch-block#${module.file}#${getLine(catchBlock)}:${scopeCounterRef.value++}`;
|
|
1163
|
+
const catchSemanticId = this.generateSemanticId('catch-block', scopeTracker);
|
|
1164
|
+
scopes.push({
|
|
1165
|
+
id: catchScopeId,
|
|
1166
|
+
type: 'SCOPE',
|
|
1167
|
+
scopeType: 'catch-block',
|
|
1168
|
+
semanticId: catchSemanticId,
|
|
1169
|
+
file: module.file,
|
|
1170
|
+
line: getLine(catchBlock),
|
|
1171
|
+
parentScopeId
|
|
1172
|
+
});
|
|
1173
|
+
if (scopeTracker) {
|
|
1174
|
+
scopeTracker.enterCountedScope('catch');
|
|
1175
|
+
}
|
|
1176
|
+
// Handle catch parameter (e.g., catch (e))
|
|
1177
|
+
if (catchBlock.param) {
|
|
1178
|
+
const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
|
|
1179
|
+
errorVarInfo.forEach(varInfo => {
|
|
1180
|
+
const legacyId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1181
|
+
const varId = scopeTracker
|
|
1182
|
+
? computeSemanticId('VARIABLE', varInfo.name, scopeTracker.getContext())
|
|
1183
|
+
: legacyId;
|
|
1184
|
+
variableDeclarations.push({
|
|
1185
|
+
id: varId,
|
|
1186
|
+
type: 'VARIABLE',
|
|
1187
|
+
name: varInfo.name,
|
|
1188
|
+
file: module.file,
|
|
1189
|
+
line: varInfo.loc.start.line,
|
|
1190
|
+
parentScopeId: catchScopeId
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
this.processBlockVariables(tryPath.get('handler.body'), catchScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
|
|
1195
|
+
if (scopeTracker) {
|
|
1196
|
+
scopeTracker.exitScope();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
// Create and process finally block if present
|
|
1200
|
+
if (tryNode.finalizer) {
|
|
1201
|
+
const finallyScopeId = `SCOPE#finally-block#${module.file}#${getLine(tryNode.finalizer)}:${scopeCounterRef.value++}`;
|
|
1202
|
+
const finallySemanticId = this.generateSemanticId('finally-block', scopeTracker);
|
|
1203
|
+
scopes.push({
|
|
1204
|
+
id: finallyScopeId,
|
|
1205
|
+
type: 'SCOPE',
|
|
1206
|
+
scopeType: 'finally-block',
|
|
1207
|
+
semanticId: finallySemanticId,
|
|
1208
|
+
file: module.file,
|
|
1209
|
+
line: getLine(tryNode.finalizer),
|
|
1210
|
+
parentScopeId
|
|
1211
|
+
});
|
|
1212
|
+
if (scopeTracker) {
|
|
1213
|
+
scopeTracker.enterCountedScope('finally');
|
|
1214
|
+
}
|
|
1215
|
+
const finalizerPath = tryPath.get('finalizer');
|
|
1216
|
+
if (finalizerPath.node) {
|
|
1217
|
+
this.processBlockVariables(finalizerPath, finallyScopeId, module, variableDeclarations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker);
|
|
1218
|
+
}
|
|
1219
|
+
if (scopeTracker) {
|
|
1220
|
+
scopeTracker.exitScope();
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
tryPath.skip();
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Factory method to create IfStatement handler.
|
|
1227
|
+
* Creates if scope with condition parsing and optional else scope.
|
|
1228
|
+
* Tracks if/else scope transitions via ifElseScopeMap.
|
|
1229
|
+
*
|
|
1230
|
+
* @param parentScopeId - Parent scope ID for the scope nodes
|
|
1231
|
+
* @param module - Module context
|
|
1232
|
+
* @param scopes - Collection to push scope nodes to
|
|
1233
|
+
* @param ifScopeCounterRef - Counter for unique if scope IDs
|
|
1234
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1235
|
+
* @param sourceCode - Source code for extracting condition text
|
|
1236
|
+
* @param ifElseScopeMap - Map to track if/else scope transitions
|
|
1237
|
+
*/
|
|
1238
|
+
createIfStatementHandler(parentScopeId, module, scopes, ifScopeCounterRef, scopeTracker, sourceCode, ifElseScopeMap) {
|
|
1239
|
+
return {
|
|
1240
|
+
enter: (ifPath) => {
|
|
1241
|
+
const ifNode = ifPath.node;
|
|
1242
|
+
const condition = sourceCode.substring(ifNode.test.start, ifNode.test.end) || 'condition';
|
|
1243
|
+
const counterId = ifScopeCounterRef.value++;
|
|
1244
|
+
const ifScopeId = `SCOPE#if#${module.file}#${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`;
|
|
1245
|
+
// Parse condition to extract constraints
|
|
1246
|
+
const constraints = ConditionParser.parse(ifNode.test);
|
|
1247
|
+
const ifSemanticId = this.generateSemanticId('if_statement', scopeTracker);
|
|
1248
|
+
scopes.push({
|
|
1249
|
+
id: ifScopeId,
|
|
1250
|
+
type: 'SCOPE',
|
|
1251
|
+
scopeType: 'if_statement',
|
|
1252
|
+
name: `if:${getLine(ifNode)}:${getColumn(ifNode)}:${counterId}`,
|
|
1253
|
+
semanticId: ifSemanticId,
|
|
1254
|
+
conditional: true,
|
|
1255
|
+
condition,
|
|
1256
|
+
constraints: constraints.length > 0 ? constraints : undefined,
|
|
1257
|
+
file: module.file,
|
|
1258
|
+
line: getLine(ifNode),
|
|
1259
|
+
parentScopeId
|
|
1260
|
+
});
|
|
1261
|
+
// Enter scope for semantic ID generation
|
|
1262
|
+
if (scopeTracker) {
|
|
1263
|
+
scopeTracker.enterCountedScope('if');
|
|
1264
|
+
}
|
|
1265
|
+
// Handle else branch if present
|
|
1266
|
+
if (ifNode.alternate && !t.isIfStatement(ifNode.alternate)) {
|
|
1267
|
+
// Only create else scope for actual else block, not else-if
|
|
1268
|
+
const elseCounterId = ifScopeCounterRef.value++;
|
|
1269
|
+
const elseScopeId = `SCOPE#else#${module.file}#${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`;
|
|
1270
|
+
const negatedConstraints = constraints.length > 0 ? ConditionParser.negate(constraints) : undefined;
|
|
1271
|
+
const elseSemanticId = this.generateSemanticId('else_statement', scopeTracker);
|
|
1272
|
+
scopes.push({
|
|
1273
|
+
id: elseScopeId,
|
|
1274
|
+
type: 'SCOPE',
|
|
1275
|
+
scopeType: 'else_statement',
|
|
1276
|
+
name: `else:${getLine(ifNode.alternate)}:${getColumn(ifNode.alternate)}:${elseCounterId}`,
|
|
1277
|
+
semanticId: elseSemanticId,
|
|
1278
|
+
conditional: true,
|
|
1279
|
+
constraints: negatedConstraints,
|
|
1280
|
+
file: module.file,
|
|
1281
|
+
line: getLine(ifNode.alternate),
|
|
1282
|
+
parentScopeId
|
|
1283
|
+
});
|
|
1284
|
+
// Store info to switch to else scope when we enter alternate
|
|
1285
|
+
ifElseScopeMap.set(ifNode, { inElse: false, hasElse: true });
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
ifElseScopeMap.set(ifNode, { inElse: false, hasElse: false });
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
exit: (ifPath) => {
|
|
1292
|
+
const ifNode = ifPath.node;
|
|
1293
|
+
// Exit the current scope (either if or else)
|
|
1294
|
+
if (scopeTracker) {
|
|
1295
|
+
scopeTracker.exitScope();
|
|
1296
|
+
}
|
|
1297
|
+
// If we were in else, we already exited else scope
|
|
1298
|
+
// If we only had if, we exit if scope (done above)
|
|
1299
|
+
ifElseScopeMap.delete(ifNode);
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
846
1302
|
}
|
|
847
1303
|
/**
|
|
848
|
-
*
|
|
1304
|
+
* Factory method to create BlockStatement handler for tracking if/else transitions.
|
|
1305
|
+
* When entering an else block, switches scope from if to else.
|
|
1306
|
+
*
|
|
1307
|
+
* @param scopeTracker - Tracker for semantic ID generation
|
|
1308
|
+
* @param ifElseScopeMap - Map to track if/else scope transitions
|
|
849
1309
|
*/
|
|
850
|
-
|
|
1310
|
+
createIfElseBlockStatementHandler(scopeTracker, ifElseScopeMap) {
|
|
1311
|
+
return {
|
|
1312
|
+
enter: (blockPath) => {
|
|
1313
|
+
// Check if this block is the alternate of an IfStatement
|
|
1314
|
+
const parent = blockPath.parent;
|
|
1315
|
+
if (t.isIfStatement(parent) && parent.alternate === blockPath.node) {
|
|
1316
|
+
const scopeInfo = ifElseScopeMap.get(parent);
|
|
1317
|
+
if (scopeInfo && scopeInfo.hasElse && !scopeInfo.inElse && scopeTracker) {
|
|
1318
|
+
// Exit if scope, enter else scope
|
|
1319
|
+
scopeTracker.exitScope();
|
|
1320
|
+
scopeTracker.enterCountedScope('else');
|
|
1321
|
+
scopeInfo.inElse = true;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Анализирует тело функции и извлекает переменные, вызовы, условные блоки.
|
|
1329
|
+
* Uses ScopeTracker from collections for semantic ID generation.
|
|
1330
|
+
*/
|
|
1331
|
+
analyzeFunctionBody(funcPath, parentScopeId, module, collections) {
|
|
851
1332
|
// Extract with defaults for optional properties
|
|
852
1333
|
const functions = (collections.functions ?? []);
|
|
853
1334
|
const scopes = (collections.scopes ?? []);
|
|
@@ -868,6 +1349,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
868
1349
|
const httpRequestCounterRef = (collections.httpRequestCounterRef ?? { value: 0 });
|
|
869
1350
|
const literalCounterRef = (collections.literalCounterRef ?? { value: 0 });
|
|
870
1351
|
const anonymousFunctionCounterRef = (collections.anonymousFunctionCounterRef ?? { value: 0 });
|
|
1352
|
+
const scopeTracker = collections.scopeTracker;
|
|
871
1353
|
const processedNodes = collections.processedNodes ?? {
|
|
872
1354
|
functions: new Set(),
|
|
873
1355
|
classes: new Set(),
|
|
@@ -884,223 +1366,73 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
884
1366
|
const processedVarDecls = processedNodes.varDecls;
|
|
885
1367
|
const processedMethodCalls = processedNodes.methodCalls;
|
|
886
1368
|
const processedEventListeners = processedNodes.eventListeners;
|
|
1369
|
+
// Track if/else scope transitions
|
|
1370
|
+
const ifElseScopeMap = new Map();
|
|
887
1371
|
funcPath.traverse({
|
|
888
1372
|
VariableDeclaration: (varPath) => {
|
|
889
|
-
|
|
890
|
-
const isConst = varNode.kind === 'const';
|
|
891
|
-
varNode.declarations.forEach(declarator => {
|
|
892
|
-
const variables = this.extractVariableNamesFromPattern(declarator.id);
|
|
893
|
-
variables.forEach(varInfo => {
|
|
894
|
-
const literalValue = declarator.init ? ExpressionEvaluator.extractLiteralValue(declarator.init) : null;
|
|
895
|
-
const isLiteral = literalValue !== null;
|
|
896
|
-
const isNewExpression = declarator.init && declarator.init.type === 'NewExpression';
|
|
897
|
-
const shouldBeConstant = isConst && (isLiteral || isNewExpression);
|
|
898
|
-
const varId = shouldBeConstant
|
|
899
|
-
? `CONSTANT#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`
|
|
900
|
-
: `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
901
|
-
parentScopeVariables.add({
|
|
902
|
-
name: varInfo.name,
|
|
903
|
-
id: varId,
|
|
904
|
-
scopeId: parentScopeId
|
|
905
|
-
});
|
|
906
|
-
if (shouldBeConstant) {
|
|
907
|
-
const constantData = {
|
|
908
|
-
id: varId,
|
|
909
|
-
type: 'CONSTANT',
|
|
910
|
-
name: varInfo.name,
|
|
911
|
-
file: module.file,
|
|
912
|
-
line: varInfo.loc.start.line,
|
|
913
|
-
parentScopeId
|
|
914
|
-
};
|
|
915
|
-
if (isLiteral) {
|
|
916
|
-
constantData.value = literalValue;
|
|
917
|
-
}
|
|
918
|
-
variableDeclarations.push(constantData);
|
|
919
|
-
const init = declarator.init;
|
|
920
|
-
if (isNewExpression && t.isNewExpression(init) && t.isIdentifier(init.callee)) {
|
|
921
|
-
const className = init.callee.name;
|
|
922
|
-
classInstantiations.push({
|
|
923
|
-
variableId: varId,
|
|
924
|
-
variableName: varInfo.name,
|
|
925
|
-
className: className,
|
|
926
|
-
line: varInfo.loc.start.line,
|
|
927
|
-
parentScopeId
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
else {
|
|
932
|
-
variableDeclarations.push({
|
|
933
|
-
id: varId,
|
|
934
|
-
type: 'VARIABLE',
|
|
935
|
-
name: varInfo.name,
|
|
936
|
-
file: module.file,
|
|
937
|
-
line: varInfo.loc.start.line,
|
|
938
|
-
parentScopeId
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
if (declarator.init) {
|
|
942
|
-
this.trackVariableAssignment(declarator.init, varId, varInfo.name, module, varInfo.loc.start.line, literals, variableAssignments, literalCounterRef);
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
});
|
|
946
|
-
},
|
|
947
|
-
ForStatement: (forPath) => {
|
|
948
|
-
const forNode = forPath.node;
|
|
949
|
-
const scopeId = `SCOPE#for-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
|
|
950
|
-
const semanticId = this.generateSemanticId('for-loop', scopeCtx);
|
|
951
|
-
scopes.push({
|
|
952
|
-
id: scopeId,
|
|
953
|
-
type: 'SCOPE',
|
|
954
|
-
scopeType: 'for-loop',
|
|
955
|
-
semanticId,
|
|
956
|
-
file: module.file,
|
|
957
|
-
line: forNode.loc.start.line,
|
|
958
|
-
parentScopeId
|
|
959
|
-
});
|
|
960
|
-
},
|
|
961
|
-
ForInStatement: (forPath) => {
|
|
962
|
-
const forNode = forPath.node;
|
|
963
|
-
const scopeId = `SCOPE#for-in-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
|
|
964
|
-
const semanticId = this.generateSemanticId('for-in-loop', scopeCtx);
|
|
965
|
-
scopes.push({
|
|
966
|
-
id: scopeId,
|
|
967
|
-
type: 'SCOPE',
|
|
968
|
-
scopeType: 'for-in-loop',
|
|
969
|
-
semanticId,
|
|
970
|
-
file: module.file,
|
|
971
|
-
line: forNode.loc.start.line,
|
|
972
|
-
parentScopeId
|
|
973
|
-
});
|
|
974
|
-
},
|
|
975
|
-
ForOfStatement: (forPath) => {
|
|
976
|
-
const forNode = forPath.node;
|
|
977
|
-
const scopeId = `SCOPE#for-of-loop#${module.file}#${forNode.loc.start.line}:${scopeCounterRef.value++}`;
|
|
978
|
-
const semanticId = this.generateSemanticId('for-of-loop', scopeCtx);
|
|
979
|
-
scopes.push({
|
|
980
|
-
id: scopeId,
|
|
981
|
-
type: 'SCOPE',
|
|
982
|
-
scopeType: 'for-of-loop',
|
|
983
|
-
semanticId,
|
|
984
|
-
file: module.file,
|
|
985
|
-
line: forNode.loc.start.line,
|
|
986
|
-
parentScopeId
|
|
987
|
-
});
|
|
988
|
-
},
|
|
989
|
-
WhileStatement: (whilePath) => {
|
|
990
|
-
const whileNode = whilePath.node;
|
|
991
|
-
const scopeId = `SCOPE#while-loop#${module.file}#${whileNode.loc.start.line}:${scopeCounterRef.value++}`;
|
|
992
|
-
const semanticId = this.generateSemanticId('while-loop', scopeCtx);
|
|
993
|
-
scopes.push({
|
|
994
|
-
id: scopeId,
|
|
995
|
-
type: 'SCOPE',
|
|
996
|
-
scopeType: 'while-loop',
|
|
997
|
-
semanticId,
|
|
998
|
-
file: module.file,
|
|
999
|
-
line: whileNode.loc.start.line,
|
|
1000
|
-
parentScopeId
|
|
1001
|
-
});
|
|
1002
|
-
},
|
|
1003
|
-
DoWhileStatement: (doPath) => {
|
|
1004
|
-
const doNode = doPath.node;
|
|
1005
|
-
const scopeId = `SCOPE#do-while-loop#${module.file}#${doNode.loc.start.line}:${scopeCounterRef.value++}`;
|
|
1006
|
-
const semanticId = this.generateSemanticId('do-while-loop', scopeCtx);
|
|
1007
|
-
scopes.push({
|
|
1008
|
-
id: scopeId,
|
|
1009
|
-
type: 'SCOPE',
|
|
1010
|
-
scopeType: 'do-while-loop',
|
|
1011
|
-
semanticId,
|
|
1012
|
-
file: module.file,
|
|
1013
|
-
line: doNode.loc.start.line,
|
|
1014
|
-
parentScopeId
|
|
1015
|
-
});
|
|
1373
|
+
this.handleVariableDeclaration(varPath, parentScopeId, module, variableDeclarations, classInstantiations, literals, variableAssignments, varDeclCounterRef, literalCounterRef, scopeTracker, parentScopeVariables);
|
|
1016
1374
|
},
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
type: 'SCOPE',
|
|
1024
|
-
scopeType: 'try-block',
|
|
1025
|
-
semanticId: trySemanticId,
|
|
1026
|
-
file: module.file,
|
|
1027
|
-
line: tryNode.loc.start.line,
|
|
1028
|
-
parentScopeId
|
|
1029
|
-
});
|
|
1030
|
-
if (tryNode.handler) {
|
|
1031
|
-
const catchBlock = tryNode.handler;
|
|
1032
|
-
const catchScopeId = `SCOPE#catch-block#${module.file}#${catchBlock.loc.start.line}:${scopeCounterRef.value++}`;
|
|
1033
|
-
const catchSemanticId = this.generateSemanticId('catch-block', scopeCtx);
|
|
1034
|
-
scopes.push({
|
|
1035
|
-
id: catchScopeId,
|
|
1036
|
-
type: 'SCOPE',
|
|
1037
|
-
scopeType: 'catch-block',
|
|
1038
|
-
semanticId: catchSemanticId,
|
|
1039
|
-
file: module.file,
|
|
1040
|
-
line: catchBlock.loc.start.line,
|
|
1041
|
-
parentScopeId
|
|
1042
|
-
});
|
|
1043
|
-
if (catchBlock.param) {
|
|
1044
|
-
const errorVarInfo = this.extractVariableNamesFromPattern(catchBlock.param);
|
|
1045
|
-
errorVarInfo.forEach(varInfo => {
|
|
1046
|
-
const varId = `VARIABLE#${varInfo.name}#${module.file}#${varInfo.loc.start.line}:${varInfo.loc.start.column}:${varDeclCounterRef.value++}`;
|
|
1047
|
-
variableDeclarations.push({
|
|
1048
|
-
id: varId,
|
|
1049
|
-
type: 'VARIABLE',
|
|
1050
|
-
name: varInfo.name,
|
|
1051
|
-
file: module.file,
|
|
1052
|
-
line: varInfo.loc.start.line,
|
|
1053
|
-
parentScopeId: catchScopeId
|
|
1054
|
-
});
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1375
|
+
// Detect indexed array assignments: arr[i] = value
|
|
1376
|
+
AssignmentExpression: (assignPath) => {
|
|
1377
|
+
const assignNode = assignPath.node;
|
|
1378
|
+
// Initialize collection if not exists
|
|
1379
|
+
if (!collections.arrayMutations) {
|
|
1380
|
+
collections.arrayMutations = [];
|
|
1057
1381
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
scopeType: 'finally-block',
|
|
1065
|
-
semanticId: finallySemanticId,
|
|
1066
|
-
file: module.file,
|
|
1067
|
-
line: tryNode.finalizer.loc.start.line,
|
|
1068
|
-
parentScopeId
|
|
1069
|
-
});
|
|
1382
|
+
const arrayMutations = collections.arrayMutations;
|
|
1383
|
+
// Check for indexed array assignment: arr[i] = value
|
|
1384
|
+
this.detectIndexedArrayAssignment(assignNode, module, arrayMutations);
|
|
1385
|
+
// Initialize object mutations collection if not exists
|
|
1386
|
+
if (!collections.objectMutations) {
|
|
1387
|
+
collections.objectMutations = [];
|
|
1070
1388
|
}
|
|
1389
|
+
const objectMutations = collections.objectMutations;
|
|
1390
|
+
// Check for object property assignment: obj.prop = value
|
|
1391
|
+
this.detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker);
|
|
1392
|
+
},
|
|
1393
|
+
ForStatement: this.createLoopScopeHandler('for', 'for-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1394
|
+
ForInStatement: this.createLoopScopeHandler('for-in', 'for-in-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1395
|
+
ForOfStatement: this.createLoopScopeHandler('for-of', 'for-of-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1396
|
+
WhileStatement: this.createLoopScopeHandler('while', 'while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1397
|
+
DoWhileStatement: this.createLoopScopeHandler('do-while', 'do-while-loop', parentScopeId, module, scopes, scopeCounterRef, scopeTracker),
|
|
1398
|
+
TryStatement: (tryPath) => {
|
|
1399
|
+
this.handleTryStatement(tryPath, parentScopeId, module, scopes, variableDeclarations, literals, variableAssignments, scopeCounterRef, varDeclCounterRef, literalCounterRef, scopeTracker);
|
|
1071
1400
|
},
|
|
1072
1401
|
SwitchStatement: (switchPath) => {
|
|
1073
1402
|
const switchNode = switchPath.node;
|
|
1074
|
-
const scopeId = `SCOPE#switch-case#${module.file}#${switchNode
|
|
1075
|
-
const semanticId = this.generateSemanticId('switch-case',
|
|
1403
|
+
const scopeId = `SCOPE#switch-case#${module.file}#${getLine(switchNode)}:${scopeCounterRef.value++}`;
|
|
1404
|
+
const semanticId = this.generateSemanticId('switch-case', scopeTracker);
|
|
1076
1405
|
scopes.push({
|
|
1077
1406
|
id: scopeId,
|
|
1078
1407
|
type: 'SCOPE',
|
|
1079
1408
|
scopeType: 'switch-case',
|
|
1080
1409
|
semanticId,
|
|
1081
1410
|
file: module.file,
|
|
1082
|
-
line: switchNode
|
|
1411
|
+
line: getLine(switchNode),
|
|
1083
1412
|
parentScopeId
|
|
1084
1413
|
});
|
|
1085
1414
|
},
|
|
1086
1415
|
FunctionExpression: (funcPath) => {
|
|
1087
1416
|
const node = funcPath.node;
|
|
1088
|
-
const funcName = node.id ? node.id.name : this.generateAnonymousName(
|
|
1089
|
-
|
|
1417
|
+
const funcName = node.id ? node.id.name : this.generateAnonymousName(scopeTracker);
|
|
1418
|
+
// Use semantic ID as primary ID when scopeTracker available
|
|
1419
|
+
const legacyId = `FUNCTION#${funcName}#${module.file}#${getLine(node)}:${getColumn(node)}:${functionCounterRef.value++}`;
|
|
1420
|
+
const functionId = scopeTracker
|
|
1421
|
+
? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
|
|
1422
|
+
: legacyId;
|
|
1090
1423
|
functions.push({
|
|
1091
1424
|
id: functionId,
|
|
1092
|
-
stableId: functionId,
|
|
1093
1425
|
type: 'FUNCTION',
|
|
1094
1426
|
name: funcName,
|
|
1095
1427
|
file: module.file,
|
|
1096
|
-
line: node
|
|
1097
|
-
column: node
|
|
1428
|
+
line: getLine(node),
|
|
1429
|
+
column: getColumn(node),
|
|
1098
1430
|
async: node.async || false,
|
|
1099
1431
|
generator: node.generator || false,
|
|
1100
1432
|
parentScopeId
|
|
1101
1433
|
});
|
|
1102
|
-
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${node
|
|
1103
|
-
const closureSemanticId = this.generateSemanticId('closure',
|
|
1434
|
+
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${getLine(node)}`;
|
|
1435
|
+
const closureSemanticId = this.generateSemanticId('closure', scopeTracker);
|
|
1104
1436
|
scopes.push({
|
|
1105
1437
|
id: nestedScopeId,
|
|
1106
1438
|
type: 'SCOPE',
|
|
@@ -1109,22 +1441,24 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1109
1441
|
semanticId: closureSemanticId,
|
|
1110
1442
|
conditional: false,
|
|
1111
1443
|
file: module.file,
|
|
1112
|
-
line: node
|
|
1444
|
+
line: getLine(node),
|
|
1113
1445
|
parentFunctionId: functionId,
|
|
1114
1446
|
capturesFrom: parentScopeId
|
|
1115
1447
|
});
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1448
|
+
// Enter nested function scope for semantic ID generation
|
|
1449
|
+
if (scopeTracker) {
|
|
1450
|
+
scopeTracker.enterScope(funcName, 'function');
|
|
1451
|
+
}
|
|
1452
|
+
this.analyzeFunctionBody(funcPath, nestedScopeId, module, collections);
|
|
1453
|
+
if (scopeTracker) {
|
|
1454
|
+
scopeTracker.exitScope();
|
|
1455
|
+
}
|
|
1122
1456
|
funcPath.skip();
|
|
1123
1457
|
},
|
|
1124
1458
|
ArrowFunctionExpression: (arrowPath) => {
|
|
1125
1459
|
const node = arrowPath.node;
|
|
1126
|
-
const line = node
|
|
1127
|
-
const column = node
|
|
1460
|
+
const line = getLine(node);
|
|
1461
|
+
const column = getColumn(node);
|
|
1128
1462
|
// Определяем имя (anonymous если не присвоено переменной)
|
|
1129
1463
|
const parent = arrowPath.parent;
|
|
1130
1464
|
let funcName;
|
|
@@ -1133,12 +1467,15 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1133
1467
|
}
|
|
1134
1468
|
else {
|
|
1135
1469
|
// Используем scope-level счётчик для стабильного semanticId
|
|
1136
|
-
funcName = this.generateAnonymousName(
|
|
1470
|
+
funcName = this.generateAnonymousName(scopeTracker);
|
|
1137
1471
|
}
|
|
1138
|
-
|
|
1472
|
+
// Use semantic ID as primary ID when scopeTracker available
|
|
1473
|
+
const legacyId = `FUNCTION#${funcName}:${line}:${column}:${functionCounterRef.value++}`;
|
|
1474
|
+
const functionId = scopeTracker
|
|
1475
|
+
? computeSemanticId('FUNCTION', funcName, scopeTracker.getContext())
|
|
1476
|
+
: legacyId;
|
|
1139
1477
|
functions.push({
|
|
1140
1478
|
id: functionId,
|
|
1141
|
-
stableId: functionId,
|
|
1142
1479
|
type: 'FUNCTION',
|
|
1143
1480
|
name: funcName,
|
|
1144
1481
|
file: module.file,
|
|
@@ -1150,7 +1487,7 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1150
1487
|
});
|
|
1151
1488
|
if (node.body.type === 'BlockStatement') {
|
|
1152
1489
|
const nestedScopeId = `SCOPE#${funcName}:body#${module.file}#${line}`;
|
|
1153
|
-
const arrowSemanticId = this.generateSemanticId('arrow_body',
|
|
1490
|
+
const arrowSemanticId = this.generateSemanticId('arrow_body', scopeTracker);
|
|
1154
1491
|
scopes.push({
|
|
1155
1492
|
id: nestedScopeId,
|
|
1156
1493
|
type: 'SCOPE',
|
|
@@ -1163,12 +1500,14 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1163
1500
|
parentFunctionId: functionId,
|
|
1164
1501
|
capturesFrom: parentScopeId
|
|
1165
1502
|
});
|
|
1166
|
-
//
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1503
|
+
// Enter arrow function scope for semantic ID generation
|
|
1504
|
+
if (scopeTracker) {
|
|
1505
|
+
scopeTracker.enterScope(funcName, 'arrow');
|
|
1506
|
+
}
|
|
1507
|
+
this.analyzeFunctionBody(arrowPath, nestedScopeId, module, collections);
|
|
1508
|
+
if (scopeTracker) {
|
|
1509
|
+
scopeTracker.exitScope();
|
|
1510
|
+
}
|
|
1172
1511
|
}
|
|
1173
1512
|
arrowPath.skip();
|
|
1174
1513
|
},
|
|
@@ -1188,191 +1527,552 @@ export class JSASTAnalyzer extends Plugin {
|
|
|
1188
1527
|
scope.modifies.push({
|
|
1189
1528
|
variableId: variable.id,
|
|
1190
1529
|
variableName: varName,
|
|
1191
|
-
line: updateNode
|
|
1530
|
+
line: getLine(updateNode)
|
|
1192
1531
|
});
|
|
1193
1532
|
}
|
|
1194
1533
|
}
|
|
1195
1534
|
}
|
|
1196
1535
|
},
|
|
1197
1536
|
// IF statements - создаём условные scope и обходим содержимое для CALL узлов
|
|
1198
|
-
IfStatement: (
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
conditional: true,
|
|
1214
|
-
condition,
|
|
1215
|
-
constraints: constraints.length > 0 ? constraints : undefined,
|
|
1216
|
-
file: module.file,
|
|
1217
|
-
line: ifNode.loc.start.line,
|
|
1218
|
-
parentScopeId
|
|
1219
|
-
});
|
|
1220
|
-
// Обходим содержимое if-блока (consequent)
|
|
1221
|
-
ifPath.get('consequent').traverse({
|
|
1222
|
-
CallExpression: (callPath) => {
|
|
1223
|
-
const callNode = callPath.node;
|
|
1224
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1225
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1226
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
processedCallSites.add(nodeKey);
|
|
1230
|
-
callSites.push({
|
|
1231
|
-
id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc.start.line}:${callNode.loc.start.column}:${callSiteCounterRef.value++}`,
|
|
1232
|
-
type: 'CALL',
|
|
1233
|
-
name: callNode.callee.name,
|
|
1234
|
-
file: module.file,
|
|
1235
|
-
line: callNode.loc.start.line,
|
|
1236
|
-
parentScopeId: ifScopeId,
|
|
1237
|
-
targetFunctionName: callNode.callee.name
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
},
|
|
1241
|
-
NewExpression: (newPath) => {
|
|
1242
|
-
const newNode = newPath.node;
|
|
1243
|
-
if (newNode.callee.type === 'Identifier') {
|
|
1244
|
-
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1245
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1248
|
-
processedCallSites.add(nodeKey);
|
|
1249
|
-
callSites.push({
|
|
1250
|
-
id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc.start.line}:${newNode.loc.start.column}:${callSiteCounterRef.value++}`,
|
|
1251
|
-
type: 'CALL',
|
|
1252
|
-
name: newNode.callee.name,
|
|
1253
|
-
file: module.file,
|
|
1254
|
-
line: newNode.loc.start.line,
|
|
1255
|
-
parentScopeId: ifScopeId,
|
|
1256
|
-
targetFunctionName: newNode.callee.name,
|
|
1257
|
-
isNew: true
|
|
1258
|
-
});
|
|
1259
|
-
}
|
|
1537
|
+
IfStatement: this.createIfStatementHandler(parentScopeId, module, scopes, ifScopeCounterRef, scopeTracker, collections.code ?? '', ifElseScopeMap),
|
|
1538
|
+
// Track when we enter the alternate (else) block of an IfStatement
|
|
1539
|
+
BlockStatement: this.createIfElseBlockStatementHandler(scopeTracker, ifElseScopeMap),
|
|
1540
|
+
// Function call expressions
|
|
1541
|
+
CallExpression: (callPath) => {
|
|
1542
|
+
this.handleCallExpression(callPath.node, processedCallSites, processedMethodCalls, callSites, methodCalls, module, callSiteCounterRef, scopeTracker, parentScopeId, collections);
|
|
1543
|
+
},
|
|
1544
|
+
// NewExpression (constructor calls)
|
|
1545
|
+
NewExpression: (newPath) => {
|
|
1546
|
+
const newNode = newPath.node;
|
|
1547
|
+
// Handle simple constructor: new Foo()
|
|
1548
|
+
if (newNode.callee.type === 'Identifier') {
|
|
1549
|
+
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1550
|
+
if (processedCallSites.has(nodeKey)) {
|
|
1551
|
+
return;
|
|
1260
1552
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
name:
|
|
1274
|
-
semanticId: elseSemanticId,
|
|
1275
|
-
conditional: true,
|
|
1276
|
-
constraints: negatedConstraints,
|
|
1553
|
+
processedCallSites.add(nodeKey);
|
|
1554
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
1555
|
+
const constructorName = newNode.callee.name;
|
|
1556
|
+
const legacyId = `CALL#new:${constructorName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
|
|
1557
|
+
let newCallId = legacyId;
|
|
1558
|
+
if (scopeTracker) {
|
|
1559
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:new:${constructorName}`);
|
|
1560
|
+
newCallId = computeSemanticId('CALL', `new:${constructorName}`, scopeTracker.getContext(), { discriminator });
|
|
1561
|
+
}
|
|
1562
|
+
callSites.push({
|
|
1563
|
+
id: newCallId,
|
|
1564
|
+
type: 'CALL',
|
|
1565
|
+
name: constructorName,
|
|
1277
1566
|
file: module.file,
|
|
1278
|
-
line:
|
|
1279
|
-
parentScopeId
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
ifPath.get('alternate').traverse({
|
|
1283
|
-
CallExpression: (callPath) => {
|
|
1284
|
-
const callNode = callPath.node;
|
|
1285
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1286
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1287
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
processedCallSites.add(nodeKey);
|
|
1291
|
-
callSites.push({
|
|
1292
|
-
id: `CALL#${callNode.callee.name}#${module.file}#${callNode.loc.start.line}:${callNode.loc.start.column}:${callSiteCounterRef.value++}`,
|
|
1293
|
-
type: 'CALL',
|
|
1294
|
-
name: callNode.callee.name,
|
|
1295
|
-
file: module.file,
|
|
1296
|
-
line: callNode.loc.start.line,
|
|
1297
|
-
parentScopeId: elseScopeId,
|
|
1298
|
-
targetFunctionName: callNode.callee.name
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
},
|
|
1302
|
-
NewExpression: (newPath) => {
|
|
1303
|
-
const newNode = newPath.node;
|
|
1304
|
-
if (newNode.callee.type === 'Identifier') {
|
|
1305
|
-
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1306
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
processedCallSites.add(nodeKey);
|
|
1310
|
-
callSites.push({
|
|
1311
|
-
id: `CALL#new:${newNode.callee.name}#${module.file}#${newNode.loc.start.line}:${newNode.loc.start.column}:${callSiteCounterRef.value++}`,
|
|
1312
|
-
type: 'CALL',
|
|
1313
|
-
name: newNode.callee.name,
|
|
1314
|
-
file: module.file,
|
|
1315
|
-
line: newNode.loc.start.line,
|
|
1316
|
-
parentScopeId: elseScopeId,
|
|
1317
|
-
targetFunctionName: newNode.callee.name,
|
|
1318
|
-
isNew: true
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1567
|
+
line: getLine(newNode),
|
|
1568
|
+
parentScopeId,
|
|
1569
|
+
targetFunctionName: constructorName,
|
|
1570
|
+
isNew: true
|
|
1322
1571
|
});
|
|
1323
1572
|
}
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const callNode = callPath.node;
|
|
1333
|
-
// Обычные вызовы функций (greet(), main())
|
|
1334
|
-
if (callNode.callee.type === 'Identifier') {
|
|
1335
|
-
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1336
|
-
if (processedCallSites.has(nodeKey)) {
|
|
1573
|
+
// Handle namespaced constructor: new ns.Constructor()
|
|
1574
|
+
else if (newNode.callee.type === 'MemberExpression') {
|
|
1575
|
+
const memberCallee = newNode.callee;
|
|
1576
|
+
const object = memberCallee.object;
|
|
1577
|
+
const property = memberCallee.property;
|
|
1578
|
+
if (object.type === 'Identifier' && property.type === 'Identifier') {
|
|
1579
|
+
const nodeKey = `new:${newNode.start}:${newNode.end}`;
|
|
1580
|
+
if (processedMethodCalls.has(nodeKey)) {
|
|
1337
1581
|
return;
|
|
1338
1582
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1583
|
+
processedMethodCalls.add(nodeKey);
|
|
1584
|
+
const objectName = object.name;
|
|
1585
|
+
const constructorName = property.name;
|
|
1586
|
+
const fullName = `${objectName}.${constructorName}`;
|
|
1587
|
+
// Generate semantic ID for method-style constructor call
|
|
1588
|
+
const legacyId = `CALL#new:${fullName}#${module.file}#${getLine(newNode)}:${getColumn(newNode)}:${callSiteCounterRef.value++}`;
|
|
1589
|
+
let newMethodCallId = legacyId;
|
|
1590
|
+
if (scopeTracker) {
|
|
1591
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:new:${fullName}`);
|
|
1592
|
+
newMethodCallId = computeSemanticId('CALL', `new:${fullName}`, scopeTracker.getContext(), { discriminator });
|
|
1593
|
+
}
|
|
1594
|
+
methodCalls.push({
|
|
1595
|
+
id: newMethodCallId,
|
|
1342
1596
|
type: 'CALL',
|
|
1343
|
-
name:
|
|
1597
|
+
name: fullName,
|
|
1598
|
+
object: objectName,
|
|
1599
|
+
method: constructorName,
|
|
1344
1600
|
file: module.file,
|
|
1345
|
-
line:
|
|
1601
|
+
line: getLine(newNode),
|
|
1602
|
+
column: getColumn(newNode),
|
|
1346
1603
|
parentScopeId,
|
|
1347
|
-
|
|
1604
|
+
isNew: true
|
|
1348
1605
|
});
|
|
1349
1606
|
}
|
|
1350
1607
|
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Handle CallExpression nodes: direct function calls (greet(), main())
|
|
1613
|
+
* and method calls (obj.method(), data.process()).
|
|
1614
|
+
*
|
|
1615
|
+
* Handles:
|
|
1616
|
+
* - Direct function calls (Identifier callee) → callSites collection
|
|
1617
|
+
* - Method calls (MemberExpression callee) → methodCalls collection
|
|
1618
|
+
* - Array mutation detection (push, unshift, splice)
|
|
1619
|
+
* - Object.assign() detection
|
|
1620
|
+
*
|
|
1621
|
+
* @param callNode - The call expression AST node
|
|
1622
|
+
* @param processedCallSites - Set of already processed call site keys to avoid duplicates
|
|
1623
|
+
* @param processedMethodCalls - Set of already processed method call keys to avoid duplicates
|
|
1624
|
+
* @param callSites - Collection for direct function calls
|
|
1625
|
+
* @param methodCalls - Collection for method calls
|
|
1626
|
+
* @param module - Current module being analyzed
|
|
1627
|
+
* @param callSiteCounterRef - Counter for legacy ID generation
|
|
1628
|
+
* @param scopeTracker - Optional scope tracker for semantic ID generation
|
|
1629
|
+
* @param parentScopeId - ID of the parent scope containing this call
|
|
1630
|
+
* @param collections - Full collections object for array/object mutations
|
|
1631
|
+
*/
|
|
1632
|
+
handleCallExpression(callNode, processedCallSites, processedMethodCalls, callSites, methodCalls, module, callSiteCounterRef, scopeTracker, parentScopeId, collections) {
|
|
1633
|
+
// Handle direct function calls (greet(), main())
|
|
1634
|
+
if (callNode.callee.type === 'Identifier') {
|
|
1635
|
+
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1636
|
+
if (processedCallSites.has(nodeKey)) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
processedCallSites.add(nodeKey);
|
|
1640
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
1641
|
+
const calleeName = callNode.callee.name;
|
|
1642
|
+
const legacyId = `CALL#${calleeName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
|
|
1643
|
+
let callId = legacyId;
|
|
1644
|
+
if (scopeTracker) {
|
|
1645
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:${calleeName}`);
|
|
1646
|
+
callId = computeSemanticId('CALL', calleeName, scopeTracker.getContext(), { discriminator });
|
|
1647
|
+
}
|
|
1648
|
+
callSites.push({
|
|
1649
|
+
id: callId,
|
|
1650
|
+
type: 'CALL',
|
|
1651
|
+
name: calleeName,
|
|
1652
|
+
file: module.file,
|
|
1653
|
+
line: getLine(callNode),
|
|
1654
|
+
parentScopeId,
|
|
1655
|
+
targetFunctionName: calleeName
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
// Handle method calls (obj.method(), data.process())
|
|
1659
|
+
else if (callNode.callee.type === 'MemberExpression') {
|
|
1660
|
+
const memberCallee = callNode.callee;
|
|
1661
|
+
const object = memberCallee.object;
|
|
1662
|
+
const property = memberCallee.property;
|
|
1663
|
+
const isComputed = memberCallee.computed;
|
|
1664
|
+
if ((object.type === 'Identifier' || object.type === 'ThisExpression') && property.type === 'Identifier') {
|
|
1665
|
+
const nodeKey = `${callNode.start}:${callNode.end}`;
|
|
1666
|
+
if (processedMethodCalls.has(nodeKey)) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
processedMethodCalls.add(nodeKey);
|
|
1670
|
+
const objectName = object.type === 'Identifier' ? object.name : 'this';
|
|
1671
|
+
const methodName = isComputed ? '<computed>' : property.name;
|
|
1672
|
+
const fullName = `${objectName}.${methodName}`;
|
|
1673
|
+
// Generate semantic ID (primary) or legacy ID (fallback)
|
|
1674
|
+
const legacyId = `CALL#${fullName}#${module.file}#${getLine(callNode)}:${getColumn(callNode)}:${callSiteCounterRef.value++}`;
|
|
1675
|
+
let methodCallId = legacyId;
|
|
1676
|
+
if (scopeTracker) {
|
|
1677
|
+
const discriminator = scopeTracker.getItemCounter(`CALL:${fullName}`);
|
|
1678
|
+
methodCallId = computeSemanticId('CALL', fullName, scopeTracker.getContext(), { discriminator });
|
|
1679
|
+
}
|
|
1680
|
+
methodCalls.push({
|
|
1681
|
+
id: methodCallId,
|
|
1682
|
+
type: 'CALL',
|
|
1683
|
+
name: fullName,
|
|
1684
|
+
object: objectName,
|
|
1685
|
+
method: methodName,
|
|
1686
|
+
computed: isComputed,
|
|
1687
|
+
computedPropertyVar: isComputed ? property.name : null,
|
|
1688
|
+
file: module.file,
|
|
1689
|
+
line: getLine(callNode),
|
|
1690
|
+
column: getColumn(callNode),
|
|
1691
|
+
parentScopeId
|
|
1692
|
+
});
|
|
1693
|
+
// Check for array mutation methods (push, unshift, splice)
|
|
1694
|
+
const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
|
|
1695
|
+
if (ARRAY_MUTATION_METHODS.includes(methodName)) {
|
|
1696
|
+
// Initialize collection if not exists
|
|
1697
|
+
if (!collections.arrayMutations) {
|
|
1698
|
+
collections.arrayMutations = [];
|
|
1699
|
+
}
|
|
1700
|
+
const arrayMutations = collections.arrayMutations;
|
|
1701
|
+
this.detectArrayMutationInFunction(callNode, objectName, methodName, module, arrayMutations, scopeTracker);
|
|
1702
|
+
}
|
|
1703
|
+
// Check for Object.assign() calls
|
|
1704
|
+
if (objectName === 'Object' && methodName === 'assign') {
|
|
1705
|
+
// Initialize collection if not exists
|
|
1706
|
+
if (!collections.objectMutations) {
|
|
1707
|
+
collections.objectMutations = [];
|
|
1708
|
+
}
|
|
1709
|
+
const objectMutations = collections.objectMutations;
|
|
1710
|
+
this.detectObjectAssignInFunction(callNode, module, objectMutations, scopeTracker);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
// REG-117: Nested array mutations like obj.arr.push(item)
|
|
1714
|
+
// object is MemberExpression, property is the method name
|
|
1715
|
+
else if (object.type === 'MemberExpression' && property.type === 'Identifier') {
|
|
1716
|
+
const nestedMember = object;
|
|
1717
|
+
const methodName = property.name;
|
|
1718
|
+
const ARRAY_MUTATION_METHODS = ['push', 'unshift', 'splice'];
|
|
1719
|
+
if (ARRAY_MUTATION_METHODS.includes(methodName)) {
|
|
1720
|
+
// Extract base object and property from nested MemberExpression
|
|
1721
|
+
const base = nestedMember.object;
|
|
1722
|
+
const prop = nestedMember.property;
|
|
1723
|
+
// Only handle single-level nesting: obj.arr.push() or this.items.push()
|
|
1724
|
+
if ((base.type === 'Identifier' || base.type === 'ThisExpression') &&
|
|
1725
|
+
!nestedMember.computed &&
|
|
1726
|
+
prop.type === 'Identifier') {
|
|
1727
|
+
const baseObjectName = base.type === 'Identifier' ? base.name : 'this';
|
|
1728
|
+
const propertyName = prop.name;
|
|
1729
|
+
// Initialize collection if not exists
|
|
1730
|
+
if (!collections.arrayMutations) {
|
|
1731
|
+
collections.arrayMutations = [];
|
|
1361
1732
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
name: newNode.callee.name,
|
|
1367
|
-
file: module.file,
|
|
1368
|
-
line: newNode.loc.start.line,
|
|
1369
|
-
parentScopeId,
|
|
1370
|
-
targetFunctionName: newNode.callee.name,
|
|
1371
|
-
isNew: true
|
|
1372
|
-
});
|
|
1733
|
+
const arrayMutations = collections.arrayMutations;
|
|
1734
|
+
this.detectArrayMutationInFunction(callNode, `${baseObjectName}.${propertyName}`, // arrayName for ID purposes
|
|
1735
|
+
methodName, module, arrayMutations, scopeTracker, true, // isNested
|
|
1736
|
+
baseObjectName, propertyName);
|
|
1373
1737
|
}
|
|
1374
1738
|
}
|
|
1375
1739
|
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Detect array mutation calls (push, unshift, splice) inside functions
|
|
1744
|
+
* and collect mutation info for FLOWS_INTO edge creation in GraphBuilder
|
|
1745
|
+
*
|
|
1746
|
+
* REG-117: Added isNested, baseObjectName, propertyName for nested mutations
|
|
1747
|
+
*
|
|
1748
|
+
* @param callNode - The call expression node
|
|
1749
|
+
* @param arrayName - Name of the array being mutated
|
|
1750
|
+
* @param method - The mutation method (push, unshift, splice)
|
|
1751
|
+
* @param module - Current module being analyzed
|
|
1752
|
+
* @param arrayMutations - Collection to push mutation info into
|
|
1753
|
+
* @param scopeTracker - Optional scope tracker for semantic IDs
|
|
1754
|
+
* @param isNested - REG-117: true if this is a nested mutation (obj.arr.push)
|
|
1755
|
+
* @param baseObjectName - REG-117: base object name for nested mutations
|
|
1756
|
+
* @param propertyName - REG-117: property name for nested mutations
|
|
1757
|
+
*/
|
|
1758
|
+
detectArrayMutationInFunction(callNode, arrayName, method, module, arrayMutations, scopeTracker, isNested, baseObjectName, propertyName) {
|
|
1759
|
+
const mutationArgs = [];
|
|
1760
|
+
// For splice, only arguments from index 2 onwards are insertions
|
|
1761
|
+
// splice(start, deleteCount, item1, item2, ...)
|
|
1762
|
+
callNode.arguments.forEach((arg, index) => {
|
|
1763
|
+
// Skip start and deleteCount for splice
|
|
1764
|
+
if (method === 'splice' && index < 2)
|
|
1765
|
+
return;
|
|
1766
|
+
const argInfo = {
|
|
1767
|
+
argIndex: method === 'splice' ? index - 2 : index,
|
|
1768
|
+
isSpread: arg.type === 'SpreadElement',
|
|
1769
|
+
valueType: 'EXPRESSION' // Default
|
|
1770
|
+
};
|
|
1771
|
+
let actualArg = arg;
|
|
1772
|
+
if (arg.type === 'SpreadElement') {
|
|
1773
|
+
actualArg = arg.argument;
|
|
1774
|
+
}
|
|
1775
|
+
// Determine value type
|
|
1776
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(actualArg);
|
|
1777
|
+
if (literalValue !== null) {
|
|
1778
|
+
argInfo.valueType = 'LITERAL';
|
|
1779
|
+
argInfo.literalValue = literalValue;
|
|
1780
|
+
}
|
|
1781
|
+
else if (actualArg.type === 'Identifier') {
|
|
1782
|
+
argInfo.valueType = 'VARIABLE';
|
|
1783
|
+
argInfo.valueName = actualArg.name;
|
|
1784
|
+
}
|
|
1785
|
+
else if (actualArg.type === 'ObjectExpression') {
|
|
1786
|
+
argInfo.valueType = 'OBJECT_LITERAL';
|
|
1787
|
+
}
|
|
1788
|
+
else if (actualArg.type === 'ArrayExpression') {
|
|
1789
|
+
argInfo.valueType = 'ARRAY_LITERAL';
|
|
1790
|
+
}
|
|
1791
|
+
else if (actualArg.type === 'CallExpression') {
|
|
1792
|
+
argInfo.valueType = 'CALL';
|
|
1793
|
+
argInfo.callLine = actualArg.loc?.start.line;
|
|
1794
|
+
argInfo.callColumn = actualArg.loc?.start.column;
|
|
1795
|
+
}
|
|
1796
|
+
mutationArgs.push(argInfo);
|
|
1376
1797
|
});
|
|
1798
|
+
// Only record if there are actual insertions
|
|
1799
|
+
if (mutationArgs.length > 0) {
|
|
1800
|
+
const line = callNode.loc?.start.line ?? 0;
|
|
1801
|
+
const column = callNode.loc?.start.column ?? 0;
|
|
1802
|
+
// Generate semantic ID for array mutation if scopeTracker available
|
|
1803
|
+
let mutationId;
|
|
1804
|
+
if (scopeTracker) {
|
|
1805
|
+
const discriminator = scopeTracker.getItemCounter(`ARRAY_MUTATION:${arrayName}.${method}`);
|
|
1806
|
+
mutationId = computeSemanticId('ARRAY_MUTATION', `${arrayName}.${method}`, scopeTracker.getContext(), { discriminator });
|
|
1807
|
+
}
|
|
1808
|
+
arrayMutations.push({
|
|
1809
|
+
id: mutationId,
|
|
1810
|
+
arrayName,
|
|
1811
|
+
mutationMethod: method,
|
|
1812
|
+
file: module.file,
|
|
1813
|
+
line,
|
|
1814
|
+
column,
|
|
1815
|
+
insertedValues: mutationArgs,
|
|
1816
|
+
// REG-117: Nested mutation fields
|
|
1817
|
+
isNested,
|
|
1818
|
+
baseObjectName,
|
|
1819
|
+
propertyName
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Detect indexed array assignment: arr[i] = value
|
|
1825
|
+
* Creates ArrayMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
1826
|
+
*
|
|
1827
|
+
* @param assignNode - The assignment expression node
|
|
1828
|
+
* @param module - Current module being analyzed
|
|
1829
|
+
* @param arrayMutations - Collection to push mutation info into
|
|
1830
|
+
*/
|
|
1831
|
+
detectIndexedArrayAssignment(assignNode, module, arrayMutations) {
|
|
1832
|
+
// Check for indexed array assignment: arr[i] = value
|
|
1833
|
+
if (assignNode.left.type === 'MemberExpression' && assignNode.left.computed) {
|
|
1834
|
+
const memberExpr = assignNode.left;
|
|
1835
|
+
// Only process NumericLiteral keys - those are clearly array indexed assignments
|
|
1836
|
+
// e.g., arr[0] = value, arr[1] = value
|
|
1837
|
+
// All other computed keys (StringLiteral, Identifier, expressions) are handled as object mutations
|
|
1838
|
+
// This avoids duplicate edge creation for ambiguous cases like obj[key] = value
|
|
1839
|
+
if (memberExpr.property.type !== 'NumericLiteral') {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
// Get array name (only simple identifiers for now)
|
|
1843
|
+
if (memberExpr.object.type === 'Identifier') {
|
|
1844
|
+
const arrayName = memberExpr.object.name;
|
|
1845
|
+
const value = assignNode.right;
|
|
1846
|
+
const argInfo = {
|
|
1847
|
+
argIndex: 0,
|
|
1848
|
+
isSpread: false,
|
|
1849
|
+
valueType: 'EXPRESSION'
|
|
1850
|
+
};
|
|
1851
|
+
// Determine value type
|
|
1852
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(value);
|
|
1853
|
+
if (literalValue !== null) {
|
|
1854
|
+
argInfo.valueType = 'LITERAL';
|
|
1855
|
+
argInfo.literalValue = literalValue;
|
|
1856
|
+
}
|
|
1857
|
+
else if (value.type === 'Identifier') {
|
|
1858
|
+
argInfo.valueType = 'VARIABLE';
|
|
1859
|
+
argInfo.valueName = value.name;
|
|
1860
|
+
}
|
|
1861
|
+
else if (value.type === 'ObjectExpression') {
|
|
1862
|
+
argInfo.valueType = 'OBJECT_LITERAL';
|
|
1863
|
+
}
|
|
1864
|
+
else if (value.type === 'ArrayExpression') {
|
|
1865
|
+
argInfo.valueType = 'ARRAY_LITERAL';
|
|
1866
|
+
}
|
|
1867
|
+
else if (value.type === 'CallExpression') {
|
|
1868
|
+
argInfo.valueType = 'CALL';
|
|
1869
|
+
argInfo.callLine = value.loc?.start.line;
|
|
1870
|
+
argInfo.callColumn = value.loc?.start.column;
|
|
1871
|
+
}
|
|
1872
|
+
// Use defensive loc checks instead of ! assertions
|
|
1873
|
+
const line = assignNode.loc?.start.line ?? 0;
|
|
1874
|
+
const column = assignNode.loc?.start.column ?? 0;
|
|
1875
|
+
arrayMutations.push({
|
|
1876
|
+
arrayName,
|
|
1877
|
+
mutationMethod: 'indexed',
|
|
1878
|
+
file: module.file,
|
|
1879
|
+
line: line,
|
|
1880
|
+
column: column,
|
|
1881
|
+
insertedValues: [argInfo]
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Detect object property assignment: obj.prop = value, obj['prop'] = value
|
|
1888
|
+
* Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
1889
|
+
*
|
|
1890
|
+
* @param assignNode - The assignment expression node
|
|
1891
|
+
* @param module - Current module being analyzed
|
|
1892
|
+
* @param objectMutations - Collection to push mutation info into
|
|
1893
|
+
* @param scopeTracker - Optional scope tracker for semantic IDs
|
|
1894
|
+
*/
|
|
1895
|
+
detectObjectPropertyAssignment(assignNode, module, objectMutations, scopeTracker) {
|
|
1896
|
+
// Check for property assignment: obj.prop = value or obj['prop'] = value
|
|
1897
|
+
if (assignNode.left.type !== 'MemberExpression')
|
|
1898
|
+
return;
|
|
1899
|
+
const memberExpr = assignNode.left;
|
|
1900
|
+
// Skip NumericLiteral indexed assignment (handled by array mutation handler)
|
|
1901
|
+
// Array mutation handler processes: arr[0] (numeric literal index)
|
|
1902
|
+
// Object mutation handler processes: obj.prop, obj['prop'], obj[key], obj[expr]
|
|
1903
|
+
if (memberExpr.computed && memberExpr.property.type === 'NumericLiteral') {
|
|
1904
|
+
return; // Let array mutation handler deal with this
|
|
1905
|
+
}
|
|
1906
|
+
// Get object name and enclosing class context for 'this'
|
|
1907
|
+
let objectName;
|
|
1908
|
+
let enclosingClassName;
|
|
1909
|
+
if (memberExpr.object.type === 'Identifier') {
|
|
1910
|
+
objectName = memberExpr.object.name;
|
|
1911
|
+
}
|
|
1912
|
+
else if (memberExpr.object.type === 'ThisExpression') {
|
|
1913
|
+
objectName = 'this';
|
|
1914
|
+
// REG-152: Extract enclosing class name from scope context
|
|
1915
|
+
if (scopeTracker) {
|
|
1916
|
+
enclosingClassName = scopeTracker.getEnclosingScope('CLASS');
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
else {
|
|
1920
|
+
// Complex expressions like obj.nested.prop = value
|
|
1921
|
+
// For now, skip these (documented limitation)
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
// Get property name
|
|
1925
|
+
let propertyName;
|
|
1926
|
+
let mutationType;
|
|
1927
|
+
let computedPropertyVar;
|
|
1928
|
+
if (!memberExpr.computed) {
|
|
1929
|
+
// obj.prop
|
|
1930
|
+
if (memberExpr.property.type === 'Identifier') {
|
|
1931
|
+
propertyName = memberExpr.property.name;
|
|
1932
|
+
mutationType = 'property';
|
|
1933
|
+
}
|
|
1934
|
+
else {
|
|
1935
|
+
return; // Unexpected property type
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
else {
|
|
1939
|
+
// obj['prop'] or obj[key]
|
|
1940
|
+
if (memberExpr.property.type === 'StringLiteral') {
|
|
1941
|
+
propertyName = memberExpr.property.value;
|
|
1942
|
+
mutationType = 'property'; // String literal is effectively a property name
|
|
1943
|
+
}
|
|
1944
|
+
else {
|
|
1945
|
+
propertyName = '<computed>';
|
|
1946
|
+
mutationType = 'computed';
|
|
1947
|
+
// Capture variable name for later resolution in enrichment phase
|
|
1948
|
+
if (memberExpr.property.type === 'Identifier') {
|
|
1949
|
+
computedPropertyVar = memberExpr.property.name;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
// Extract value info
|
|
1954
|
+
const value = assignNode.right;
|
|
1955
|
+
const valueInfo = this.extractMutationValue(value);
|
|
1956
|
+
// Use defensive loc checks
|
|
1957
|
+
const line = assignNode.loc?.start.line ?? 0;
|
|
1958
|
+
const column = assignNode.loc?.start.column ?? 0;
|
|
1959
|
+
// Generate semantic ID if scopeTracker available
|
|
1960
|
+
let mutationId;
|
|
1961
|
+
if (scopeTracker) {
|
|
1962
|
+
const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:${objectName}.${propertyName}`);
|
|
1963
|
+
mutationId = computeSemanticId('OBJECT_MUTATION', `${objectName}.${propertyName}`, scopeTracker.getContext(), { discriminator });
|
|
1964
|
+
}
|
|
1965
|
+
objectMutations.push({
|
|
1966
|
+
id: mutationId,
|
|
1967
|
+
objectName,
|
|
1968
|
+
enclosingClassName, // REG-152: Class name for 'this' mutations
|
|
1969
|
+
propertyName,
|
|
1970
|
+
mutationType,
|
|
1971
|
+
computedPropertyVar,
|
|
1972
|
+
file: module.file,
|
|
1973
|
+
line,
|
|
1974
|
+
column,
|
|
1975
|
+
value: valueInfo
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Extract value information from an expression for mutation tracking
|
|
1980
|
+
*/
|
|
1981
|
+
extractMutationValue(value) {
|
|
1982
|
+
const valueInfo = {
|
|
1983
|
+
valueType: 'EXPRESSION' // Default
|
|
1984
|
+
};
|
|
1985
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(value);
|
|
1986
|
+
if (literalValue !== null) {
|
|
1987
|
+
valueInfo.valueType = 'LITERAL';
|
|
1988
|
+
valueInfo.literalValue = literalValue;
|
|
1989
|
+
}
|
|
1990
|
+
else if (value.type === 'Identifier') {
|
|
1991
|
+
valueInfo.valueType = 'VARIABLE';
|
|
1992
|
+
valueInfo.valueName = value.name;
|
|
1993
|
+
}
|
|
1994
|
+
else if (value.type === 'ObjectExpression') {
|
|
1995
|
+
valueInfo.valueType = 'OBJECT_LITERAL';
|
|
1996
|
+
}
|
|
1997
|
+
else if (value.type === 'ArrayExpression') {
|
|
1998
|
+
valueInfo.valueType = 'ARRAY_LITERAL';
|
|
1999
|
+
}
|
|
2000
|
+
else if (value.type === 'CallExpression') {
|
|
2001
|
+
valueInfo.valueType = 'CALL';
|
|
2002
|
+
valueInfo.callLine = value.loc?.start.line;
|
|
2003
|
+
valueInfo.callColumn = value.loc?.start.column;
|
|
2004
|
+
}
|
|
2005
|
+
return valueInfo;
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Detect Object.assign() calls inside functions
|
|
2009
|
+
* Creates ObjectMutationInfo for FLOWS_INTO edge generation in GraphBuilder
|
|
2010
|
+
*/
|
|
2011
|
+
detectObjectAssignInFunction(callNode, module, objectMutations, scopeTracker) {
|
|
2012
|
+
// Need at least 2 arguments: target and at least one source
|
|
2013
|
+
if (callNode.arguments.length < 2)
|
|
2014
|
+
return;
|
|
2015
|
+
// First argument is target
|
|
2016
|
+
const targetArg = callNode.arguments[0];
|
|
2017
|
+
let targetName;
|
|
2018
|
+
if (targetArg.type === 'Identifier') {
|
|
2019
|
+
targetName = targetArg.name;
|
|
2020
|
+
}
|
|
2021
|
+
else if (targetArg.type === 'ObjectExpression') {
|
|
2022
|
+
targetName = '<anonymous>';
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const line = callNode.loc?.start.line ?? 0;
|
|
2028
|
+
const column = callNode.loc?.start.column ?? 0;
|
|
2029
|
+
for (let i = 1; i < callNode.arguments.length; i++) {
|
|
2030
|
+
let arg = callNode.arguments[i];
|
|
2031
|
+
let isSpread = false;
|
|
2032
|
+
if (arg.type === 'SpreadElement') {
|
|
2033
|
+
isSpread = true;
|
|
2034
|
+
arg = arg.argument;
|
|
2035
|
+
}
|
|
2036
|
+
const valueInfo = {
|
|
2037
|
+
valueType: 'EXPRESSION',
|
|
2038
|
+
argIndex: i - 1,
|
|
2039
|
+
isSpread
|
|
2040
|
+
};
|
|
2041
|
+
const literalValue = ExpressionEvaluator.extractLiteralValue(arg);
|
|
2042
|
+
if (literalValue !== null) {
|
|
2043
|
+
valueInfo.valueType = 'LITERAL';
|
|
2044
|
+
valueInfo.literalValue = literalValue;
|
|
2045
|
+
}
|
|
2046
|
+
else if (arg.type === 'Identifier') {
|
|
2047
|
+
valueInfo.valueType = 'VARIABLE';
|
|
2048
|
+
valueInfo.valueName = arg.name;
|
|
2049
|
+
}
|
|
2050
|
+
else if (arg.type === 'ObjectExpression') {
|
|
2051
|
+
valueInfo.valueType = 'OBJECT_LITERAL';
|
|
2052
|
+
}
|
|
2053
|
+
else if (arg.type === 'ArrayExpression') {
|
|
2054
|
+
valueInfo.valueType = 'ARRAY_LITERAL';
|
|
2055
|
+
}
|
|
2056
|
+
else if (arg.type === 'CallExpression') {
|
|
2057
|
+
valueInfo.valueType = 'CALL';
|
|
2058
|
+
valueInfo.callLine = arg.loc?.start.line;
|
|
2059
|
+
valueInfo.callColumn = arg.loc?.start.column;
|
|
2060
|
+
}
|
|
2061
|
+
let mutationId;
|
|
2062
|
+
if (scopeTracker) {
|
|
2063
|
+
const discriminator = scopeTracker.getItemCounter(`OBJECT_MUTATION:Object.assign:${targetName}`);
|
|
2064
|
+
mutationId = computeSemanticId('OBJECT_MUTATION', `Object.assign:${targetName}`, scopeTracker.getContext(), { discriminator });
|
|
2065
|
+
}
|
|
2066
|
+
objectMutations.push({
|
|
2067
|
+
id: mutationId,
|
|
2068
|
+
objectName: targetName,
|
|
2069
|
+
propertyName: '<assign>',
|
|
2070
|
+
mutationType: 'assign',
|
|
2071
|
+
file: module.file,
|
|
2072
|
+
line,
|
|
2073
|
+
column,
|
|
2074
|
+
value: valueInfo
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
1377
2077
|
}
|
|
1378
2078
|
}
|