auto-cr-rules 2.0.89 → 2.0.96

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.
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeModule = void 0;
4
+ // 热路径定义:循环体 + 常见数组回调(map/forEach/...)的函数体。
5
+ var HOT_CALLBACK_METHODS = new Set([
6
+ 'map',
7
+ 'forEach',
8
+ 'reduce',
9
+ 'reduceRight',
10
+ 'filter',
11
+ 'some',
12
+ 'every',
13
+ 'find',
14
+ 'findIndex',
15
+ 'flatMap',
16
+ ]);
17
+ // 共享 AST 遍历入口:一次遍历同时抽取 imports/loops/callbacks/hotPath/tryStatements。
18
+ // 这样规则只需读取索引即可,避免每条规则重复扫 AST。
19
+ var analyzeModule = function (ast) {
20
+ var imports = [];
21
+ var loops = [];
22
+ var callbacks = [];
23
+ var tryStatements = [];
24
+ var hotPath = {
25
+ callExpressions: [],
26
+ newExpressions: [],
27
+ regExpLiterals: [],
28
+ };
29
+ var walk = function (node, inHot) {
30
+ var _a;
31
+ if (!node || typeof node !== 'object') {
32
+ return;
33
+ }
34
+ var candidate = node;
35
+ // 先收集与类型无关的索引,避免遗漏。
36
+ if (candidate.type === 'ImportDeclaration') {
37
+ var declaration = candidate;
38
+ imports.push({
39
+ kind: 'static',
40
+ value: declaration.source.value,
41
+ span: declaration.source.span,
42
+ });
43
+ // ImportDeclaration 不需要继续深挖(内部结构固定),可以提前返回。
44
+ return;
45
+ }
46
+ if (candidate.type === 'TryStatement') {
47
+ tryStatements.push(candidate);
48
+ }
49
+ // 热路径正则字面量只在热路径内收集,避免无关代码噪声。
50
+ if (candidate.type === 'RegExpLiteral' && inHot) {
51
+ hotPath.regExpLiterals.push(candidate);
52
+ }
53
+ switch (candidate.type) {
54
+ case 'ForStatement': {
55
+ var statement = candidate;
56
+ loops.push({ type: 'ForStatement', node: statement });
57
+ // for 循环条件与循环体都视为热路径。
58
+ walk(statement.init, inHot);
59
+ walk(statement.test, true);
60
+ walk(statement.update, true);
61
+ walk(statement.body, true);
62
+ return;
63
+ }
64
+ case 'WhileStatement': {
65
+ var statement = candidate;
66
+ loops.push({ type: 'WhileStatement', node: statement });
67
+ walk(statement.test, true);
68
+ walk(statement.body, true);
69
+ return;
70
+ }
71
+ case 'DoWhileStatement': {
72
+ var statement = candidate;
73
+ loops.push({ type: 'DoWhileStatement', node: statement });
74
+ walk(statement.body, true);
75
+ walk(statement.test, true);
76
+ return;
77
+ }
78
+ case 'ForInStatement':
79
+ case 'ForOfStatement': {
80
+ var statement = candidate;
81
+ loops.push({ type: statement.type, node: statement });
82
+ walk(statement.left, inHot);
83
+ walk(statement.right, inHot);
84
+ walk(statement.body, true);
85
+ return;
86
+ }
87
+ case 'CallExpression': {
88
+ // 统一在这里处理 import/require、热路径调用点、数组回调。
89
+ handleCallExpression(candidate, inHot, imports, callbacks, hotPath, walk);
90
+ return;
91
+ }
92
+ case 'NewExpression': {
93
+ var expression = candidate;
94
+ // new 表达式可能构造 RegExp,因此也纳入热路径集合。
95
+ if (inHot) {
96
+ hotPath.newExpressions.push(expression);
97
+ }
98
+ walk(expression.callee, inHot);
99
+ (_a = expression.arguments) === null || _a === void 0 ? void 0 : _a.forEach(function (argument) { return walk(argument.expression, inHot); });
100
+ return;
101
+ }
102
+ case 'OptionalChainingExpression': {
103
+ var expression = candidate;
104
+ walk(expression.base, inHot);
105
+ return;
106
+ }
107
+ case 'FunctionDeclaration':
108
+ case 'FunctionExpression':
109
+ case 'ArrowFunctionExpression': {
110
+ // 新函数不继承热路径,避免跨函数误标记。
111
+ walkFunctionBody(candidate, false, walk);
112
+ return;
113
+ }
114
+ default:
115
+ break;
116
+ }
117
+ // 通用遍历:对未知节点递归扫描子节点。
118
+ var record = candidate;
119
+ for (var _i = 0, _b = Object.values(record); _i < _b.length; _i++) {
120
+ var value = _b[_i];
121
+ if (Array.isArray(value)) {
122
+ for (var _c = 0, value_1 = value; _c < value_1.length; _c++) {
123
+ var item = value_1[_c];
124
+ walk(item, inHot);
125
+ }
126
+ }
127
+ else {
128
+ walk(value, inHot);
129
+ }
130
+ }
131
+ };
132
+ walk(ast, false);
133
+ // 对分析结果做 freeze,防止规则侧误修改。
134
+ return Object.freeze({
135
+ imports: Object.freeze(imports),
136
+ loops: Object.freeze(loops),
137
+ callbacks: Object.freeze(callbacks),
138
+ tryStatements: Object.freeze(tryStatements),
139
+ hotPath: Object.freeze({
140
+ callExpressions: Object.freeze(hotPath.callExpressions),
141
+ newExpressions: Object.freeze(hotPath.newExpressions),
142
+ regExpLiterals: Object.freeze(hotPath.regExpLiterals),
143
+ }),
144
+ });
145
+ };
146
+ exports.analyzeModule = analyzeModule;
147
+ var handleCallExpression = function (callExpression, inHot, imports, callbacks, hotPath, walk) {
148
+ if (inHot) {
149
+ hotPath.callExpressions.push(callExpression);
150
+ }
151
+ // 解析 import(...) / require(...),写入 import 索引。
152
+ var reference = extractImportReference(callExpression);
153
+ if (reference) {
154
+ imports.push(reference);
155
+ }
156
+ // 判断是否为数组高阶回调,回调函数体应当视为热路径。
157
+ var isHotCallback = isHotCallbackMethod(callExpression.callee);
158
+ walk(callExpression.callee, inHot);
159
+ if (!callExpression.arguments) {
160
+ return;
161
+ }
162
+ callExpression.arguments.forEach(function (argument, index) {
163
+ var expression = argument.expression;
164
+ // 约定数组回调的第一个参数是回调函数体,标记为热路径。
165
+ if (isHotCallback && index === 0 && isFunctionLike(expression)) {
166
+ callbacks.push({
167
+ method: getMemberMethodName(callExpression.callee),
168
+ callExpression: callExpression,
169
+ callback: expression,
170
+ });
171
+ // 回调函数体在热路径内执行,遍历时显式传入 true。
172
+ walkFunctionBody(expression, true, walk);
173
+ return;
174
+ }
175
+ walk(expression, inHot);
176
+ });
177
+ };
178
+ var walkFunctionBody = function (fn, inHot, walk) {
179
+ // 先遍历参数/装饰器等非 body 字段,保持 inHot=false,避免把定义期表达式算进热路径。
180
+ // 例如:默认参数表达式不应被当作热路径执行。
181
+ // TypeScript 对 AST 节点没有索引签名,这里先转为 unknown 再转 Record。
182
+ var record = fn;
183
+ for (var _i = 0, _a = Object.entries(record); _i < _a.length; _i++) {
184
+ var _b = _a[_i], key = _b[0], value = _b[1];
185
+ if (key === 'body') {
186
+ continue;
187
+ }
188
+ if (key === 'params' && Array.isArray(value)) {
189
+ for (var _c = 0, value_2 = value; _c < value_2.length; _c++) {
190
+ var param = value_2[_c];
191
+ walk(param, false);
192
+ }
193
+ continue;
194
+ }
195
+ walk(value, false);
196
+ }
197
+ // body 按当前规则标记热路径(回调函数体会传入 true)。
198
+ walk(fn.body, inHot);
199
+ };
200
+ var isFunctionLike = function (candidate) {
201
+ return candidate.type === 'FunctionExpression' || candidate.type === 'ArrowFunctionExpression';
202
+ };
203
+ // 判断是否为数组高阶方法(如 arr.map/arr.forEach)。
204
+ var isHotCallbackMethod = function (callee) {
205
+ if (!callee || typeof callee !== 'object') {
206
+ return false;
207
+ }
208
+ var candidate = callee;
209
+ if (candidate.type !== 'MemberExpression') {
210
+ return false;
211
+ }
212
+ var member = callee;
213
+ var property = member.property;
214
+ if (property.type === 'Identifier') {
215
+ return HOT_CALLBACK_METHODS.has(property.value);
216
+ }
217
+ if (property.type === 'Computed' && property.expression.type === 'StringLiteral') {
218
+ return HOT_CALLBACK_METHODS.has(property.expression.value);
219
+ }
220
+ return false;
221
+ };
222
+ // 提取形如 obj.method(...) 的方法名,用于回调索引。
223
+ var getMemberMethodName = function (expression) {
224
+ if (!expression || expression.type !== 'MemberExpression') {
225
+ return null;
226
+ }
227
+ var member = expression;
228
+ var property = member.property;
229
+ if (property.type === 'Identifier') {
230
+ return property.value;
231
+ }
232
+ if (property.type === 'Computed' && property.expression.type === 'StringLiteral') {
233
+ return property.expression.value;
234
+ }
235
+ return null;
236
+ };
237
+ // 从调用表达式中提取 import/require 的字符串字面量参数。
238
+ var extractImportReference = function (node) {
239
+ if (!node.arguments.length) {
240
+ return null;
241
+ }
242
+ var firstArgument = node.arguments[0];
243
+ if (!firstArgument || isSpread(firstArgument)) {
244
+ return null;
245
+ }
246
+ var literal = getStringLiteral(firstArgument);
247
+ if (!literal) {
248
+ return null;
249
+ }
250
+ if (node.callee.type === 'Import') {
251
+ return createImportReference('dynamic', literal);
252
+ }
253
+ if (isRequireIdentifier(node.callee) || isRequireMember(node.callee)) {
254
+ return createImportReference('require', literal);
255
+ }
256
+ return null;
257
+ };
258
+ var createImportReference = function (kind, literal) { return ({
259
+ kind: kind,
260
+ value: literal.value,
261
+ span: literal.span,
262
+ }); };
263
+ var isSpread = function (argument) { return Boolean(argument.spread); };
264
+ var getStringLiteral = function (argument) {
265
+ if (argument.expression.type === 'StringLiteral') {
266
+ return argument.expression;
267
+ }
268
+ return null;
269
+ };
270
+ var isRequireIdentifier = function (expression) {
271
+ return expression.type === 'Identifier' && expression.value === 'require';
272
+ };
273
+ var isRequireMember = function (expression) {
274
+ return (expression.type === 'MemberExpression' &&
275
+ expression.object.type === 'Identifier' &&
276
+ expression.object.value === 'require');
277
+ };
package/dist/messages.js CHANGED
@@ -13,6 +13,10 @@ var ruleTranslations = {
13
13
  var chain = _a.chain;
14
14
  return "\u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56: ".concat(chain);
15
15
  },
16
+ unresolvedImport: function (_a) {
17
+ var value = _a.value;
18
+ return "\u65E0\u6CD5\u89E3\u6790\u5BFC\u5165 \"".concat(value, "\"\uFF0C\u8BF7\u68C0\u67E5 tsconfig paths/baseUrl/rootDirs \u6216 package.json exports\u3002");
19
+ },
16
20
  noCatastrophicRegex: function (_a) {
17
21
  var pattern = _a.pattern;
18
22
  return "\u70ED\u8DEF\u5F84\u6B63\u5219\u5305\u542B\u5D4C\u5957\u7684\u65E0\u9650\u91CF\u8BCD\uFF0C\u53EF\u80FD\u5F15\u53D1\u707E\u96BE\u6027\u56DE\u6EAF: ".concat(pattern);
@@ -33,6 +37,10 @@ var ruleTranslations = {
33
37
  var chain = _a.chain;
34
38
  return "Circular dependency detected: ".concat(chain);
35
39
  },
40
+ unresolvedImport: function (_a) {
41
+ var value = _a.value;
42
+ return "Unable to resolve import \"".concat(value, "\". Check tsconfig paths/baseUrl/rootDirs or package.json exports.");
43
+ },
36
44
  noCatastrophicRegex: function (_a) {
37
45
  var pattern = _a.pattern;
38
46
  return "Regex in a hot path contains nested unbounded quantifiers and may trigger catastrophic backtracking: ".concat(pattern);
@@ -2,11 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noCatastrophicRegex = void 0;
4
4
  var types_1 = require("../types");
5
- var hotPath_1 = require("./utils/hotPath");
6
5
  // 检测热路径中可能引发灾难性回溯的嵌套无限量词正则。
7
6
  // 只处理字面量或静态字符串/模板字符串构造的 RegExp,忽略动态拼接。
8
7
  exports.noCatastrophicRegex = (0, types_1.defineRule)('no-catastrophic-regex', { tag: 'performance', severity: types_1.RuleSeverity.Optimizing }, function (_a) {
9
- var ast = _a.ast, helpers = _a.helpers, language = _a.language, messages = _a.messages;
8
+ var analysis = _a.analysis, helpers = _a.helpers, language = _a.language, messages = _a.messages;
10
9
  var suggestions = language === 'zh'
11
10
  ? [
12
11
  { text: '避免嵌套无限量词,给量词增加上限或使用更具体的匹配。' },
@@ -16,42 +15,45 @@ exports.noCatastrophicRegex = (0, types_1.defineRule)('no-catastrophic-regex', {
16
15
  { text: 'Avoid nested unbounded quantifiers by adding bounds or more specific tokens.' },
17
16
  { text: 'Split the regex into multiple passes or pre-filter before matching.' },
18
17
  ];
19
- (0, hotPath_1.walkHotPath)(ast, function (node, inHot) {
20
- if (!inHot || !node || typeof node !== 'object') {
21
- return;
18
+ // hotPath 索引已由共享遍历生成,这里直接遍历相关节点。
19
+ for (var _i = 0, _b = analysis.hotPath.regExpLiterals; _i < _b.length; _i++) {
20
+ var literal = _b[_i];
21
+ if (!hasNestedUnboundedQuantifier(literal.pattern)) {
22
+ continue;
22
23
  }
23
- var candidate = node;
24
- // 直接的 /.../ 字面量。
25
- if (candidate.type === 'RegExpLiteral') {
26
- var literal = candidate;
27
- if (!hasNestedUnboundedQuantifier(literal.pattern)) {
28
- return;
29
- }
30
- helpers.reportViolation({
31
- description: messages.noCatastrophicRegex({ pattern: literal.pattern }),
32
- code: literal.pattern,
33
- suggestions: suggestions,
34
- span: literal.span,
35
- }, literal.span);
36
- return;
24
+ helpers.reportViolation({
25
+ description: messages.noCatastrophicRegex({ pattern: literal.pattern }),
26
+ code: literal.pattern,
27
+ suggestions: suggestions,
28
+ span: literal.span,
29
+ }, literal.span);
30
+ }
31
+ for (var _c = 0, _d = analysis.hotPath.callExpressions; _c < _d.length; _c++) {
32
+ var callExpression = _d[_c];
33
+ var info = extractRegExpPattern(callExpression);
34
+ if (!info || !hasNestedUnboundedQuantifier(info.pattern)) {
35
+ continue;
37
36
  }
38
- // RegExp('...') 或 new RegExp('...') 的静态模式。
39
- if (candidate.type === 'CallExpression' || candidate.type === 'NewExpression') {
40
- var info = extractRegExpPattern(candidate);
41
- if (!info) {
42
- return;
43
- }
44
- if (!hasNestedUnboundedQuantifier(info.pattern)) {
45
- return;
46
- }
47
- helpers.reportViolation({
48
- description: messages.noCatastrophicRegex({ pattern: info.pattern }),
49
- code: info.pattern,
50
- suggestions: suggestions,
51
- span: info.span,
52
- }, info.span);
37
+ helpers.reportViolation({
38
+ description: messages.noCatastrophicRegex({ pattern: info.pattern }),
39
+ code: info.pattern,
40
+ suggestions: suggestions,
41
+ span: info.span,
42
+ }, info.span);
43
+ }
44
+ for (var _e = 0, _f = analysis.hotPath.newExpressions; _e < _f.length; _e++) {
45
+ var newExpression = _f[_e];
46
+ var info = extractRegExpPattern(newExpression);
47
+ if (!info || !hasNestedUnboundedQuantifier(info.pattern)) {
48
+ continue;
53
49
  }
54
- });
50
+ helpers.reportViolation({
51
+ description: messages.noCatastrophicRegex({ pattern: info.pattern }),
52
+ code: info.pattern,
53
+ suggestions: suggestions,
54
+ span: info.span,
55
+ }, info.span);
56
+ }
55
57
  });
56
58
  // 提取静态 RegExp 字符串,动态表达式直接跳过。
57
59
  var extractRegExpPattern = function (expression) {