auto-cr-rules 2.0.76 → 2.0.79

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 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.
@@ -121,6 +121,7 @@ npx auto-cr-cmd --output json -- ./src | jq
121
121
  {
122
122
  "rules": {
123
123
  "no-deep-relative-imports": "error",
124
+ "no-circular-dependencies": "warning",
124
125
  "no-swallowed-errors": "off"
125
126
  }
126
127
  }
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 发布命令,保持两个包的版本同步。
@@ -121,6 +121,7 @@ npx auto-cr-cmd --output json -- ./src | jq
121
121
  {
122
122
  "rules": {
123
123
  "no-deep-relative-imports": "error",
124
+ "no-circular-dependencies": "warning",
124
125
  "no-swallowed-errors": "off"
125
126
  }
126
127
  }
package/dist/imports.js CHANGED
@@ -10,6 +10,7 @@ var isSwcNode = function (value) {
10
10
  }
11
11
  return typeof value.type === 'string';
12
12
  };
13
+ // 递归遍历 AST,遇到带 type 的节点就回调。
13
14
  var traverse = function (value, visitor) {
14
15
  if (Array.isArray(value)) {
15
16
  for (var _i = 0, value_1 = value; _i < value_1.length; _i++) {
@@ -34,6 +35,7 @@ var traverse = function (value, visitor) {
34
35
  };
35
36
  var isImportDeclaration = function (node) { return node.type === 'ImportDeclaration'; };
36
37
  var isCallExpression = function (node) { return node.type === 'CallExpression'; };
38
+ // 收集模块中的所有导入引用(包含动态 import 与 require)。
37
39
  var collectImportReferences = function (module) {
38
40
  var results = [];
39
41
  traverse(module, function (node) {
@@ -55,6 +57,7 @@ var collectImportReferences = function (module) {
55
57
  return results;
56
58
  };
57
59
  exports.collectImportReferences = collectImportReferences;
60
+ // 从调用表达式中提取 import/require 的字符串字面量参数。
58
61
  var extractFromCallExpression = function (node) {
59
62
  if (!node.arguments.length) {
60
63
  return null;
package/dist/messages.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRuleMessages = void 0;
4
+ // 规则文案多语言表(由 rules 通过 messages 接口访问)。
4
5
  var ruleTranslations = {
5
6
  zh: {
6
7
  noDeepRelativeImports: function (_a) {
@@ -25,7 +26,9 @@ var ruleTranslations = {
25
26
  },
26
27
  },
27
28
  };
29
+ // 固定文案对象,避免运行期被意外修改。
28
30
  Object.values(ruleTranslations).forEach(function (messages) { return Object.freeze(messages); });
31
+ // 根据语言返回对应的规则文案实现。
29
32
  var createRuleMessages = function (language) {
30
33
  var _a;
31
34
  return (_a = ruleTranslations[language]) !== null && _a !== void 0 ? _a : ruleTranslations.zh;
@@ -7,4 +7,5 @@ var noCircularDependencies_1 = require("./noCircularDependencies");
7
7
  Object.defineProperty(exports, "noCircularDependencies", { enumerable: true, get: function () { return noCircularDependencies_1.noCircularDependencies; } });
8
8
  var noSwallowedErrors_1 = require("./noSwallowedErrors");
9
9
  Object.defineProperty(exports, "noSwallowedErrors", { enumerable: true, get: function () { return noSwallowedErrors_1.noSwallowedErrors; } });
10
+ // 内置规则列表,按默认顺序执行。
10
11
  exports.builtinRules = [noDeepRelativeImports_1.noDeepRelativeImports, noCircularDependencies_1.noCircularDependencies, noSwallowedErrors_1.noSwallowedErrors];
@@ -16,10 +16,19 @@ exports.noCircularDependencies = void 0;
16
16
  var fs_1 = __importDefault(require("fs"));
17
17
  var path_1 = __importDefault(require("path"));
18
18
  var types_1 = require("../types");
19
+ /**
20
+ * 检测相对路径形成的循环依赖:
21
+ * - 仅解析 .ts/.tsx/.js/.jsx/.mjs/.cjs;
22
+ * - 从当前文件的 import 出发,沿依赖图寻找回到自身的路径;
23
+ * - 输出完整环路链路,便于定位循环发生的文件。
24
+ */
19
25
  var SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
26
+ // 避免在大型仓库里构图过深或过大导致卡顿。
20
27
  var MAX_GRAPH_NODES = 2000;
21
28
  var MAX_GRAPH_DEPTH = 80;
29
+ // 跨文件缓存解析结果,减少重复 IO 与正则扫描。
22
30
  var resolvedImportCache = new Map();
31
+ // 记录已上报的环路(做规范化),避免同一环路重复报错。
23
32
  var reportedCycles = new Set();
24
33
  exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependencies', { tag: 'base', severity: types_1.RuleSeverity.Warning }, function (_a) {
25
34
  var _b, _c;
@@ -28,7 +37,7 @@ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependenci
28
37
  var root = resolveProjectRoot(origin);
29
38
  var moduleStart = (_c = (_b = ast.span) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : 0;
30
39
  var lineIndex = buildLineIndex(source);
31
- // Seed cache with SWC-collected imports for the current file.
40
+ // 先用 SWC 提供的 import 列表初始化当前文件的依赖,保证准确性与性能。
32
41
  resolvedImportCache.set(origin, resolveFromReferences(origin, helpers.imports, root));
33
42
  for (var _i = 0, _d = helpers.imports; _i < _d.length; _i++) {
34
43
  var reference = _d[_i];
@@ -39,6 +48,7 @@ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependenci
39
48
  if (!target) {
40
49
  continue;
41
50
  }
51
+ // 从目标模块回溯,如果能再次回到 origin,即存在环路。
42
52
  var pathToOrigin = findPathToOrigin(target, origin, root);
43
53
  if (!pathToOrigin) {
44
54
  continue;
@@ -49,6 +59,7 @@ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependenci
49
59
  continue;
50
60
  }
51
61
  reportedCycles.add(cycleKey);
62
+ // 统一输出相对路径,便于直接定位到仓库内文件。
52
63
  var displayChain = formatCycle(cycle, root);
53
64
  var description = messages.circularDependency({ chain: displayChain });
54
65
  var suggestions = language === 'zh'
@@ -60,6 +71,7 @@ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependenci
60
71
  { text: 'Split modules to avoid mutual dependencies.' },
61
72
  { text: 'Extract shared logic into a dedicated module to break the cycle.' },
62
73
  ];
74
+ // 优先使用 SWC 的 span 计算行号,作为高可信位置;失败时再用文本匹配兜底。
63
75
  var computedLine = reference.span
64
76
  ? resolveLine(lineIndex, bytePosToCharIndex(source, moduleStart, reference.span.start))
65
77
  : undefined;
@@ -74,6 +86,7 @@ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependenci
74
86
  }, reference.span);
75
87
  }
76
88
  });
89
+ // 选择项目根目录:优先使用当前工作目录,找不到则向上寻找最近的 package.json。
77
90
  var resolveProjectRoot = function (filePath) {
78
91
  var cwd = path_1.default.resolve(process.cwd());
79
92
  if (isWithinRoot(filePath, cwd)) {
@@ -90,10 +103,12 @@ var resolveProjectRoot = function (filePath) {
90
103
  }
91
104
  return cwd;
92
105
  };
106
+ // 防止解析路径逃逸到仓库外,避免跨项目误报。
93
107
  var isWithinRoot = function (filePath, root) {
94
108
  var relative = path_1.default.relative(root, filePath);
95
109
  return relative === '' || (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
96
110
  };
111
+ // 将当前文件的 import 列表解析为真实文件路径(只处理相对路径)。
97
112
  var resolveFromReferences = function (origin, references, root) {
98
113
  var resolved = new Set();
99
114
  for (var _i = 0, references_1 = references; _i < references_1.length; _i++) {
@@ -108,6 +123,8 @@ var resolveFromReferences = function (origin, references, root) {
108
123
  }
109
124
  return Array.from(resolved);
110
125
  };
126
+ // DFS 搜索依赖图中是否存在一条从 start 回到 origin 的路径。
127
+ // 通过节点数与深度上限,避免超大项目中搜索失控。
111
128
  var findPathToOrigin = function (start, origin, root) {
112
129
  var nodesVisited = 0;
113
130
  var visiting = new Set();
@@ -145,6 +162,8 @@ var findPathToOrigin = function (start, origin, root) {
145
162
  };
146
163
  return walk(start, 0);
147
164
  };
165
+ // 读取文件并通过简单正则抽取 import/require/export-from。
166
+ // 这里不重新解析 AST,成本低但可能漏掉非常规写法。
148
167
  var getResolvedImports = function (filePath, root) {
149
168
  var cached = resolvedImportCache.get(filePath);
150
169
  if (cached) {
@@ -179,6 +198,7 @@ var readFileSafe = function (filePath) {
179
198
  return null;
180
199
  }
181
200
  };
201
+ // 用正则匹配常见的导入写法,覆盖 import / dynamic import / require / export-from。
182
202
  var extractImportSpecifiers = function (source) {
183
203
  var results = [];
184
204
  var patterns = [
@@ -196,6 +216,8 @@ var extractImportSpecifiers = function (source) {
196
216
  }
197
217
  return results;
198
218
  };
219
+ // 解析相对路径到真实文件:支持自动补全扩展名与目录 index。
220
+ // 并确保解析结果在项目根目录内。
199
221
  var resolveImportFile = function (fromFile, specifier, root) {
200
222
  if (!specifier.startsWith('.')) {
201
223
  return null;
@@ -267,6 +289,7 @@ var resolveFromDirectory = function (basePath) {
267
289
  }
268
290
  return null;
269
291
  };
292
+ // 将环路规范化为稳定 key,避免同一环路从不同入口重复报错。
270
293
  var buildCycleKey = function (cycle) {
271
294
  if (cycle.length <= 2) {
272
295
  return cycle.join('->');
@@ -282,6 +305,7 @@ var buildCycleKey = function (cycle) {
282
305
  }
283
306
  return best;
284
307
  };
308
+ // 输出尽量相对路径,便于直接定位文件。
285
309
  var formatCycle = function (cycle, root) {
286
310
  var formatted = cycle.map(function (entry) {
287
311
  var relative = path_1.default.relative(root, entry);
@@ -373,6 +397,11 @@ var selectLineNumber = function (computed, fallback) {
373
397
  if (computed === undefined) {
374
398
  return fallback;
375
399
  }
400
+ // 当两者只差一行时,优先使用文本匹配结果,避免出现 +1 的偏移问题。
401
+ if (Math.abs(computed - fallback) <= 1) {
402
+ return fallback;
403
+ }
404
+ // 若 span 指向了更早的注释块,则回退到更靠后的文本行。
376
405
  if (computed < fallback) {
377
406
  return fallback;
378
407
  }
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noDeepRelativeImports = void 0;
4
4
  var types_1 = require("../types");
5
5
  var MAX_DEPTH = 2;
6
+ // 检测相对路径过深的导入,避免维护困难与重构风险。
6
7
  exports.noDeepRelativeImports = (0, types_1.defineRule)('no-deep-relative-imports', { tag: 'base', severity: types_1.RuleSeverity.Warning }, function (_a) {
7
8
  var _b, _c;
8
9
  var ast = _a.ast, helpers = _a.helpers, messages = _a.messages, language = _a.language, source = _a.source;
9
- // Build a per-file line index so we can convert byte offsets emitted by SWC back to line numbers.
10
+ // 构建行号索引,方便将 SWC byte offset 转为行号。
10
11
  var moduleStart = (_c = (_b = ast.span) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : 0;
11
12
  var lineIndex = buildLineIndex(source);
12
13
  for (var _i = 0, _d = helpers.imports; _i < _d.length; _i++) {
@@ -26,11 +27,12 @@ exports.noDeepRelativeImports = (0, types_1.defineRule)('no-deep-relative-import
26
27
  { text: 'Use a path alias (for example: @shared/deep/utils).' },
27
28
  { text: 'Create an index file at a higher level to re-export the module and shorten the import.' },
28
29
  ];
30
+ // 优先使用 span 计算行号,若异常则退回到文本匹配。
29
31
  var computedLine = reference.span
30
32
  ? resolveLine(lineIndex, bytePosToCharIndex(source, moduleStart, reference.span.start))
31
33
  : undefined;
32
34
  var fallbackLine = findImportLine(source, reference.value);
33
- // Prefer the larger value so we never point at leading block comments when the byte offset is truncated.
35
+ // 取更大的行号,避免 span 截断时指向注释块。
34
36
  var line = selectLineNumber(computedLine, fallbackLine);
35
37
  helpers.reportViolation({
36
38
  description: description,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noSwallowedErrors = void 0;
4
4
  var types_1 = require("../types");
5
+ // 检测 try/catch/finally 中未处理任何可执行语句的情况(吞掉异常)。
5
6
  exports.noSwallowedErrors = (0, types_1.defineRule)('no-swallowed-errors', { tag: 'base', severity: types_1.RuleSeverity.Warning }, function (_a) {
6
7
  var _b, _c;
7
8
  var ast = _a.ast, helpers = _a.helpers, messages = _a.messages, source = _a.source;
@@ -13,9 +14,11 @@ exports.noSwallowedErrors = (0, types_1.defineRule)('no-swallowed-errors', { tag
13
14
  var finallyBlock = (_c = tryStatement.finalizer) !== null && _c !== void 0 ? _c : null;
14
15
  var catchHasExecutable = catchBlock ? hasExecutableStatements(catchBlock.stmts) : false;
15
16
  var finallyHasExecutable = finallyBlock ? hasExecutableStatements(finallyBlock.stmts) : false;
17
+ // 任意一段有真实逻辑,则认为异常被处理或至少被记录。
16
18
  if (catchHasExecutable || finallyHasExecutable) {
17
19
  return;
18
20
  }
21
+ // 尽量指向 catch/finally 块本身,保证定位直观。
19
22
  var reportSpan = (_e = (_d = catchBlock === null || catchBlock === void 0 ? void 0 : catchBlock.span) !== null && _d !== void 0 ? _d : finallyBlock === null || finallyBlock === void 0 ? void 0 : finallyBlock.span) !== null && _e !== void 0 ? _e : tryStatement.span;
20
23
  var charIndex = bytePosToCharIndex(source, moduleStart, reportSpan.start);
21
24
  var computedLine = resolveLine(lineIndex, charIndex);
package/dist/runtime.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRuleContext = void 0;
4
4
  var imports_1 = require("./imports");
5
5
  var messages_1 = require("./messages");
6
+ // 构建规则执行所需的上下文:包含 AST、imports、文案与统一的 helper 方法。
6
7
  var createRuleContext = function (_a) {
7
8
  var ast = _a.ast, filePath = _a.filePath, source = _a.source, reporter = _a.reporter, language = _a.language;
8
9
  var imports = (0, imports_1.collectImportReferences)(ast);
@@ -19,11 +20,13 @@ var createRuleContext = function (_a) {
19
20
  });
20
21
  };
21
22
  exports.createRuleContext = createRuleContext;
23
+ // 规则 helper:统一封装路径判断、相对深度与违规上报逻辑。
22
24
  var createRuleHelpers = function (reporter, imports) {
23
25
  var isRelativePath = function (value) { return value.startsWith('.'); };
24
26
  var relativeDepth = function (value) {
25
27
  return (value.match(/\.\.\//g) || []).length;
26
28
  };
29
+ // reportViolation 可兼容 string 或结构化对象,并自动选择合适的 reporter 方法。
27
30
  var reportViolation = function (input, spanArg) {
28
31
  var normalized = normalizeViolationInput(input, spanArg);
29
32
  if (typeof reporter.record === 'function') {
@@ -48,6 +51,7 @@ var createRuleHelpers = function (reporter, imports) {
48
51
  reportViolation: reportViolation,
49
52
  };
50
53
  };
54
+ // 统一规则输出结构,便于 reporter 处理 span/line/suggestions。
51
55
  function normalizeViolationInput(input, fallbackSpan) {
52
56
  var _a, _b;
53
57
  if (typeof input === 'string') {
package/dist/types.js CHANGED
@@ -13,6 +13,7 @@ var __assign = (this && this.__assign) || function () {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.toRule = exports.isRule = exports.RuleSeverity = void 0;
15
15
  exports.defineRule = defineRule;
16
+ // 规则严重级别,决定 reporter 的输出样式与统计。
16
17
  var RuleSeverity;
17
18
  (function (RuleSeverity) {
18
19
  RuleSeverity["Error"] = "error";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-cr-rules",
3
- "version": "2.0.76",
3
+ "version": "2.0.79",
4
4
  "description": "Extensible static analysis rule set for the auto-cr automated code review toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/types/index.d.ts",