blast-radius-analyzer 1.2.1

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.
Files changed (49) hide show
  1. package/README.md +108 -0
  2. package/TEST-REPORT.md +379 -0
  3. package/dist/core/AnalysisCache.d.ts +59 -0
  4. package/dist/core/AnalysisCache.js +156 -0
  5. package/dist/core/BlastRadiusAnalyzer.d.ts +99 -0
  6. package/dist/core/BlastRadiusAnalyzer.js +510 -0
  7. package/dist/core/CallStackBuilder.d.ts +63 -0
  8. package/dist/core/CallStackBuilder.js +269 -0
  9. package/dist/core/DataFlowAnalyzer.d.ts +215 -0
  10. package/dist/core/DataFlowAnalyzer.js +1115 -0
  11. package/dist/core/DependencyGraph.d.ts +55 -0
  12. package/dist/core/DependencyGraph.js +541 -0
  13. package/dist/core/ImpactTracer.d.ts +96 -0
  14. package/dist/core/ImpactTracer.js +398 -0
  15. package/dist/core/PropagationTracker.d.ts +73 -0
  16. package/dist/core/PropagationTracker.js +502 -0
  17. package/dist/core/PropertyAccessTracker.d.ts +56 -0
  18. package/dist/core/PropertyAccessTracker.js +281 -0
  19. package/dist/core/SymbolAnalyzer.d.ts +139 -0
  20. package/dist/core/SymbolAnalyzer.js +608 -0
  21. package/dist/core/TypeFlowAnalyzer.d.ts +120 -0
  22. package/dist/core/TypeFlowAnalyzer.js +654 -0
  23. package/dist/core/TypePropagationAnalyzer.d.ts +58 -0
  24. package/dist/core/TypePropagationAnalyzer.js +269 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.js +952 -0
  27. package/dist/types.d.ts +102 -0
  28. package/dist/types.js +5 -0
  29. package/package.json +39 -0
  30. package/src/core/AnalysisCache.ts +189 -0
  31. package/src/core/CallStackBuilder.ts +345 -0
  32. package/src/core/DataFlowAnalyzer.ts +1403 -0
  33. package/src/core/DependencyGraph.ts +584 -0
  34. package/src/core/ImpactTracer.ts +521 -0
  35. package/src/core/PropagationTracker.ts +630 -0
  36. package/src/core/PropertyAccessTracker.ts +349 -0
  37. package/src/core/SymbolAnalyzer.ts +746 -0
  38. package/src/core/TypeFlowAnalyzer.ts +844 -0
  39. package/src/core/TypePropagationAnalyzer.ts +332 -0
  40. package/src/index.ts +1071 -0
  41. package/src/types.ts +163 -0
  42. package/test-cases/.blast-radius-cache/file-states.json +14 -0
  43. package/test-cases/config.ts +13 -0
  44. package/test-cases/consumer.ts +12 -0
  45. package/test-cases/nested.ts +25 -0
  46. package/test-cases/simple.ts +62 -0
  47. package/test-cases/tsconfig.json +11 -0
  48. package/test-cases/user.ts +32 -0
  49. package/tsconfig.json +16 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * CallStackBuilder - 调用栈视图构建器
3
+ *
4
+ * 从改动点向上追踪,构建完整的调用链视图
5
+ */
6
+ import { Project, Node, SyntaxKind } from 'ts-morph';
7
+ import * as path from 'path';
8
+ export class CallStackBuilder {
9
+ project;
10
+ projectRoot;
11
+ constructor(projectRoot, tsConfigPath) {
12
+ this.projectRoot = projectRoot;
13
+ this.project = new Project({
14
+ tsConfigFilePath: tsConfigPath,
15
+ skipAddingFilesFromTsConfig: true,
16
+ });
17
+ }
18
+ /**
19
+ * 添加源文件到项目
20
+ */
21
+ addSourceFiles(patterns) {
22
+ for (const pattern of patterns) {
23
+ this.project.addSourceFilesAtPaths(pattern);
24
+ }
25
+ // 过滤 node_modules
26
+ const sourceFiles = this.project.getSourceFiles();
27
+ for (const sf of sourceFiles) {
28
+ if (sf.getFilePath().includes('node_modules')) {
29
+ sf.forget();
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * 构建调用栈视图(从改动点向上追踪到入口)
35
+ */
36
+ buildCallStack(targetSymbol, targetFile) {
37
+ // 找到目标函数的定义
38
+ const definition = this.findSymbolDefinition(targetSymbol, targetFile);
39
+ if (!definition)
40
+ return null;
41
+ const root = {
42
+ name: targetSymbol,
43
+ file: definition.file,
44
+ line: definition.line,
45
+ type: 'function',
46
+ children: [],
47
+ };
48
+ // 递归追踪调用者
49
+ this.traceCallers(root, new Set(), 0, 10);
50
+ // 计算深度
51
+ const depth = this.calculateDepth(root);
52
+ // 构建路径字符串
53
+ const pathStr = this.buildPathString(root);
54
+ return { root, depth, path: pathStr };
55
+ }
56
+ /**
57
+ * 查找符号定义
58
+ */
59
+ findSymbolDefinition(symbolName, inFile) {
60
+ const sourceFile = this.project.getSourceFile(inFile);
61
+ if (!sourceFile)
62
+ return null;
63
+ let result = null;
64
+ sourceFile.forEachDescendant(node => {
65
+ if (Node.isFunctionDeclaration(node)) {
66
+ const func = node;
67
+ if (func.getName() === symbolName) {
68
+ result = {
69
+ file: inFile,
70
+ line: func.getStartLineNumber(),
71
+ type: 'function',
72
+ };
73
+ }
74
+ }
75
+ if (Node.isVariableStatement(node)) {
76
+ const varDecl = node.getFirstDescendantByKind(SyntaxKind.VariableDeclaration);
77
+ if (varDecl && varDecl.getName() === symbolName) {
78
+ result = {
79
+ file: inFile,
80
+ line: varDecl.getStartLineNumber(),
81
+ type: 'variable',
82
+ };
83
+ }
84
+ }
85
+ });
86
+ return result;
87
+ }
88
+ /**
89
+ * 递归追踪调用者
90
+ */
91
+ traceCallers(node, visited, depth, maxDepth) {
92
+ if (depth > maxDepth)
93
+ return;
94
+ // 找所有调用此函数的地方
95
+ const callers = this.findCallers(node.name, node.file);
96
+ for (const caller of callers) {
97
+ const key = `${caller.file}:${caller.line}:${caller.name}`;
98
+ if (visited.has(key))
99
+ continue;
100
+ visited.add(key);
101
+ // 创建调用者节点
102
+ const callerNode = {
103
+ name: caller.name,
104
+ file: caller.file,
105
+ line: caller.line,
106
+ type: caller.type,
107
+ children: [],
108
+ callSite: {
109
+ line: caller.callLine,
110
+ expression: caller.callExpression,
111
+ },
112
+ };
113
+ node.children.push(callerNode);
114
+ // 递归追踪调用者的调用者
115
+ this.traceCallers(callerNode, visited, depth + 1, maxDepth);
116
+ }
117
+ }
118
+ /**
119
+ * 查找调用某个函数的所有地方
120
+ */
121
+ findCallers(symbolName, definedInFile) {
122
+ const results = [];
123
+ for (const sourceFile of this.project.getSourceFiles()) {
124
+ const filePath = sourceFile.getFilePath();
125
+ sourceFile.forEachDescendant((node) => {
126
+ // 查找函数定义
127
+ let funcInfo = null;
128
+ if (Node.isFunctionDeclaration(node)) {
129
+ const func = node;
130
+ const name = func.getName();
131
+ if (name) {
132
+ funcInfo = {
133
+ name,
134
+ file: filePath,
135
+ line: func.getStartLineNumber(),
136
+ type: 'function',
137
+ };
138
+ }
139
+ }
140
+ else if (Node.isArrowFunction(node)) {
141
+ // 检查是否是某个 const/let 声明的箭头函数
142
+ const parent = node.getParent();
143
+ if (parent && Node.isVariableDeclaration(parent)) {
144
+ const varDecl = parent;
145
+ const name = varDecl.getName();
146
+ if (name && !name.startsWith('_')) {
147
+ funcInfo = {
148
+ name,
149
+ file: filePath,
150
+ line: node.getStartLineNumber(),
151
+ type: 'arrow',
152
+ };
153
+ }
154
+ }
155
+ }
156
+ else if (Node.isMethodDeclaration(node)) {
157
+ const method = node;
158
+ const name = method.getName();
159
+ funcInfo = {
160
+ name,
161
+ file: filePath,
162
+ line: method.getStartLineNumber(),
163
+ type: 'method',
164
+ };
165
+ }
166
+ else if (Node.isPropertyAssignment(node) && Node.isFunctionExpression(node.getInitializer())) {
167
+ // React 组件: onClick={() => ...}
168
+ const prop = node;
169
+ const name = prop.getName();
170
+ funcInfo = {
171
+ name: name || 'anonymous',
172
+ file: filePath,
173
+ line: node.getStartLineNumber(),
174
+ type: 'component',
175
+ };
176
+ }
177
+ // 如果找到了函数定义,检查是否调用了目标符号
178
+ if (funcInfo && funcInfo.name !== symbolName) {
179
+ const calls = this.findCallsInNode(node, symbolName);
180
+ for (const call of calls) {
181
+ results.push({
182
+ ...funcInfo,
183
+ callLine: call.line,
184
+ callExpression: call.expression,
185
+ });
186
+ }
187
+ }
188
+ });
189
+ }
190
+ return results;
191
+ }
192
+ /**
193
+ * 在节点内查找对某个符号的调用
194
+ */
195
+ findCallsInNode(node, symbolName) {
196
+ const results = [];
197
+ node.forEachDescendant((child) => {
198
+ if (Node.isCallExpression(child)) {
199
+ const callExpr = child;
200
+ const expr = callExpr.getExpression();
201
+ if (Node.isIdentifier(expr)) {
202
+ const name = expr.getText();
203
+ if (name === symbolName) {
204
+ results.push({
205
+ line: callExpr.getStartLineNumber(),
206
+ expression: callExpr.getText().slice(0, 50),
207
+ });
208
+ }
209
+ }
210
+ }
211
+ });
212
+ return results;
213
+ }
214
+ /**
215
+ * 计算树深度
216
+ */
217
+ calculateDepth(node) {
218
+ if (node.children.length === 0)
219
+ return 0;
220
+ return 1 + Math.max(...node.children.map(c => this.calculateDepth(c)));
221
+ }
222
+ /**
223
+ * 构建路径字符串
224
+ */
225
+ buildPathString(node) {
226
+ const result = [];
227
+ const build = (n) => {
228
+ result.push(`${path.basename(n.file)}:${n.line} (${n.name})`);
229
+ if (n.children.length > 0) {
230
+ build(n.children[0]);
231
+ }
232
+ };
233
+ build(node);
234
+ return result;
235
+ }
236
+ /**
237
+ * 生成文本格式的调用栈视图
238
+ */
239
+ formatAsText(tree, changedSymbol) {
240
+ const lines = [];
241
+ lines.push('');
242
+ lines.push('═══════════════════════════════════════════════════════════════');
243
+ lines.push(' 📞 调用栈视图 (Call Stack) ');
244
+ lines.push('═══════════════════════════════════════════════════════════════');
245
+ lines.push('');
246
+ const renderNode = (n, prefix, isLast, isRoot) => {
247
+ const connector = isLast ? '└─' : '├─';
248
+ const current = isRoot
249
+ ? `📍 ${n.name} (改动点)`
250
+ : `${connector} ${n.name}`;
251
+ lines.push(`${prefix}${current} → ${path.basename(n.file)}:${n.line}`);
252
+ if (n.callSite) {
253
+ lines.push(`${prefix} │`);
254
+ lines.push(`${prefix} └── 调用: 第${n.callSite.line}行 "${n.callSite.expression}"`);
255
+ }
256
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
257
+ n.children.forEach((child, idx) => {
258
+ const isChildLast = idx === n.children.length - 1;
259
+ renderNode(child, childPrefix, isChildLast, false);
260
+ });
261
+ };
262
+ renderNode(tree.root, '', true, true);
263
+ lines.push('');
264
+ lines.push(`深度: ${tree.depth} 层`);
265
+ lines.push(`路径: ${tree.path.join(' → ')}`);
266
+ lines.push('');
267
+ return lines.join('\n');
268
+ }
269
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Commercial-Grade Data Flow Analyzer
3
+ *
4
+ * Implements:
5
+ * - Interprocedural data flow analysis (cross-function tracking)
6
+ * - Control flow sensitivity (branches, loops, exceptions)
7
+ * - Path sensitivity (different branches = different type states)
8
+ * - Context sensitivity (same function, different call sites = different types)
9
+ * - Worklist algorithm with fixed-point computation
10
+ * - Lattice-based abstract interpretation
11
+ * - Symbolic execution for branch conditions
12
+ * - Points-to analysis for reference tracking
13
+ * - Taint analysis for security-sensitive data
14
+ * - Escape analysis for closure/global escape
15
+ */
16
+ /**
17
+ * Abstract value in the data flow lattice
18
+ */
19
+ export interface AbstractValue {
20
+ /** Type representation */
21
+ type: string;
22
+ /** Possible values (constants) */
23
+ constants: Set<string>;
24
+ /** Is null/undefined possible */
25
+ nullable: boolean;
26
+ /** Property types if object */
27
+ properties: Map<string, AbstractValue>;
28
+ /** Array element type if array */
29
+ elementType?: AbstractValue;
30
+ /** Is this value tainted (user input, etc.) */
31
+ tainted: boolean;
32
+ /** Where did this value escape (closure, global, return) */
33
+ escapes: Set<'closure' | 'global' | 'parameter' | 'return'>;
34
+ }
35
+ /**
36
+ * Data flow fact at a program point
37
+ */
38
+ export interface DataFlowFact {
39
+ /** Variable name -> abstract value */
40
+ env: Map<string, AbstractValue>;
41
+ /** Type constraints */
42
+ constraints: TypeConstraint[];
43
+ /** Path condition (branch predicates) */
44
+ pathCondition: PathCondition[];
45
+ }
46
+ /**
47
+ * Type constraint
48
+ */
49
+ export interface TypeConstraint {
50
+ variable: string;
51
+ predicate: string;
52
+ thenTypes?: Map<string, AbstractValue>;
53
+ elseTypes?: Map<string, AbstractValue>;
54
+ }
55
+ /**
56
+ * Path condition from branch predicates
57
+ */
58
+ export interface PathCondition {
59
+ expression: string;
60
+ /** true = then branch, false = else branch */
61
+ polarity: boolean;
62
+ }
63
+ /**
64
+ * Analysis result with full flow paths
65
+ */
66
+ export interface DataFlowResult {
67
+ hasDataLeaks: boolean;
68
+ flowPaths: FlowPath[];
69
+ taintedPaths: TaintedPath[];
70
+ typeNarrowing: Map<string, {
71
+ line: number;
72
+ types: string[];
73
+ }[]>;
74
+ statistics: {
75
+ nodesAnalyzed: number;
76
+ blocksConstructed: number;
77
+ callSitesAnalyzed: number;
78
+ fixedPointIterations: number;
79
+ constraintsGenerated: number;
80
+ typesNarrowed: number;
81
+ promiseUnwraps: number;
82
+ conditionalBranches: number;
83
+ escapedValues: number;
84
+ taintedValues: number;
85
+ pathsTracked: number;
86
+ };
87
+ confidence: 'high' | 'medium' | 'low';
88
+ duration: number;
89
+ /** All facts at exit of each block */
90
+ finalFacts: Map<string, DataFlowFact>;
91
+ }
92
+ export interface FlowPath {
93
+ source: string;
94
+ sink: string;
95
+ path: string[];
96
+ typeAtSink: string;
97
+ typeAtSource: string;
98
+ isTainted: boolean;
99
+ }
100
+ export interface TaintedPath {
101
+ source: string;
102
+ sink: string;
103
+ taintSource: 'user-input' | 'file-read' | 'network' | 'environment';
104
+ path: string[];
105
+ }
106
+ export declare class DataFlowAnalyzer {
107
+ private program;
108
+ private checker;
109
+ private sourceFiles;
110
+ private cfgCache;
111
+ private worklist;
112
+ private analyzedCallSites;
113
+ private maxIterations;
114
+ private maxCallDepth;
115
+ private trackTaint;
116
+ private trackEscapes;
117
+ constructor(projectRoot: string, tsConfigPath: string);
118
+ /**
119
+ * MAIN ENTRY POINT - Full interprocedural data flow analysis
120
+ */
121
+ analyzeDataFlow(functionName: string, functionFile: string): DataFlowResult;
122
+ /**
123
+ * Find function declaration
124
+ */
125
+ private findFunction;
126
+ /**
127
+ * Build Control Flow Graph with basic blocks
128
+ */
129
+ private buildCFG;
130
+ /**
131
+ * Create entry fact with parameter bindings
132
+ */
133
+ private createEntryFact;
134
+ /**
135
+ * Create abstract value from TypeScript type
136
+ */
137
+ private createAbstractValue;
138
+ /**
139
+ * WORKLIST ALGORITHM - Lattice-based fixed-point computation
140
+ *
141
+ * This is the core of the data flow analysis.
142
+ * It iterates until no facts change (fixed point is reached).
143
+ */
144
+ private runWorklistAnalysis;
145
+ /**
146
+ * JOIN operation - combine facts from multiple predecessors
147
+ */
148
+ private joinFacts;
149
+ /**
150
+ * LATTICE MEET - intersection of abstract values
151
+ */
152
+ private latticeMeet;
153
+ /**
154
+ * TRANSFER FUNCTION - apply a statement's effect on facts
155
+ */
156
+ private transfer;
157
+ /**
158
+ * Transfer function for expressions
159
+ */
160
+ private transferExpr;
161
+ /**
162
+ * Evaluate expression to get abstract value
163
+ */
164
+ private evaluateExpr;
165
+ /**
166
+ * Analyze a call site (interprocedural analysis)
167
+ */
168
+ private analyzeCallSite;
169
+ /**
170
+ * Check if expression is a taint source
171
+ */
172
+ private isTaintedSource;
173
+ /**
174
+ * Check if expression causes escape
175
+ */
176
+ private doesEscape;
177
+ /**
178
+ * Analyze async patterns (Promise, await)
179
+ */
180
+ private analyzeAsyncPatterns;
181
+ /**
182
+ * Check for data leaks (tainted -> escape)
183
+ */
184
+ private checkDataLeaks;
185
+ /**
186
+ * Clone a data flow fact
187
+ */
188
+ private cloneFact;
189
+ /**
190
+ * Narrow types based on branch condition
191
+ * E.g., if (x != null) narrows x from T | null to T
192
+ * if (x > 1000) narrows the possible range of x
193
+ */
194
+ private narrowTypesFromCondition;
195
+ /**
196
+ * Check if two facts are equal
197
+ */
198
+ private factsEqual;
199
+ /**
200
+ * Get line number
201
+ */
202
+ private getLine;
203
+ /**
204
+ * Calculate confidence level
205
+ */
206
+ private calculateConfidence;
207
+ /**
208
+ * Create empty result
209
+ */
210
+ private createEmptyResult;
211
+ /**
212
+ * Format as text
213
+ */
214
+ formatAsText(result: DataFlowResult, functionName: string): string;
215
+ }