eslint-plugin-better-codes 1.0.1 → 1.0.4

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
@@ -51,6 +51,16 @@ module.exports = {
51
51
  ### function-with-try-catch
52
52
 
53
53
  强制函数必须包含 try-catch 语句块
54
+ - 普通函数
55
+ - 箭头函数
56
+ - 方法函数
57
+
58
+ 不校验的情况:
59
+ - React组件函数
60
+ - 函数体只有一行代码的函数
61
+ - 用于map/forEach中返回元素/组件用于渲染的箭头函数
62
+
63
+ <!-- TODO:待解决的问题:针对返回结果为true/false的函数,使用try...catch进入catch分支之后改返回false还是true还是不返回(undefined),因为按照之前的逻辑报错之后后续的代码不应再执行的,但是现在会直接继续走false/true的逻辑,造成逻辑错误 -->
54
64
 
55
65
  ## 许可证
56
66
 
@@ -3,89 +3,271 @@
3
3
  * 对于有函数体且包含语句的函数,如果没有 try/catch 报警
4
4
  */
5
5
  function containsTryStatement(node) {
6
- let found = false;
7
- const seen = new Set();
8
- function traverse(n) {
9
- if (!n || found) return;
10
- if (seen.has(n)) return;
11
- if (Array.isArray(n)) {
12
- for (const item of n) {
13
- traverse(item);
14
- if (found) return;
15
- }
16
- return;
17
- }
18
- if (typeof n !== "object") return;
19
- seen.add(n);
20
- if (n.type === "TryStatement") {
21
- found = true;
22
- return;
23
- }
24
- for (const key of Object.keys(n)) {
25
- if (found) break;
26
- const child = n[key];
27
- if (child && typeof child === "object") {
28
- traverse(child);
29
- }
30
- }
31
- }
32
- traverse(node);
33
- return found;
6
+ let found = false;
7
+ const seen = new Set();
8
+ function traverse(n) {
9
+ try {
10
+ if (!n || found) return;
11
+ if (seen.has(n)) return;
12
+ if (Array.isArray(n)) {
13
+ for (const item of n) {
14
+ traverse(item);
15
+ if (found) return;
16
+ }
17
+ return;
18
+ }
19
+ if (typeof n !== "object") return;
20
+ seen.add(n);
21
+ if (n.type === "TryStatement") {
22
+ found = true;
23
+ return;
24
+ }
25
+ for (const key of Object.keys(n)) {
26
+ if (found) break;
27
+ // 避免遍历父指针和非 AST 节点的额外属性,防止从一个函数遍历到其他函数
28
+ if (
29
+ key === "parent" ||
30
+ key === "loc" ||
31
+ key === "range" ||
32
+ key === "leadingComments" ||
33
+ key === "trailingComments" ||
34
+ key === "comments" ||
35
+ key === "tokens"
36
+ ) {
37
+ continue;
38
+ }
39
+ const child = n[key];
40
+ if (child && typeof child === "object") {
41
+ traverse(child);
42
+ }
43
+ }
44
+ } catch (err) {
45
+ console.error(err);
46
+ }
47
+ }
48
+ traverse(node);
49
+ return found;
34
50
  }
35
51
 
36
52
  // 判断是否为React组件函数
37
53
  function isReactComponentFunction(node) {
38
- return node.type === "FunctionDeclaration" && node.id && node.id.name === "render";
54
+ try {
55
+ let name;
56
+
57
+ // 尝试从节点自身或父节点取名(支持 const X = () => {})
58
+ if (
59
+ node.type === "FunctionDeclaration" ||
60
+ node.type === "FunctionExpression" ||
61
+ node.type === "ArrowFunctionExpression"
62
+ ) {
63
+ if (node.id && node.id.name) name = node.id.name;
64
+ else if (
65
+ node.parent &&
66
+ node.parent.type === "VariableDeclarator" &&
67
+ node.parent.id &&
68
+ node.parent.id.name
69
+ ) {
70
+ name = node.parent.id.name;
71
+ } else if (
72
+ node.parent &&
73
+ node.parent.type === "Property" &&
74
+ node.parent.key
75
+ ) {
76
+ name =
77
+ node.parent.key.name ||
78
+ (node.parent.key.value && String(node.parent.key.value));
79
+ }
80
+ } else if (node.type === "MethodDefinition" || node.type === "Property") {
81
+ name =
82
+ node.key &&
83
+ (node.key.name || (node.key.value && String(node.key.value)));
84
+ }
85
+ // 名字首字母大写视为组件
86
+ if (name && /^[A-Z]/.test(name)) return true;
87
+
88
+ // 检查是否包含或返回 JSX
89
+ function containsJSX(n, seen = new Set()) {
90
+ try {
91
+ if (!n || seen.has(n)) return false;
92
+ seen.add(n);
93
+ if (Array.isArray(n)) {
94
+ for (const it of n) if (containsJSX(it, seen)) return true;
95
+ return false;
96
+ }
97
+ if (typeof n !== "object") return false;
98
+ if (n.type === "JSXElement" || n.type === "JSXFragment") return true;
99
+ // 对于返回语句,直接检查返回的表达式
100
+ if (n.type === "ReturnStatement" && n.argument)
101
+ return containsJSX(n.argument, seen);
102
+ for (const k of Object.keys(n)) {
103
+ if (k === "parent" || k === "loc" || k === "range") continue;
104
+ if (containsJSX(n[k], seen)) return true;
105
+ }
106
+ return false;
107
+ } catch (err) {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ if (node.body && containsJSX(node.body)) return true;
113
+
114
+ // 检查是否被 React.memo/forwardRef 等包裹:父是 CallExpression,callee 为 Identifier 或 MemberExpression 包含 memo/forwardRef
115
+ let p = node.parent;
116
+ if (p && p.type === "CallExpression") {
117
+ const callee = p.callee;
118
+ if (callee) {
119
+ if (
120
+ callee.type === "Identifier" &&
121
+ /memo|forwardRef/i.test(callee.name)
122
+ )
123
+ return true;
124
+ if (
125
+ callee.type === "MemberExpression" &&
126
+ callee.property &&
127
+ /memo|forwardRef/i.test(callee.property.name)
128
+ )
129
+ return true;
130
+ }
131
+ }
132
+
133
+ return false;
134
+ } catch (err) {
135
+ return false;
136
+ }
39
137
  }
40
138
 
41
- module.exports = {
42
- meta: {
43
- type: "suggestion",
44
- docs: {
45
- description: "提醒函数内部应包含 try...catch 以便处理异常",
46
- category: "Best Practices",
47
- recommended: false,
48
- },
49
- schema: [],
50
- },
51
- create(context) {
52
- function reportIfMissingTry(node, name) {
53
- // 箭头函数表达式且为表达式体,跳过
54
- if (node.type === "ArrowFunctionExpression" && node.body && node.body.type !== "BlockStatement") {
55
- return;
56
- }
57
- const body = node.body;
58
- if (!body || body.type !== "BlockStatement") return;
59
- const stmts = body.body || [];
60
- // 如果函数体为空或只有注释/空,则跳过
61
- if (stmts.length === 0) return;
62
- // 如果函数体中包含 TryStatement,说明已处理,跳过
63
- if (containsTryStatement(body)) return;
64
- context.report({
65
- node,
66
- message: `${name || '函数'} 未包含 try...catch,建议添加错误处理`,
67
- });
68
- }
139
+ // 判断函数体是否只有一行
140
+ function isOnlyOneLine(body) {
141
+ try {
142
+ if (!body || body.type !== "BlockStatement") return false;
143
+ const stmts = body.body || [];
144
+ return stmts.length === 1 && stmts[0].type === "ExpressionStatement";
145
+ } catch (err) {
146
+ return false;
147
+ }
148
+ }
69
149
 
70
- return {
71
- FunctionDeclaration(node) {
72
- const name = node.id && node.id.name;
73
- reportIfMissingTry(node, name);
74
- },
75
- FunctionExpression(node) {
76
- const name = node.id && node.id.name;
77
- reportIfMissingTry(node, name);
78
- },
79
- ArrowFunctionExpression(node) {
80
- reportIfMissingTry(node, '箭头函数');
81
- },
82
- MethodDefinition(node) {
83
- // 跳过 constructor/get/set
84
- if (node.kind === 'constructor' || node.kind === 'get' || node.kind === 'set' || isReactComponentFunction(node)) return;
85
- const key = node.key && (node.key.name || (node.key.value && String(node.key.value)));
86
- if (node.value) reportIfMissingTry(node.value, key || '方法');
87
- },
88
- };
89
- },
90
- };
150
+ // 判断函数是否作为 array methods 的回调(如 map/forEach)直接返回用于渲染的元素/组件
151
+ function isArrayMethodCallback(node) {
152
+ try {
153
+ if (!node || !node.parent) return false;
154
+ // 父节点应为 CallExpression,且当前函数是该调用的一个参数
155
+ const p = node.parent;
156
+ if (p.type !== "CallExpression") return false;
157
+ const args = p.arguments || [];
158
+ if (!args.includes(node)) return false;
159
+ const callee = p.callee;
160
+ if (!callee) return false;
161
+ // 支持形如 data.map(...) 或 data?.map(...)
162
+ if (callee.type === "MemberExpression") {
163
+ const prop = callee.property;
164
+ const name = prop && (prop.name || (prop.value && String(prop.value)));
165
+ // 判断返回语句
166
+ const returnNode = node.body?.body?.at(-1);
167
+ if (
168
+ name &&
169
+ /^(map|forEach)$/i.test(name) &&
170
+ returnNode &&
171
+ returnNode.type === "ReturnStatement" &&
172
+ JSON.stringify(returnNode.argument?.raw || "").startsWith("<") &&
173
+ JSON.stringify(returnNode.argument?.raw || "").endsWith(">")
174
+ ) {
175
+ return true;
176
+ }
177
+ }
178
+
179
+ return false;
180
+ } catch (err) {
181
+ console.log(err);
182
+ }
183
+ }
91
184
 
185
+ module.exports = {
186
+ meta: {
187
+ type: "suggestion",
188
+ docs: {
189
+ description: "提醒函数内部应包含 try...catch 以便处理异常",
190
+ category: "Best Practices",
191
+ recommended: false,
192
+ },
193
+ schema: [],
194
+ },
195
+ create(context) {
196
+ function reportIfMissingTry(node) {
197
+ try {
198
+ // 箭头函数表达式且为表达式体,跳过
199
+ if (
200
+ (node.type === "ArrowFunctionExpression" &&
201
+ node.body &&
202
+ node.body.type !== "BlockStatement") ||
203
+ // 作为 React 组件/组件函数跳过
204
+ isReactComponentFunction(node) ||
205
+ // 只有一行表达式的函数跳过
206
+ isOnlyOneLine(node.body) ||
207
+ // 作为 array 方法回调(如 map/forEach)用于渲染时跳过
208
+ isArrayMethodCallback(node)
209
+ ) {
210
+ return;
211
+ }
212
+ const body = node.body;
213
+ if (!body || body.type !== "BlockStatement") return;
214
+ const stmts = body.body || [];
215
+ // 如果函数体为空或只有注释/空,则跳过
216
+ if (stmts.length === 0) return;
217
+ // 如果函数体中包含 TryStatement,说明已处理,跳过
218
+ if (containsTryStatement(body)) return;
219
+ context.report({
220
+ node,
221
+ message: `函数未包含 try...catch,建议添加错误处理`,
222
+ });
223
+ } catch (err) {
224
+ console.error(err);
225
+ }
226
+ }
227
+
228
+ return {
229
+ FunctionDeclaration(node) {
230
+ try {
231
+ const name = node.id && node.id.name;
232
+ reportIfMissingTry(node, name);
233
+ } catch (err) {
234
+ console.error(err);
235
+ }
236
+ },
237
+ FunctionExpression(node) {
238
+ try {
239
+ const name = node.id && node.id.name;
240
+ reportIfMissingTry(node, name);
241
+ } catch (err) {
242
+ console.error(err);
243
+ }
244
+ },
245
+ ArrowFunctionExpression(node) {
246
+ try {
247
+ const name = node.id && node.id.name;
248
+ reportIfMissingTry(node, name);
249
+ } catch (err) {
250
+ console.error(err);
251
+ }
252
+ },
253
+ MethodDefinition(node) {
254
+ try {
255
+ // 跳过 constructor/get/set
256
+ if (
257
+ node.kind === "constructor" ||
258
+ node.kind === "get" ||
259
+ node.kind === "set" ||
260
+ isReactComponentFunction(node)
261
+ )
262
+ return;
263
+ const key =
264
+ node.key &&
265
+ (node.key.name || (node.key.value && String(node.key.value)));
266
+ if (node.value) reportIfMissingTry(node.value, key || "方法");
267
+ } catch (err) {
268
+ console.error(err);
269
+ }
270
+ },
271
+ };
272
+ },
273
+ };
@@ -42,15 +42,19 @@ function isFunctionComment(comment, isBlock = false) {
42
42
  * @returns {Boolean} 是否为函数的功能注释
43
43
  */
44
44
  function isFuncComment(comment) {
45
- if (
46
- comment.includes("@param") ||
47
- comment.includes("@returns") ||
48
- comment.includes("@Description") ||
49
- comment.includes("@description") ||
50
- comment.includes("@comment") ||
51
- comment.includes("@Comment")
52
- ) {
53
- return true;
45
+ try {
46
+ if (
47
+ comment.includes("@param") ||
48
+ comment.includes("@returns") ||
49
+ comment.includes("@Description") ||
50
+ comment.includes("@description") ||
51
+ comment.includes("@comment") ||
52
+ comment.includes("@Comment")
53
+ ) {
54
+ return true;
55
+ }
56
+ } catch (err) {
57
+ console.error(err);
54
58
  }
55
59
  }
56
60
 
@@ -77,90 +81,94 @@ module.exports = {
77
81
  ],
78
82
  },
79
83
  create(context) {
80
- const sourceCode = context.getSourceCode();
81
- const options = context.options[0] || {};
82
- const minLines = options.minLines || 5;
83
- let ignoredCount = 0; // 忽略次数
84
- let singleLineCount = 0; // 单行注释行数 只计算最终实际校验生效的
85
- return {
86
- Program() {
87
- const comments = sourceCode.getAllComments();
88
- comments.forEach(comment => {
89
- if (comment.loc) {
90
- // 单行注释
91
- if (comment.type === "Line") {
92
- if (isFunctionComment(comment.value)) {
93
- return;
94
- }
95
- singleLineCount++;
84
+ try {
85
+ const sourceCode = context.getSourceCode();
86
+ const options = context.options[0] || {};
87
+ const minLines = options.minLines || 5;
88
+ let ignoredCount = 0; // 忽略次数
89
+ let singleLineCount = 0; // 单行注释行数 只计算最终实际校验生效的
90
+ return {
91
+ Program() {
92
+ const comments = sourceCode.getAllComments();
93
+ comments.forEach(comment => {
94
+ if (comment.loc) {
95
+ // 单行注释
96
+ if (comment.type === "Line") {
97
+ if (isFunctionComment(comment.value)) {
98
+ return;
99
+ }
100
+ singleLineCount++;
96
101
 
97
- const lines = comment.value.split("\n");
98
- // 过滤空行,只计算有实际内容的行
99
- const codeLines = lines.filter(line => {
100
- const trimmed = line.trim();
101
- return trimmed.length > 0;
102
- });
103
- const isIgnored = codeLines[0].endsWith("ignore-eslint"); // 因默认添加了ignore-eslint,进行忽略校验
104
- if (isIgnored && ignoredCount < minLines) {
105
- ignoredCount++;
106
- singleLineCount--;
107
- }
108
- // 优先检查忽略次数:当忽略累计次数达到阈值时,优先提示并禁止继续忽略
109
- if (ignoredCount >= minLines) {
110
- context.report({
111
- node: comment,
112
- message: `总忽略次数大于等于${minLines}次,请删除或保留为有效代码`,
113
- data: {
114
- lines: ignoredCount,
115
- },
116
- });
117
- return;
118
- }
119
- // 其次检查单行注释总数
120
- if (singleLineCount >= minLines) {
121
- context.report({
122
- node: comment,
123
- message: `单行注释大于等于 ${minLines} 行代码,请删除或保留为有效代码`,
124
- data: {
125
- lines: singleLineCount,
126
- },
127
- });
128
- return;
129
- }
130
- // 如果注释块包含多行代码逻辑
131
- if (codeLines.length >= minLines) {
132
- context.report({
133
- node: comment,
134
- message: `注释掉了 ${codeLines.length} 行代码,请删除或保留为有效代码`,
135
- data: {
136
- lines: codeLines.length,
137
- },
102
+ const lines = comment.value.split("\n");
103
+ // 过滤空行,只计算有实际内容的行
104
+ const codeLines = lines.filter(line => {
105
+ const trimmed = line.trim();
106
+ return trimmed.length > 0;
138
107
  });
108
+ const isIgnored = codeLines[0].endsWith("ignore-eslint"); // 因默认添加了ignore-eslint,进行忽略校验
109
+ if (isIgnored && ignoredCount < minLines) {
110
+ ignoredCount++;
111
+ singleLineCount--;
112
+ }
113
+ // 优先检查忽略次数:当忽略累计次数达到阈值时,优先提示并禁止继续忽略
114
+ if (ignoredCount >= minLines) {
115
+ context.report({
116
+ node: comment,
117
+ message: `总忽略次数大于等于${minLines}次,请删除或保留为有效代码`,
118
+ data: {
119
+ lines: ignoredCount,
120
+ },
121
+ });
122
+ return;
123
+ }
124
+ // 其次检查单行注释总数
125
+ if (singleLineCount >= minLines) {
126
+ context.report({
127
+ node: comment,
128
+ message: `单行注释大于等于 ${minLines} 行代码,请删除或保留为有效代码`,
129
+ data: {
130
+ lines: singleLineCount,
131
+ },
132
+ });
133
+ return;
134
+ }
135
+ // 如果注释块包含多行代码逻辑
136
+ if (codeLines.length >= minLines) {
137
+ context.report({
138
+ node: comment,
139
+ message: `注释掉了 ${codeLines.length} 行代码,请删除或保留为有效代码`,
140
+ data: {
141
+ lines: codeLines.length,
142
+ },
143
+ });
144
+ }
139
145
  }
140
- }
141
- // 多行注释
142
- if (comment.type === "Block") {
143
- if (isFunctionComment(comment.value, true)) {
144
- return;
145
- }
146
- const lines = comment.value.split("\n");
147
- const codeLines = lines.filter(line => {
148
- const trimmed = line.trim();
149
- return trimmed !== "*" && trimmed.length > 0 && trimmed; // 过滤掉空行和只包含*的行
150
- });
151
- if (codeLines.length >= minLines) {
152
- context.report({
153
- node: comment,
154
- message: `注释掉了 ${codeLines.length} 行代码,请删除或保留为有效代码`,
155
- data: {
156
- lines: codeLines.length,
157
- },
146
+ // 多行注释
147
+ if (comment.type === "Block") {
148
+ if (isFunctionComment(comment.value, true)) {
149
+ return;
150
+ }
151
+ const lines = comment.value.split("\n");
152
+ const codeLines = lines.filter(line => {
153
+ const trimmed = line.trim();
154
+ return trimmed !== "*" && trimmed.length > 0 && trimmed; // 过滤掉空行和只包含*的行
158
155
  });
156
+ if (codeLines.length >= minLines) {
157
+ context.report({
158
+ node: comment,
159
+ message: `注释掉了 ${codeLines.length} 行代码,请删除或保留为有效代码`,
160
+ data: {
161
+ lines: codeLines.length,
162
+ },
163
+ });
164
+ }
159
165
  }
160
166
  }
161
- }
162
- });
163
- },
164
- };
167
+ });
168
+ },
169
+ };
170
+ } catch (err) {
171
+ console.error(err);
172
+ }
165
173
  },
166
174
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-better-codes",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "npx eslint ./test.js"