frontend-guardian-core 3.0.0 → 3.4.0
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/bin/fg-core.js +40 -1
- package/bin/fg-lsp.js +67 -0
- package/dist/engine/cache.d.ts +10 -3
- package/dist/engine/cache.d.ts.map +1 -1
- package/dist/engine/cache.js +30 -4
- package/dist/engine/cache.js.map +1 -1
- package/dist/engine/rule-engine.d.ts +30 -0
- package/dist/engine/rule-engine.d.ts.map +1 -1
- package/dist/engine/rule-engine.js +159 -3
- package/dist/engine/rule-engine.js.map +1 -1
- package/dist/ide/incremental-diagnostic.d.ts +84 -0
- package/dist/ide/incremental-diagnostic.d.ts.map +1 -0
- package/dist/ide/incremental-diagnostic.js +136 -0
- package/dist/ide/incremental-diagnostic.js.map +1 -0
- package/dist/ide/lsp-server.d.ts +23 -0
- package/dist/ide/lsp-server.d.ts.map +1 -0
- package/dist/ide/lsp-server.js +187 -0
- package/dist/ide/lsp-server.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/concurrent.d.ts +18 -0
- package/dist/utils/concurrent.d.ts.map +1 -1
- package/dist/utils/concurrent.js +59 -0
- package/dist/utils/concurrent.js.map +1 -1
- package/dist/utils/history-compare.d.ts +60 -0
- package/dist/utils/history-compare.d.ts.map +1 -0
- package/dist/utils/history-compare.js +246 -0
- package/dist/utils/history-compare.js.map +1 -0
- package/package.json +5 -2
package/bin/fg-core.js
CHANGED
|
@@ -46,6 +46,9 @@ import {
|
|
|
46
46
|
formatWorkspaceJson,
|
|
47
47
|
AIFixSuggester,
|
|
48
48
|
detectAIConfig,
|
|
49
|
+
compareHistoryReports,
|
|
50
|
+
formatHistoryCompare,
|
|
51
|
+
formatHistoryCompareJson,
|
|
49
52
|
} from "../dist/index.js";
|
|
50
53
|
import pc from "picocolors";
|
|
51
54
|
import { runWatchMode } from "./watch-mode.js";
|
|
@@ -78,12 +81,13 @@ const MODULE_RULES = {
|
|
|
78
81
|
|
|
79
82
|
function showHelp() {
|
|
80
83
|
console.log(`
|
|
81
|
-
Frontend Guardian Core v3.
|
|
84
|
+
Frontend Guardian Core v3.4.0
|
|
82
85
|
|
|
83
86
|
Usage:
|
|
84
87
|
fg-core <project-dir> [options]
|
|
85
88
|
|
|
86
89
|
Options:
|
|
90
|
+
--scan 全量扫描(等价于 --module all)
|
|
87
91
|
--module <name> 扫描模块: i18n | performance | a11y | security | naming | cross-file | component | hooks | platform | svelte | all
|
|
88
92
|
--severity <level> 最低严重级别: critical | warning | suggestion (默认: suggestion)
|
|
89
93
|
--files <pattern> 仅扫描匹配的文件
|
|
@@ -120,6 +124,7 @@ Options:
|
|
|
120
124
|
--history 查看历史扫描记录
|
|
121
125
|
--history-module <m> 历史记录按模块过滤(配合 --history)
|
|
122
126
|
--history-limit <n> 历史记录显示条数限制(默认 20)
|
|
127
|
+
--history-compare [c] [p] 对比历史报告:不指定则对比最近两次;指定一个则与该报告对比最近一次;指定两个则对比指定报告
|
|
123
128
|
--generate-dashboard 生成团队趋势看板 HTML 页面
|
|
124
129
|
--monorepo 启用 Monorepo 模式:自动检测 workspace 并扫描所有子包
|
|
125
130
|
--workspace <name> 仅扫描指定 workspace 包(可多次使用,配合 --monorepo)
|
|
@@ -185,6 +190,8 @@ async function main() {
|
|
|
185
190
|
history: false,
|
|
186
191
|
historyModule: undefined,
|
|
187
192
|
historyLimit: 20,
|
|
193
|
+
historyCompare: false,
|
|
194
|
+
historyCompareArgs: [],
|
|
188
195
|
generateDashboard: false,
|
|
189
196
|
monorepo: false,
|
|
190
197
|
workspace: [],
|
|
@@ -196,6 +203,9 @@ async function main() {
|
|
|
196
203
|
|
|
197
204
|
for (let i = 0; i < args.length; i++) {
|
|
198
205
|
switch (args[i]) {
|
|
206
|
+
case "--scan":
|
|
207
|
+
options.module = "all";
|
|
208
|
+
break;
|
|
199
209
|
case "--module":
|
|
200
210
|
options.module = args[++i];
|
|
201
211
|
break;
|
|
@@ -307,6 +317,13 @@ async function main() {
|
|
|
307
317
|
case "--history-limit":
|
|
308
318
|
options.historyLimit = parseInt(args[++i], 10) || 20;
|
|
309
319
|
break;
|
|
320
|
+
case "--history-compare":
|
|
321
|
+
options.historyCompare = true;
|
|
322
|
+
// 收集后续非 -- 开头的参数作为对比报告引用
|
|
323
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
324
|
+
options.historyCompareArgs.push(args[++i]);
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
310
327
|
case "--generate-dashboard":
|
|
311
328
|
options.generateDashboard = true;
|
|
312
329
|
break;
|
|
@@ -421,6 +438,28 @@ async function main() {
|
|
|
421
438
|
process.exit(0);
|
|
422
439
|
}
|
|
423
440
|
|
|
441
|
+
// v3.1.0: 历史报告对比
|
|
442
|
+
if (options.historyCompare) {
|
|
443
|
+
const [currentRef, previousRef] = options.historyCompareArgs;
|
|
444
|
+
const result = compareHistoryReports({
|
|
445
|
+
projectDir: options.projectDir,
|
|
446
|
+
current: currentRef,
|
|
447
|
+
previous: previousRef,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (!result) {
|
|
451
|
+
console.log(pc.yellow("⚠️ 暂无历史报告可供对比。请先运行 --save-report 生成历史数据。"));
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (options.json) {
|
|
456
|
+
console.log(JSON.stringify(formatHistoryCompareJson(result), null, 2));
|
|
457
|
+
} else {
|
|
458
|
+
console.log(formatHistoryCompare(result));
|
|
459
|
+
}
|
|
460
|
+
process.exit(0);
|
|
461
|
+
}
|
|
462
|
+
|
|
424
463
|
// v2.8.0: 生成趋势看板
|
|
425
464
|
if (options.generateDashboard) {
|
|
426
465
|
const hr = new HistoryReport(options.projectDir);
|
package/bin/fg-lsp.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Frontend Guardian LSP Server
|
|
4
|
+
* Usage: fg-lsp --stdio [--project-dir <dir>] [--config <file>]
|
|
5
|
+
*
|
|
6
|
+
* v3.3.0: Language Server Protocol 实现,为 IDE 提供实时诊断和快速修复。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { runLSPServer } from "../dist/ide/lsp-server.js";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
|
|
15
|
+
// 解析参数
|
|
16
|
+
let projectDir = process.cwd();
|
|
17
|
+
let configFile;
|
|
18
|
+
let minSeverity = "suggestion";
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === "--project-dir" || arg === "-p") {
|
|
23
|
+
projectDir = resolve(args[++i] ?? process.cwd());
|
|
24
|
+
} else if (arg === "--config" || arg === "-c") {
|
|
25
|
+
configFile = resolve(args[++i] ?? "");
|
|
26
|
+
} else if (arg === "--severity" || arg === "-s") {
|
|
27
|
+
minSeverity = args[++i] ?? "suggestion";
|
|
28
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
29
|
+
console.log(`Frontend Guardian LSP Server v3.3.0
|
|
30
|
+
|
|
31
|
+
Usage: fg-lsp [options]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--project-dir, -p <dir> 项目根目录(默认当前目录)
|
|
35
|
+
--config, -c <file> 配置文件路径
|
|
36
|
+
--severity, -s <level> 最低严重级别: critical|warning|suggestion(默认 suggestion)
|
|
37
|
+
--help, -h 显示帮助
|
|
38
|
+
|
|
39
|
+
Environment:
|
|
40
|
+
FG_PROJECT_DIR 项目根目录(优先级低于 --project-dir)
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
fg-lsp --stdio
|
|
44
|
+
fg-lsp --project-dir ./my-project --config .frontend-guardian.yml
|
|
45
|
+
`);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 环境变量回退
|
|
51
|
+
if (process.env.FG_PROJECT_DIR && !args.includes("--project-dir") && !args.includes("-p")) {
|
|
52
|
+
projectDir = resolve(process.env.FG_PROJECT_DIR);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 验证项目目录
|
|
56
|
+
if (!existsSync(projectDir)) {
|
|
57
|
+
console.error(`Error: Project directory does not exist: ${projectDir}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 启动 LSP 服务器
|
|
62
|
+
runLSPServer({
|
|
63
|
+
projectDir,
|
|
64
|
+
configFile,
|
|
65
|
+
minSeverity,
|
|
66
|
+
debounceMs: 300,
|
|
67
|
+
});
|
package/dist/engine/cache.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 2. 缓存持久化到 .frontend-guardian/cache.json
|
|
7
7
|
* 3. 自动过期策略(默认 7 天)
|
|
8
8
|
* 4. 兼容 --staged / --diff 增量模式
|
|
9
|
+
* 5. v3.2.0: AST 内存缓存 LRU 淘汰策略,防止大项目 OOM
|
|
9
10
|
*/
|
|
10
11
|
import type { Issue } from "../types.js";
|
|
11
12
|
export interface CacheEntry {
|
|
@@ -35,7 +36,9 @@ export declare class SmartCache {
|
|
|
35
36
|
private ttl;
|
|
36
37
|
/** v2.1.0: 内存级 AST 缓存(无需持久化,进程内复用) */
|
|
37
38
|
private astCache;
|
|
38
|
-
|
|
39
|
+
/** v3.2.0: AST 缓存最大条目数 */
|
|
40
|
+
private maxAstCacheSize;
|
|
41
|
+
constructor(projectDir: string, ttl?: number, maxAstCacheSize?: number);
|
|
39
42
|
/** 计算文件内容哈希 */
|
|
40
43
|
static computeHash(content: string): string;
|
|
41
44
|
/** 检查文件是否已缓存且未过期 */
|
|
@@ -56,10 +59,14 @@ export declare class SmartCache {
|
|
|
56
59
|
valid: number;
|
|
57
60
|
expired: number;
|
|
58
61
|
};
|
|
59
|
-
/** 获取缓存的 AST
|
|
62
|
+
/** 获取缓存的 AST(内存级,不持久化)— v3.2.0 增加 LRU 淘汰 */
|
|
60
63
|
getAst(filePath: string, content: string): unknown | undefined;
|
|
61
|
-
/** 缓存 AST 解析结果 */
|
|
64
|
+
/** 缓存 AST 解析结果 — v3.2.0 增加 LRU 淘汰 */
|
|
62
65
|
setAst(filePath: string, content: string, ast: unknown): void;
|
|
66
|
+
/** v3.2.0: 获取 AST 缓存当前大小 */
|
|
67
|
+
getAstCacheSize(): number;
|
|
68
|
+
/** v3.2.0: 获取 AST 缓存上限 */
|
|
69
|
+
getAstCacheLimit(): number;
|
|
63
70
|
/** 清理过期缓存 */
|
|
64
71
|
gc(): number;
|
|
65
72
|
/** 加载缓存 manifest */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/engine/cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/engine/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,UAAU;IACvB,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,WAAW;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,eAAe;IACf,YAAY,EAAE,MAAM,CAAC;CACxB;AASD,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,sCAAsC;IACtC,OAAO,CAAC,QAAQ,CAAqD;IACrE,0BAA0B;IAC1B,OAAO,CAAC,eAAe,CAAS;gBAEpB,UAAU,EAAE,MAAM,EAAE,GAAG,GAAE,MAAoB,EAAE,eAAe,CAAC,EAAE,MAAM;IAQnF,eAAe;IACf,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI3C,oBAAoB;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAkBpD,kBAAkB;IAClB,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,SAAS;IAK1C,eAAe;IACf,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI;IAS7D,aAAa;IACb,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIlC,uBAAuB;IACvB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IASxC,cAAc;IACd,IAAI,IAAI,IAAI;IAWZ,aAAa;IACb,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAkB7D,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAiB9D,qCAAqC;IACrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI;IAiB7D,4BAA4B;IAC5B,eAAe,IAAI,MAAM;IAIzB,0BAA0B;IAC1B,gBAAgB,IAAI,MAAM;IAI1B,aAAa;IACb,EAAE,IAAI,MAAM;IAcZ,oBAAoB;IACpB,OAAO,CAAC,YAAY;CAoBvB"}
|
package/dist/engine/cache.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 2. 缓存持久化到 .frontend-guardian/cache.json
|
|
8
8
|
* 3. 自动过期策略(默认 7 天)
|
|
9
9
|
* 4. 兼容 --staged / --diff 增量模式
|
|
10
|
+
* 5. v3.2.0: AST 内存缓存 LRU 淘汰策略,防止大项目 OOM
|
|
10
11
|
*/
|
|
11
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
13
|
exports.SmartCache = void 0;
|
|
@@ -16,7 +17,9 @@ const node_path_1 = require("node:path");
|
|
|
16
17
|
/** 缓存默认存活时间(7 天) */
|
|
17
18
|
const DEFAULT_TTL = 7 * 24 * 60 * 60 * 1000;
|
|
18
19
|
/** 引擎版本,规则变更时递增使缓存失效 */
|
|
19
|
-
const ENGINE_VERSION = "2.0
|
|
20
|
+
const ENGINE_VERSION = "3.2.0";
|
|
21
|
+
/** v3.2.0: AST 内存缓存默认最大条目数 */
|
|
22
|
+
const DEFAULT_AST_CACHE_SIZE = 200;
|
|
20
23
|
class SmartCache {
|
|
21
24
|
manifest;
|
|
22
25
|
cacheDir;
|
|
@@ -24,10 +27,13 @@ class SmartCache {
|
|
|
24
27
|
ttl;
|
|
25
28
|
/** v2.1.0: 内存级 AST 缓存(无需持久化,进程内复用) */
|
|
26
29
|
astCache = new Map();
|
|
27
|
-
|
|
30
|
+
/** v3.2.0: AST 缓存最大条目数 */
|
|
31
|
+
maxAstCacheSize;
|
|
32
|
+
constructor(projectDir, ttl = DEFAULT_TTL, maxAstCacheSize) {
|
|
28
33
|
this.cacheDir = (0, node_path_1.resolve)(projectDir, ".frontend-guardian");
|
|
29
34
|
this.cacheFile = (0, node_path_1.resolve)(this.cacheDir, "cache.json");
|
|
30
35
|
this.ttl = ttl;
|
|
36
|
+
this.maxAstCacheSize = maxAstCacheSize ?? DEFAULT_AST_CACHE_SIZE;
|
|
31
37
|
this.manifest = this.loadManifest();
|
|
32
38
|
}
|
|
33
39
|
/** 计算文件内容哈希 */
|
|
@@ -107,7 +113,7 @@ class SmartCache {
|
|
|
107
113
|
return { total: Object.keys(this.manifest.entries).length, valid, expired };
|
|
108
114
|
}
|
|
109
115
|
// ── v2.1.0: AST 内存缓存 ──────────────────────────────────────────────
|
|
110
|
-
/** 获取缓存的 AST
|
|
116
|
+
/** 获取缓存的 AST(内存级,不持久化)— v3.2.0 增加 LRU 淘汰 */
|
|
111
117
|
getAst(filePath, content) {
|
|
112
118
|
const cached = this.astCache.get(filePath);
|
|
113
119
|
if (!cached)
|
|
@@ -117,15 +123,35 @@ class SmartCache {
|
|
|
117
123
|
this.astCache.delete(filePath);
|
|
118
124
|
return undefined;
|
|
119
125
|
}
|
|
126
|
+
// v3.2.0: LRU — 访问后移到末尾(最新)
|
|
127
|
+
this.astCache.delete(filePath);
|
|
128
|
+
this.astCache.set(filePath, cached);
|
|
120
129
|
return cached.ast;
|
|
121
130
|
}
|
|
122
|
-
/** 缓存 AST 解析结果 */
|
|
131
|
+
/** 缓存 AST 解析结果 — v3.2.0 增加 LRU 淘汰 */
|
|
123
132
|
setAst(filePath, content, ast) {
|
|
133
|
+
// v3.2.0: 超出上限时淘汰最久未访问的条目(Map 头部)
|
|
134
|
+
if (this.astCache.size >= this.maxAstCacheSize && !this.astCache.has(filePath)) {
|
|
135
|
+
const firstKey = this.astCache.keys().next().value;
|
|
136
|
+
if (firstKey) {
|
|
137
|
+
this.astCache.delete(firstKey);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// 删除旧条目(如果存在),然后重新插入到末尾(最新)
|
|
141
|
+
this.astCache.delete(filePath);
|
|
124
142
|
this.astCache.set(filePath, {
|
|
125
143
|
hash: SmartCache.computeHash(content),
|
|
126
144
|
ast,
|
|
127
145
|
});
|
|
128
146
|
}
|
|
147
|
+
/** v3.2.0: 获取 AST 缓存当前大小 */
|
|
148
|
+
getAstCacheSize() {
|
|
149
|
+
return this.astCache.size;
|
|
150
|
+
}
|
|
151
|
+
/** v3.2.0: 获取 AST 缓存上限 */
|
|
152
|
+
getAstCacheLimit() {
|
|
153
|
+
return this.maxAstCacheSize;
|
|
154
|
+
}
|
|
129
155
|
/** 清理过期缓存 */
|
|
130
156
|
gc() {
|
|
131
157
|
const now = Date.now();
|
package/dist/engine/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/engine/cache.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/engine/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,8 @@ export interface EngineOptions {
|
|
|
44
44
|
skipLargeFilesThreshold?: number;
|
|
45
45
|
/** v2.6.0: 外部传入的 SmartCache 实例(用于 Watch 模式复用缓存) */
|
|
46
46
|
cacheInstance?: SmartCache;
|
|
47
|
+
/** v3.2.0: 增量扫描时通过 import 图分析扩展扫描范围(变更文件及其依赖方) */
|
|
48
|
+
incrementalImportGraph?: boolean;
|
|
47
49
|
}
|
|
48
50
|
export declare class RuleEngine {
|
|
49
51
|
private registry;
|
|
@@ -74,6 +76,14 @@ export declare class RuleEngine {
|
|
|
74
76
|
private moduleToCategory;
|
|
75
77
|
/** 执行扫描 */
|
|
76
78
|
scan(module: string): Promise<ScanResult>;
|
|
79
|
+
/**
|
|
80
|
+
* v3.3.0: 公共单文件扫描方法(用于 IDE 增量诊断)
|
|
81
|
+
* 快速扫描单个文件,返回所有匹配的 issues
|
|
82
|
+
* @param filePath 文件绝对路径
|
|
83
|
+
* @param module 模块名(可选,用于规则过滤)
|
|
84
|
+
* @returns 扫描发现的 issues
|
|
85
|
+
*/
|
|
86
|
+
scanSingleFile(filePath: string, module?: string): Promise<Issue[]>;
|
|
77
87
|
/** 扫描单个文件(带智能缓存 + 大文件跳过) */
|
|
78
88
|
private scanFile;
|
|
79
89
|
/** 获取扫描文件列表 */
|
|
@@ -82,6 +92,26 @@ export declare class RuleEngine {
|
|
|
82
92
|
private getDiffFiles;
|
|
83
93
|
/** 智能推断扫描范围:未提交修改 → 最近 5 次提交 → 全量 */
|
|
84
94
|
private getAutoScopeFiles;
|
|
95
|
+
/**
|
|
96
|
+
* 构建项目的 import 图(简化版)
|
|
97
|
+
* 对于每个文件,解析其 import 语句,建立双向映射:
|
|
98
|
+
* - importMap: file → 它导入的文件集合
|
|
99
|
+
* - reverseMap: file → 导入它的文件集合
|
|
100
|
+
*
|
|
101
|
+
* 简化策略:只解析相对路径 import(./ 或 ../),不解析 node_modules。
|
|
102
|
+
* 因为增量扫描的核心场景是:修改一个内部模块后,哪些文件会受影响。
|
|
103
|
+
*/
|
|
104
|
+
private buildImportGraph;
|
|
105
|
+
/**
|
|
106
|
+
* 基于 import 图扩展增量扫描文件列表
|
|
107
|
+
* 除变更文件本身外,还扫描所有导入这些文件的文件(上游依赖方)。
|
|
108
|
+
*
|
|
109
|
+
* 原理:如果 A 被 B import,A 修改后 B 的语义可能变化(如 A 导出的类型、常量变更)。
|
|
110
|
+
* 因此 B 也需要重新扫描。
|
|
111
|
+
*/
|
|
112
|
+
private expandIncrementalFiles;
|
|
113
|
+
/** 同步获取项目内所有源文件(用于 import 图构建) */
|
|
114
|
+
private getAllSourceFilesSync;
|
|
85
115
|
/** 创建 RuleUtils */
|
|
86
116
|
private createUtils;
|
|
87
117
|
/** 计算每行起始偏移 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-engine.d.ts","sourceRoot":"","sources":["../../src/engine/rule-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EACR,IAAI,EAEJ,KAAK,EACL,QAAQ,EACR,UAAU,EAMV,UAAU,EACb,MAAM,YAAY,CAAC;AASpB,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKxC,MAAM,WAAW,aAAa;IAC1B,YAAY;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,cAAc;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW;IACX,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU;IACV,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,aAAa,CAAC,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"rule-engine.d.ts","sourceRoot":"","sources":["../../src/engine/rule-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -136,8 +136,8 @@ class RuleEngine {
|
|
|
136
136
|
const files = await this.getScanFiles();
|
|
137
137
|
let filesWithIssues = 0;
|
|
138
138
|
console.log(picocolors_1.default.blue(`🔍 [${module}] 扫描 ${files.length} 个文件,${activeRules.length} 条规则...`));
|
|
139
|
-
//
|
|
140
|
-
const concurrency = this.options.concurrency ?? (0, concurrent_js_1.
|
|
139
|
+
// v3.2.0: 自适应并发 — 根据文件数、规则数和 CPU 动态调整
|
|
140
|
+
const concurrency = this.options.concurrency ?? (0, concurrent_js_1.getAdaptiveConcurrency)(files.length, activeRules.length);
|
|
141
141
|
const fileResults = await (0, concurrent_js_1.concurrentMap)(files, concurrency, (file) => this.scanFile(file, activeRules));
|
|
142
142
|
let filesSkipped = 0;
|
|
143
143
|
for (const { issues: fileIssues, skipped } of fileResults) {
|
|
@@ -189,6 +189,28 @@ class RuleEngine {
|
|
|
189
189
|
}
|
|
190
190
|
return result;
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* v3.3.0: 公共单文件扫描方法(用于 IDE 增量诊断)
|
|
194
|
+
* 快速扫描单个文件,返回所有匹配的 issues
|
|
195
|
+
* @param filePath 文件绝对路径
|
|
196
|
+
* @param module 模块名(可选,用于规则过滤)
|
|
197
|
+
* @returns 扫描发现的 issues
|
|
198
|
+
*/
|
|
199
|
+
async scanSingleFile(filePath, module) {
|
|
200
|
+
const category = module ? this.moduleToCategory(module) : undefined;
|
|
201
|
+
const activeRules = category
|
|
202
|
+
? this.filterRules({
|
|
203
|
+
category,
|
|
204
|
+
framework: this.projectMeta.framework,
|
|
205
|
+
platform: this.projectMeta.platforms[0],
|
|
206
|
+
componentLib: this.projectMeta.componentLib,
|
|
207
|
+
})
|
|
208
|
+
: this.getRules();
|
|
209
|
+
if (activeRules.length === 0)
|
|
210
|
+
return [];
|
|
211
|
+
const result = await this.scanFile(filePath, activeRules);
|
|
212
|
+
return result.issues;
|
|
213
|
+
}
|
|
192
214
|
/** 扫描单个文件(带智能缓存 + 大文件跳过) */
|
|
193
215
|
async scanFile(filePath, rules) {
|
|
194
216
|
try {
|
|
@@ -264,7 +286,15 @@ class RuleEngine {
|
|
|
264
286
|
}
|
|
265
287
|
// 过滤出符合扩展名的文件
|
|
266
288
|
const include = this.config.scan?.includeExtensions || [".js", ".ts", ".jsx", ".tsx", ".vue"];
|
|
267
|
-
|
|
289
|
+
let filtered = diffFiles.filter((f) => include.some((ext) => f.endsWith(ext)));
|
|
290
|
+
// v3.2.0: 通过 import 图分析扩展扫描范围
|
|
291
|
+
if (this.options.incrementalImportGraph !== false && filtered.length > 0 && filtered.length < 500) {
|
|
292
|
+
const expanded = this.expandIncrementalFiles(filtered, include);
|
|
293
|
+
if (expanded.length > filtered.length) {
|
|
294
|
+
console.log(picocolors_1.default.cyan(` 📎 import 图扩展: ${filtered.length} → ${expanded.length} 个文件`));
|
|
295
|
+
filtered = expanded;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
268
298
|
if (this.options.autoScope && filtered.length > 0) {
|
|
269
299
|
console.log(picocolors_1.default.cyan(`🔍 智能扫描范围: ${filtered.length} 个文件`));
|
|
270
300
|
}
|
|
@@ -367,6 +397,132 @@ class RuleEngine {
|
|
|
367
397
|
return [];
|
|
368
398
|
}
|
|
369
399
|
}
|
|
400
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
401
|
+
// v3.2.0: 增量扫描 import 图分析
|
|
402
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
403
|
+
/**
|
|
404
|
+
* 构建项目的 import 图(简化版)
|
|
405
|
+
* 对于每个文件,解析其 import 语句,建立双向映射:
|
|
406
|
+
* - importMap: file → 它导入的文件集合
|
|
407
|
+
* - reverseMap: file → 导入它的文件集合
|
|
408
|
+
*
|
|
409
|
+
* 简化策略:只解析相对路径 import(./ 或 ../),不解析 node_modules。
|
|
410
|
+
* 因为增量扫描的核心场景是:修改一个内部模块后,哪些文件会受影响。
|
|
411
|
+
*/
|
|
412
|
+
buildImportGraph(files) {
|
|
413
|
+
const importMap = new Map();
|
|
414
|
+
const reverseMap = new Map();
|
|
415
|
+
for (const file of files) {
|
|
416
|
+
importMap.set(file, new Set());
|
|
417
|
+
}
|
|
418
|
+
for (const file of files) {
|
|
419
|
+
try {
|
|
420
|
+
const source = (0, node_fs_1.readFileSync)(file, "utf-8");
|
|
421
|
+
// 简单正则提取相对路径 import(不依赖 AST,速度更快)
|
|
422
|
+
const importRegex = /import\s+.*?\s+from\s+['"](\.\.?\/[^'"]+)['"]|import\s*\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g;
|
|
423
|
+
let match;
|
|
424
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
425
|
+
const importPath = match[1] || match[2];
|
|
426
|
+
if (!importPath)
|
|
427
|
+
continue;
|
|
428
|
+
// 解析相对路径为绝对路径
|
|
429
|
+
const { resolve: pathResolve, dirname } = require("node:path");
|
|
430
|
+
let resolved = pathResolve(dirname(file), importPath);
|
|
431
|
+
// 尝试补充扩展名
|
|
432
|
+
const exts = [".ts", ".tsx", ".js", ".jsx", ".vue"];
|
|
433
|
+
let found = false;
|
|
434
|
+
for (const ext of exts) {
|
|
435
|
+
if (files.includes(resolved + ext)) {
|
|
436
|
+
resolved = resolved + ext;
|
|
437
|
+
found = true;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
// 支持目录下的 index 文件
|
|
441
|
+
if (files.includes(pathResolve(resolved, "index" + ext))) {
|
|
442
|
+
resolved = pathResolve(resolved, "index" + ext);
|
|
443
|
+
found = true;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (found) {
|
|
448
|
+
importMap.get(file)?.add(resolved);
|
|
449
|
+
if (!reverseMap.has(resolved)) {
|
|
450
|
+
reverseMap.set(resolved, new Set());
|
|
451
|
+
}
|
|
452
|
+
reverseMap.get(resolved).add(file);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// 读取失败则跳过
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { importMap, reverseMap };
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* 基于 import 图扩展增量扫描文件列表
|
|
464
|
+
* 除变更文件本身外,还扫描所有导入这些文件的文件(上游依赖方)。
|
|
465
|
+
*
|
|
466
|
+
* 原理:如果 A 被 B import,A 修改后 B 的语义可能变化(如 A 导出的类型、常量变更)。
|
|
467
|
+
* 因此 B 也需要重新扫描。
|
|
468
|
+
*/
|
|
469
|
+
expandIncrementalFiles(changedFiles, includeExts) {
|
|
470
|
+
try {
|
|
471
|
+
// 1. 获取项目内所有候选文件(用于构建 import 图)
|
|
472
|
+
const allFiles = this.getAllSourceFilesSync(includeExts);
|
|
473
|
+
if (allFiles.length > 5000) {
|
|
474
|
+
// 超大项目:import 图构建成本高,跳过扩展
|
|
475
|
+
return changedFiles;
|
|
476
|
+
}
|
|
477
|
+
// 2. 构建 import 图
|
|
478
|
+
const { reverseMap } = this.buildImportGraph(allFiles);
|
|
479
|
+
// 3. 收集变更文件及其所有导入方
|
|
480
|
+
const expanded = new Set(changedFiles);
|
|
481
|
+
const queue = [...changedFiles];
|
|
482
|
+
const visited = new Set(changedFiles);
|
|
483
|
+
while (queue.length > 0) {
|
|
484
|
+
const current = queue.shift();
|
|
485
|
+
const importers = reverseMap.get(current);
|
|
486
|
+
if (!importers)
|
|
487
|
+
continue;
|
|
488
|
+
for (const importer of importers) {
|
|
489
|
+
if (!visited.has(importer)) {
|
|
490
|
+
visited.add(importer);
|
|
491
|
+
expanded.add(importer);
|
|
492
|
+
queue.push(importer);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return Array.from(expanded);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return changedFiles;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/** 同步获取项目内所有源文件(用于 import 图构建) */
|
|
503
|
+
getAllSourceFilesSync(includeExts) {
|
|
504
|
+
try {
|
|
505
|
+
const { globbySync } = require("globby");
|
|
506
|
+
const patterns = includeExts.map((ext) => `**/*${ext}`);
|
|
507
|
+
const exclude = [
|
|
508
|
+
"**/node_modules/**",
|
|
509
|
+
"**/dist/**",
|
|
510
|
+
"**/build/**",
|
|
511
|
+
"**/.git/**",
|
|
512
|
+
"**/coverage/**",
|
|
513
|
+
...(this.options.exclude || []),
|
|
514
|
+
...(this.config.scan?.excludeDirs?.map((d) => `**/${d}/**`) || []),
|
|
515
|
+
];
|
|
516
|
+
return globbySync(patterns, {
|
|
517
|
+
cwd: this.options.projectDir,
|
|
518
|
+
ignore: exclude,
|
|
519
|
+
absolute: true,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return [];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
370
526
|
/** 创建 RuleUtils */
|
|
371
527
|
createUtils(filePath, source) {
|
|
372
528
|
const lineOffsets = this.computeLineOffsets(source);
|