@lsby/eslint-plugin 0.0.19 → 0.0.21
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
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const no_definite_assignment_assertion_1 = __importDefault(require("./lib/rules/no-definite-assignment-assertion"));
|
|
7
|
-
const no_early_return_on_equality_1 = __importDefault(require("./lib/rules/no-early-return-on-equality"));
|
|
8
7
|
const no_else_on_equality_1 = __importDefault(require("./lib/rules/no-else-on-equality"));
|
|
9
8
|
const no_negation_1 = __importDefault(require("./lib/rules/no-negation"));
|
|
10
9
|
const no_switch_default_1 = __importDefault(require("./lib/rules/no-switch-default"));
|
|
@@ -14,7 +13,6 @@ module.exports = {
|
|
|
14
13
|
'prefer-let': prefer_let_1.default,
|
|
15
14
|
'no-negation': no_negation_1.default,
|
|
16
15
|
'no-definite-assignment-assertion': no_definite_assignment_assertion_1.default,
|
|
17
|
-
'no-early-return-on-equality': no_early_return_on_equality_1.default,
|
|
18
16
|
'no-else-on-equality': no_else_on_equality_1.default,
|
|
19
17
|
'no-switch-default': no_switch_default_1.default,
|
|
20
18
|
},
|
|
@@ -1,37 +1,164 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// 禁止 else(当条件是等于或不等于时)
|
|
3
3
|
// 对于等于/不等于的条件判断,else 表示"除当前条件外的所有可能"
|
|
4
|
-
//
|
|
4
|
+
// 当状态集合未来扩展到多于2个时, else部分会默默吃掉新增状态却无任何提示
|
|
5
|
+
// 这很容易造成意外的状态遗漏
|
|
5
6
|
// 应当使用提早返回 或 switch + 穷尽检查
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
6
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const ts = __importStar(require("typescript"));
|
|
7
42
|
const rule = {
|
|
8
43
|
meta: {
|
|
9
44
|
type: 'problem',
|
|
10
45
|
docs: { description: '当条件是等于或不等于时,禁止使用 else 分支,防止隐含的状态遗漏' },
|
|
11
46
|
messages: {
|
|
12
|
-
noElse: '禁止在等于/不等于条件下使用 else
|
|
47
|
+
noElse: '禁止在等于/不等于条件下使用 else 分支。当条件状态扩展到多于2个时, else部分会默默吃掉新增状态却无任何提示。请改用提前返回(early return)或 switch 语句',
|
|
13
48
|
},
|
|
14
49
|
schema: [],
|
|
15
50
|
},
|
|
16
51
|
defaultOptions: [],
|
|
17
52
|
create(context) {
|
|
53
|
+
const parserServices = context.parserServices;
|
|
54
|
+
const typeChecker = parserServices?.program?.getTypeChecker();
|
|
55
|
+
/**
|
|
56
|
+
* 计算类型中的字面量值个数
|
|
57
|
+
*/
|
|
58
|
+
function countLiteralVariants(type) {
|
|
59
|
+
// 先检查联合类型,因为它可能包含其他标志
|
|
60
|
+
if ((type.flags & ts.TypeFlags.Union) !== 0) {
|
|
61
|
+
const union = type;
|
|
62
|
+
return union.types.length;
|
|
63
|
+
}
|
|
64
|
+
// 布尔类型有 2 个值:true 和 false
|
|
65
|
+
if ((type.flags & ts.TypeFlags.Boolean) !== 0) {
|
|
66
|
+
return 2;
|
|
67
|
+
}
|
|
68
|
+
// null 类型(1 个值)
|
|
69
|
+
if ((type.flags & ts.TypeFlags.Null) !== 0) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
// undefined 类型(1 个值)
|
|
73
|
+
if ((type.flags & ts.TypeFlags.Undefined) !== 0) {
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
// 字面量类型
|
|
77
|
+
if ((type.flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral)) !==
|
|
78
|
+
0) {
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
// 其他类型(如 number、string 等)认为是无限的
|
|
82
|
+
return Infinity;
|
|
83
|
+
}
|
|
18
84
|
/**
|
|
19
|
-
*
|
|
85
|
+
* 检查条件中是否包含等于或不等于操作符,以及被比较的值
|
|
86
|
+
* 返回 [operand1, operand2] 或 null
|
|
20
87
|
*/
|
|
21
|
-
function
|
|
88
|
+
function getEqualityOperands(node) {
|
|
22
89
|
if (node.type === 'BinaryExpression') {
|
|
23
|
-
|
|
90
|
+
const binExpr = node;
|
|
91
|
+
if (['===', '!==', '==', '!='].includes(binExpr.operator)) {
|
|
92
|
+
return [binExpr.left, binExpr.right];
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
24
95
|
}
|
|
25
96
|
if (node.type === 'LogicalExpression') {
|
|
26
|
-
|
|
97
|
+
const left = getEqualityOperands(node.left);
|
|
98
|
+
if (left)
|
|
99
|
+
return left;
|
|
100
|
+
return getEqualityOperands(node.right);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 检查是否应该允许 else(状态数 ≤ 2)
|
|
106
|
+
* 返回 true 表示允许 else,false 表示不允许
|
|
107
|
+
*/
|
|
108
|
+
function shouldAllowElse(leftExpr, rightExpr) {
|
|
109
|
+
if (!typeChecker || !parserServices?.esTreeNodeToTSNodeMap) {
|
|
110
|
+
// 无法获取类型信息时,按原始规则:不允许 else
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
// 获取 TypeScript 节点
|
|
115
|
+
const leftNode = parserServices.esTreeNodeToTSNodeMap.get(leftExpr);
|
|
116
|
+
const rightNode = parserServices.esTreeNodeToTSNodeMap.get(rightExpr);
|
|
117
|
+
if (!leftNode || !rightNode) {
|
|
118
|
+
// 无法映射节点时,按原始规则:不允许 else
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
// 获取两个操作数的类型
|
|
122
|
+
let leftType = typeChecker.getTypeAtLocation(leftNode);
|
|
123
|
+
let rightType = typeChecker.getTypeAtLocation(rightNode);
|
|
124
|
+
// 对于标识符,尝试获取声明的类型
|
|
125
|
+
if (leftExpr.type === 'Identifier') {
|
|
126
|
+
const leftSymbol = typeChecker.getSymbolAtLocation(leftNode);
|
|
127
|
+
if (leftSymbol && leftSymbol.valueDeclaration) {
|
|
128
|
+
leftType = typeChecker.getTypeOfSymbolAtLocation(leftSymbol, leftSymbol.valueDeclaration);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (rightExpr.type === 'Identifier') {
|
|
132
|
+
const rightSymbol = typeChecker.getSymbolAtLocation(rightNode);
|
|
133
|
+
if (rightSymbol && rightSymbol.valueDeclaration) {
|
|
134
|
+
rightType = typeChecker.getTypeOfSymbolAtLocation(rightSymbol, rightSymbol.valueDeclaration);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 计算每边的字面量值个数
|
|
138
|
+
const leftCount = countLiteralVariants(leftType);
|
|
139
|
+
const rightCount = countLiteralVariants(rightType);
|
|
140
|
+
// 只有当两边都是有限的且都 ≤ 2 个状态时,才允许 else
|
|
141
|
+
const bothFinite = isFinite(leftCount) && isFinite(rightCount);
|
|
142
|
+
const allow = bothFinite && leftCount <= 2 && rightCount <= 2;
|
|
143
|
+
return allow;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// 如果出错,按原始规则:不允许 else
|
|
147
|
+
return false;
|
|
27
148
|
}
|
|
28
|
-
return false;
|
|
29
149
|
}
|
|
30
150
|
return {
|
|
31
151
|
IfStatement(node) {
|
|
32
152
|
// 仅在 if 条件是等于或不等于的情况下,禁止 else
|
|
33
|
-
if (node.alternate !== null
|
|
34
|
-
|
|
153
|
+
if (node.alternate !== null) {
|
|
154
|
+
const operands = getEqualityOperands(node.test);
|
|
155
|
+
if (operands) {
|
|
156
|
+
const [left, right] = operands;
|
|
157
|
+
// 检查是否应该允许 else(基于类型的字面量值个数)
|
|
158
|
+
if (!shouldAllowElse(left, right)) {
|
|
159
|
+
context.report({ node: node.alternate, messageId: 'noElse' });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
35
162
|
}
|
|
36
163
|
},
|
|
37
164
|
};
|
package/package.json
CHANGED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// 禁止等于条件的if提前返回,但允许不等于条件的提前返回
|
|
3
|
-
// 使用等于条件的提前返回容易遗漏状态,应该用正向的不等于条件或结构化流程
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
const rule = {
|
|
6
|
-
meta: {
|
|
7
|
-
type: 'problem',
|
|
8
|
-
docs: {
|
|
9
|
-
description: '禁止在等于条件下使用提前返回(early return)。使用不等于条件的提前返回会更清晰地表达程序的主要流程',
|
|
10
|
-
},
|
|
11
|
-
messages: {
|
|
12
|
-
noEarlyReturnOnEquality: '禁止在等于条件下使用提前返回。建议改用不等于条件的提前返回,使主流程更清晰。例如:将 if (x === y) return; 改为 if (x !== y) { 主要逻辑 }',
|
|
13
|
-
},
|
|
14
|
-
schema: [],
|
|
15
|
-
},
|
|
16
|
-
defaultOptions: [],
|
|
17
|
-
create(context) {
|
|
18
|
-
/**
|
|
19
|
-
* 检查条件中是否是等于操作符(不包括不等于)
|
|
20
|
-
*/
|
|
21
|
-
function isEqualityOperator(node) {
|
|
22
|
-
if (node.type === 'BinaryExpression') {
|
|
23
|
-
return ['===', '=='].includes(node.operator);
|
|
24
|
-
}
|
|
25
|
-
if (node.type === 'LogicalExpression') {
|
|
26
|
-
return isEqualityOperator(node.left) || isEqualityOperator(node.right);
|
|
27
|
-
}
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* 检查语句是否是提前返回或抛出(仅包含这些语句)
|
|
32
|
-
*/
|
|
33
|
-
function isEarlyExit(node) {
|
|
34
|
-
if (!node)
|
|
35
|
-
return false;
|
|
36
|
-
// 直接是 return 或 throw
|
|
37
|
-
if (node.type === 'ReturnStatement' || node.type === 'ThrowStatement') {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
// 是 block statement,检查其中的所有语句是否都是 return 或 throw
|
|
41
|
-
if (node.type === 'BlockStatement') {
|
|
42
|
-
return (node.body.length > 0 &&
|
|
43
|
-
node.body.every((stmt) => stmt.type === 'ReturnStatement' || stmt.type === 'ThrowStatement'));
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
IfStatement(node) {
|
|
49
|
-
// 检查:条件是等于 & if 分支是提前返回 & 没有 else 分支
|
|
50
|
-
if (!node.alternate && isEqualityOperator(node.test) && isEarlyExit(node.consequent)) {
|
|
51
|
-
context.report({ node, messageId: 'noEarlyReturnOnEquality' });
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
exports.default = rule;
|