auto-cr-cmd 2.0.78 → 2.0.80
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 +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/config/autocrrc.js +7 -0
- package/dist/config/ignore.js +9 -0
- package/dist/config.js +3 -0
- package/dist/i18n/index.js +13 -0
- package/dist/index.js +161 -29
- package/dist/report/index.js +9 -0
- package/dist/rules/loader.js +5 -0
- package/dist/types/i18n/index.d.ts +5 -0
- package/dist/types/report/index.d.ts +2 -0
- package/dist/types/utils/file.d.ts +2 -0
- package/dist/utils/file.js +3 -0
- package/dist/utils/stdin.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
## Feature Highlights (Automated Code Review & Static Analysis)
|
|
24
24
|
|
|
25
|
-
- **Built-in Rule Library**: Ships with SWC AST static analysis rules out of the box, such as `no-deep-relative-imports`.
|
|
25
|
+
- **Built-in Rule Library**: Ships with SWC AST static analysis rules out of the box, such as `no-deep-relative-imports`, `no-circular-dependencies`, and `no-swallowed-errors`.
|
|
26
26
|
- **Extensible SDK**: `auto-cr-rules` exposes helpers like `defineRule` and `helpers.imports`, reducing the friction of authoring custom TypeScript / JavaScript rules.
|
|
27
27
|
- **Workspace Friendly**: Manage both the CLI and rule package via pnpm workspaces and validate the full pipeline with a single build.
|
|
28
28
|
- **Publishing Toolkit**: Version bump scripts and npm publish commands keep both packages in sync.
|
|
@@ -43,6 +43,7 @@ Common flags:
|
|
|
43
43
|
- `--language <zh|en>`: Switch CLI output language (defaults to auto-detection).
|
|
44
44
|
- `--rule-dir <directory>`: Load additional custom rules from a directory or package.
|
|
45
45
|
- `--output <text|json>`: Choose between human-friendly text logs or structured JSON results (defaults to `text`).
|
|
46
|
+
- `--progress [stdout|stderr]`: Force progress display (text output only); use `stderr` to keep stdout clean for piping.
|
|
46
47
|
- `--config <path>`: Point to a `.autocrrc.json` or `.autocrrc.js` file to enable/disable rules.
|
|
47
48
|
- `--ignore-path <path>`: Point to a `.autocrignore.json` or `.autocrignore.js` file to exclude files/directories from scanning.
|
|
48
49
|
- `--tsconfig <path>`: Use a custom `tsconfig.json` (defaults to `<cwd>/tsconfig.json`).
|
|
@@ -121,6 +122,7 @@ npx auto-cr-cmd --output json -- ./src | jq
|
|
|
121
122
|
{
|
|
122
123
|
"rules": {
|
|
123
124
|
"no-deep-relative-imports": "error",
|
|
125
|
+
"no-circular-dependencies": "warning",
|
|
124
126
|
"no-swallowed-errors": "off"
|
|
125
127
|
}
|
|
126
128
|
}
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
## 特性亮点(自动化代码审查 & 静态代码分析)
|
|
24
24
|
|
|
25
|
-
- **内置规则库**:默认集成 SWC AST 静态分析规则,例如 `no-deep-relative-imports`。
|
|
25
|
+
- **内置规则库**:默认集成 SWC AST 静态分析规则,例如 `no-deep-relative-imports`、`no-circular-dependencies`、`no-swallowed-errors`。
|
|
26
26
|
- **可扩展 SDK**:`auto-cr-rules` 暴露 `defineRule`、`helpers.imports` 等工具,降低编写 TypeScript / JavaScript 自定义规则的复杂度。
|
|
27
27
|
- **工作区管理**:使用 pnpm workspace 同时管理 CLI 与规则包,一次构建即可验证完整流程。
|
|
28
28
|
- **发布友好**:内置版本递增脚本与 npm 发布命令,保持两个包的版本同步。
|
|
@@ -43,6 +43,7 @@ npx auto-cr-cmd --language zh [需要扫描的代码目录]
|
|
|
43
43
|
- `--language <zh|en>`:切换 CLI 输出语言(默认为自动检测)。
|
|
44
44
|
- `--rule-dir <directory>`:加载额外的自定义规则目录或包。
|
|
45
45
|
- `--output <text|json>`:选择输出格式,`text` 为友好的终端日志,`json` 用于集成脚本(默认为 `text`)。
|
|
46
|
+
- `--progress [stdout|stderr]`:强制显示扫描进度(仅 text 输出),可选输出到 stdout/stderr,使用 stderr 可避免影响 stdout 管道。
|
|
46
47
|
- `--config <path>`:指定 `.autocrrc.json` 或 `.autocrrc.js` 配置文件路径,用于开启/关闭规则。
|
|
47
48
|
- `--ignore-path <path>`:指定 `.autocrignore.json` 或 `.autocrignore.js` 忽略文件路径,用于排除扫描。
|
|
48
49
|
- `--tsconfig <path>`:指定自定义 `tsconfig.json` 路径(默认读取 `<cwd>/tsconfig.json`)。
|
|
@@ -121,6 +122,7 @@ npx auto-cr-cmd --output json -- ./src | jq
|
|
|
121
122
|
{
|
|
122
123
|
"rules": {
|
|
123
124
|
"no-deep-relative-imports": "error",
|
|
125
|
+
"no-circular-dependencies": "warning",
|
|
124
126
|
"no-swallowed-errors": "off"
|
|
125
127
|
}
|
|
126
128
|
}
|
package/dist/config/autocrrc.js
CHANGED
|
@@ -20,7 +20,9 @@ var fs_1 = __importDefault(require("fs"));
|
|
|
20
20
|
var path_1 = __importDefault(require("path"));
|
|
21
21
|
var auto_cr_rules_1 = require("auto-cr-rules");
|
|
22
22
|
var i18n_1 = require("../i18n");
|
|
23
|
+
// 支持的配置文件候选名(从当前工作目录开始查找)。
|
|
23
24
|
var RC_CANDIDATES = ['.autocrrc.json', '.autocrrc.js'];
|
|
25
|
+
// 读取 .autocrrc 并校验结构;任何解析失败都转成 warning 不中断扫描。
|
|
24
26
|
function loadAutoCrRc(configPath) {
|
|
25
27
|
var warnings = [];
|
|
26
28
|
var t = (0, i18n_1.getTranslator)();
|
|
@@ -55,6 +57,7 @@ function loadAutoCrRc(configPath) {
|
|
|
55
57
|
return { warnings: warnings };
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
// 将 rules 配置应用到内置/自定义规则上,支持关闭与调整 severity。
|
|
58
61
|
function applyRuleConfig(rules, ruleSettings, onWarning) {
|
|
59
62
|
if (!ruleSettings || Object.keys(ruleSettings).length === 0) {
|
|
60
63
|
return rules;
|
|
@@ -86,6 +89,7 @@ function applyRuleConfig(rules, ruleSettings, onWarning) {
|
|
|
86
89
|
}
|
|
87
90
|
return configured;
|
|
88
91
|
}
|
|
92
|
+
// 优先使用显式路径,否则在工作目录内按候选名查找。
|
|
89
93
|
function resolveConfigPath(explicitPath) {
|
|
90
94
|
if (explicitPath) {
|
|
91
95
|
return path_1.default.isAbsolute(explicitPath) ? explicitPath : path_1.default.resolve(process.cwd(), explicitPath);
|
|
@@ -99,6 +103,7 @@ function resolveConfigPath(explicitPath) {
|
|
|
99
103
|
}
|
|
100
104
|
return null;
|
|
101
105
|
}
|
|
106
|
+
// 读取配置文件:支持 JSON 与 JS 导出。
|
|
102
107
|
function readConfigFile(filePath) {
|
|
103
108
|
if (filePath.endsWith('.json')) {
|
|
104
109
|
var raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
@@ -110,6 +115,7 @@ function readConfigFile(filePath) {
|
|
|
110
115
|
}
|
|
111
116
|
return {};
|
|
112
117
|
}
|
|
118
|
+
// 兼容 default 导出(CommonJS/ESM)。
|
|
113
119
|
function unwrapDefault(value) {
|
|
114
120
|
var _a;
|
|
115
121
|
if (isRecord(value) && 'default' in value) {
|
|
@@ -120,6 +126,7 @@ function unwrapDefault(value) {
|
|
|
120
126
|
function isRecord(value) {
|
|
121
127
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
122
128
|
}
|
|
129
|
+
// 统一配置格式:支持字符串/数字/布尔值等多种简写形式。
|
|
123
130
|
function normalizeRuleSetting(input) {
|
|
124
131
|
if (input === undefined) {
|
|
125
132
|
return undefined;
|
package/dist/config/ignore.js
CHANGED
|
@@ -9,7 +9,9 @@ var fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
var path_1 = __importDefault(require("path"));
|
|
10
10
|
var picomatch_1 = __importDefault(require("picomatch"));
|
|
11
11
|
var i18n_1 = require("../i18n");
|
|
12
|
+
// 忽略文件候选名(从当前工作目录开始查找)。
|
|
12
13
|
var IGNORE_CANDIDATES = ['.autocrignore.json', '.autocrignore.js'];
|
|
14
|
+
// 加载忽略配置:支持 JSON/JS/文本多种形式,失败仅输出 warning。
|
|
13
15
|
function loadIgnoreConfig(configPath) {
|
|
14
16
|
var warnings = [];
|
|
15
17
|
var t = (0, i18n_1.getTranslator)();
|
|
@@ -36,6 +38,7 @@ function loadIgnoreConfig(configPath) {
|
|
|
36
38
|
return { patterns: [], warnings: warnings, baseDir: baseDir };
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
// 构建忽略匹配器,匹配绝对路径与相对路径两种形式。
|
|
39
42
|
function createIgnoreMatcher(patterns, baseDir) {
|
|
40
43
|
if (baseDir === void 0) { baseDir = process.cwd(); }
|
|
41
44
|
if (!patterns.length) {
|
|
@@ -51,6 +54,7 @@ function createIgnoreMatcher(patterns, baseDir) {
|
|
|
51
54
|
return matchers.some(function (matcher) { return matcher(normalized) || matcher(relative); });
|
|
52
55
|
};
|
|
53
56
|
}
|
|
57
|
+
// 解析忽略配置路径:显式传入优先,其次按候选名查找。
|
|
54
58
|
function resolveConfigPath(explicitPath) {
|
|
55
59
|
if (explicitPath) {
|
|
56
60
|
return path_1.default.isAbsolute(explicitPath) ? explicitPath : path_1.default.resolve(process.cwd(), explicitPath);
|
|
@@ -64,6 +68,7 @@ function resolveConfigPath(explicitPath) {
|
|
|
64
68
|
}
|
|
65
69
|
return null;
|
|
66
70
|
}
|
|
71
|
+
// 读取忽略文件:JSON/JS 导出均可。
|
|
67
72
|
function readIgnoreFile(filePath) {
|
|
68
73
|
if (filePath.endsWith('.json')) {
|
|
69
74
|
var raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
@@ -75,6 +80,8 @@ function readIgnoreFile(filePath) {
|
|
|
75
80
|
}
|
|
76
81
|
return [];
|
|
77
82
|
}
|
|
83
|
+
// 规范化 ignore 内容:
|
|
84
|
+
// - 支持字符串数组 / 单个字符串(按行拆分)/ { ignore } / { default }。
|
|
78
85
|
function normalizeIgnorePayload(payload) {
|
|
79
86
|
var values = [];
|
|
80
87
|
if (Array.isArray(payload)) {
|
|
@@ -97,6 +104,7 @@ function normalizeIgnorePayload(payload) {
|
|
|
97
104
|
}
|
|
98
105
|
return values;
|
|
99
106
|
}
|
|
107
|
+
// 过滤空行与注释行。
|
|
100
108
|
function normalizeEntry(entry) {
|
|
101
109
|
if (typeof entry !== 'string') {
|
|
102
110
|
return null;
|
|
@@ -110,6 +118,7 @@ function normalizeEntry(entry) {
|
|
|
110
118
|
function isRecord(value) {
|
|
111
119
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
112
120
|
}
|
|
121
|
+
// 统一路径分隔符为 POSIX,保证跨平台匹配一致。
|
|
113
122
|
function toPosix(p) {
|
|
114
123
|
return p.split(path_1.default.sep).join('/');
|
|
115
124
|
}
|
package/dist/config.js
CHANGED
|
@@ -21,6 +21,7 @@ var path_1 = __importDefault(require("path"));
|
|
|
21
21
|
var jsonc_parser_1 = require("jsonc-parser");
|
|
22
22
|
var i18n_1 = require("./i18n");
|
|
23
23
|
var consola_1 = __importDefault(require("consola"));
|
|
24
|
+
// 缓存 tsconfig 解析结果,避免每个文件都重复读取与解析。
|
|
24
25
|
var cachedTsConfig;
|
|
25
26
|
var tsConfigPathOverride = null;
|
|
26
27
|
function setTsConfigPath(path) {
|
|
@@ -43,6 +44,7 @@ function formatParseErrors(errors, content) {
|
|
|
43
44
|
})
|
|
44
45
|
.join('; ');
|
|
45
46
|
}
|
|
47
|
+
// 读取并解析 tsconfig;失败时只记录警告,不中断扫描流程。
|
|
46
48
|
function readTsConfig() {
|
|
47
49
|
if (cachedTsConfig !== undefined) {
|
|
48
50
|
return cachedTsConfig;
|
|
@@ -128,6 +130,7 @@ function createEsParserConfig(extension, options, enableDecorators) {
|
|
|
128
130
|
importAttributes: true,
|
|
129
131
|
};
|
|
130
132
|
}
|
|
133
|
+
// 按文件扩展名推导 SWC 的 parser 选项,并结合 tsconfig 的 target/decorators。
|
|
131
134
|
function loadParseOptions(filePath) {
|
|
132
135
|
var extension = path_1.default.extname(filePath).toLowerCase();
|
|
133
136
|
var tsConfig = readTsConfig();
|
package/dist/i18n/index.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.normalizeLanguage = normalizeLanguage;
|
|
|
4
4
|
exports.setLanguage = setLanguage;
|
|
5
5
|
exports.getLanguage = getLanguage;
|
|
6
6
|
exports.getTranslator = getTranslator;
|
|
7
|
+
// 中英文文案表:CLI 与 reporter 统一从这里取文案,避免散落硬编码。
|
|
7
8
|
var translations = {
|
|
8
9
|
zh: {
|
|
9
10
|
noPathsProvided: function () { return '未提供文件或目录路径,跳过代码扫描'; },
|
|
@@ -18,6 +19,10 @@ var translations = {
|
|
|
18
19
|
var file = _a.file;
|
|
19
20
|
return "\u626B\u63CF\u6587\u4EF6: ".concat(file);
|
|
20
21
|
},
|
|
22
|
+
scanProgress: function (_a) {
|
|
23
|
+
var percent = _a.percent, current = _a.current, total = _a.total;
|
|
24
|
+
return "auto-cr-cmd \u626B\u63CF\u8FDB\u5EA6: ".concat(percent, "% (").concat(current, "/").concat(total, ")");
|
|
25
|
+
},
|
|
21
26
|
scanComplete: function () { return '代码扫描完成'; },
|
|
22
27
|
scanError: function () { return '代码扫描过程中发生错误:'; },
|
|
23
28
|
parseFileFailed: function (_a) {
|
|
@@ -131,6 +136,10 @@ var translations = {
|
|
|
131
136
|
var file = _a.file;
|
|
132
137
|
return "Scanning file: ".concat(file);
|
|
133
138
|
},
|
|
139
|
+
scanProgress: function (_a) {
|
|
140
|
+
var percent = _a.percent, current = _a.current, total = _a.total;
|
|
141
|
+
return "auto-cr-cmd scan progress: ".concat(percent, "% (").concat(current, "/").concat(total, ")");
|
|
142
|
+
},
|
|
134
143
|
scanComplete: function () { return 'Code scan complete'; },
|
|
135
144
|
scanError: function () { return 'An error occurred during code scanning:'; },
|
|
136
145
|
parseFileFailed: function (_a) {
|
|
@@ -232,8 +241,10 @@ var translations = {
|
|
|
232
241
|
},
|
|
233
242
|
},
|
|
234
243
|
};
|
|
244
|
+
// 当前语言与翻译器为全局单例,方便在各处同步使用。
|
|
235
245
|
var currentLanguage = 'zh';
|
|
236
246
|
var currentTranslator = translations.zh;
|
|
247
|
+
// 对输入语言进行归一化,未知语言回退到中文。
|
|
237
248
|
function normalizeLanguage(input) {
|
|
238
249
|
if (!input) {
|
|
239
250
|
return 'zh';
|
|
@@ -253,9 +264,11 @@ function setLanguage(language) {
|
|
|
253
264
|
currentTranslator = translations[normalized];
|
|
254
265
|
return currentTranslator;
|
|
255
266
|
}
|
|
267
|
+
// 获取当前语言标识(用于 reporter 输出与规则文案)。
|
|
256
268
|
function getLanguage() {
|
|
257
269
|
return currentLanguage;
|
|
258
270
|
}
|
|
271
|
+
// 获取当前翻译器(用于 CLI/报错/提示文案)。
|
|
259
272
|
function getTranslator() {
|
|
260
273
|
return currentTranslator;
|
|
261
274
|
}
|
package/dist/index.js
CHANGED
|
@@ -95,14 +95,112 @@ var isScannableFile = function (filePath) {
|
|
|
95
95
|
* 4. 逐文件扫描并汇总输出。
|
|
96
96
|
*/
|
|
97
97
|
function run() {
|
|
98
|
-
return __awaiter(this, arguments, void 0, function (filePaths, ruleDir, format, configPath, ignorePath) {
|
|
99
|
-
var t, notifications, log, validPaths, ignoreConfig, isIgnored_1, allFiles, _i, validPaths_1, targetPath, stat, directoryFiles, scannableFiles, customRules, rcConfig, rules, filesWithErrors, filesWithWarnings, filesWithOptimizing, totalViolations, totalErrorViolations, totalWarningViolations, totalOptimizingViolations, fileSummaries, _a, scannableFiles_1, file, summary, error_1;
|
|
98
|
+
return __awaiter(this, arguments, void 0, function (filePaths, ruleDir, format, configPath, ignorePath, progressOption) {
|
|
99
|
+
var t, notifications, progressStream, runningUnderPnpm, forceProgress, progressStreamHasTty, progressEnabled, progressPinned, progressFlushLine, progressStyle, progressTotal, progressCurrent, progressLastPercent, clearProgressLine, renderProgress, startProgress, advanceProgress, finishProgress, reporterHooks, log, validPaths, ignoreConfig, isIgnored_1, allFiles, _i, validPaths_1, targetPath, stat, directoryFiles, scannableFiles, customRules, rcConfig, rules, filesWithErrors, filesWithWarnings, filesWithOptimizing, totalViolations, totalErrorViolations, totalWarningViolations, totalOptimizingViolations, fileSummaries, _a, scannableFiles_1, file, summary, error_1;
|
|
100
|
+
var _b;
|
|
100
101
|
if (filePaths === void 0) { filePaths = []; }
|
|
101
|
-
return __generator(this, function (
|
|
102
|
-
switch (
|
|
102
|
+
return __generator(this, function (_c) {
|
|
103
|
+
switch (_c.label) {
|
|
103
104
|
case 0:
|
|
104
105
|
t = (0, i18n_1.getTranslator)();
|
|
105
106
|
notifications = [];
|
|
107
|
+
progressStream = (progressOption === null || progressOption === void 0 ? void 0 : progressOption.stream) === 'stderr' ? process.stderr : process.stdout;
|
|
108
|
+
runningUnderPnpm = /pnpm\//.test((_b = process.env.npm_config_user_agent) !== null && _b !== void 0 ? _b : '');
|
|
109
|
+
forceProgress = Boolean(progressOption === null || progressOption === void 0 ? void 0 : progressOption.force);
|
|
110
|
+
progressStreamHasTty = Boolean(progressStream.isTTY);
|
|
111
|
+
progressEnabled = format === 'text' && (forceProgress || progressStreamHasTty || runningUnderPnpm);
|
|
112
|
+
progressPinned = progressEnabled;
|
|
113
|
+
progressFlushLine = progressEnabled && !progressStreamHasTty;
|
|
114
|
+
progressStyle = progressStreamHasTty
|
|
115
|
+
? { prefix: '\x1b[44m\x1b[97m', reset: '\x1b[0m' }
|
|
116
|
+
: { prefix: '', reset: '' };
|
|
117
|
+
progressTotal = 0;
|
|
118
|
+
progressCurrent = 0;
|
|
119
|
+
progressLastPercent = -1;
|
|
120
|
+
clearProgressLine = function () {
|
|
121
|
+
if (!progressEnabled) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (progressPinned) {
|
|
125
|
+
progressStream.write('\x1b7');
|
|
126
|
+
progressStream.write('\x1b[1;1H');
|
|
127
|
+
progressStream.write('\x1b[2K');
|
|
128
|
+
progressStream.write('\x1b8');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
progressStream.write('\r\x1b[2K');
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
renderProgress = function (force) {
|
|
135
|
+
if (force === void 0) { force = false; }
|
|
136
|
+
if (!progressEnabled || progressTotal === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
var percent = Math.min(100, Math.floor((progressCurrent / progressTotal) * 100));
|
|
140
|
+
if (!force && percent === progressLastPercent) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
progressLastPercent = percent;
|
|
144
|
+
var message = t.scanProgress({ percent: percent, current: progressCurrent, total: progressTotal });
|
|
145
|
+
var styledMessage = progressStyle.prefix ? "".concat(progressStyle.prefix).concat(message) : message;
|
|
146
|
+
if (progressPinned) {
|
|
147
|
+
progressStream.write('\x1b7');
|
|
148
|
+
progressStream.write('\x1b[1;1H');
|
|
149
|
+
progressStream.write('\x1b[2K');
|
|
150
|
+
progressStream.write(styledMessage);
|
|
151
|
+
if (progressStyle.prefix) {
|
|
152
|
+
progressStream.write('\x1b[K');
|
|
153
|
+
progressStream.write(progressStyle.reset);
|
|
154
|
+
}
|
|
155
|
+
if (progressFlushLine) {
|
|
156
|
+
progressStream.write('\n');
|
|
157
|
+
}
|
|
158
|
+
progressStream.write('\x1b8');
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
progressStream.write("\r".concat(styledMessage));
|
|
162
|
+
if (progressStyle.prefix) {
|
|
163
|
+
progressStream.write('\x1b[K');
|
|
164
|
+
progressStream.write(progressStyle.reset);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
progressStream.write('\x1b[K');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
startProgress = function (total) {
|
|
172
|
+
if (!progressEnabled) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
progressTotal = total;
|
|
176
|
+
progressCurrent = 0;
|
|
177
|
+
progressLastPercent = -1;
|
|
178
|
+
renderProgress(true);
|
|
179
|
+
};
|
|
180
|
+
advanceProgress = function () {
|
|
181
|
+
if (!progressEnabled || progressTotal === 0) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
progressCurrent = Math.min(progressCurrent + 1, progressTotal);
|
|
185
|
+
renderProgress();
|
|
186
|
+
};
|
|
187
|
+
finishProgress = function () {
|
|
188
|
+
if (!progressEnabled) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (progressTotal > 0) {
|
|
192
|
+
progressCurrent = progressTotal;
|
|
193
|
+
progressLastPercent = -1;
|
|
194
|
+
renderProgress();
|
|
195
|
+
clearProgressLine();
|
|
196
|
+
}
|
|
197
|
+
progressTotal = 0;
|
|
198
|
+
progressCurrent = 0;
|
|
199
|
+
progressLastPercent = -1;
|
|
200
|
+
};
|
|
201
|
+
reporterHooks = {
|
|
202
|
+
onAfterReport: function () { return renderProgress(true); },
|
|
203
|
+
};
|
|
106
204
|
log = function (level, message, detail) {
|
|
107
205
|
var detailText;
|
|
108
206
|
if (detail !== undefined) {
|
|
@@ -130,11 +228,12 @@ function run() {
|
|
|
130
228
|
else {
|
|
131
229
|
logger(message, detail);
|
|
132
230
|
}
|
|
231
|
+
renderProgress(true);
|
|
133
232
|
}
|
|
134
233
|
};
|
|
135
|
-
|
|
234
|
+
_c.label = 1;
|
|
136
235
|
case 1:
|
|
137
|
-
|
|
236
|
+
_c.trys.push([1, 6, , 7]);
|
|
138
237
|
if (filePaths.length === 0) {
|
|
139
238
|
log('info', t.noPathsProvided());
|
|
140
239
|
return [2 /*return*/, {
|
|
@@ -221,14 +320,15 @@ function run() {
|
|
|
221
320
|
totalWarningViolations = 0;
|
|
222
321
|
totalOptimizingViolations = 0;
|
|
223
322
|
fileSummaries = [];
|
|
323
|
+
startProgress(scannableFiles.length);
|
|
224
324
|
_a = 0, scannableFiles_1 = scannableFiles;
|
|
225
|
-
|
|
325
|
+
_c.label = 2;
|
|
226
326
|
case 2:
|
|
227
327
|
if (!(_a < scannableFiles_1.length)) return [3 /*break*/, 5];
|
|
228
328
|
file = scannableFiles_1[_a];
|
|
229
|
-
return [4 /*yield*/, analyzeFile(file, rules, format, log)];
|
|
329
|
+
return [4 /*yield*/, analyzeFile(file, rules, format, log, reporterHooks)];
|
|
230
330
|
case 3:
|
|
231
|
-
summary =
|
|
331
|
+
summary = _c.sent();
|
|
232
332
|
if (summary.severityCounts.error > 0) {
|
|
233
333
|
filesWithErrors += 1;
|
|
234
334
|
}
|
|
@@ -249,26 +349,29 @@ function run() {
|
|
|
249
349
|
errorViolations: summary.errorViolations,
|
|
250
350
|
violations: summary.violations,
|
|
251
351
|
});
|
|
252
|
-
|
|
352
|
+
advanceProgress();
|
|
353
|
+
_c.label = 4;
|
|
253
354
|
case 4:
|
|
254
355
|
_a++;
|
|
255
356
|
return [3 /*break*/, 2];
|
|
256
|
-
case 5:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
357
|
+
case 5:
|
|
358
|
+
finishProgress();
|
|
359
|
+
return [2 /*return*/, {
|
|
360
|
+
scannedFiles: scannableFiles.length,
|
|
361
|
+
filesWithErrors: filesWithErrors,
|
|
362
|
+
filesWithWarnings: filesWithWarnings,
|
|
363
|
+
filesWithOptimizing: filesWithOptimizing,
|
|
364
|
+
violationTotals: {
|
|
365
|
+
total: totalViolations,
|
|
366
|
+
error: totalErrorViolations,
|
|
367
|
+
warning: totalWarningViolations,
|
|
368
|
+
optimizing: totalOptimizingViolations,
|
|
369
|
+
},
|
|
370
|
+
files: fileSummaries,
|
|
371
|
+
notifications: notifications,
|
|
372
|
+
}];
|
|
270
373
|
case 6:
|
|
271
|
-
error_1 =
|
|
374
|
+
error_1 = _c.sent();
|
|
272
375
|
throw error_1 instanceof Error ? error_1 : new Error(String(error_1));
|
|
273
376
|
case 7: return [2 /*return*/];
|
|
274
377
|
}
|
|
@@ -282,14 +385,14 @@ function run() {
|
|
|
282
385
|
* - 逐条执行规则,收集 reporter 输出;
|
|
283
386
|
* - 汇总为文件级统计。
|
|
284
387
|
*/
|
|
285
|
-
function analyzeFile(file, rules, format, log) {
|
|
388
|
+
function analyzeFile(file, rules, format, log, reporterHooks) {
|
|
286
389
|
return __awaiter(this, void 0, void 0, function () {
|
|
287
390
|
var source, reporter, t, ast, parseOptions, language, baseContext, sharedHelpers, _loop_1, _i, rules_1, rule, summary;
|
|
288
391
|
return __generator(this, function (_a) {
|
|
289
392
|
switch (_a.label) {
|
|
290
393
|
case 0:
|
|
291
394
|
source = (0, file_1.readFile)(file);
|
|
292
|
-
reporter = (0, report_1.createReporter)(file, source, { format: format });
|
|
395
|
+
reporter = (0, report_1.createReporter)(file, source, __assign({ format: format }, reporterHooks));
|
|
293
396
|
t = (0, i18n_1.getTranslator)();
|
|
294
397
|
try {
|
|
295
398
|
parseOptions = (0, config_1.loadParseOptions)(file);
|
|
@@ -386,6 +489,7 @@ function analyzeFile(file, rules, format, log) {
|
|
|
386
489
|
}
|
|
387
490
|
function normalizeViolationInput(input, spanArg) {
|
|
388
491
|
var _a;
|
|
492
|
+
// 规则既可以直接输出字符串,也可以输出结构化对象;这里统一为标准格式。
|
|
389
493
|
if (typeof input === 'string') {
|
|
390
494
|
return {
|
|
391
495
|
message: input,
|
|
@@ -436,6 +540,7 @@ function normalizeViolationInput(input, spanArg) {
|
|
|
436
540
|
span: spanArg,
|
|
437
541
|
};
|
|
438
542
|
}
|
|
543
|
+
// CLI 输出格式解析:仅允许 text/json。
|
|
439
544
|
function parseOutputFormat(value) {
|
|
440
545
|
if (!value) {
|
|
441
546
|
return 'text';
|
|
@@ -446,6 +551,22 @@ function parseOutputFormat(value) {
|
|
|
446
551
|
}
|
|
447
552
|
throw new Error("Unsupported output format: ".concat(value, ". Use \"text\" or \"json\"."));
|
|
448
553
|
}
|
|
554
|
+
function parseProgressOption(value) {
|
|
555
|
+
if (!value) {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
if (value === true) {
|
|
559
|
+
return { force: true, stream: 'stdout' };
|
|
560
|
+
}
|
|
561
|
+
if (typeof value === 'string') {
|
|
562
|
+
var normalized = value.toLowerCase();
|
|
563
|
+
if (normalized === 'stdout' || normalized === 'stderr') {
|
|
564
|
+
return { force: true, stream: normalized };
|
|
565
|
+
}
|
|
566
|
+
throw new Error("Unsupported progress output: ".concat(value, ". Use \"stdout\" or \"stderr\"."));
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
449
570
|
function severityToLabel(severity) {
|
|
450
571
|
switch (severity) {
|
|
451
572
|
case auto_cr_rules_1.RuleSeverity.Warning:
|
|
@@ -477,6 +598,7 @@ function formatViolationForJson(violation) {
|
|
|
477
598
|
return payload;
|
|
478
599
|
}
|
|
479
600
|
function formatJsonOutput(result) {
|
|
601
|
+
// JSON 输出用于 CI/脚本解析,保持结构稳定。
|
|
480
602
|
return {
|
|
481
603
|
summary: {
|
|
482
604
|
scannedFiles: result.scannedFiles,
|
|
@@ -503,13 +625,15 @@ commander_1.program
|
|
|
503
625
|
.option('-c, --config <path>', '配置文件路径 (.autocrrc.json|.autocrrc.js) / Config file path (.autocrrc.json|.autocrrc.js)')
|
|
504
626
|
.option('--ignore-path <path>', '忽略文件列表路径 (.autocrignore.json|.autocrignore.js) / Ignore file path (.autocrignore.json|.autocrignore.js)')
|
|
505
627
|
.option('--tsconfig <path>', '自定义 tsconfig 路径 / Custom tsconfig path')
|
|
628
|
+
.option('--progress [stream]', '强制显示扫描进度,可选 stdout/stderr / Force showing scan progress (stdout/stderr)')
|
|
506
629
|
.option('--stdin', '从标准输入读取扫描路径 / Read file paths from STDIN')
|
|
507
|
-
.parse(process.argv);
|
|
630
|
+
.parse(process.argv.filter(function (arg) { return arg !== '--'; }));
|
|
508
631
|
var options = commander_1.program.opts();
|
|
509
632
|
var cliArguments = commander_1.program.args;
|
|
510
633
|
(0, i18n_1.setLanguage)((_a = options.language) !== null && _a !== void 0 ? _a : process.env.LANG);
|
|
511
634
|
(0, config_1.setTsConfigPath)(options.tsconfig ? path_1.default.resolve(process.cwd(), options.tsconfig) : undefined);
|
|
512
635
|
var outputFormat;
|
|
636
|
+
var progressOption;
|
|
513
637
|
try {
|
|
514
638
|
outputFormat = parseOutputFormat(options.output);
|
|
515
639
|
}
|
|
@@ -518,6 +642,14 @@ catch (error) {
|
|
|
518
642
|
consola_1.consola.error(message);
|
|
519
643
|
process.exit(1);
|
|
520
644
|
}
|
|
645
|
+
try {
|
|
646
|
+
progressOption = parseProgressOption(options.progress);
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
var message = error instanceof Error ? error.message : String(error);
|
|
650
|
+
consola_1.consola.error(message);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
521
653
|
;
|
|
522
654
|
(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
523
655
|
var stdinTargets, combinedTargets, filePaths, result, t, payload, exitCode, language, resultMessage, exitCode, error_3, t, detail, payload;
|
|
@@ -530,7 +662,7 @@ catch (error) {
|
|
|
530
662
|
stdinTargets = _a.sent();
|
|
531
663
|
combinedTargets = __spreadArray(__spreadArray([], cliArguments, true), stdinTargets, true);
|
|
532
664
|
filePaths = combinedTargets.map(function (target) { return path_1.default.resolve(process.cwd(), target); });
|
|
533
|
-
return [4 /*yield*/, run(filePaths, options.ruleDir, outputFormat, options.config, options.ignorePath)];
|
|
665
|
+
return [4 /*yield*/, run(filePaths, options.ruleDir, outputFormat, options.config, options.ignorePath, progressOption)];
|
|
534
666
|
case 2:
|
|
535
667
|
result = _a.sent();
|
|
536
668
|
t = (0, i18n_1.getTranslator)();
|
package/dist/report/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var consola_1 = __importDefault(require("consola"));
|
|
|
30
30
|
var i18n_1 = require("../i18n");
|
|
31
31
|
var UNTAGGED_TAG = 'untagged';
|
|
32
32
|
var DEFAULT_FORMAT = 'text';
|
|
33
|
+
// 不同严重级别映射到不同日志级别。
|
|
33
34
|
var severityLoggers = (_a = {},
|
|
34
35
|
_a[auto_cr_rules_1.RuleSeverity.Error] = consola_1.default.error,
|
|
35
36
|
_a[auto_cr_rules_1.RuleSeverity.Warning] = consola_1.default.warn,
|
|
@@ -43,6 +44,9 @@ function createReporter(filePath, source, options) {
|
|
|
43
44
|
var language = (0, i18n_1.getLanguage)();
|
|
44
45
|
var records = [];
|
|
45
46
|
var format = (_a = options.format) !== null && _a !== void 0 ? _a : DEFAULT_FORMAT;
|
|
47
|
+
var onBeforeReport = options.onBeforeReport;
|
|
48
|
+
var onAfterReport = options.onAfterReport;
|
|
49
|
+
// 累计单文件的违规统计,便于输出文件级 summary。
|
|
46
50
|
var totalViolations = 0;
|
|
47
51
|
var errorViolations = 0;
|
|
48
52
|
var severityCounts = {
|
|
@@ -94,6 +98,7 @@ function createReporter(filePath, source, options) {
|
|
|
94
98
|
var _a, _b;
|
|
95
99
|
var tag = (_a = rule.tag) !== null && _a !== void 0 ? _a : UNTAGGED_TAG;
|
|
96
100
|
var severity = (_b = rule.severity) !== null && _b !== void 0 ? _b : auto_cr_rules_1.RuleSeverity.Error;
|
|
101
|
+
// 规则级 reporter 会把具体信息写入 records。
|
|
97
102
|
var store = function (payload) {
|
|
98
103
|
pushRecord({
|
|
99
104
|
tag: tag,
|
|
@@ -137,6 +142,7 @@ function createReporter(filePath, source, options) {
|
|
|
137
142
|
};
|
|
138
143
|
return reporterWithRecord;
|
|
139
144
|
};
|
|
145
|
+
// flush 会输出(text 模式)并返回结构化 summary,同时重置内部状态。
|
|
140
146
|
var flush = function () {
|
|
141
147
|
var violationSnapshot = records.map(function (record) { return (__assign(__assign({}, record), { suggestions: record.suggestions ? __spreadArray([], record.suggestions, true) : undefined })); });
|
|
142
148
|
var summary = {
|
|
@@ -150,6 +156,7 @@ function createReporter(filePath, source, options) {
|
|
|
150
156
|
violations: violationSnapshot,
|
|
151
157
|
};
|
|
152
158
|
if (violationSnapshot.length > 0 && format === 'text') {
|
|
159
|
+
onBeforeReport === null || onBeforeReport === void 0 ? void 0 : onBeforeReport();
|
|
153
160
|
var locale = language === 'zh' ? 'zh-CN' : 'en-US';
|
|
154
161
|
var formatter_1 = new Intl.DateTimeFormat(locale, {
|
|
155
162
|
hour: '2-digit',
|
|
@@ -181,11 +188,13 @@ function createReporter(filePath, source, options) {
|
|
|
181
188
|
consola_1.default.log("".concat(indent_1).concat(t.reporterSuggestionLabel(), ": ").concat(suggestionLine));
|
|
182
189
|
}
|
|
183
190
|
});
|
|
191
|
+
onAfterReport === null || onAfterReport === void 0 ? void 0 : onAfterReport();
|
|
184
192
|
}
|
|
185
193
|
records.length = 0;
|
|
186
194
|
resetCounters();
|
|
187
195
|
return summary;
|
|
188
196
|
};
|
|
197
|
+
// 每次 flush 后清零,避免跨文件混淆统计。
|
|
189
198
|
var resetCounters = function () {
|
|
190
199
|
totalViolations = 0;
|
|
191
200
|
errorViolations = 0;
|
package/dist/rules/loader.js
CHANGED
|
@@ -10,7 +10,9 @@ var consola_1 = require("consola");
|
|
|
10
10
|
var file_1 = require("../utils/file");
|
|
11
11
|
var i18n_1 = require("../i18n");
|
|
12
12
|
var auto_cr_rules_1 = require("auto-cr-rules");
|
|
13
|
+
// 自定义规则加载器:只解析 JS/CJS/MJS 文件,便于在运行时动态引入。
|
|
13
14
|
var SUPPORTED_EXTENSIONS = ['.js', '.cjs', '.mjs'];
|
|
15
|
+
// 从指定目录读取规则文件,并转换为 Rule 列表。
|
|
14
16
|
function loadCustomRules(ruleDir) {
|
|
15
17
|
var t = (0, i18n_1.getTranslator)();
|
|
16
18
|
if (!ruleDir) {
|
|
@@ -30,6 +32,7 @@ function loadCustomRules(ruleDir) {
|
|
|
30
32
|
try {
|
|
31
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
34
|
var moduleExports = require(file);
|
|
35
|
+
// 支持模块直接导出 rule / rules / default / 数组。
|
|
33
36
|
var rules = extractRules(moduleExports, file);
|
|
34
37
|
if (!rules.length) {
|
|
35
38
|
consola_1.consola.warn(t.customRuleNoExport({ file: file }));
|
|
@@ -43,6 +46,7 @@ function loadCustomRules(ruleDir) {
|
|
|
43
46
|
}
|
|
44
47
|
return loaded;
|
|
45
48
|
}
|
|
49
|
+
// 兼容多种导出形态:默认导出、命名导出或数组。
|
|
46
50
|
function extractRules(moduleExports, origin) {
|
|
47
51
|
var collected = [];
|
|
48
52
|
collected.push.apply(collected, normalizeCandidate(moduleExports, origin));
|
|
@@ -60,6 +64,7 @@ function extractRules(moduleExports, origin) {
|
|
|
60
64
|
}
|
|
61
65
|
return collected;
|
|
62
66
|
}
|
|
67
|
+
// 把候选导出统一转为 Rule;无法识别的会被忽略。
|
|
63
68
|
function normalizeCandidate(candidate, origin) {
|
|
64
69
|
if (!candidate) {
|
|
65
70
|
return [];
|
|
@@ -10,6 +10,11 @@ interface Translator {
|
|
|
10
10
|
scanningFile(params: {
|
|
11
11
|
file: string;
|
|
12
12
|
}): string;
|
|
13
|
+
scanProgress(params: {
|
|
14
|
+
percent: number;
|
|
15
|
+
current: number;
|
|
16
|
+
total: number;
|
|
17
|
+
}): string;
|
|
13
18
|
scanComplete(): string;
|
|
14
19
|
scanError(): string;
|
|
15
20
|
parseFileFailed(params: {
|
|
@@ -3,6 +3,8 @@ import type { Rule, RuleReporter } from 'auto-cr-rules';
|
|
|
3
3
|
export type ReporterFormat = 'text' | 'json';
|
|
4
4
|
interface ReporterOptions {
|
|
5
5
|
format?: ReporterFormat;
|
|
6
|
+
onBeforeReport?: () => void;
|
|
7
|
+
onAfterReport?: () => void;
|
|
6
8
|
}
|
|
7
9
|
export interface Reporter extends RuleReporter {
|
|
8
10
|
forRule(rule: Pick<Rule, 'name' | 'tag' | 'severity'>): RuleReporter;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare const readFile: (path: string) => string;
|
|
2
2
|
/**
|
|
3
3
|
* 递归获取目录下所有 TypeScript 和 JavaScript 文件
|
|
4
|
+
* - 默认跳过 node_modules
|
|
5
|
+
* - 可通过 shouldIgnore 进一步过滤路径
|
|
4
6
|
*/
|
|
5
7
|
export declare function getAllFiles(dirPath: string, arrayOfFiles?: string[], extensions?: string[], options?: {
|
|
6
8
|
skipNodeModules?: boolean;
|
package/dist/utils/file.js
CHANGED
|
@@ -10,12 +10,15 @@ var fs_1 = __importDefault(require("fs"));
|
|
|
10
10
|
var path_1 = __importDefault(require("path"));
|
|
11
11
|
var consola_1 = require("consola");
|
|
12
12
|
var i18n_1 = require("../i18n");
|
|
13
|
+
// 统一读取文本文件(UTF-8),供解析与规则执行使用。
|
|
13
14
|
var readFile = function (path) {
|
|
14
15
|
return fs_1.default.readFileSync(path, 'utf-8');
|
|
15
16
|
};
|
|
16
17
|
exports.readFile = readFile;
|
|
17
18
|
/**
|
|
18
19
|
* 递归获取目录下所有 TypeScript 和 JavaScript 文件
|
|
20
|
+
* - 默认跳过 node_modules
|
|
21
|
+
* - 可通过 shouldIgnore 进一步过滤路径
|
|
19
22
|
*/
|
|
20
23
|
function getAllFiles(dirPath, arrayOfFiles, extensions, options) {
|
|
21
24
|
if (arrayOfFiles === void 0) { arrayOfFiles = []; }
|
package/dist/utils/stdin.js
CHANGED
|
@@ -37,6 +37,10 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.readPathsFromStdin = readPathsFromStdin;
|
|
40
|
+
// 从 STDIN 读取路径列表:
|
|
41
|
+
// - 默认仅在非 TTY 时读取;
|
|
42
|
+
// - 支持 NUL 分隔与换行分隔两种格式;
|
|
43
|
+
// - 保留空格,仅移除空行与 CR。
|
|
40
44
|
function readPathsFromStdin(shouldForceRead) {
|
|
41
45
|
return __awaiter(this, void 0, void 0, function () {
|
|
42
46
|
var shouldRead;
|
|
@@ -75,11 +79,11 @@ function readPathsFromStdin(shouldForceRead) {
|
|
|
75
79
|
if (buf.length === 0) {
|
|
76
80
|
return finish([]);
|
|
77
81
|
}
|
|
78
|
-
//
|
|
82
|
+
// 优先使用 NUL 分隔(适配 xargs -0 等工具),否则按换行切分。
|
|
79
83
|
var hasNul = buf.includes(0); // 0x00
|
|
80
84
|
var payload = buf.toString('utf8');
|
|
81
85
|
var parts = hasNul ? payload.split('\0') : payload.split(/\r?\n/);
|
|
82
|
-
//
|
|
86
|
+
// 保留文件名中的空格,只去掉末尾 CR 并过滤空行。
|
|
83
87
|
var lines = parts
|
|
84
88
|
.map(function (s) { return (s.endsWith('\r') ? s.slice(0, -1) : s); })
|
|
85
89
|
.filter(function (s) { return s.length > 0; });
|