eslint-plugin-better-codes 1.0.0 → 1.0.2

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,11 @@ module.exports = {
51
51
  ### function-with-try-catch
52
52
 
53
53
  强制函数必须包含 try-catch 语句块
54
+ - 普通函数
55
+ - 箭头函数
56
+ - 方法函数
57
+
58
+ 除去React组件函数
54
59
 
55
60
  ## 许可证
56
61
 
@@ -3,84 +3,220 @@
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
- module.exports = {
37
- meta: {
38
- type: "suggestion",
39
- docs: {
40
- description: "提醒函数内部应包含 try...catch 以便处理异常",
41
- category: "Best Practices",
42
- recommended: false,
43
- },
44
- schema: [],
45
- },
46
- create(context) {
47
- function reportIfMissingTry(node, name) {
48
- // 箭头函数表达式且为表达式体,跳过
49
- if (node.type === "ArrowFunctionExpression" && node.body && node.body.type !== "BlockStatement") {
50
- return;
51
- }
52
- const body = node.body;
53
- if (!body || body.type !== "BlockStatement") return;
54
- const stmts = body.body || [];
55
- // 如果函数体为空或只有注释/空,则跳过
56
- if (stmts.length === 0) return;
57
- // 如果函数体中包含 TryStatement,说明已处理,跳过
58
- if (containsTryStatement(body)) return;
59
- context.report({
60
- node,
61
- message: `${name || '函数'} 未包含 try...catch,建议添加错误处理`,
62
- });
63
- }
52
+ // 判断是否为React组件函数
53
+ function isReactComponentFunction(node) {
54
+ try {
55
+ let name;
64
56
 
65
- return {
66
- FunctionDeclaration(node) {
67
- const name = node.id && node.id.name;
68
- reportIfMissingTry(node, name);
69
- },
70
- FunctionExpression(node) {
71
- const name = node.id && node.id.name;
72
- reportIfMissingTry(node, name);
73
- },
74
- ArrowFunctionExpression(node) {
75
- reportIfMissingTry(node, '箭头函数');
76
- },
77
- MethodDefinition(node) {
78
- // 跳过 constructor/get/set
79
- if (node.kind === 'constructor' || node.kind === 'get' || node.kind === 'set') return;
80
- const key = node.key && (node.key.name || (node.key.value && String(node.key.value)));
81
- if (node.value) reportIfMissingTry(node.value, key || '方法');
82
- },
83
- };
84
- },
85
- };
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
+ }
86
132
 
133
+ return false;
134
+ } catch (err) {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ export default {
140
+ meta: {
141
+ type: "suggestion",
142
+ docs: {
143
+ description: "提醒函数内部应包含 try...catch 以便处理异常",
144
+ category: "Best Practices",
145
+ recommended: false,
146
+ },
147
+ schema: [],
148
+ },
149
+ create(context) {
150
+ function reportIfMissingTry(node) {
151
+ try {
152
+ // 箭头函数表达式且为表达式体,跳过
153
+ if (
154
+ (node.type === "ArrowFunctionExpression" &&
155
+ node.body &&
156
+ node.body.type !== "BlockStatement") ||
157
+ isReactComponentFunction(node)
158
+ ) {
159
+ return;
160
+ }
161
+ const body = node.body;
162
+ if (!body || body.type !== "BlockStatement") return;
163
+ const stmts = body.body || [];
164
+ // 如果函数体为空或只有注释/空,则跳过
165
+ if (stmts.length === 0) return;
166
+ // 如果函数体中包含 TryStatement,说明已处理,跳过
167
+ if (containsTryStatement(body)) return;
168
+ context.report({
169
+ node,
170
+ message: `函数未包含 try...catch,建议添加错误处理`,
171
+ });
172
+ } catch (err) {
173
+ console.error(err);
174
+ }
175
+ }
176
+
177
+ return {
178
+ FunctionDeclaration(node) {
179
+ try {
180
+ const name = node.id && node.id.name;
181
+ reportIfMissingTry(node, name);
182
+ } catch (err) {
183
+ console.error(err);
184
+ }
185
+ },
186
+ FunctionExpression(node) {
187
+ try {
188
+ const name = node.id && node.id.name;
189
+ reportIfMissingTry(node, name);
190
+ } catch (err) {
191
+ console.error(err);
192
+ }
193
+ },
194
+ ArrowFunctionExpression(node) {
195
+ try {
196
+ const name = node.id && node.id.name;
197
+ reportIfMissingTry(node, name);
198
+ } catch (err) {
199
+ console.error(err);
200
+ }
201
+ },
202
+ MethodDefinition(node) {
203
+ try {
204
+ // 跳过 constructor/get/set
205
+ if (
206
+ node.kind === "constructor" ||
207
+ node.kind === "get" ||
208
+ node.kind === "set" ||
209
+ isReactComponentFunction(node)
210
+ )
211
+ return;
212
+ const key =
213
+ node.key &&
214
+ (node.key.name || (node.key.value && String(node.key.value)));
215
+ if (node.value) reportIfMissingTry(node.value, key || "方法");
216
+ } catch (err) {
217
+ console.error(err);
218
+ }
219
+ },
220
+ };
221
+ },
222
+ };
@@ -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.0",
3
+ "version": "1.0.2",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "npx eslint ./test.js"