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,58 @@
1
+ /**
2
+ * TypePropagationAnalyzer - 类型传播分析
3
+ *
4
+ * 追踪函数返回类型的变化如何影响使用该函数的地方
5
+ *
6
+ * 原理:
7
+ * 1. 获取函数的返回类型
8
+ * 2. 如果返回的是命名类型(interface/type),找到所有使用该类型的地方
9
+ * 3. 报告哪些代码可能受影响
10
+ */
11
+ export interface TypeReference {
12
+ file: string;
13
+ line: number;
14
+ column: number;
15
+ typeName: string;
16
+ usage: 'variable' | 'propertyAccess' | 'functionCall' | 'return' | 'parameter';
17
+ context: string;
18
+ affectedSymbol?: string;
19
+ }
20
+ export interface TypePropagationResult {
21
+ functionName: string;
22
+ file: string;
23
+ returnType: string;
24
+ typeDefinitionFile?: string;
25
+ references: TypeReference[];
26
+ affectedVariables: Array<{
27
+ variableName: string;
28
+ file: string;
29
+ line: number;
30
+ accesses: string[];
31
+ }>;
32
+ }
33
+ export declare class TypePropagationAnalyzer {
34
+ private project;
35
+ private projectRoot;
36
+ private checker;
37
+ constructor(projectRoot: string, tsConfigPath?: string);
38
+ /**
39
+ * 分析函数返回类型的影响
40
+ */
41
+ analyzeFunctionReturnType(functionName: string, inFile: string): TypePropagationResult | null;
42
+ /**
43
+ * 查找类型的使用位置
44
+ */
45
+ private findTypeUsages;
46
+ /**
47
+ * 查找受影响的变量(调用函数并访问其属性的变量)
48
+ */
49
+ private findAffectedVariables;
50
+ /**
51
+ * 查找对变量的属性访问
52
+ */
53
+ private findPropertyAccessesOnVariable;
54
+ /**
55
+ * 生成类型传播报告
56
+ */
57
+ generateReport(result: TypePropagationResult): string;
58
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * TypePropagationAnalyzer - 类型传播分析
3
+ *
4
+ * 追踪函数返回类型的变化如何影响使用该函数的地方
5
+ *
6
+ * 原理:
7
+ * 1. 获取函数的返回类型
8
+ * 2. 如果返回的是命名类型(interface/type),找到所有使用该类型的地方
9
+ * 3. 报告哪些代码可能受影响
10
+ */
11
+ import { Project, SyntaxKind } from 'ts-morph';
12
+ import * as path from 'path';
13
+ export class TypePropagationAnalyzer {
14
+ project;
15
+ projectRoot;
16
+ checker;
17
+ constructor(projectRoot, tsConfigPath) {
18
+ this.projectRoot = projectRoot;
19
+ this.project = new Project({
20
+ tsConfigFilePath: tsConfigPath ?? `${projectRoot}/tsconfig.json`,
21
+ skipAddingFilesFromTsConfig: true,
22
+ });
23
+ this.project.addSourceFilesAtPaths(`${projectRoot}/src/**/*.ts`);
24
+ const ls = this.project.getLanguageService();
25
+ this.checker = ls.getProgram().getTypeChecker();
26
+ }
27
+ /**
28
+ * 分析函数返回类型的影响
29
+ */
30
+ analyzeFunctionReturnType(functionName, inFile) {
31
+ // 找到函数定义
32
+ const sourceFile = this.project.getSourceFile(inFile);
33
+ if (!sourceFile)
34
+ return null;
35
+ // 尝试找函数
36
+ const functions = sourceFile.getFunctions();
37
+ let targetFunc = null;
38
+ let targetVar = null;
39
+ let isVariable = false;
40
+ for (const func of functions) {
41
+ if (func.getName() === functionName) {
42
+ targetFunc = func;
43
+ break;
44
+ }
45
+ }
46
+ // 如果没找到函数,尝试找变量
47
+ if (!targetFunc) {
48
+ const exported = sourceFile.getExportedDeclarations();
49
+ if (exported.has(functionName)) {
50
+ const decls = exported.get(functionName);
51
+ if (decls && decls.length > 0) {
52
+ const decl = decls[0];
53
+ if (decl.getKind() === SyntaxKind.VariableDeclaration) {
54
+ targetVar = decl;
55
+ isVariable = true;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ if (!targetFunc && !targetVar)
61
+ return null;
62
+ // 获取返回类型
63
+ let returnType;
64
+ let returnTypeText;
65
+ if (isVariable && targetVar) {
66
+ // 变量的情况
67
+ const varType = this.checker.getTypeAtLocation(targetVar);
68
+ returnType = varType;
69
+ returnTypeText = varType.getText();
70
+ }
71
+ else if (targetFunc) {
72
+ // 函数的情况
73
+ const signature = this.checker.getSignatureFromDeclaration(targetFunc);
74
+ if (!signature)
75
+ return null;
76
+ returnType = signature.getReturnType();
77
+ returnTypeText = returnType.getText();
78
+ }
79
+ else {
80
+ return null;
81
+ }
82
+ const returnTypeSymbol = returnType.getSymbol();
83
+ console.log(`[TypePropagation] ${functionName} returns: ${returnTypeText}`);
84
+ // 如果返回的是命名类型,找到该类型的定义和使用
85
+ let typeDefinitionFile;
86
+ let typeName = returnTypeText;
87
+ if (returnTypeSymbol) {
88
+ const declarations = returnTypeSymbol.getDeclarations();
89
+ if (declarations.length > 0) {
90
+ typeDefinitionFile = declarations[0].getSourceFile().getFilePath();
91
+ console.log(`[TypePropagation] Type defined at: ${typeDefinitionFile}`);
92
+ }
93
+ }
94
+ // 查找所有使用这个返回类型的地方
95
+ const references = this.findTypeUsages(typeName, returnType);
96
+ // 查找受影响的变量
97
+ const affectedVariables = this.findAffectedVariables(functionName, returnType);
98
+ return {
99
+ functionName,
100
+ file: inFile,
101
+ returnType: returnTypeText,
102
+ typeDefinitionFile,
103
+ references,
104
+ affectedVariables,
105
+ };
106
+ }
107
+ /**
108
+ * 查找类型的使用位置
109
+ */
110
+ findTypeUsages(typeName, returnType) {
111
+ const refs = [];
112
+ // 获取类型的属性
113
+ const typeProperties = returnType.getProperties();
114
+ const propertyNames = typeProperties.map(p => p.getName());
115
+ // 遍历所有源文件
116
+ for (const sourceFile of this.project.getSourceFiles()) {
117
+ const filePath = sourceFile.getFilePath();
118
+ // 跳过 node_modules
119
+ if (filePath.includes('node_modules'))
120
+ continue;
121
+ sourceFile.forEachDescendant((node) => {
122
+ // 检查类型引用
123
+ if (node.getKind() === SyntaxKind.TypeReference) {
124
+ const typeRefText = node.getText();
125
+ if (typeRefText.includes(typeName) || typeName.includes(typeRefText)) {
126
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
127
+ refs.push({
128
+ file: filePath,
129
+ line,
130
+ column,
131
+ typeName,
132
+ usage: 'parameter',
133
+ context: `Type reference: ${typeRefText}`,
134
+ });
135
+ }
136
+ }
137
+ // 检查变量声明
138
+ if (node.getKind() === SyntaxKind.VariableDeclaration) {
139
+ const varNode = node;
140
+ const varName = varNode.getName();
141
+ try {
142
+ const varType = this.checker.getTypeAtLocation(node);
143
+ const varTypeText = varType.getText();
144
+ if (varTypeText.includes(typeName)) {
145
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
146
+ refs.push({
147
+ file: filePath,
148
+ line,
149
+ column,
150
+ typeName,
151
+ usage: 'variable',
152
+ context: `Variable ${varName}: ${varTypeText}`,
153
+ affectedSymbol: varName,
154
+ });
155
+ }
156
+ }
157
+ catch { }
158
+ }
159
+ });
160
+ }
161
+ return refs;
162
+ }
163
+ /**
164
+ * 查找受影响的变量(调用函数并访问其属性的变量)
165
+ */
166
+ findAffectedVariables(functionName, returnType) {
167
+ const affected = [];
168
+ const returnProperties = returnType.getProperties().map(p => p.getName());
169
+ // 遍历所有源文件找调用点
170
+ for (const sourceFile of this.project.getSourceFiles()) {
171
+ const filePath = sourceFile.getFilePath();
172
+ if (filePath.includes('node_modules'))
173
+ continue;
174
+ sourceFile.forEachDescendant((node) => {
175
+ // 找函数调用
176
+ if (node.getKind() === SyntaxKind.CallExpression) {
177
+ const callNode = node;
178
+ const callExpr = callNode.getExpression();
179
+ if (callExpr.getText() === functionName) {
180
+ // 找到了调用
181
+ // 检查是否有变量接收返回值
182
+ const parent = node.getParent();
183
+ if (parent?.getKind() === SyntaxKind.VariableDeclaration) {
184
+ const varNode = parent;
185
+ const varName = varNode.getName();
186
+ // 查找这个变量的属性访问
187
+ const accesses = [];
188
+ const funcBody = sourceFile; // 简化处理
189
+ // 在同一个作用域查找属性访问
190
+ this.findPropertyAccessesOnVariable(sourceFile, varName, returnProperties, accesses);
191
+ if (accesses.length > 0) {
192
+ const { line } = sourceFile.getLineAndColumnAtPos(node.getStart());
193
+ affected.push({
194
+ variableName: varName,
195
+ file: filePath,
196
+ line,
197
+ accesses,
198
+ });
199
+ }
200
+ }
201
+ }
202
+ }
203
+ });
204
+ }
205
+ return affected;
206
+ }
207
+ /**
208
+ * 查找对变量的属性访问
209
+ */
210
+ findPropertyAccessesOnVariable(sourceFile, variableName, returnProperties, accesses) {
211
+ sourceFile.forEachDescendant((node) => {
212
+ if (node.getKind() === SyntaxKind.PropertyAccessExpression) {
213
+ const propNode = node;
214
+ const exprText = propNode.getExpression().getText();
215
+ if (exprText === variableName) {
216
+ const propName = propNode.getName();
217
+ if (returnProperties.includes(propName)) {
218
+ accesses.push(propName);
219
+ }
220
+ }
221
+ }
222
+ });
223
+ }
224
+ /**
225
+ * 生成类型传播报告
226
+ */
227
+ generateReport(result) {
228
+ const lines = [];
229
+ lines.push('═══════════════════════════════════════════════════════════════');
230
+ lines.push(' TYPE PROPAGATION ANALYSIS ');
231
+ lines.push('═══════════════════════════════════════════════════════════════');
232
+ lines.push('');
233
+ lines.push(`📌 Function: ${result.functionName}`);
234
+ lines.push(`📁 File: ${result.file}`);
235
+ lines.push(`🔤 Return Type: ${result.returnType}`);
236
+ if (result.typeDefinitionFile) {
237
+ lines.push(`📄 Type Defined At: ${result.typeDefinitionFile}`);
238
+ }
239
+ lines.push('');
240
+ if (result.affectedVariables.length > 0) {
241
+ lines.push('─── Affected Variables ──────────────────────────────────────');
242
+ for (const av of result.affectedVariables) {
243
+ lines.push(` 📍 ${av.variableName} (${path.basename(av.file)}:${av.line})`);
244
+ lines.push(` Accessed properties: ${av.accesses.join(', ')}`);
245
+ }
246
+ lines.push('');
247
+ }
248
+ if (result.references.length > 0) {
249
+ lines.push('─── Type References ─────────────────────────────────────────');
250
+ const byFile = new Map();
251
+ for (const ref of result.references) {
252
+ if (!byFile.has(ref.file))
253
+ byFile.set(ref.file, []);
254
+ byFile.get(ref.file).push(ref);
255
+ }
256
+ for (const [file, refs] of byFile) {
257
+ lines.push(` 📄 ${path.basename(file)}`);
258
+ for (const ref of refs.slice(0, 3)) {
259
+ lines.push(` • ${ref.usage}: ${ref.context} (line ${ref.line})`);
260
+ }
261
+ if (refs.length > 3) {
262
+ lines.push(` ... and ${refs.length - 3} more`);
263
+ }
264
+ }
265
+ lines.push('');
266
+ }
267
+ return lines.join('\n');
268
+ }
269
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Blast Radius Analyzer - 改动影响范围分析器
4
+ *
5
+ * 使用符号级分析 + 数据流追踪
6
+ * 支持任意 JavaScript/TypeScript 项目
7
+ *
8
+ * Usage:
9
+ * blast-radius --project ./src --change src/api/user.ts
10
+ * blast-radius --project ./src --change src/utils/helper.ts --symbol helperFunc
11
+ * blast-radius --project ./src --change src/api/task.ts --graph
12
+ */
13
+ export {};