@lsby/eslint-plugin 0.0.27 → 0.0.28
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.
|
@@ -1,143 +1,87 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// 禁止对字面量枚举进行 if
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
-
}
|
|
10
|
-
Object.defineProperty(o, k2, desc);
|
|
11
|
-
}) : (function(o, m, k, k2) {
|
|
12
|
-
if (k2 === undefined) k2 = k;
|
|
13
|
-
o[k2] = m[k];
|
|
14
|
-
}));
|
|
15
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
-
}) : function(o, v) {
|
|
18
|
-
o["default"] = v;
|
|
19
|
-
});
|
|
20
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
-
var ownKeys = function(o) {
|
|
22
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
-
var ar = [];
|
|
24
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
-
return ar;
|
|
26
|
-
};
|
|
27
|
-
return ownKeys(o);
|
|
28
|
-
};
|
|
29
|
-
return function (mod) {
|
|
30
|
-
if (mod && mod.__esModule) return mod;
|
|
31
|
-
var result = {};
|
|
32
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
-
__setModuleDefault(result, mod);
|
|
34
|
-
return result;
|
|
35
|
-
};
|
|
36
|
-
})();
|
|
2
|
+
// 禁止对字面量枚举进行 if-else 判断
|
|
3
|
+
// 因为一旦字面量状态扩充, else 就会默默吃掉新增的状态
|
|
4
|
+
// 当判断字面量枚举时, 应该使用 switch 来穷尽所有情况
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
37
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
const
|
|
9
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
39
10
|
const rule = {
|
|
40
11
|
meta: {
|
|
41
12
|
type: 'suggestion',
|
|
42
|
-
docs: { description: '禁止对字面量枚举进行 if
|
|
43
|
-
messages: {
|
|
13
|
+
docs: { description: '禁止对字面量枚举进行 if-else 判断,应使用 switch' },
|
|
14
|
+
messages: { preferSwitch: '对字面量联合类型应使用 switch 语句而不是 if-else,以确保穷尽所有分支' },
|
|
44
15
|
schema: [],
|
|
45
16
|
},
|
|
46
17
|
defaultOptions: [],
|
|
47
18
|
create(context) {
|
|
48
|
-
const parserServices = context.parserServices;
|
|
49
|
-
const typeChecker = parserServices?.program?.getTypeChecker();
|
|
50
|
-
/**
|
|
51
|
-
* 检查类型是否是字面量联合类型(枚举)
|
|
52
|
-
* 返回 true 如果是纯字面量联合类型,false 否则
|
|
53
|
-
* 排除布尔值类型(true | false 是语言的基本类型,不属于枚举)
|
|
54
|
-
*/
|
|
55
|
-
function isLiteralUnionType(type) {
|
|
56
|
-
if (!typeChecker)
|
|
57
|
-
return false;
|
|
58
|
-
// 如果是布尔类型或只包含布尔字面量,排除掉
|
|
59
|
-
if ((type.flags & ts.TypeFlags.Boolean) !== 0) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
// 检查是否是联合类型
|
|
63
|
-
if ((type.flags & ts.TypeFlags.Union) === 0) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
const union = type;
|
|
67
|
-
// 联合类型中必须至少有 2 个成员
|
|
68
|
-
if (union.types.length < 2) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
// 检查所有类型成员是否都是字面量类型(只接受字符串和数字字面量,排除布尔值)
|
|
72
|
-
for (const t of union.types) {
|
|
73
|
-
const isStringLiteral = (t.flags & ts.TypeFlags.StringLiteral) !== 0;
|
|
74
|
-
const isNumberLiteral = (t.flags & ts.TypeFlags.NumberLiteral) !== 0;
|
|
75
|
-
// 注意:不接受布尔字面量,布尔值是语言基本类型
|
|
76
|
-
if (!isStringLiteral && !isNumberLiteral) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* 获取被比较的变量的类型
|
|
84
|
-
*/
|
|
85
|
-
function getComparisonVariableType(expr) {
|
|
86
|
-
if (!typeChecker || !parserServices?.esTreeNodeToTSNodeMap) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(expr);
|
|
91
|
-
if (!tsNode)
|
|
92
|
-
return null;
|
|
93
|
-
if (expr.type === 'Identifier') {
|
|
94
|
-
const symbol = typeChecker.getSymbolAtLocation(tsNode);
|
|
95
|
-
if (symbol && symbol.valueDeclaration) {
|
|
96
|
-
return typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return typeChecker.getTypeAtLocation(tsNode);
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* 检查条件是否是简单的等于比较
|
|
107
|
-
*/
|
|
108
|
-
function getEqualityOperands(node) {
|
|
109
|
-
if (node.type === 'BinaryExpression') {
|
|
110
|
-
const binExpr = node;
|
|
111
|
-
if (['===', '!==', '==', '!='].includes(binExpr.operator)) {
|
|
112
|
-
return [binExpr.left, binExpr.right];
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
19
|
return {
|
|
118
20
|
IfStatement(node) {
|
|
119
|
-
//
|
|
120
|
-
|
|
21
|
+
// 检查是否有最终的 else 分支(递归检查)
|
|
22
|
+
const hasFinalElse = hasFinalElseBranch(node);
|
|
23
|
+
if (!hasFinalElse)
|
|
121
24
|
return;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
25
|
+
// 检查是否是字面量对比(test 是 BinaryExpression === 或 == 比较)
|
|
26
|
+
if (node.test.type === 'BinaryExpression' && (node.test.operator === '===' || node.test.operator === '==')) {
|
|
27
|
+
const { left, right } = node.test;
|
|
28
|
+
// 获取比较的变量(可能是左侧或右侧)
|
|
29
|
+
const variableNode = left.type === 'Identifier' ? left : right.type === 'Identifier' ? right : null;
|
|
30
|
+
if (!variableNode)
|
|
31
|
+
return;
|
|
32
|
+
// 使用 TypeScript 类型检查器检查类型
|
|
33
|
+
const parserServices = context.parserServices;
|
|
34
|
+
if (!parserServices || !parserServices.program)
|
|
35
|
+
return;
|
|
36
|
+
const typeChecker = parserServices.program.getTypeChecker();
|
|
37
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(variableNode);
|
|
38
|
+
const variableType = typeChecker.getTypeAtLocation(tsNode);
|
|
39
|
+
// 检查是否是字面量联合类型
|
|
40
|
+
const isLiteralUnion = isLiteralUnionType(variableType, typeChecker);
|
|
41
|
+
if (isLiteralUnion) {
|
|
42
|
+
context.report({ node, messageId: 'preferSwitch' });
|
|
43
|
+
}
|
|
138
44
|
}
|
|
139
45
|
},
|
|
140
46
|
};
|
|
141
47
|
},
|
|
142
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* 检查 if-else 链是否有最终的 else 分支
|
|
51
|
+
*/
|
|
52
|
+
function hasFinalElseBranch(node) {
|
|
53
|
+
let current = node;
|
|
54
|
+
while (current.type === 'IfStatement') {
|
|
55
|
+
if (current.alternate === null) {
|
|
56
|
+
// 没有 else 分支
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (current.alternate.type !== 'IfStatement') {
|
|
60
|
+
// 有最终的 else 分支
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// 继续检查 else if
|
|
64
|
+
current = current.alternate;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 检查类型是否是字面量联合类型
|
|
70
|
+
*/
|
|
71
|
+
function isLiteralUnionType(type, typeChecker) {
|
|
72
|
+
// 如果是联合类型,检查每个成员是否都是字面量类型
|
|
73
|
+
if (type.isUnion()) {
|
|
74
|
+
return type.types.every((memberType) => {
|
|
75
|
+
const flags = memberType.flags;
|
|
76
|
+
return ((flags & typescript_1.default.TypeFlags.StringLiteral) !== 0 ||
|
|
77
|
+
(flags & typescript_1.default.TypeFlags.NumberLiteral) !== 0 ||
|
|
78
|
+
(flags & typescript_1.default.TypeFlags.BooleanLiteral) !== 0);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// 如果是单个字面量类型,也认为是字面量联合(只有一个成员的联合)
|
|
82
|
+
const flags = type.flags;
|
|
83
|
+
return ((flags & typescript_1.default.TypeFlags.StringLiteral) !== 0 ||
|
|
84
|
+
(flags & typescript_1.default.TypeFlags.NumberLiteral) !== 0 ||
|
|
85
|
+
(flags & typescript_1.default.TypeFlags.BooleanLiteral) !== 0);
|
|
86
|
+
}
|
|
143
87
|
exports.default = rule;
|