auto-cr-rules 2.0.75 → 2.0.76

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noSwallowedErrors = exports.noDeepRelativeImports = exports.builtinRules = exports.createRuleContext = exports.toRule = exports.isRule = exports.defineRule = exports.RuleSeverity = void 0;
3
+ exports.noSwallowedErrors = exports.noCircularDependencies = exports.noDeepRelativeImports = exports.builtinRules = exports.createRuleContext = exports.toRule = exports.isRule = exports.defineRule = exports.RuleSeverity = void 0;
4
4
  var types_1 = require("./types");
5
5
  Object.defineProperty(exports, "RuleSeverity", { enumerable: true, get: function () { return types_1.RuleSeverity; } });
6
6
  var types_2 = require("./types");
@@ -12,4 +12,5 @@ Object.defineProperty(exports, "createRuleContext", { enumerable: true, get: fun
12
12
  var rules_1 = require("./rules");
13
13
  Object.defineProperty(exports, "builtinRules", { enumerable: true, get: function () { return rules_1.builtinRules; } });
14
14
  Object.defineProperty(exports, "noDeepRelativeImports", { enumerable: true, get: function () { return rules_1.noDeepRelativeImports; } });
15
+ Object.defineProperty(exports, "noCircularDependencies", { enumerable: true, get: function () { return rules_1.noCircularDependencies; } });
15
16
  Object.defineProperty(exports, "noSwallowedErrors", { enumerable: true, get: function () { return rules_1.noSwallowedErrors; } });
package/dist/messages.js CHANGED
@@ -8,6 +8,10 @@ var ruleTranslations = {
8
8
  return "\u5BFC\u5165\u8DEF\u5F84 \"".concat(value, "\"\uFF0C\u4E0D\u80FD\u8D85\u8FC7\u6700\u5927\u5C42\u7EA7").concat(maxDepth);
9
9
  },
10
10
  swallowedError: function () { return '捕获到的异常未被重新抛出或记录,可能导致问题被静默吞噬。'; },
11
+ circularDependency: function (_a) {
12
+ var chain = _a.chain;
13
+ return "\u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56: ".concat(chain);
14
+ },
11
15
  },
12
16
  en: {
13
17
  noDeepRelativeImports: function (_a) {
@@ -15,6 +19,10 @@ var ruleTranslations = {
15
19
  return "Import path \"".concat(value, "\" must not exceed max depth ").concat(maxDepth);
16
20
  },
17
21
  swallowedError: function () { return 'Caught exception is neither rethrown nor logged; potential swallowed error detected.'; },
22
+ circularDependency: function (_a) {
23
+ var chain = _a.chain;
24
+ return "Circular dependency detected: ".concat(chain);
25
+ },
18
26
  },
19
27
  };
20
28
  Object.values(ruleTranslations).forEach(function (messages) { return Object.freeze(messages); });
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noSwallowedErrors = exports.noDeepRelativeImports = exports.builtinRules = void 0;
3
+ exports.noSwallowedErrors = exports.noCircularDependencies = exports.noDeepRelativeImports = exports.builtinRules = void 0;
4
4
  var noDeepRelativeImports_1 = require("./noDeepRelativeImports");
5
5
  Object.defineProperty(exports, "noDeepRelativeImports", { enumerable: true, get: function () { return noDeepRelativeImports_1.noDeepRelativeImports; } });
6
+ var noCircularDependencies_1 = require("./noCircularDependencies");
7
+ Object.defineProperty(exports, "noCircularDependencies", { enumerable: true, get: function () { return noCircularDependencies_1.noCircularDependencies; } });
6
8
  var noSwallowedErrors_1 = require("./noSwallowedErrors");
7
9
  Object.defineProperty(exports, "noSwallowedErrors", { enumerable: true, get: function () { return noSwallowedErrors_1.noSwallowedErrors; } });
8
- exports.builtinRules = [noDeepRelativeImports_1.noDeepRelativeImports, noSwallowedErrors_1.noSwallowedErrors];
10
+ exports.builtinRules = [noDeepRelativeImports_1.noDeepRelativeImports, noCircularDependencies_1.noCircularDependencies, noSwallowedErrors_1.noSwallowedErrors];
@@ -0,0 +1,380 @@
1
+ "use strict";
2
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
+ if (ar || !(i in from)) {
5
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
+ ar[i] = from[i];
7
+ }
8
+ }
9
+ return to.concat(ar || Array.prototype.slice.call(from));
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.noCircularDependencies = void 0;
16
+ var fs_1 = __importDefault(require("fs"));
17
+ var path_1 = __importDefault(require("path"));
18
+ var types_1 = require("../types");
19
+ var SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
20
+ var MAX_GRAPH_NODES = 2000;
21
+ var MAX_GRAPH_DEPTH = 80;
22
+ var resolvedImportCache = new Map();
23
+ var reportedCycles = new Set();
24
+ exports.noCircularDependencies = (0, types_1.defineRule)('no-circular-dependencies', { tag: 'base', severity: types_1.RuleSeverity.Warning }, function (_a) {
25
+ var _b, _c;
26
+ var ast = _a.ast, filePath = _a.filePath, helpers = _a.helpers, language = _a.language, messages = _a.messages, source = _a.source;
27
+ var origin = path_1.default.resolve(filePath);
28
+ var root = resolveProjectRoot(origin);
29
+ var moduleStart = (_c = (_b = ast.span) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : 0;
30
+ var lineIndex = buildLineIndex(source);
31
+ // Seed cache with SWC-collected imports for the current file.
32
+ resolvedImportCache.set(origin, resolveFromReferences(origin, helpers.imports, root));
33
+ for (var _i = 0, _d = helpers.imports; _i < _d.length; _i++) {
34
+ var reference = _d[_i];
35
+ if (!helpers.isRelativePath(reference.value)) {
36
+ continue;
37
+ }
38
+ var target = resolveImportFile(origin, reference.value, root);
39
+ if (!target) {
40
+ continue;
41
+ }
42
+ var pathToOrigin = findPathToOrigin(target, origin, root);
43
+ if (!pathToOrigin) {
44
+ continue;
45
+ }
46
+ var cycle = __spreadArray([origin], pathToOrigin, true);
47
+ var cycleKey = buildCycleKey(cycle);
48
+ if (reportedCycles.has(cycleKey)) {
49
+ continue;
50
+ }
51
+ reportedCycles.add(cycleKey);
52
+ var displayChain = formatCycle(cycle, root);
53
+ var description = messages.circularDependency({ chain: displayChain });
54
+ var suggestions = language === 'zh'
55
+ ? [
56
+ { text: '考虑拆分模块,避免相互依赖。' },
57
+ { text: '抽取共享逻辑到独立模块以打破循环。' },
58
+ ]
59
+ : [
60
+ { text: 'Split modules to avoid mutual dependencies.' },
61
+ { text: 'Extract shared logic into a dedicated module to break the cycle.' },
62
+ ];
63
+ var computedLine = reference.span
64
+ ? resolveLine(lineIndex, bytePosToCharIndex(source, moduleStart, reference.span.start))
65
+ : undefined;
66
+ var fallbackLine = findImportLine(source, reference.value);
67
+ var line = selectLineNumber(computedLine, fallbackLine);
68
+ helpers.reportViolation({
69
+ description: description,
70
+ code: displayChain,
71
+ suggestions: suggestions,
72
+ span: reference.span,
73
+ line: line,
74
+ }, reference.span);
75
+ }
76
+ });
77
+ var resolveProjectRoot = function (filePath) {
78
+ var cwd = path_1.default.resolve(process.cwd());
79
+ if (isWithinRoot(filePath, cwd)) {
80
+ return cwd;
81
+ }
82
+ var current = path_1.default.dirname(filePath);
83
+ var last = '';
84
+ while (current !== last) {
85
+ if (fs_1.default.existsSync(path_1.default.join(current, 'package.json'))) {
86
+ return current;
87
+ }
88
+ last = current;
89
+ current = path_1.default.dirname(current);
90
+ }
91
+ return cwd;
92
+ };
93
+ var isWithinRoot = function (filePath, root) {
94
+ var relative = path_1.default.relative(root, filePath);
95
+ return relative === '' || (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
96
+ };
97
+ var resolveFromReferences = function (origin, references, root) {
98
+ var resolved = new Set();
99
+ for (var _i = 0, references_1 = references; _i < references_1.length; _i++) {
100
+ var reference = references_1[_i];
101
+ if (!reference.value.startsWith('.')) {
102
+ continue;
103
+ }
104
+ var target = resolveImportFile(origin, reference.value, root);
105
+ if (target) {
106
+ resolved.add(target);
107
+ }
108
+ }
109
+ return Array.from(resolved);
110
+ };
111
+ var findPathToOrigin = function (start, origin, root) {
112
+ var nodesVisited = 0;
113
+ var visiting = new Set();
114
+ var deadEnds = new Set();
115
+ var walk = function (current, depth) {
116
+ if (depth > MAX_GRAPH_DEPTH) {
117
+ return null;
118
+ }
119
+ if (current === origin) {
120
+ return [origin];
121
+ }
122
+ if (deadEnds.has(current)) {
123
+ return null;
124
+ }
125
+ if (visiting.has(current)) {
126
+ return null;
127
+ }
128
+ nodesVisited += 1;
129
+ if (nodesVisited > MAX_GRAPH_NODES) {
130
+ return null;
131
+ }
132
+ visiting.add(current);
133
+ var neighbors = getResolvedImports(current, root);
134
+ for (var _i = 0, neighbors_1 = neighbors; _i < neighbors_1.length; _i++) {
135
+ var next = neighbors_1[_i];
136
+ var result = walk(next, depth + 1);
137
+ if (result) {
138
+ visiting.delete(current);
139
+ return __spreadArray([current], result, true);
140
+ }
141
+ }
142
+ visiting.delete(current);
143
+ deadEnds.add(current);
144
+ return null;
145
+ };
146
+ return walk(start, 0);
147
+ };
148
+ var getResolvedImports = function (filePath, root) {
149
+ var cached = resolvedImportCache.get(filePath);
150
+ if (cached) {
151
+ return cached;
152
+ }
153
+ var source = readFileSafe(filePath);
154
+ if (!source) {
155
+ resolvedImportCache.set(filePath, []);
156
+ return [];
157
+ }
158
+ var resolved = new Set();
159
+ var specifiers = extractImportSpecifiers(source);
160
+ for (var _i = 0, specifiers_1 = specifiers; _i < specifiers_1.length; _i++) {
161
+ var spec = specifiers_1[_i];
162
+ if (!spec.startsWith('.')) {
163
+ continue;
164
+ }
165
+ var target = resolveImportFile(filePath, spec, root);
166
+ if (target) {
167
+ resolved.add(target);
168
+ }
169
+ }
170
+ var list = Array.from(resolved);
171
+ resolvedImportCache.set(filePath, list);
172
+ return list;
173
+ };
174
+ var readFileSafe = function (filePath) {
175
+ try {
176
+ return fs_1.default.readFileSync(filePath, 'utf-8');
177
+ }
178
+ catch (_a) {
179
+ return null;
180
+ }
181
+ };
182
+ var extractImportSpecifiers = function (source) {
183
+ var results = [];
184
+ var patterns = [
185
+ /import\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
186
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
187
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
188
+ /export\s+[^'"]*from\s+['"]([^'"]+)['"]/g,
189
+ ];
190
+ for (var _i = 0, patterns_1 = patterns; _i < patterns_1.length; _i++) {
191
+ var pattern = patterns_1[_i];
192
+ var match = void 0;
193
+ while ((match = pattern.exec(source)) !== null) {
194
+ results.push(match[1]);
195
+ }
196
+ }
197
+ return results;
198
+ };
199
+ var resolveImportFile = function (fromFile, specifier, root) {
200
+ if (!specifier.startsWith('.')) {
201
+ return null;
202
+ }
203
+ var cleaned = specifier.split(/[?#]/)[0];
204
+ var basePath = path_1.default.resolve(path_1.default.dirname(fromFile), cleaned);
205
+ var resolved = resolveFile(basePath) ||
206
+ resolveWithExtensions(basePath) ||
207
+ resolveFromDirectory(basePath);
208
+ if (!resolved) {
209
+ return null;
210
+ }
211
+ if (!isWithinRoot(resolved, root)) {
212
+ return null;
213
+ }
214
+ if (resolved.endsWith('.d.ts')) {
215
+ return null;
216
+ }
217
+ return resolved;
218
+ };
219
+ var resolveFile = function (candidate) {
220
+ if (!fs_1.default.existsSync(candidate)) {
221
+ return null;
222
+ }
223
+ try {
224
+ if (fs_1.default.statSync(candidate).isFile()) {
225
+ return candidate;
226
+ }
227
+ }
228
+ catch (_a) {
229
+ return null;
230
+ }
231
+ return null;
232
+ };
233
+ var resolveWithExtensions = function (basePath) {
234
+ var ext = path_1.default.extname(basePath);
235
+ if (ext && SUPPORTED_EXTENSIONS.includes(ext)) {
236
+ return resolveFile(basePath);
237
+ }
238
+ for (var _i = 0, SUPPORTED_EXTENSIONS_1 = SUPPORTED_EXTENSIONS; _i < SUPPORTED_EXTENSIONS_1.length; _i++) {
239
+ var extension = SUPPORTED_EXTENSIONS_1[_i];
240
+ var candidate = "".concat(basePath).concat(extension);
241
+ var resolved = resolveFile(candidate);
242
+ if (resolved) {
243
+ return resolved;
244
+ }
245
+ }
246
+ return null;
247
+ };
248
+ var resolveFromDirectory = function (basePath) {
249
+ if (!fs_1.default.existsSync(basePath)) {
250
+ return null;
251
+ }
252
+ try {
253
+ if (!fs_1.default.statSync(basePath).isDirectory()) {
254
+ return null;
255
+ }
256
+ }
257
+ catch (_a) {
258
+ return null;
259
+ }
260
+ for (var _i = 0, SUPPORTED_EXTENSIONS_2 = SUPPORTED_EXTENSIONS; _i < SUPPORTED_EXTENSIONS_2.length; _i++) {
261
+ var extension = SUPPORTED_EXTENSIONS_2[_i];
262
+ var candidate = path_1.default.join(basePath, "index".concat(extension));
263
+ var resolved = resolveFile(candidate);
264
+ if (resolved) {
265
+ return resolved;
266
+ }
267
+ }
268
+ return null;
269
+ };
270
+ var buildCycleKey = function (cycle) {
271
+ if (cycle.length <= 2) {
272
+ return cycle.join('->');
273
+ }
274
+ var unique = cycle.slice(0, -1).map(function (entry) { return path_1.default.normalize(entry); });
275
+ var best = unique.join('->');
276
+ for (var index = 1; index < unique.length; index += 1) {
277
+ var rotated = unique.slice(index).concat(unique.slice(0, index));
278
+ var candidate = rotated.join('->');
279
+ if (candidate < best) {
280
+ best = candidate;
281
+ }
282
+ }
283
+ return best;
284
+ };
285
+ var formatCycle = function (cycle, root) {
286
+ var formatted = cycle.map(function (entry) {
287
+ var relative = path_1.default.relative(root, entry);
288
+ if (!relative || relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
289
+ return entry;
290
+ }
291
+ return relative;
292
+ });
293
+ return formatted.join(' -> ');
294
+ };
295
+ var buildLineIndex = function (source) {
296
+ var offsets = [0];
297
+ for (var index = 0; index < source.length; index += 1) {
298
+ if (source[index] === '\n') {
299
+ offsets.push(index + 1);
300
+ }
301
+ }
302
+ return { offsets: offsets };
303
+ };
304
+ var resolveLine = function (_a, position) {
305
+ var offsets = _a.offsets;
306
+ var low = 0;
307
+ var high = offsets.length - 1;
308
+ while (low <= high) {
309
+ var mid = Math.floor((low + high) / 2);
310
+ var current = offsets[mid];
311
+ if (current === position) {
312
+ return mid + 1;
313
+ }
314
+ if (current < position) {
315
+ low = mid + 1;
316
+ }
317
+ else {
318
+ high = mid - 1;
319
+ }
320
+ }
321
+ return high + 1;
322
+ };
323
+ var readUtf8Character = function (source, index, code) {
324
+ if (code <= 0x7f) {
325
+ return { bytes: 1, nextIndex: index + 1 };
326
+ }
327
+ if (code <= 0x7ff) {
328
+ return { bytes: 2, nextIndex: index + 1 };
329
+ }
330
+ if (code >= 0xd800 && code <= 0xdbff && index + 1 < source.length) {
331
+ var next = source.charCodeAt(index + 1);
332
+ if (next >= 0xdc00 && next <= 0xdfff) {
333
+ return { bytes: 4, nextIndex: index + 2 };
334
+ }
335
+ }
336
+ return { bytes: 3, nextIndex: index + 1 };
337
+ };
338
+ var bytePosToCharIndex = function (source, moduleStart, bytePos) {
339
+ var target = Math.max(bytePos - moduleStart, 0);
340
+ if (target === 0) {
341
+ return 0;
342
+ }
343
+ var index = 0;
344
+ var byteOffset = 0;
345
+ while (index < source.length) {
346
+ var code = source.charCodeAt(index);
347
+ var _a = readUtf8Character(source, index, code), bytes = _a.bytes, nextIndex = _a.nextIndex;
348
+ if (byteOffset + bytes > target) {
349
+ return index;
350
+ }
351
+ byteOffset += bytes;
352
+ index = nextIndex;
353
+ }
354
+ return source.length;
355
+ };
356
+ var findImportLine = function (source, value) {
357
+ var lines = source.split(/\r?\n/);
358
+ for (var index = 0; index < lines.length; index += 1) {
359
+ var line = lines[index];
360
+ if (line.includes('import') && line.includes(value)) {
361
+ return index + 1;
362
+ }
363
+ if (line.includes('require') && line.includes(value)) {
364
+ return index + 1;
365
+ }
366
+ }
367
+ return undefined;
368
+ };
369
+ var selectLineNumber = function (computed, fallback) {
370
+ if (fallback === undefined) {
371
+ return computed;
372
+ }
373
+ if (computed === undefined) {
374
+ return fallback;
375
+ }
376
+ if (computed < fallback) {
377
+ return fallback;
378
+ }
379
+ return computed;
380
+ };
@@ -3,4 +3,4 @@ export { RuleSeverity } from './types';
3
3
  export { defineRule, isRule, toRule } from './types';
4
4
  export { createRuleContext } from './runtime';
5
5
  export type { RuleContextOptions } from './runtime';
6
- export { builtinRules, noDeepRelativeImports, noSwallowedErrors } from './rules';
6
+ export { builtinRules, noDeepRelativeImports, noCircularDependencies, noSwallowedErrors } from './rules';
@@ -1,5 +1,6 @@
1
1
  import type { Rule } from '../types';
2
2
  import { noDeepRelativeImports } from './noDeepRelativeImports';
3
+ import { noCircularDependencies } from './noCircularDependencies';
3
4
  import { noSwallowedErrors } from './noSwallowedErrors';
4
5
  export declare const builtinRules: Rule[];
5
- export { noDeepRelativeImports, noSwallowedErrors };
6
+ export { noDeepRelativeImports, noCircularDependencies, noSwallowedErrors };
@@ -0,0 +1 @@
1
+ export declare const noCircularDependencies: import("../types").Rule;
@@ -42,6 +42,9 @@ export interface RuleMessages {
42
42
  maxDepth: number;
43
43
  }): string;
44
44
  swallowedError(): string;
45
+ circularDependency(params: {
46
+ chain: string;
47
+ }): string;
45
48
  }
46
49
  export interface RuleHelpers {
47
50
  readonly imports: ReadonlyArray<ImportReference>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-cr-rules",
3
- "version": "2.0.75",
3
+ "version": "2.0.76",
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",
@@ -40,6 +40,7 @@
40
40
  "@swc/types": "^0.1.25"
41
41
  },
42
42
  "devDependencies": {
43
+ "@types/node": "^25.0.9",
43
44
  "tslib": "^2.8.1",
44
45
  "typescript": "^5.9.2"
45
46
  }