@grafema/core 0.1.1-alpha → 0.2.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Orchestrator.d.ts +7 -0
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +25 -3
- package/dist/config/ConfigLoader.d.ts +18 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +65 -3
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +139 -0
- package/dist/core/NodeFactory.d.ts +44 -5
- package/dist/core/NodeFactory.d.ts.map +1 -1
- package/dist/core/NodeFactory.js +52 -7
- package/dist/core/nodes/ArrayLiteralNode.d.ts.map +1 -1
- package/dist/core/nodes/ArrayLiteralNode.js +4 -2
- package/dist/core/nodes/BranchNode.d.ts +41 -0
- package/dist/core/nodes/BranchNode.d.ts.map +1 -0
- package/dist/core/nodes/BranchNode.js +82 -0
- package/dist/core/nodes/CallSiteNode.d.ts +2 -2
- package/dist/core/nodes/CallSiteNode.d.ts.map +1 -1
- package/dist/core/nodes/CallSiteNode.js +9 -5
- package/dist/core/nodes/CaseNode.d.ts +43 -0
- package/dist/core/nodes/CaseNode.d.ts.map +1 -0
- package/dist/core/nodes/CaseNode.js +81 -0
- package/dist/core/nodes/ClassNode.d.ts +2 -2
- package/dist/core/nodes/ClassNode.d.ts.map +1 -1
- package/dist/core/nodes/ClassNode.js +8 -4
- package/dist/core/nodes/ConstantNode.d.ts +2 -2
- package/dist/core/nodes/ConstantNode.d.ts.map +1 -1
- package/dist/core/nodes/ConstantNode.js +6 -4
- package/dist/core/nodes/ConstructorCallNode.d.ts +51 -0
- package/dist/core/nodes/ConstructorCallNode.d.ts.map +1 -0
- package/dist/core/nodes/ConstructorCallNode.js +171 -0
- package/dist/core/nodes/DatabaseQueryNode.d.ts +3 -2
- package/dist/core/nodes/DatabaseQueryNode.d.ts.map +1 -1
- package/dist/core/nodes/DatabaseQueryNode.js +5 -2
- package/dist/core/nodes/DecoratorNode.d.ts +2 -2
- package/dist/core/nodes/DecoratorNode.d.ts.map +1 -1
- package/dist/core/nodes/DecoratorNode.js +5 -3
- package/dist/core/nodes/EnumNode.d.ts +2 -2
- package/dist/core/nodes/EnumNode.d.ts.map +1 -1
- package/dist/core/nodes/EnumNode.js +5 -3
- package/dist/core/nodes/EventListenerNode.d.ts +4 -4
- package/dist/core/nodes/EventListenerNode.d.ts.map +1 -1
- package/dist/core/nodes/EventListenerNode.js +7 -4
- package/dist/core/nodes/ExportNode.d.ts +2 -2
- package/dist/core/nodes/ExportNode.d.ts.map +1 -1
- package/dist/core/nodes/ExportNode.js +8 -4
- package/dist/core/nodes/ExpressionNode.d.ts +2 -2
- package/dist/core/nodes/ExpressionNode.d.ts.map +1 -1
- package/dist/core/nodes/ExpressionNode.js +6 -4
- package/dist/core/nodes/ExternalModuleNode.d.ts +4 -0
- package/dist/core/nodes/ExternalModuleNode.d.ts.map +1 -1
- package/dist/core/nodes/ExternalModuleNode.js +10 -2
- package/dist/core/nodes/HttpRequestNode.d.ts +4 -4
- package/dist/core/nodes/HttpRequestNode.d.ts.map +1 -1
- package/dist/core/nodes/HttpRequestNode.js +7 -4
- package/dist/core/nodes/ImportNode.d.ts +10 -2
- package/dist/core/nodes/ImportNode.d.ts.map +1 -1
- package/dist/core/nodes/ImportNode.js +21 -4
- package/dist/core/nodes/InterfaceNode.d.ts +2 -2
- package/dist/core/nodes/InterfaceNode.d.ts.map +1 -1
- package/dist/core/nodes/InterfaceNode.js +5 -3
- package/dist/core/nodes/LiteralNode.d.ts +2 -2
- package/dist/core/nodes/LiteralNode.d.ts.map +1 -1
- package/dist/core/nodes/LiteralNode.js +6 -4
- package/dist/core/nodes/MethodCallNode.d.ts +2 -2
- package/dist/core/nodes/MethodCallNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodCallNode.js +9 -5
- package/dist/core/nodes/MethodNode.d.ts +2 -2
- package/dist/core/nodes/MethodNode.d.ts.map +1 -1
- package/dist/core/nodes/MethodNode.js +8 -4
- package/dist/core/nodes/ObjectLiteralNode.d.ts.map +1 -1
- package/dist/core/nodes/ObjectLiteralNode.js +4 -2
- package/dist/core/nodes/ParameterNode.d.ts +2 -2
- package/dist/core/nodes/ParameterNode.d.ts.map +1 -1
- package/dist/core/nodes/ParameterNode.js +5 -3
- package/dist/core/nodes/TypeNode.d.ts +2 -2
- package/dist/core/nodes/TypeNode.d.ts.map +1 -1
- package/dist/core/nodes/TypeNode.js +5 -3
- package/dist/core/nodes/VariableDeclarationNode.d.ts +2 -2
- package/dist/core/nodes/VariableDeclarationNode.d.ts.map +1 -1
- package/dist/core/nodes/VariableDeclarationNode.js +9 -5
- package/dist/core/nodes/index.d.ts +3 -0
- package/dist/core/nodes/index.d.ts.map +1 -1
- package/dist/core/nodes/index.js +3 -0
- package/dist/data/builtins/BuiltinRegistry.d.ts +78 -0
- package/dist/data/builtins/BuiltinRegistry.d.ts.map +1 -0
- package/dist/data/builtins/BuiltinRegistry.js +110 -0
- package/dist/data/builtins/definitions.d.ts +28 -0
- package/dist/data/builtins/definitions.d.ts.map +1 -0
- package/dist/data/builtins/definitions.js +250 -0
- package/dist/data/builtins/index.d.ts +10 -0
- package/dist/data/builtins/index.d.ts.map +1 -0
- package/dist/data/builtins/index.js +8 -0
- package/dist/data/builtins/jsGlobals.d.ts +18 -0
- package/dist/data/builtins/jsGlobals.d.ts.map +1 -0
- package/dist/data/builtins/jsGlobals.js +26 -0
- package/dist/data/builtins/types.d.ts +34 -0
- package/dist/data/builtins/types.d.ts.map +1 -0
- package/dist/data/builtins/types.js +7 -0
- package/dist/data/globals/definitions.d.ts +27 -0
- package/dist/data/globals/definitions.d.ts.map +1 -0
- package/dist/data/globals/definitions.js +117 -0
- package/dist/data/globals/index.d.ts +36 -0
- package/dist/data/globals/index.d.ts.map +1 -0
- package/dist/data/globals/index.js +52 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +23 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -1
- package/dist/diagnostics/DiagnosticReporter.js +88 -0
- package/dist/diagnostics/index.d.ts +1 -1
- package/dist/diagnostics/index.d.ts.map +1 -1
- package/dist/errors/GrafemaError.d.ts +43 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -1
- package/dist/errors/GrafemaError.js +50 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/plugins/analysis/DatabaseAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/DatabaseAnalyzer.js +3 -2
- package/dist/plugins/analysis/ExpressAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressAnalyzer.js +3 -1
- package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts +148 -0
- package/dist/plugins/analysis/ExpressResponseAnalyzer.d.ts.map +1 -0
- package/dist/plugins/analysis/ExpressResponseAnalyzer.js +495 -0
- package/dist/plugins/analysis/ExpressRouteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/ExpressRouteAnalyzer.js +53 -18
- package/dist/plugins/analysis/FetchAnalyzer.d.ts +40 -0
- package/dist/plugins/analysis/FetchAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/FetchAnalyzer.js +163 -15
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts +157 -26
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.js +2418 -191
- package/dist/plugins/analysis/RustAnalyzer.js +4 -4
- package/dist/plugins/analysis/SQLiteAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SQLiteAnalyzer.js +4 -2
- package/dist/plugins/analysis/SocketIOAnalyzer.d.ts +9 -0
- package/dist/plugins/analysis/SocketIOAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/SocketIOAnalyzer.js +91 -7
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts +173 -0
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.js +1256 -65
- package/dist/plugins/analysis/ast/types.d.ts +294 -0
- package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts +5 -1
- package/dist/plugins/analysis/ast/visitors/ASTVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts +1 -0
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/CallExpressionVisitor.js +12 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts +10 -0
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/FunctionVisitor.js +62 -0
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts +4 -0
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +101 -0
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts +16 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/VariableVisitor.js +233 -39
- package/dist/plugins/discovery/WorkspaceDiscovery.d.ts.map +1 -1
- package/dist/plugins/discovery/WorkspaceDiscovery.js +9 -4
- package/dist/plugins/enrichment/AliasTracker.d.ts.map +1 -1
- package/dist/plugins/enrichment/AliasTracker.js +16 -1
- package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts +32 -0
- package/dist/plugins/enrichment/ArgumentParameterLinker.d.ts.map +1 -0
- package/dist/plugins/enrichment/ArgumentParameterLinker.js +175 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts +51 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.d.ts.map +1 -0
- package/dist/plugins/enrichment/ClosureCaptureEnricher.js +205 -0
- package/dist/plugins/enrichment/ExternalCallResolver.d.ts +42 -0
- package/dist/plugins/enrichment/ExternalCallResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/ExternalCallResolver.js +213 -0
- package/dist/plugins/enrichment/FunctionCallResolver.d.ts +58 -0
- package/dist/plugins/enrichment/FunctionCallResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/FunctionCallResolver.js +340 -0
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts +16 -3
- package/dist/plugins/enrichment/HTTPConnectionEnricher.d.ts.map +1 -1
- package/dist/plugins/enrichment/HTTPConnectionEnricher.js +64 -20
- package/dist/plugins/enrichment/MethodCallResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MethodCallResolver.js +15 -1
- package/dist/plugins/enrichment/MountPointResolver.d.ts +14 -12
- package/dist/plugins/enrichment/MountPointResolver.d.ts.map +1 -1
- package/dist/plugins/enrichment/MountPointResolver.js +172 -151
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts +44 -0
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.d.ts.map +1 -0
- package/dist/plugins/enrichment/NodejsBuiltinsResolver.js +271 -0
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts +5 -27
- package/dist/plugins/enrichment/ValueDomainAnalyzer.d.ts.map +1 -1
- package/dist/plugins/enrichment/ValueDomainAnalyzer.js +62 -139
- package/dist/plugins/indexing/JSModuleIndexer.d.ts +15 -0
- package/dist/plugins/indexing/JSModuleIndexer.d.ts.map +1 -1
- package/dist/plugins/indexing/JSModuleIndexer.js +58 -0
- package/dist/plugins/indexing/RustModuleIndexer.d.ts +1 -1
- package/dist/plugins/indexing/RustModuleIndexer.js +4 -4
- package/dist/plugins/validation/BrokenImportValidator.d.ts +31 -0
- package/dist/plugins/validation/BrokenImportValidator.d.ts.map +1 -0
- package/dist/plugins/validation/BrokenImportValidator.js +249 -0
- package/dist/plugins/validation/CallResolverValidator.d.ts +21 -10
- package/dist/plugins/validation/CallResolverValidator.d.ts.map +1 -1
- package/dist/plugins/validation/CallResolverValidator.js +101 -76
- package/dist/plugins/validation/DataFlowValidator.d.ts.map +1 -1
- package/dist/plugins/validation/DataFlowValidator.js +49 -41
- package/dist/plugins/validation/GraphConnectivityValidator.d.ts.map +1 -1
- package/dist/plugins/validation/GraphConnectivityValidator.js +25 -1
- package/dist/plugins/validation/SQLInjectionValidator.d.ts.map +1 -1
- package/dist/plugins/validation/SQLInjectionValidator.js +2 -3
- package/dist/queries/findCallsInFunction.d.ts +52 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +135 -0
- package/dist/queries/findContainingFunction.d.ts +45 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +54 -0
- package/dist/queries/index.d.ts +14 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +11 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +299 -0
- package/dist/queries/types.d.ts +163 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +9 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +124 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +112 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +13 -18
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.js +47 -51
- package/dist/storage/backends/typeValidation.d.ts.map +1 -1
- package/dist/storage/backends/typeValidation.js +1 -0
- package/package.json +3 -3
- package/src/Orchestrator.ts +35 -3
- package/src/config/ConfigLoader.ts +94 -3
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/NodeFactory.ts +72 -8
- package/src/core/nodes/ArrayLiteralNode.ts +3 -2
- package/src/core/nodes/BranchNode.ts +113 -0
- package/src/core/nodes/CallSiteNode.ts +7 -5
- package/src/core/nodes/CaseNode.ts +123 -0
- package/src/core/nodes/ClassNode.ts +6 -4
- package/src/core/nodes/ConstantNode.ts +5 -4
- package/src/core/nodes/ConstructorCallNode.ts +217 -0
- package/src/core/nodes/DatabaseQueryNode.ts +5 -1
- package/src/core/nodes/DecoratorNode.ts +4 -3
- package/src/core/nodes/EnumNode.ts +4 -3
- package/src/core/nodes/EventListenerNode.ts +7 -4
- package/src/core/nodes/ExportNode.ts +6 -4
- package/src/core/nodes/ExpressionNode.ts +5 -4
- package/src/core/nodes/ExternalModuleNode.ts +11 -2
- package/src/core/nodes/HttpRequestNode.ts +7 -4
- package/src/core/nodes/ImportNode.ts +31 -4
- package/src/core/nodes/InterfaceNode.ts +4 -3
- package/src/core/nodes/LiteralNode.ts +5 -4
- package/src/core/nodes/MethodCallNode.ts +7 -5
- package/src/core/nodes/MethodNode.ts +6 -4
- package/src/core/nodes/ObjectLiteralNode.ts +3 -2
- package/src/core/nodes/ParameterNode.ts +4 -3
- package/src/core/nodes/TypeNode.ts +4 -3
- package/src/core/nodes/VariableDeclarationNode.ts +7 -5
- package/src/core/nodes/index.ts +3 -0
- package/src/data/builtins/BuiltinRegistry.ts +124 -0
- package/src/data/builtins/definitions.ts +267 -0
- package/src/data/builtins/index.ts +10 -0
- package/src/data/builtins/jsGlobals.ts +28 -0
- package/src/data/builtins/types.ts +36 -0
- package/src/data/globals/definitions.ts +156 -0
- package/src/data/globals/index.ts +66 -0
- package/src/diagnostics/DiagnosticReporter.ts +120 -0
- package/src/diagnostics/index.ts +1 -1
- package/src/errors/GrafemaError.ts +65 -0
- package/src/index.ts +45 -0
- package/src/plugins/analysis/DatabaseAnalyzer.ts +4 -2
- package/src/plugins/analysis/ExpressAnalyzer.ts +5 -1
- package/src/plugins/analysis/ExpressResponseAnalyzer.ts +636 -0
- package/src/plugins/analysis/ExpressRouteAnalyzer.ts +57 -18
- package/src/plugins/analysis/FetchAnalyzer.ts +204 -16
- package/src/plugins/analysis/JSASTAnalyzer.ts +2958 -260
- package/src/plugins/analysis/RustAnalyzer.ts +4 -4
- package/src/plugins/analysis/SQLiteAnalyzer.ts +5 -2
- package/src/plugins/analysis/SocketIOAnalyzer.ts +121 -7
- package/src/plugins/analysis/ast/GraphBuilder.ts +1578 -70
- package/src/plugins/analysis/ast/types.ts +387 -0
- package/src/plugins/analysis/ast/visitors/ASTVisitor.ts +8 -0
- package/src/plugins/analysis/ast/visitors/CallExpressionVisitor.ts +16 -1
- package/src/plugins/analysis/ast/visitors/FunctionVisitor.ts +77 -2
- package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +112 -1
- package/src/plugins/analysis/ast/visitors/VariableVisitor.ts +272 -47
- package/src/plugins/discovery/WorkspaceDiscovery.ts +11 -4
- package/src/plugins/enrichment/AliasTracker.ts +22 -1
- package/src/plugins/enrichment/ArgumentParameterLinker.ts +240 -0
- package/src/plugins/enrichment/ClosureCaptureEnricher.ts +267 -0
- package/src/plugins/enrichment/ExternalCallResolver.ts +262 -0
- package/src/plugins/enrichment/FunctionCallResolver.ts +456 -0
- package/src/plugins/enrichment/HTTPConnectionEnricher.ts +70 -20
- package/src/plugins/enrichment/MethodCallResolver.ts +21 -1
- package/src/plugins/enrichment/MountPointResolver.ts +206 -198
- package/src/plugins/enrichment/NodejsBuiltinsResolver.ts +365 -0
- package/src/plugins/enrichment/ValueDomainAnalyzer.ts +67 -184
- package/src/plugins/indexing/JSModuleIndexer.ts +66 -0
- package/src/plugins/indexing/RustModuleIndexer.ts +4 -4
- package/src/plugins/validation/BrokenImportValidator.ts +325 -0
- package/src/plugins/validation/CallResolverValidator.ts +129 -109
- package/src/plugins/validation/DataFlowValidator.ts +75 -58
- package/src/plugins/validation/GraphConnectivityValidator.ts +39 -1
- package/src/plugins/validation/SQLInjectionValidator.ts +2 -5
- package/src/queries/README.md +46 -0
- package/src/queries/findCallsInFunction.ts +206 -0
- package/src/queries/findContainingFunction.ts +83 -0
- package/src/queries/index.ts +23 -0
- package/src/queries/traceValues.ts +398 -0
- package/src/queries/types.ts +187 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +64 -68
- package/src/storage/backends/typeValidation.ts +1 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExpressResponseAnalyzer - detects Express response patterns
|
|
3
|
+
*
|
|
4
|
+
* For each http:route node:
|
|
5
|
+
* 1. Follow HANDLED_BY edge to get handler function
|
|
6
|
+
* 2. Traverse handler AST for res.json(...), res.send(...) patterns
|
|
7
|
+
* 3. Create RESPONDS_WITH edge from http:route to response argument node
|
|
8
|
+
*
|
|
9
|
+
* Patterns:
|
|
10
|
+
* - res.json({ data })
|
|
11
|
+
* - res.send(variable)
|
|
12
|
+
* - res.status(200).json(data)
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from 'fs';
|
|
15
|
+
import { parse } from '@babel/parser';
|
|
16
|
+
import traverseModule from '@babel/traverse';
|
|
17
|
+
import { Plugin, createSuccessResult, createErrorResult } from '../Plugin.js';
|
|
18
|
+
import { getLine } from './ast/utils/location.js';
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
const traverse = traverseModule.default || traverseModule;
|
|
21
|
+
const RESPONSE_METHODS = ['json', 'send'];
|
|
22
|
+
export class ExpressResponseAnalyzer extends Plugin {
|
|
23
|
+
responseNodeCounter = 0;
|
|
24
|
+
get metadata() {
|
|
25
|
+
return {
|
|
26
|
+
name: 'ExpressResponseAnalyzer',
|
|
27
|
+
phase: 'ANALYSIS',
|
|
28
|
+
priority: 74, // After ExpressRouteAnalyzer (75)
|
|
29
|
+
creates: {
|
|
30
|
+
nodes: [],
|
|
31
|
+
edges: ['RESPONDS_WITH']
|
|
32
|
+
},
|
|
33
|
+
dependencies: ['ExpressRouteAnalyzer', 'JSASTAnalyzer']
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async execute(context) {
|
|
37
|
+
const logger = this.log(context);
|
|
38
|
+
try {
|
|
39
|
+
const { graph } = context;
|
|
40
|
+
// Get all http:route nodes
|
|
41
|
+
const routes = [];
|
|
42
|
+
for await (const node of graph.queryNodes({ type: 'http:route' })) {
|
|
43
|
+
routes.push(node);
|
|
44
|
+
}
|
|
45
|
+
logger.info('Processing routes', { count: routes.length });
|
|
46
|
+
let edgesCreated = 0;
|
|
47
|
+
for (const route of routes) {
|
|
48
|
+
const result = await this.analyzeRouteResponses(route, graph);
|
|
49
|
+
edgesCreated += result;
|
|
50
|
+
}
|
|
51
|
+
logger.info('Analysis complete', { edgesCreated });
|
|
52
|
+
return createSuccessResult({ nodes: 0, edges: edgesCreated }, { routesAnalyzed: routes.length });
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.error('Analysis failed', { error });
|
|
56
|
+
return createErrorResult(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Analyze a single http:route for response patterns
|
|
61
|
+
*/
|
|
62
|
+
async analyzeRouteResponses(route, graph) {
|
|
63
|
+
let edgesCreated = 0;
|
|
64
|
+
try {
|
|
65
|
+
// Get HANDLED_BY edges to find handler function
|
|
66
|
+
const handledByEdges = await graph.getOutgoingEdges(route.id, ['HANDLED_BY']);
|
|
67
|
+
if (handledByEdges.length === 0) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
// Get handler function node
|
|
71
|
+
const handlerEdge = handledByEdges[0];
|
|
72
|
+
const handlerNode = await graph.getNode(handlerEdge.dst);
|
|
73
|
+
if (!handlerNode || !handlerNode.file) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
// Parse the file and find response calls in handler
|
|
77
|
+
const code = readFileSync(handlerNode.file, 'utf-8');
|
|
78
|
+
const ast = parse(code, {
|
|
79
|
+
sourceType: 'module',
|
|
80
|
+
plugins: ['jsx', 'typescript']
|
|
81
|
+
});
|
|
82
|
+
// Find response calls within the handler's line range
|
|
83
|
+
const responseCalls = this.findResponseCalls(ast, handlerNode.file, handlerNode.line);
|
|
84
|
+
// Create RESPONDS_WITH edges
|
|
85
|
+
for (const call of responseCalls) {
|
|
86
|
+
// Try to resolve to existing variable, or create a unique response node
|
|
87
|
+
const dstNodeId = await this.resolveOrCreateResponseNode(graph, handlerNode.file, call, route.id, handlerNode.id // Handler's semantic ID for scope resolution
|
|
88
|
+
);
|
|
89
|
+
await graph.addEdge({
|
|
90
|
+
type: 'RESPONDS_WITH',
|
|
91
|
+
src: route.id,
|
|
92
|
+
dst: dstNodeId,
|
|
93
|
+
metadata: {
|
|
94
|
+
responseMethod: call.method
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
edgesCreated++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Silent - per-route errors shouldn't spam logs
|
|
102
|
+
}
|
|
103
|
+
return edgesCreated;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Find res.json/res.send calls within a function at given line
|
|
107
|
+
*/
|
|
108
|
+
findResponseCalls(ast, file, handlerLine) {
|
|
109
|
+
const calls = [];
|
|
110
|
+
// Using object wrapper to satisfy TypeScript's control flow analysis
|
|
111
|
+
const found = { path: null };
|
|
112
|
+
// First pass: find the handler function at the specified line
|
|
113
|
+
traverse(ast, {
|
|
114
|
+
'ArrowFunctionExpression|FunctionExpression|FunctionDeclaration': (path) => {
|
|
115
|
+
const node = path.node;
|
|
116
|
+
const line = getLine(node);
|
|
117
|
+
if (line === handlerLine) {
|
|
118
|
+
found.path = path;
|
|
119
|
+
path.stop();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (!found.path) {
|
|
124
|
+
return calls;
|
|
125
|
+
}
|
|
126
|
+
const handlerPath = found.path;
|
|
127
|
+
// Second pass: traverse only within the handler to find response calls
|
|
128
|
+
const handlerNode = handlerPath.node;
|
|
129
|
+
// Get parameter names to identify 'res'
|
|
130
|
+
const resParamName = this.getResponseParamName(handlerNode);
|
|
131
|
+
if (!resParamName) {
|
|
132
|
+
return calls;
|
|
133
|
+
}
|
|
134
|
+
// Traverse the handler body for res.json/res.send calls
|
|
135
|
+
handlerPath.traverse({
|
|
136
|
+
CallExpression: (callPath) => {
|
|
137
|
+
const callNode = callPath.node;
|
|
138
|
+
const callee = callNode.callee;
|
|
139
|
+
// Check for res.json() or res.send() or res.status().json() patterns
|
|
140
|
+
const responseInfo = this.extractResponseInfo(callee, resParamName);
|
|
141
|
+
if (!responseInfo) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Get the argument being sent
|
|
145
|
+
if (callNode.arguments.length === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const arg = callNode.arguments[0];
|
|
149
|
+
const argLine = getLine(arg);
|
|
150
|
+
const argColumn = arg.loc?.start.column ?? 0;
|
|
151
|
+
calls.push({
|
|
152
|
+
method: responseInfo.method,
|
|
153
|
+
argLine,
|
|
154
|
+
argColumn,
|
|
155
|
+
argType: arg.type,
|
|
156
|
+
line: getLine(callNode),
|
|
157
|
+
identifierName: arg.type === 'Identifier' ? arg.name : undefined
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return calls;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get response parameter name from function params (typically 'res')
|
|
165
|
+
*/
|
|
166
|
+
getResponseParamName(func) {
|
|
167
|
+
const params = func.params;
|
|
168
|
+
// Express handlers: (req, res) or (req, res, next)
|
|
169
|
+
if (params.length >= 2) {
|
|
170
|
+
const resParam = params[1];
|
|
171
|
+
if (resParam.type === 'Identifier') {
|
|
172
|
+
return resParam.name;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Extract response method info from callee
|
|
179
|
+
* Handles: res.json(), res.send(), res.status(200).json()
|
|
180
|
+
*/
|
|
181
|
+
extractResponseInfo(callee, resParamName) {
|
|
182
|
+
// Direct call: res.json() or res.send()
|
|
183
|
+
if (callee.type === 'MemberExpression') {
|
|
184
|
+
const memberExpr = callee;
|
|
185
|
+
const property = memberExpr.property;
|
|
186
|
+
if (property.type !== 'Identifier') {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const methodName = property.name;
|
|
190
|
+
// Check for res.json() or res.send()
|
|
191
|
+
if (memberExpr.object.type === 'Identifier' &&
|
|
192
|
+
memberExpr.object.name === resParamName &&
|
|
193
|
+
RESPONSE_METHODS.includes(methodName)) {
|
|
194
|
+
return { method: methodName };
|
|
195
|
+
}
|
|
196
|
+
// Check for res.status(200).json() chain
|
|
197
|
+
if (memberExpr.object.type === 'CallExpression' &&
|
|
198
|
+
RESPONSE_METHODS.includes(methodName)) {
|
|
199
|
+
const chainedCall = memberExpr.object;
|
|
200
|
+
if (this.isResMethodCall(chainedCall.callee, resParamName, 'status')) {
|
|
201
|
+
return { method: methodName };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if callee is res.methodName()
|
|
209
|
+
*/
|
|
210
|
+
isResMethodCall(callee, resParamName, methodName) {
|
|
211
|
+
if (callee.type !== 'MemberExpression') {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const memberExpr = callee;
|
|
215
|
+
return (memberExpr.object.type === 'Identifier' &&
|
|
216
|
+
memberExpr.object.name === resParamName &&
|
|
217
|
+
memberExpr.property.type === 'Identifier' &&
|
|
218
|
+
memberExpr.property.name === methodName);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Resolve response node: find existing variable or create stub.
|
|
222
|
+
*
|
|
223
|
+
* For Identifier arguments (e.g., res.json(statusData)):
|
|
224
|
+
* 1. Try to find existing VARIABLE/PARAMETER/CONSTANT with same name in handler scope
|
|
225
|
+
* 2. If found, return existing node ID (no stub needed)
|
|
226
|
+
* 3. If not found, fall back to creating stub (external/global variables)
|
|
227
|
+
*
|
|
228
|
+
* For non-Identifier arguments (ObjectExpression, CallExpression, etc.):
|
|
229
|
+
* - Always create stub node (existing behavior)
|
|
230
|
+
*
|
|
231
|
+
* @param graph - Graph backend
|
|
232
|
+
* @param file - Handler file path
|
|
233
|
+
* @param call - Response call info (includes identifierName)
|
|
234
|
+
* @param routeId - Route ID (for metadata)
|
|
235
|
+
* @param handlerSemanticId - Handler function's semantic ID (for scope matching)
|
|
236
|
+
* @returns Node ID (existing or newly created)
|
|
237
|
+
*/
|
|
238
|
+
async resolveOrCreateResponseNode(graph, file, call, routeId, handlerSemanticId) {
|
|
239
|
+
const { argLine, argColumn, argType, identifierName } = call;
|
|
240
|
+
// For Identifier arguments, try to find existing variable/parameter
|
|
241
|
+
if (argType === 'Identifier' && identifierName) {
|
|
242
|
+
const existingNodeId = await this.findIdentifierInScope(graph, file, identifierName, handlerSemanticId, argLine);
|
|
243
|
+
if (existingNodeId) {
|
|
244
|
+
return existingNodeId; // Use existing node, no stub needed
|
|
245
|
+
}
|
|
246
|
+
// Fall through to create stub if not found (external/global variables)
|
|
247
|
+
}
|
|
248
|
+
// For non-Identifier or not-found, create stub node (existing logic)
|
|
249
|
+
return this.createResponseArgumentNode(graph, file, argLine, argColumn, argType, routeId);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Find existing VARIABLE/CONSTANT/PARAMETER node in handler scope.
|
|
253
|
+
*
|
|
254
|
+
* Strategy:
|
|
255
|
+
* 1. Parse handler semantic ID to extract scope prefix
|
|
256
|
+
* 2. Query VARIABLE/CONSTANT nodes: match by name, file, scope prefix, and line <= useLine
|
|
257
|
+
* 3. Query PARAMETER nodes: match by name, file, parentFunctionId === handlerSemanticId
|
|
258
|
+
*
|
|
259
|
+
* Scope matching:
|
|
260
|
+
* - Handler ID: "routes.js->anonymous[1]->FUNCTION->anonymous[1]"
|
|
261
|
+
* - Scope prefix: "routes.js->anonymous[1]->"
|
|
262
|
+
* - Variable ID: "routes.js->anonymous[1]->VARIABLE->statusData" (matches prefix)
|
|
263
|
+
* - External ID: "utils.js->VARIABLE->config" (different file)
|
|
264
|
+
*
|
|
265
|
+
* @param graph - Graph backend
|
|
266
|
+
* @param file - File path
|
|
267
|
+
* @param name - Variable name to find
|
|
268
|
+
* @param handlerSemanticId - Handler function's semantic ID
|
|
269
|
+
* @param useLine - Line where identifier is used (variable must be declared before this)
|
|
270
|
+
* @returns Node ID if found, null otherwise
|
|
271
|
+
*/
|
|
272
|
+
async findIdentifierInScope(graph, file, name, handlerSemanticId, useLine) {
|
|
273
|
+
// Extract scope prefix from handler semantic ID
|
|
274
|
+
const handlerScopePrefix = this.extractScopePrefix(handlerSemanticId);
|
|
275
|
+
// Query VARIABLE nodes
|
|
276
|
+
for await (const node of graph.queryNodes({ type: 'VARIABLE' })) {
|
|
277
|
+
if (node.name === name && node.file === file) {
|
|
278
|
+
// Check if in handler scope and declared before usage
|
|
279
|
+
if (node.id.startsWith(handlerScopePrefix) && node.line <= useLine) {
|
|
280
|
+
return node.id;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Query CONSTANT nodes
|
|
285
|
+
for await (const node of graph.queryNodes({ type: 'CONSTANT' })) {
|
|
286
|
+
if (node.name === name && node.file === file) {
|
|
287
|
+
if (node.id.startsWith(handlerScopePrefix) && node.line <= useLine) {
|
|
288
|
+
return node.id;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Query PARAMETER nodes
|
|
293
|
+
for await (const node of graph.queryNodes({ type: 'PARAMETER' })) {
|
|
294
|
+
if (node.name === name && node.file === file) {
|
|
295
|
+
// Parameters belong to the function directly
|
|
296
|
+
const parentFunctionId = node.parentFunctionId;
|
|
297
|
+
if (parentFunctionId === handlerSemanticId) {
|
|
298
|
+
return node.id;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Also check module-level variables (scope prefix would be just "file.js->")
|
|
303
|
+
// For module-level constants, they should be accessible from any function in the file
|
|
304
|
+
const modulePrefix = this.extractModulePrefix(handlerSemanticId);
|
|
305
|
+
if (modulePrefix) {
|
|
306
|
+
// Check module-level VARIABLE
|
|
307
|
+
for await (const node of graph.queryNodes({ type: 'VARIABLE' })) {
|
|
308
|
+
if (node.name === name && node.file === file) {
|
|
309
|
+
// Module-level variables have IDs like "file.js->VARIABLE->name" (3 parts)
|
|
310
|
+
// Function-local variables have IDs like "file.js->funcName->VARIABLE->name" (4+ parts)
|
|
311
|
+
// Only match true module-level variables by checking structure
|
|
312
|
+
if (this.isModuleLevelId(node.id, modulePrefix) && node.line <= useLine) {
|
|
313
|
+
return node.id;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Check module-level CONSTANT
|
|
318
|
+
for await (const node of graph.queryNodes({ type: 'CONSTANT' })) {
|
|
319
|
+
if (node.name === name && node.file === file) {
|
|
320
|
+
if (this.isModuleLevelId(node.id, modulePrefix) && node.line <= useLine) {
|
|
321
|
+
return node.id;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return null; // Not found - will create stub
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Extract scope prefix from handler function's semantic ID.
|
|
330
|
+
*
|
|
331
|
+
* Handler function semantic IDs follow the pattern:
|
|
332
|
+
* {file}->{scope_path}->{type}->{name}
|
|
333
|
+
*
|
|
334
|
+
* Variables declared INSIDE the handler have IDs where the handler's NAME
|
|
335
|
+
* becomes part of THEIR scope path:
|
|
336
|
+
* {file}->{handler_name}->{type}->{var_name}
|
|
337
|
+
*
|
|
338
|
+
* Examples:
|
|
339
|
+
* - Handler: "index.js->global->FUNCTION->anonymous[0]"
|
|
340
|
+
* -> Variables inside: "index.js->anonymous[0]->CONSTANT->statusData"
|
|
341
|
+
* -> Scope prefix: "index.js->anonymous[0]->"
|
|
342
|
+
*
|
|
343
|
+
* - Handler: "routes.js->anonymous[1]->FUNCTION->anonymous[1]"
|
|
344
|
+
* -> Variables inside: "routes.js->anonymous[1]->VARIABLE->data"
|
|
345
|
+
* -> Scope prefix: "routes.js->anonymous[1]->"
|
|
346
|
+
*
|
|
347
|
+
* - Handler: "app.js->global->FUNCTION->handleRequest"
|
|
348
|
+
* -> Variables inside: "app.js->handleRequest->VARIABLE->result"
|
|
349
|
+
* -> Scope prefix: "app.js->handleRequest->"
|
|
350
|
+
*
|
|
351
|
+
* Algorithm:
|
|
352
|
+
* 1. Split by "->"
|
|
353
|
+
* 2. Take file (first part) and handler name (last part)
|
|
354
|
+
* 3. Join with "->" and add trailing "->"
|
|
355
|
+
*
|
|
356
|
+
* @param semanticId - Handler function's semantic ID
|
|
357
|
+
* @returns Scope prefix for matching variables declared inside the handler
|
|
358
|
+
*/
|
|
359
|
+
extractScopePrefix(semanticId) {
|
|
360
|
+
const parts = semanticId.split('->');
|
|
361
|
+
// Semantic ID format: file->scope->TYPE->name
|
|
362
|
+
// We need file + function name (last part) to match variables inside the function
|
|
363
|
+
if (parts.length >= 4) {
|
|
364
|
+
const file = parts[0];
|
|
365
|
+
const functionName = parts[parts.length - 1]; // Function name is the last part
|
|
366
|
+
return `${file}->${functionName}->`;
|
|
367
|
+
}
|
|
368
|
+
// Fallback: use first two parts (shouldn't happen for well-formed IDs)
|
|
369
|
+
if (parts.length >= 2) {
|
|
370
|
+
return `${parts[0]}->${parts[1]}->`;
|
|
371
|
+
}
|
|
372
|
+
return semanticId;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Extract module prefix from semantic ID (for module-level variable access).
|
|
376
|
+
*
|
|
377
|
+
* Examples:
|
|
378
|
+
* - "routes.js->anonymous[1]->FUNCTION->anonymous[1]" -> "routes.js->"
|
|
379
|
+
* - "app.js->startServer->FUNCTION->startServer" -> "app.js->"
|
|
380
|
+
*
|
|
381
|
+
* @param semanticId - Handler function's semantic ID
|
|
382
|
+
* @returns Module prefix for matching module-level variables
|
|
383
|
+
*/
|
|
384
|
+
extractModulePrefix(semanticId) {
|
|
385
|
+
const parts = semanticId.split('->');
|
|
386
|
+
if (parts.length >= 1 && parts[0]) {
|
|
387
|
+
return `${parts[0]}->`;
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Check if a semantic ID represents a true module-level variable.
|
|
393
|
+
*
|
|
394
|
+
* Semantic IDs have format: file->scope->TYPE->name
|
|
395
|
+
* - Module-level variables have "global" as the scope: "file.js->global->TYPE->name"
|
|
396
|
+
* - Function-local variables have function name as scope: "file.js->funcName->TYPE->name"
|
|
397
|
+
*
|
|
398
|
+
* Examples:
|
|
399
|
+
* - "index.js->global->CONSTANT->CONFIG" -> true (module-level)
|
|
400
|
+
* - "index.js->global->VARIABLE->counter" -> true (module-level)
|
|
401
|
+
* - "index.js->anonymous[0]->CONSTANT->data" -> false (function-local)
|
|
402
|
+
* - "routes.js->handler->VARIABLE->result" -> false (function-local)
|
|
403
|
+
*
|
|
404
|
+
* @param nodeId - The node's semantic ID
|
|
405
|
+
* @param modulePrefix - The module prefix (e.g., "index.js->")
|
|
406
|
+
* @returns true if this is a module-level variable
|
|
407
|
+
*/
|
|
408
|
+
isModuleLevelId(nodeId, modulePrefix) {
|
|
409
|
+
if (!nodeId.startsWith(modulePrefix)) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
// Check if the scope part (second component) is "global"
|
|
413
|
+
const parts = nodeId.split('->');
|
|
414
|
+
// Expected format: ["file.js", "global", "TYPE", "name"]
|
|
415
|
+
// Check that second part is "global" (module scope)
|
|
416
|
+
return parts.length >= 4 && parts[1] === 'global';
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Create a node for the response argument
|
|
420
|
+
*/
|
|
421
|
+
async createResponseArgumentNode(graph, file, line, column, astType, routeId) {
|
|
422
|
+
// Map AST type to node type and create appropriate node
|
|
423
|
+
switch (astType) {
|
|
424
|
+
case 'ObjectExpression': {
|
|
425
|
+
// Include counter to make the node unique even for same location
|
|
426
|
+
const counter = this.responseNodeCounter++;
|
|
427
|
+
const id = `OBJECT_LITERAL#response:${counter}#${file}#${line}:${column}`;
|
|
428
|
+
await graph.addNode({
|
|
429
|
+
id,
|
|
430
|
+
type: 'OBJECT_LITERAL',
|
|
431
|
+
name: '<response>',
|
|
432
|
+
file,
|
|
433
|
+
line,
|
|
434
|
+
column,
|
|
435
|
+
parentRouteId: routeId
|
|
436
|
+
});
|
|
437
|
+
return id;
|
|
438
|
+
}
|
|
439
|
+
case 'Identifier': {
|
|
440
|
+
// For identifiers, we link to the variable that's being returned
|
|
441
|
+
const counter = this.responseNodeCounter++;
|
|
442
|
+
const id = `VARIABLE#response:${counter}#${file}#${line}:${column}`;
|
|
443
|
+
await graph.addNode({
|
|
444
|
+
id,
|
|
445
|
+
type: 'VARIABLE',
|
|
446
|
+
name: '<response>',
|
|
447
|
+
file,
|
|
448
|
+
line,
|
|
449
|
+
column
|
|
450
|
+
});
|
|
451
|
+
return id;
|
|
452
|
+
}
|
|
453
|
+
case 'CallExpression': {
|
|
454
|
+
const counter = this.responseNodeCounter++;
|
|
455
|
+
const id = `CALL#response:${counter}#${file}#${line}:${column}`;
|
|
456
|
+
await graph.addNode({
|
|
457
|
+
id,
|
|
458
|
+
type: 'CALL',
|
|
459
|
+
name: '<response>',
|
|
460
|
+
file,
|
|
461
|
+
line,
|
|
462
|
+
column
|
|
463
|
+
});
|
|
464
|
+
return id;
|
|
465
|
+
}
|
|
466
|
+
case 'ArrayExpression': {
|
|
467
|
+
const counter = this.responseNodeCounter++;
|
|
468
|
+
const id = `ARRAY_LITERAL#response:${counter}#${file}#${line}:${column}`;
|
|
469
|
+
await graph.addNode({
|
|
470
|
+
id,
|
|
471
|
+
type: 'ARRAY_LITERAL',
|
|
472
|
+
name: '<response>',
|
|
473
|
+
file,
|
|
474
|
+
line,
|
|
475
|
+
column
|
|
476
|
+
});
|
|
477
|
+
return id;
|
|
478
|
+
}
|
|
479
|
+
default: {
|
|
480
|
+
// Generic expression node
|
|
481
|
+
const counter = this.responseNodeCounter++;
|
|
482
|
+
const id = `EXPRESSION#response:${counter}#${file}#${line}:${column}`;
|
|
483
|
+
await graph.addNode({
|
|
484
|
+
id,
|
|
485
|
+
type: 'EXPRESSION',
|
|
486
|
+
name: '<response>',
|
|
487
|
+
file,
|
|
488
|
+
line,
|
|
489
|
+
column
|
|
490
|
+
});
|
|
491
|
+
return id;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpressRouteAnalyzer.d.ts","sourceRoot":"","sources":["../../../src/plugins/analysis/ExpressRouteAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,EAAE,MAAM,EAA0C,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpressRouteAnalyzer.d.ts","sourceRoot":"","sources":["../../../src/plugins/analysis/ExpressRouteAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,EAAE,MAAM,EAA0C,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAkDhF,qBAAa,oBAAqB,SAAQ,MAAM;IAC9C,IAAI,QAAQ,IAAI,cAAc,CAW7B;IAEK,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;YAmD9C,aAAa;CAkU5B"}
|
|
@@ -11,7 +11,7 @@ import { readFileSync } from 'fs';
|
|
|
11
11
|
import { parse } from '@babel/parser';
|
|
12
12
|
import traverseModule from '@babel/traverse';
|
|
13
13
|
import { Plugin, createSuccessResult, createErrorResult } from '../Plugin.js';
|
|
14
|
-
import { getLine } from './ast/utils/location.js';
|
|
14
|
+
import { getLine, getColumn } from './ast/utils/location.js';
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
16
|
const traverse = traverseModule.default || traverseModule;
|
|
17
17
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
|
|
@@ -140,6 +140,32 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
140
140
|
const handlers = args.slice(1);
|
|
141
141
|
// Последний handler - это route handler
|
|
142
142
|
const mainHandler = handlers[handlers.length - 1];
|
|
143
|
+
// Unwrap wrapper functions (asyncHandler, catchAsync, etc.)
|
|
144
|
+
// Pattern: wrapper(async (req, res) => {...}) -> extract inner function
|
|
145
|
+
// Also handles nested wrappers: outer(inner(handler))
|
|
146
|
+
let actualHandler = mainHandler;
|
|
147
|
+
while (actualHandler.type === 'CallExpression') {
|
|
148
|
+
const callExpr = actualHandler;
|
|
149
|
+
const firstArg = callExpr.arguments[0];
|
|
150
|
+
if (!firstArg) {
|
|
151
|
+
// No arguments - not a wrapper pattern
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (firstArg.type === 'ArrowFunctionExpression' ||
|
|
155
|
+
firstArg.type === 'FunctionExpression') {
|
|
156
|
+
// Found the actual handler function
|
|
157
|
+
actualHandler = firstArg;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
else if (firstArg.type === 'CallExpression') {
|
|
161
|
+
// Nested wrapper: outer(inner(...)) - continue unwrapping
|
|
162
|
+
actualHandler = firstArg;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// First arg is not a function or CallExpression - not a wrapper pattern
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
143
169
|
// Все предыдущие - middleware
|
|
144
170
|
const middlewareHandlers = handlers.slice(0, -1);
|
|
145
171
|
// Создаём http:route
|
|
@@ -151,10 +177,14 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
151
177
|
path: routePath,
|
|
152
178
|
file: module.file,
|
|
153
179
|
line: getLine(node),
|
|
180
|
+
column: getColumn(node),
|
|
154
181
|
routerName: objectName,
|
|
155
|
-
handlerLine:
|
|
156
|
-
? getLine(
|
|
157
|
-
: getLine(node)
|
|
182
|
+
handlerLine: actualHandler.loc
|
|
183
|
+
? getLine(actualHandler)
|
|
184
|
+
: getLine(node),
|
|
185
|
+
handlerColumn: actualHandler.loc
|
|
186
|
+
? getColumn(actualHandler)
|
|
187
|
+
: getColumn(node)
|
|
158
188
|
});
|
|
159
189
|
// Обрабатываем middleware
|
|
160
190
|
middlewareHandlers.forEach((mw, index) => {
|
|
@@ -181,6 +211,7 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
181
211
|
name: middlewareName,
|
|
182
212
|
file: module.file,
|
|
183
213
|
line: mwNode.loc ? getLine(mwNode) : getLine(node),
|
|
214
|
+
column: mwNode.loc ? getColumn(mwNode) : getColumn(node),
|
|
184
215
|
endpointId: endpointId,
|
|
185
216
|
order: index // Порядок в цепочке
|
|
186
217
|
});
|
|
@@ -218,6 +249,7 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
218
249
|
name: middlewareName,
|
|
219
250
|
file: module.file,
|
|
220
251
|
line: getLine(node),
|
|
252
|
+
column: getColumn(node),
|
|
221
253
|
mountPath: mountPath,
|
|
222
254
|
isGlobal: mountPath === '/' // Global middleware если нет path
|
|
223
255
|
});
|
|
@@ -230,10 +262,11 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
230
262
|
});
|
|
231
263
|
// Создаём ENDPOINT ноды
|
|
232
264
|
for (const endpoint of endpoints) {
|
|
233
|
-
// Сохраняем
|
|
265
|
+
// Сохраняем handler location ПЕРЕД destructuring
|
|
234
266
|
const handlerLine = endpoint.handlerLine;
|
|
267
|
+
const handlerColumn = endpoint.handlerColumn;
|
|
235
268
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
236
|
-
const { handlerLine:
|
|
269
|
+
const { handlerLine: _hl, handlerColumn: _hc, routerName, ...endpointData } = endpoint;
|
|
237
270
|
await graph.addNode(endpointData);
|
|
238
271
|
endpointsCreated++;
|
|
239
272
|
// MODULE -> CONTAINS -> ENDPOINT
|
|
@@ -243,22 +276,24 @@ export class ExpressRouteAnalyzer extends Plugin {
|
|
|
243
276
|
dst: endpoint.id
|
|
244
277
|
});
|
|
245
278
|
edgesCreated++;
|
|
246
|
-
// Ищем FUNCTION ноду для handler
|
|
279
|
+
// Ищем FUNCTION ноду для handler по line+column
|
|
280
|
+
// NOTE: queryNodes не поддерживает line/column фильтрацию, поэтому фильтруем вручную
|
|
247
281
|
if (handlerLine) {
|
|
248
|
-
// Используем queryNodes вместо прямого доступа к graph.nodes
|
|
249
282
|
for await (const fn of graph.queryNodes({
|
|
250
283
|
type: 'FUNCTION',
|
|
251
|
-
file: module.file
|
|
252
|
-
line: handlerLine
|
|
284
|
+
file: module.file
|
|
253
285
|
})) {
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
286
|
+
// Проверяем точное совпадение line и column
|
|
287
|
+
if (fn.line === handlerLine && fn.column === handlerColumn) {
|
|
288
|
+
// ENDPOINT -> HANDLED_BY -> FUNCTION
|
|
289
|
+
await graph.addEdge({
|
|
290
|
+
type: 'HANDLED_BY',
|
|
291
|
+
src: endpoint.id,
|
|
292
|
+
dst: fn.id
|
|
293
|
+
});
|
|
294
|
+
edgesCreated++;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
262
297
|
}
|
|
263
298
|
}
|
|
264
299
|
}
|