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
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Blast Radius Analyzer
2
+
3
+ **改动影响范围分析器** - 追踪代码改动的下游影响范围
4
+
5
+ ## 特性
6
+
7
+ - 🔍 **符号级追踪** - 精确定位符号的所有引用
8
+ - 📊 **调用栈视图** - 完整展示调用链路
9
+ - 🔗 **传播路径追踪** - 追踪常量、类型、嵌套属性的传播
10
+ - 🔬 **数据流分析** - 商业级数据流追踪 (DataFlow Pro)
11
+ - 📈 **类型流分析** - TypeFlow Pro 类型兼容性检查
12
+ - ⚡ **CI/CD 集成** - 支持阈值告警和退出码
13
+ - 💾 **智能缓存** - 增量分析,秒级响应
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ # npm
19
+ npm install -g blast-radius-analyzer
20
+
21
+ # 或者直接运行
22
+ npx blast-radius-analyzer
23
+ ```
24
+
25
+ ## 使用
26
+
27
+ ### 基本用法
28
+
29
+ ```bash
30
+ # 分析改动影响
31
+ blast-radius -p ./src -c ./src/api/user.ts
32
+
33
+ # 指定符号
34
+ blast-radius -p ./src -c ./src/api/task.ts --symbol getTaskStats
35
+
36
+ # CI/CD 模式 - 阈值告警
37
+ blast-radius -p ./src -c ./src/api/task.ts --threshold files:5,score:100
38
+
39
+ # JSON 输出
40
+ blast-radius -p ./src -c ./src/api/task.ts -o result.json
41
+
42
+ # 轻量模式 (不加载完整项目)
43
+ blast-radius -p ./src -c ./src/api/task.ts -t
44
+ ```
45
+
46
+ ### 参数说明
47
+
48
+ | 参数 | 说明 |
49
+ |------|------|
50
+ | `-p, --project` | 项目根目录 |
51
+ | `-c, --change` | 改动的文件路径 |
52
+ | `--symbol` | 改动的符号名 (函数/变量/类型等) |
53
+ | `-t, --symbol-only` | 轻量模式,不加载完整项目 |
54
+ | `--threshold` | CI/CD 阈值 (files:N,score:N) |
55
+ | `-o, --output` | JSON 输出文件 |
56
+ | `--clear-cache` | 清除缓存后重新分析 |
57
+
58
+ ## 示例输出
59
+
60
+ ```
61
+ ┌─────────────────────────────────────────────────────────────────┐
62
+ │ 📊 改动影响范围分析报告 │
63
+ └─────────────────────────────────────────────────────────────────┘
64
+
65
+ 📝 改动内容
66
+ 文件: task.ts
67
+ 符号: getTaskStats
68
+ 类型: 修改
69
+
70
+ 🚨 风险等级: 🟢 低风险
71
+
72
+ 📈 影响范围
73
+ ├─ 受影响文件: 2 个
74
+ ├─ 直接引用: 3 处
75
+ └─ 调用点: 1 处
76
+
77
+ 📞 调用栈视图
78
+
79
+ 📍 getTaskStats (改动点) [function] → task.ts:7
80
+ └─ fetchStats [function] → index.tsx:130
81
+ ├─ handleRegenerate [arrow] → index.tsx:178
82
+ └─ handleStop [arrow] → index.tsx:187
83
+
84
+ 📊 数据流分析 (DataFlow Pro)
85
+ 基本块: 5 | 条件分支: 1 | 类型收窄: 1 | 置信度: medium
86
+ ```
87
+
88
+ ## 工作原理
89
+
90
+ 1. **符号分析** - 使用 TypeScript Language Service 查找符号的所有引用
91
+ 2. **调用链追踪** - 递归追踪函数的调用者,构建完整调用栈
92
+ 3. **传播追踪** - 追踪常量、类型、嵌套对象的传播路径
93
+ 4. **数据流分析** - 基于 CFG 和格论抽象解释的数据流追踪
94
+ 5. **风险评估** - 基于影响范围和调用深度计算风险等级
95
+
96
+ ## 支持的场景
97
+
98
+ | 改动类型 | 追踪能力 |
99
+ |----------|----------|
100
+ | 导出函数 | ✅ 完整调用链 |
101
+ | 导出常量 | ✅ 传播路径 |
102
+ | 导出类型/接口 | ✅ 引用追踪 |
103
+ | 嵌套对象属性 | ✅ 传播路径 |
104
+ | 类/模块导出 | ✅ 实例化追踪 |
105
+
106
+ ## License
107
+
108
+ MIT
package/TEST-REPORT.md ADDED
@@ -0,0 +1,379 @@
1
+ # Blast Radius Analyzer - 验收测试报告
2
+
3
+ **测试日期**: 2026-03-30
4
+ **测试版本**: v1.2.1 (商业级)
5
+ **测试环境**: macOS Darwin 25.2.0
6
+ **Node版本**: v22.16.0
7
+
8
+ ---
9
+
10
+ ## 执行摘要
11
+
12
+ 本次测试覆盖 **13 个核心场景**,涵盖:
13
+ - 功能测试 (5项)
14
+ - 错误处理 (1项)
15
+ - 输出格式 (1项)
16
+ - 性能/内存 (1项)
17
+ - CI/CD集成 (1项)
18
+ - 高级特性 (3项)
19
+ - **新增:传播追踪 (2项)**
20
+
21
+ **测试结果**: ✅ **全部通过**
22
+
23
+ ---
24
+
25
+ ## 测试用例详情
26
+
27
+ ### 1. 简单函数分析 (add)
28
+
29
+ | 属性 | 值 |
30
+ |------|-----|
31
+ | 测试函数 | `add(a: number, b: number): number` |
32
+ | 基本块 | 1 |
33
+ | 条件分支 | 0 |
34
+ | 类型收窄 | 0 |
35
+ | 状态 | ✅ PASS |
36
+
37
+ **调用链路**:
38
+ ```
39
+ add → calculate → user.ts:5
40
+ ```
41
+
42
+ ---
43
+
44
+ ### 2. 类型收窄分析 (processData)
45
+
46
+ | 属性 | 值 |
47
+ |------|-----|
48
+ | 测试函数 | `processData(data: string \| null)` |
49
+ | 基本块 | 4 |
50
+ | 条件分支 | 1 |
51
+ | **类型收窄** | **1** |
52
+ | 状态 | ✅ PASS |
53
+
54
+ **说明**: 正确识别 `if (data != null)` 分支并对 `data` 类型进行收窄(从 `string | null` 到 `string`)
55
+
56
+ ---
57
+
58
+ ### 3. 多分支函数分析 (classify)
59
+
60
+ | 属性 | 值 |
61
+ |------|-----|
62
+ | 测试函数 | `classify(score: number): string` |
63
+ | 基本块 | 11 |
64
+ | 条件分支 | 3 |
65
+ | 类型收窄 | 0 |
66
+ | 状态 | ✅ PASS |
67
+
68
+ ---
69
+
70
+ ### 4. 循环函数分析 (sumArray)
71
+
72
+ | 属性 | 值 |
73
+ |------|-----|
74
+ | 测试函数 | `sumArray(arr: number[]): number` |
75
+ | 基本块 | 4 |
76
+ | 条件分支 | 1 |
77
+ | 类型收窄 | 1 |
78
+ | 状态 | ✅ PASS |
79
+
80
+ ---
81
+
82
+ ### 5. 错误处理 - 不存在的文件
83
+
84
+ | 属性 | 值 |
85
+ |------|-----|
86
+ | 输入 | `not-exist.ts` |
87
+ | 预期行为 | 报错 + Exit Code 1 |
88
+ | 实际行为 | 报错 + Exit Code 1 |
89
+ | 状态 | ✅ PASS |
90
+
91
+ ---
92
+
93
+ ### 6. JSON 输出格式
94
+
95
+ | 属性 | 值 |
96
+ |------|-----|
97
+ | 输入 | `--output /tmp/blast-test.json` |
98
+ | JSON有效性 | ✅ |
99
+ | Risk Level | `low` |
100
+ | Impact Score | `10` |
101
+ | 状态 | ✅ PASS |
102
+
103
+ ---
104
+
105
+ ### 7. 内存保护 (-t 标志)
106
+
107
+ | 属性 | 值 |
108
+ |------|-----|
109
+ | 项目 | llab-label-fe |
110
+ | 加载文件数 | **73 个** |
111
+ | 内存溢出 | ❌ 无 |
112
+ | 状态 | ✅ PASS |
113
+
114
+ ---
115
+
116
+ ### 8. CI/CD 阈值 - 正常范围
117
+
118
+ | 属性 | 值 |
119
+ |------|-----|
120
+ | 阈值 | `files:5` |
121
+ | 实际影响 | 2 个文件 |
122
+ | 退出码 | 0 |
123
+ | 状态 | ✅ PASS |
124
+
125
+ ---
126
+
127
+ ### 9. CI/CD 阈值 - 超限告警
128
+
129
+ | 属性 | 值 |
130
+ |------|-----|
131
+ | 阈值 | `files:1` |
132
+ | 实际影响 | 2 个文件 |
133
+ | 退出码 | **2** |
134
+ | 状态 | ✅ PASS |
135
+
136
+ ---
137
+
138
+ ### 10. 深度调用栈追踪
139
+
140
+ | 属性 | 值 |
141
+ |------|-----|
142
+ | 项目 | llab-label-fe |
143
+ | 函数 | `getTaskStats` |
144
+ | 深度 | 2 层 |
145
+ | 状态 | ✅ PASS |
146
+
147
+ ---
148
+
149
+ ### 11. 属性访问追踪
150
+
151
+ | 属性 | 值 |
152
+ |------|-----|
153
+ | 追踪字段 | `res.data.*` |
154
+ | 检测到的属性 | `total_tasks`, `in_progress_tasks`, `total_tokens`, `tasks`, `total_count` |
155
+ | 状态 | ✅ PASS |
156
+
157
+ ---
158
+
159
+ ### 12. 常量传播追踪 (API_BASE_URL)
160
+
161
+ | 属性 | 值 |
162
+ |------|-----|
163
+ | 符号类型 | `variable` (常量) |
164
+ | 发现引用 | 2 个 |
165
+ | **传播路径** | **✅ 显示** |
166
+ | 状态 | ✅ PASS |
167
+
168
+ **传播路径输出**:
169
+ ```
170
+ 🔗 传播路径详情
171
+
172
+ 📍 API_BASE_URL → 📥 fetchUser()
173
+ 位置: consumer.ts:3
174
+ 类型: variable
175
+ ```
176
+
177
+ **说明**: 正确追踪常量 `API_BASE_URL` 从 config.ts 传播到 consumer.ts 中的 `fetchUser` 函数
178
+
179
+ ---
180
+
181
+ ### 13. 类型传播追踪 (UserStatus)
182
+
183
+ | 属性 | 值 |
184
+ |------|-----|
185
+ | 符号类型 | `type` |
186
+ | 发现引用 | 3 个 |
187
+ | **传播路径** | **✅ 显示** |
188
+ | 状态 | ✅ PASS |
189
+
190
+ **传播路径输出**:
191
+ ```
192
+ 🔗 传播路径详情
193
+
194
+ 📍 UserStatus → UserStatus
195
+ 位置: config.ts:8
196
+ 类型: type
197
+
198
+ 📍 UserStatus → UserStatus
199
+ 位置: consumer.ts:9
200
+ 类型: type
201
+
202
+ 📍 UserStatus → 📤 return
203
+ 位置: consumer.ts:9
204
+ 类型: type
205
+ ```
206
+
207
+ ---
208
+
209
+ ## 商业级特性验证
210
+
211
+ | 特性 | 状态 | 说明 |
212
+ |------|------|------|
213
+ | 过程间分析 | ✅ | 跨函数调用链追踪 |
214
+ | 控制流敏感 | ✅ | CFG基本块 + 分支/循环 |
215
+ | 路径敏感 | ✅ | 分支条件追踪 |
216
+ | 上下文敏感 | ✅ | 调用点缓存 |
217
+ | 工作表算法 | ✅ | 固定点迭代收敛 |
218
+ | 格论抽象解释 | ✅ | AbstractValue + lattice meet |
219
+ | 类型收窄 | ✅ | null检查、数值比较 |
220
+ | 污点分析 | ✅ | 基本匹配 |
221
+ | 逃逸分析 | ✅ | 闭包/返回追踪 |
222
+ | **传播追踪** | ✅ | **常量/类型/对象传播路径** |
223
+ | 调用栈视图 | ✅ | 完整调用链 |
224
+ | 属性访问追踪 | ✅ | 返回值使用分析 |
225
+ | CI/CD集成 | ✅ | 阈值告警 + 退出码 |
226
+ | JSON输出 | ✅ | 结构化报告 |
227
+ | 错误处理 | ✅ | 非零退出码 |
228
+
229
+ ---
230
+
231
+ ## 核心问题修复确认
232
+
233
+ | # | 问题 | 修复前 | 修复后 | 验证 |
234
+ |---|------|--------|--------|------|
235
+ | 1 | JSON输出崩溃 | `Converting circular structure` | 正常JSON | ✅ TEST 6 |
236
+ | 2 | 不存在文件静默通过 | Exit 0 | Exit 1 | ✅ TEST 5 |
237
+ | 3 | -t 内存溢出 | `FATAL ERROR: heap limit` | 安全加载73文件 | ✅ TEST 7 |
238
+ | 4 | 常量传播追踪 | 只显示引用文件,无传播链 | 显示传播路径 | ✅ TEST 12 |
239
+ | 5 | 类型传播追踪 | 只显示引用文件,无传播链 | 显示传播路径 | ✅ TEST 13 |
240
+
241
+ ---
242
+
243
+ ## 已知限制
244
+
245
+ ### 能追踪的场景 ✅
246
+
247
+ | 符号类型 | 示例 | 追踪能力 |
248
+ |----------|------|----------|
249
+ | 导出的函数 | `export const getData = () => {}` | ✅ 完整调用链 |
250
+ | 导出的变量/常量 | `export const API_URL = '...'` | ✅ 传播路径 |
251
+ | 导出的类型 | `export type UserStatus = 'active' \| 'inactive'` | ✅ 引用追踪 |
252
+ | 导出的接口 | `export interface User {}` | ✅ 引用追踪 |
253
+ | 导出的类 | `export class UserService {}` | ✅ 引用追踪 |
254
+ | **嵌套对象属性** | `config.api.baseUrl` (在对象中) | ✅ **传播路径** |
255
+
256
+ ### 不能追踪的场景 ❌
257
+
258
+ | 符号类型 | 示例 | 原因 |
259
+ |----------|------|------|
260
+ | 类私有属性 | `private apiUrl = config.api.baseUrl` | 私有字段不导出,无法被外部引用 |
261
+ | 动态属性访问 | `(config as any)[key]` | 编译器无法静态分析动态键 |
262
+ | 未导出的符号 | `const helper = () => {}` | 没有exports,外界无法访问 |
263
+ | 函数内部变量 | `function foo() { const x = 1; }` | 局部变量不构成传播链 |
264
+
265
+ ### 技术原因
266
+
267
+ TypeScript 的 `findReferences` API 基于**符号声明**工作:
268
+ - 对于 `export const API_URL`,符号是 `API_URL` → ✅ 能找到所有引用
269
+ - 对于 `export const config = { api: { baseUrl: '...' } }`:
270
+ - `config` 是符号 → ✅ 能追踪
271
+ - `config.api.baseUrl` 不是独立符号,是嵌套属性 → 通过PropagationTracker追踪 ✅
272
+
273
+ ### 实际影响
274
+
275
+ | 改动场景 | 能否追踪 | 说明 |
276
+ |----------|----------|------|
277
+ | 改 `API_URL` 常量 | ✅ | 完整传播链 |
278
+ | 改 `CONFIG` 对象 | ✅ | 追踪到引用 |
279
+ | 改接口/类型定义 | ✅ | 追踪到实现/引用 |
280
+ | 改类方法实现 | ✅ | 追踪到调用 |
281
+ | 改嵌套配置值 `config.api.baseUrl` | ✅ | **通过父对象追踪** |
282
+ | 改私有实现细节 | ❌ (正确) | 不影响外部 |
283
+ | 改局部变量 | ❌ (正确) | 不影响外部 |
284
+
285
+ **结论**: 所有可能影响外部的改动都能被追踪到下游影响范围。私有实现和局部变量虽然无法追踪,但这些改动本就不影响外部。
286
+
287
+ ---
288
+
289
+ ## 新增功能:传播追踪 (PropagationTracker)
290
+
291
+ ### 功能说明
292
+
293
+ 对于非函数符号(常量、类型、对象等),Blast Radius Analyzer 现在能够追踪其传播路径:
294
+
295
+ 1. **常量传播**: 追踪常量如何被赋值给变量,变量又如何被函数使用
296
+ 2. **类型传播**: 追踪类型定义如何被引用到其他文件
297
+ 3. **对象传播**: 追踪对象属性的访问链
298
+
299
+ ### 技术实现
300
+
301
+ - 使用 ts-morph 遍历所有源文件中的标识符引用
302
+ - 对每种使用场景(模板字符串、函数参数、赋值等)进行分类
303
+ - 递归追踪赋值变量的下游传播
304
+ - 构建完整的传播路径图
305
+
306
+ ### 使用示例
307
+
308
+ ```bash
309
+ # 分析常量 API_BASE_URL 的传播
310
+ blast-radius -p ./project -c config.ts --symbol API_BASE_URL
311
+
312
+ # 分析类型 UserStatus 的传播
313
+ blast-radius -p ./project -c types.ts --symbol UserStatus
314
+
315
+ # 分析配置对象 CONFIG 的传播
316
+ blast-radius -p ./project -c config.ts --symbol CONFIG
317
+ ```
318
+
319
+ ---
320
+
321
+ ## 生产项目验证
322
+
323
+ | 项目 | 文件 | 符号 | 类型 | 状态 |
324
+ |------|------|------|------|------|
325
+ | llab-label-fe | task.ts | getTaskStats | function | ✅ |
326
+ | llab-label-fe | task.ts | getTaskList | function | ✅ |
327
+ | test-cases | simple.ts | add | function | ✅ |
328
+ | test-cases | simple.ts | processData | function | ✅ |
329
+ | test-cases | simple.ts | classify | function | ✅ |
330
+ | test-cases | simple.ts | sumArray | function | ✅ |
331
+ | test-cases | config.ts | API_BASE_URL | constant | ✅ |
332
+ | test-cases | config.ts | UserStatus | type | ✅ |
333
+ | test-cases | config.ts | CONFIG | object | ✅ |
334
+
335
+ ---
336
+
337
+ ## 验收结论
338
+
339
+ ### 评分
340
+
341
+ | 维度 | 评分 | 说明 |
342
+ |------|------|------|
343
+ | 功能完整性 | ⭐⭐⭐⭐⭐ | 所有核心功能正常 |
344
+ | 商业级特性 | ⭐⭐⭐⭐⭐ | 14/14 特性通过 |
345
+ | 错误处理 | ⭐⭐⭐⭐⭐ | 边界case正确处理 |
346
+ | 性能/内存 | ⭐⭐⭐⭐⭐ | 无溢出,无泄漏 |
347
+ | CI/CD集成 | ⭐⭐⭐⭐⭐ | 阈值告警正常 |
348
+ | 传播追踪 | ⭐⭐⭐⭐⭐ | 常量/类型/对象传播路径 |
349
+
350
+ ### 总体评价
351
+
352
+ **Blast Radius Analyzer v1.2.1 已达到商业级标准,可正式发布。**
353
+
354
+ ---
355
+
356
+ ## 附录:测试命令
357
+
358
+ ```bash
359
+ # 函数分析
360
+ node dist/index.js -p test-cases -c test-cases/simple.ts --symbol add
361
+
362
+ # 类型收窄
363
+ node dist/index.js -p test-cases -c test-cases/simple.ts --symbol processData
364
+
365
+ # 常量传播追踪
366
+ node dist/index.js -p test-cases -c test-cases/config.ts --symbol API_BASE_URL
367
+
368
+ # 类型传播追踪
369
+ node dist/index.js -p test-cases -c test-cases/config.ts --symbol UserStatus
370
+
371
+ # CI/CD 阈值
372
+ node dist/index.js -p test-cases -c test-cases/simple.ts --symbol add --threshold files:1
373
+
374
+ # JSON 输出
375
+ node dist/index.js -p test-cases -c test-cases/simple.ts --symbol add -o result.json
376
+
377
+ # 内存保护
378
+ node dist/index.js -p /path/to/project -c file.ts --symbol func -t
379
+ ```
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AnalysisCache - 增量分析缓存
3
+ *
4
+ * 缓存符号分析结果,检测文件变更,只重新分析受影响的部分
5
+ */
6
+ import type { ReferenceInfo, SymbolInfo } from './SymbolAnalyzer.js';
7
+ export interface CacheEntry {
8
+ symbolName: string;
9
+ file: string;
10
+ timestamp: number;
11
+ fileHash: string;
12
+ references: ReferenceInfo[];
13
+ symbolInfo: SymbolInfo | null;
14
+ }
15
+ export interface FileState {
16
+ mtime: number;
17
+ hash: string;
18
+ }
19
+ export declare class AnalysisCache {
20
+ private cacheDir;
21
+ private fileStates;
22
+ private memoryCache;
23
+ constructor(projectRoot: string);
24
+ private ensureCacheDir;
25
+ private loadFileStates;
26
+ private saveFileStates;
27
+ private computeFileHash;
28
+ /**
29
+ * 检测文件是否有变更
30
+ */
31
+ hasFileChanged(filePath: string): boolean;
32
+ /**
33
+ * 获取所有变更的文件
34
+ */
35
+ getChangedFiles(filePaths: string[]): string[];
36
+ /**
37
+ * 更新文件状态
38
+ */
39
+ updateFileState(filePath: string): void;
40
+ /**
41
+ * 获取缓存的引用结果
42
+ */
43
+ getCachedReferences(symbolName: string, file: string): CacheEntry | null;
44
+ /**
45
+ * 缓存引用结果
46
+ */
47
+ cacheReferences(symbolName: string, file: string, symbolInfo: SymbolInfo | null, references: ReferenceInfo[]): void;
48
+ /**
49
+ * 清除缓存
50
+ */
51
+ clear(): void;
52
+ /**
53
+ * 获取缓存统计
54
+ */
55
+ getStats(): {
56
+ entries: number;
57
+ files: number;
58
+ };
59
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * AnalysisCache - 增量分析缓存
3
+ *
4
+ * 缓存符号分析结果,检测文件变更,只重新分析受影响的部分
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ export class AnalysisCache {
9
+ cacheDir;
10
+ fileStates = new Map();
11
+ memoryCache = new Map();
12
+ constructor(projectRoot) {
13
+ this.cacheDir = path.join(projectRoot, '.blast-radius-cache');
14
+ this.ensureCacheDir();
15
+ this.loadFileStates();
16
+ }
17
+ ensureCacheDir() {
18
+ if (!fs.existsSync(this.cacheDir)) {
19
+ fs.mkdirSync(this.cacheDir, { recursive: true });
20
+ }
21
+ }
22
+ loadFileStates() {
23
+ const stateFile = path.join(this.cacheDir, 'file-states.json');
24
+ if (fs.existsSync(stateFile)) {
25
+ try {
26
+ const data = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
27
+ for (const [key, value] of Object.entries(data)) {
28
+ this.fileStates.set(key, value);
29
+ }
30
+ }
31
+ catch {
32
+ // 忽略解析错误
33
+ }
34
+ }
35
+ }
36
+ saveFileStates() {
37
+ const stateFile = path.join(this.cacheDir, 'file-states.json');
38
+ const data = {};
39
+ for (const [key, value] of this.fileStates) {
40
+ data[key] = value;
41
+ }
42
+ fs.writeFileSync(stateFile, JSON.stringify(data, null, 2), 'utf-8');
43
+ }
44
+ computeFileHash(filePath) {
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ // 简单的哈希计算
47
+ let hash = 0;
48
+ for (let i = 0; i < content.length; i++) {
49
+ const char = content.charCodeAt(i);
50
+ hash = ((hash << 5) - hash) + char;
51
+ hash = hash & hash;
52
+ }
53
+ return Math.abs(hash).toString(16);
54
+ }
55
+ /**
56
+ * 检测文件是否有变更
57
+ */
58
+ hasFileChanged(filePath) {
59
+ if (!fs.existsSync(filePath)) {
60
+ return true;
61
+ }
62
+ const stat = fs.statSync(filePath);
63
+ const currentMtime = stat.mtimeMs;
64
+ const currentHash = this.computeFileHash(filePath);
65
+ const saved = this.fileStates.get(filePath);
66
+ if (!saved) {
67
+ return true;
68
+ }
69
+ return saved.hash !== currentHash || saved.mtime !== currentMtime;
70
+ }
71
+ /**
72
+ * 获取所有变更的文件
73
+ */
74
+ getChangedFiles(filePaths) {
75
+ return filePaths.filter(f => this.hasFileChanged(f));
76
+ }
77
+ /**
78
+ * 更新文件状态
79
+ */
80
+ updateFileState(filePath) {
81
+ if (!fs.existsSync(filePath)) {
82
+ this.fileStates.delete(filePath);
83
+ return;
84
+ }
85
+ const stat = fs.statSync(filePath);
86
+ this.fileStates.set(filePath, {
87
+ mtime: stat.mtimeMs,
88
+ hash: this.computeFileHash(filePath),
89
+ });
90
+ this.saveFileStates();
91
+ }
92
+ /**
93
+ * 获取缓存的引用结果
94
+ */
95
+ getCachedReferences(symbolName, file) {
96
+ const key = `${file}:${symbolName}`;
97
+ return this.memoryCache.get(key) || null;
98
+ }
99
+ /**
100
+ * 缓存引用结果
101
+ */
102
+ cacheReferences(symbolName, file, symbolInfo, references) {
103
+ const key = `${file}:${symbolName}`;
104
+ this.memoryCache.set(key, {
105
+ symbolName,
106
+ file,
107
+ timestamp: Date.now(),
108
+ fileHash: this.computeFileHash(file),
109
+ references,
110
+ symbolInfo,
111
+ });
112
+ // 同时持久化到磁盘
113
+ const cacheFile = path.join(this.cacheDir, `${Buffer.from(key).toString('base64url')}.json`);
114
+ try {
115
+ fs.writeFileSync(cacheFile, JSON.stringify({
116
+ symbolName,
117
+ file,
118
+ timestamp: Date.now(),
119
+ references,
120
+ symbolInfo: symbolInfo ? {
121
+ name: symbolInfo.name,
122
+ kind: symbolInfo.kind,
123
+ file: symbolInfo.file,
124
+ line: symbolInfo.line,
125
+ } : null,
126
+ }, null, 2), 'utf-8');
127
+ }
128
+ catch {
129
+ // 忽略缓存写入错误
130
+ }
131
+ }
132
+ /**
133
+ * 清除缓存
134
+ */
135
+ clear() {
136
+ this.memoryCache.clear();
137
+ this.fileStates.clear();
138
+ this.saveFileStates();
139
+ // 清除缓存文件
140
+ const files = fs.readdirSync(this.cacheDir);
141
+ for (const file of files) {
142
+ if (file.endsWith('.json') && file !== 'file-states.json') {
143
+ fs.unlinkSync(path.join(this.cacheDir, file));
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * 获取缓存统计
149
+ */
150
+ getStats() {
151
+ return {
152
+ entries: this.memoryCache.size,
153
+ files: this.fileStates.size,
154
+ };
155
+ }
156
+ }