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.
- package/README.md +108 -0
- package/TEST-REPORT.md +379 -0
- package/dist/core/AnalysisCache.d.ts +59 -0
- package/dist/core/AnalysisCache.js +156 -0
- package/dist/core/BlastRadiusAnalyzer.d.ts +99 -0
- package/dist/core/BlastRadiusAnalyzer.js +510 -0
- package/dist/core/CallStackBuilder.d.ts +63 -0
- package/dist/core/CallStackBuilder.js +269 -0
- package/dist/core/DataFlowAnalyzer.d.ts +215 -0
- package/dist/core/DataFlowAnalyzer.js +1115 -0
- package/dist/core/DependencyGraph.d.ts +55 -0
- package/dist/core/DependencyGraph.js +541 -0
- package/dist/core/ImpactTracer.d.ts +96 -0
- package/dist/core/ImpactTracer.js +398 -0
- package/dist/core/PropagationTracker.d.ts +73 -0
- package/dist/core/PropagationTracker.js +502 -0
- package/dist/core/PropertyAccessTracker.d.ts +56 -0
- package/dist/core/PropertyAccessTracker.js +281 -0
- package/dist/core/SymbolAnalyzer.d.ts +139 -0
- package/dist/core/SymbolAnalyzer.js +608 -0
- package/dist/core/TypeFlowAnalyzer.d.ts +120 -0
- package/dist/core/TypeFlowAnalyzer.js +654 -0
- package/dist/core/TypePropagationAnalyzer.d.ts +58 -0
- package/dist/core/TypePropagationAnalyzer.js +269 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +952 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +5 -0
- package/package.json +39 -0
- package/src/core/AnalysisCache.ts +189 -0
- package/src/core/CallStackBuilder.ts +345 -0
- package/src/core/DataFlowAnalyzer.ts +1403 -0
- package/src/core/DependencyGraph.ts +584 -0
- package/src/core/ImpactTracer.ts +521 -0
- package/src/core/PropagationTracker.ts +630 -0
- package/src/core/PropertyAccessTracker.ts +349 -0
- package/src/core/SymbolAnalyzer.ts +746 -0
- package/src/core/TypeFlowAnalyzer.ts +844 -0
- package/src/core/TypePropagationAnalyzer.ts +332 -0
- package/src/index.ts +1071 -0
- package/src/types.ts +163 -0
- package/test-cases/.blast-radius-cache/file-states.json +14 -0
- package/test-cases/config.ts +13 -0
- package/test-cases/consumer.ts +12 -0
- package/test-cases/nested.ts +25 -0
- package/test-cases/simple.ts +62 -0
- package/test-cases/tsconfig.json +11 -0
- package/test-cases/user.ts +32 -0
- package/tsconfig.json +16 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast Radius Analyzer - Type Definitions
|
|
3
|
+
* 分析代码改动影响范围的核心类型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ─── 改动信息 ────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface CodeChange {
|
|
9
|
+
file: string;
|
|
10
|
+
type: ChangeType;
|
|
11
|
+
symbol?: string; // 改动的符号名 (function/class/interface)
|
|
12
|
+
line?: number;
|
|
13
|
+
diff?: string; // 具体的 diff 内容
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ChangeType =
|
|
17
|
+
| 'add' // 新增文件/代码
|
|
18
|
+
| 'delete' // 删除
|
|
19
|
+
| 'modify' // 修改
|
|
20
|
+
| 'rename'; // 重命名
|
|
21
|
+
|
|
22
|
+
// ─── 依赖图 ──────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export interface DependencyNode {
|
|
25
|
+
id: string; // 文件路径或符号ID
|
|
26
|
+
kind: NodeKind;
|
|
27
|
+
file?: string;
|
|
28
|
+
line?: number;
|
|
29
|
+
exports?: string[]; // 导出的符号
|
|
30
|
+
imports?: ImportInfo[];
|
|
31
|
+
dependents: string[]; // 依赖此节点的节点 (反向索引)
|
|
32
|
+
depth: number; // 到改动点的距离
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type NodeKind =
|
|
36
|
+
| 'file'
|
|
37
|
+
| 'function'
|
|
38
|
+
| 'class'
|
|
39
|
+
| 'interface'
|
|
40
|
+
| 'type'
|
|
41
|
+
| 'variable'
|
|
42
|
+
| 'export';
|
|
43
|
+
|
|
44
|
+
export interface ImportInfo {
|
|
45
|
+
source: string;
|
|
46
|
+
imported: string[];
|
|
47
|
+
isReExport: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DependencyGraph {
|
|
51
|
+
nodes: Map<string, DependencyNode>;
|
|
52
|
+
edges: Edge[];
|
|
53
|
+
entryPoints: string[]; // 入口文件 (main/app entry)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Edge {
|
|
57
|
+
from: string;
|
|
58
|
+
to: string;
|
|
59
|
+
type: EdgeType;
|
|
60
|
+
symbol?: string; // 如果是符号级别的依赖
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type EdgeType =
|
|
64
|
+
| 'import' // import { x } from 'y'
|
|
65
|
+
| 'extend' // class A extends B
|
|
66
|
+
| 'implement' // class A implements B
|
|
67
|
+
| 'type-ref' // type X = Y
|
|
68
|
+
| 'call' // A() 调用
|
|
69
|
+
| 'property-access' // A.b 属性访问
|
|
70
|
+
| 'param-type'; // 参数类型引用
|
|
71
|
+
|
|
72
|
+
// ─── 影响范围评估 ────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
export interface ImpactScope {
|
|
75
|
+
changedFile: string;
|
|
76
|
+
timestamp: string;
|
|
77
|
+
|
|
78
|
+
// 影响统计
|
|
79
|
+
stats: ImpactStats;
|
|
80
|
+
|
|
81
|
+
// 受影响的层级
|
|
82
|
+
levels: ImpactLevel[];
|
|
83
|
+
|
|
84
|
+
// 详细的受影响文件
|
|
85
|
+
affectedFiles: AffectedFile[];
|
|
86
|
+
|
|
87
|
+
// 传播路径 (从改动到每个受影响点的路径)
|
|
88
|
+
propagationPaths: PropagationPath[];
|
|
89
|
+
|
|
90
|
+
// 高风险影响
|
|
91
|
+
highRiskImpacts: HighRiskImpact[];
|
|
92
|
+
|
|
93
|
+
// 建议
|
|
94
|
+
recommendations: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ImpactStats {
|
|
98
|
+
totalAffectedFiles: number;
|
|
99
|
+
directDependencies: number; // 直接依赖
|
|
100
|
+
transitiveDependencies: number; // 传递依赖
|
|
101
|
+
filesWithBreakingChanges: number;
|
|
102
|
+
criticalFiles: number; // 关键文件 (入口/配置)
|
|
103
|
+
estimatedRippleDepth: number; // 预计涟漪深度
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ImpactLevel {
|
|
107
|
+
depth: number; // 距离改动的深度
|
|
108
|
+
description: string;
|
|
109
|
+
files: string[];
|
|
110
|
+
nodeCount: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface AffectedFile {
|
|
114
|
+
file: string;
|
|
115
|
+
kind: NodeKind;
|
|
116
|
+
changeType: ChangeType;
|
|
117
|
+
impactLevel: number; // 1 = 直接依赖, 2 = 传递依赖, etc.
|
|
118
|
+
impactFactors: ImpactFactor[];
|
|
119
|
+
line?: number; // 最可能受影响的行
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ImpactFactor {
|
|
123
|
+
factor: string;
|
|
124
|
+
weight: number; // 0-1, 影响权重
|
|
125
|
+
reason: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface PropagationPath {
|
|
129
|
+
from: string;
|
|
130
|
+
to: string;
|
|
131
|
+
path: string[]; // 完整的传播路径
|
|
132
|
+
edgeTypes: EdgeType[];
|
|
133
|
+
riskLevel: RiskLevel;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
137
|
+
|
|
138
|
+
export interface HighRiskImpact {
|
|
139
|
+
file: string;
|
|
140
|
+
reason: string;
|
|
141
|
+
riskLevel: RiskLevel;
|
|
142
|
+
mitigation?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── 配置 ────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
export interface AnalyzerConfig {
|
|
148
|
+
projectRoot: string;
|
|
149
|
+
tsConfigPath?: string;
|
|
150
|
+
|
|
151
|
+
// 分析范围
|
|
152
|
+
maxDepth: number; // 最大分析深度
|
|
153
|
+
includeNodeModules: boolean; // 是否分析 node_modules
|
|
154
|
+
includeTests: boolean; // 是否分析测试文件
|
|
155
|
+
|
|
156
|
+
// 风险评估
|
|
157
|
+
criticalPatterns: string[]; // 关键文件模式 (如 **/main.ts, **/index.ts)
|
|
158
|
+
ignorePatterns: string[]; // 忽略的分析模式
|
|
159
|
+
|
|
160
|
+
// 输出
|
|
161
|
+
verbose: boolean;
|
|
162
|
+
outputFormat: 'json' | 'text' | 'html' | 'graph';
|
|
163
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"/Users/didi/Documents/code3/logic-inverse-v2/blast-radius-analyzer/test-cases/nested.ts": {
|
|
3
|
+
"mtime": 1774883918653.3655,
|
|
4
|
+
"hash": "7bfe575d"
|
|
5
|
+
},
|
|
6
|
+
"/Users/didi/Documents/code3/logic-inverse-v2/blast-radius-analyzer/test-cases/simple.ts": {
|
|
7
|
+
"mtime": 1774881971275.246,
|
|
8
|
+
"hash": "88ca972"
|
|
9
|
+
},
|
|
10
|
+
"/Users/didi/Documents/code3/logic-inverse-v2/blast-radius-analyzer/test-cases/config.ts": {
|
|
11
|
+
"mtime": 1774882783262.9082,
|
|
12
|
+
"hash": "3ff6da2a"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test case: Constants and types
|
|
2
|
+
export const API_BASE_URL = 'https://api.example.com';
|
|
3
|
+
|
|
4
|
+
export const MAX_RETRIES = 3;
|
|
5
|
+
|
|
6
|
+
export type UserStatus = 'active' | 'inactive' | 'pending';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_USER: UserStatus = 'pending';
|
|
9
|
+
|
|
10
|
+
export const CONFIG = {
|
|
11
|
+
timeout: 5000,
|
|
12
|
+
retries: 3,
|
|
13
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { API_BASE_URL, MAX_RETRIES, UserStatus, CONFIG } from './config';
|
|
2
|
+
|
|
3
|
+
export const fetchUser = async (id: string): Promise<any> => {
|
|
4
|
+
const url = `${API_BASE_URL}/users/${id}`;
|
|
5
|
+
// Use MAX_RETRIES
|
|
6
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
7
|
+
// retry logic
|
|
8
|
+
}
|
|
9
|
+
return { url, status: 'active' as UserStatus };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getConfig = () => CONFIG.timeout;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Complex test: nested property access
|
|
2
|
+
export const config = {
|
|
3
|
+
api: {
|
|
4
|
+
baseUrl: 'https://api.example.com',
|
|
5
|
+
timeout: 5000,
|
|
6
|
+
},
|
|
7
|
+
db: {
|
|
8
|
+
host: 'localhost',
|
|
9
|
+
port: 5432,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Class with methods
|
|
14
|
+
export class UserService {
|
|
15
|
+
private apiUrl = config.api.baseUrl;
|
|
16
|
+
|
|
17
|
+
getUser(id: string) {
|
|
18
|
+
return fetch(`${this.apiUrl}/users/${id}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Function using dynamic property
|
|
23
|
+
export const getConfig = (key: string) => {
|
|
24
|
+
return (config as any)[key];
|
|
25
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Test Case 1: Simple function (no branches)
|
|
2
|
+
export const add = (a: number, b: number): number => {
|
|
3
|
+
return a + b;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// Test Case 2: Function with type narrowing
|
|
7
|
+
export const processData = (data: string | null): number => {
|
|
8
|
+
if (data != null) {
|
|
9
|
+
return data.length;
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Test Case 3: Function with branches
|
|
15
|
+
export const classify = (score: number): string => {
|
|
16
|
+
if (score >= 90) {
|
|
17
|
+
return 'A';
|
|
18
|
+
} else if (score >= 80) {
|
|
19
|
+
return 'B';
|
|
20
|
+
} else if (score >= 70) {
|
|
21
|
+
return 'C';
|
|
22
|
+
} else {
|
|
23
|
+
return 'D';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Test Case 4: Function with loop
|
|
28
|
+
export const sumArray = (arr: number[]): number => {
|
|
29
|
+
let sum = 0;
|
|
30
|
+
for (const num of arr) {
|
|
31
|
+
sum += num;
|
|
32
|
+
}
|
|
33
|
+
return sum;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Test Case 5: Function with try-catch
|
|
37
|
+
export const safeParse = (json: string): any => {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(json);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Test Case 6: Async function
|
|
46
|
+
export const fetchData = async (url: string): Promise<any> => {
|
|
47
|
+
const response = await fetch(url);
|
|
48
|
+
return response.json();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Test Case 7: Taint source simulation
|
|
52
|
+
export const processUserInput = (input: string): string => {
|
|
53
|
+
return input.replace(/[<>]/g, '');
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Test Case 8: Function with multiple return paths
|
|
57
|
+
export const divide = (a: number, b: number): number | null => {
|
|
58
|
+
if (b === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return a / b;
|
|
62
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Test Case 9: Consumer of simple functions
|
|
2
|
+
import { add, processData, classify, sumArray, safeParse, processUserInput, divide } from './simple';
|
|
3
|
+
|
|
4
|
+
export const calculate = () => {
|
|
5
|
+
const result = add(1, 2);
|
|
6
|
+
return result;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const handleData = () => {
|
|
10
|
+
const len = processData("hello");
|
|
11
|
+
return len;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const grade = () => {
|
|
15
|
+
return classify(95);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const total = () => {
|
|
19
|
+
return sumArray([1, 2, 3, 4, 5]);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const parse = () => {
|
|
23
|
+
return safeParse('{"key": "value"}');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const sanitize = () => {
|
|
27
|
+
return processUserInput('<script>alert(1)</script>');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const division = () => {
|
|
31
|
+
return divide(10, 2);
|
|
32
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|