@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,3 +1,3 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- declare const rule: TSESLint.RuleModule<'useSwitchForEnumLiteral', []>;
2
+ declare const rule: TSESLint.RuleModule<'preferSwitch', []>;
3
3
  export default rule;
@@ -1,143 +1,87 @@
1
1
  "use strict";
2
- // 禁止对字面量枚举进行 if 判断并使用 else
3
- // 当判断字面量枚举时,应该使用 switch 来穷尽所有情况
4
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
- if (k2 === undefined) k2 = k;
6
- var desc = Object.getOwnPropertyDescriptor(m, k);
7
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
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 ts = __importStar(require("typescript"));
9
+ const typescript_1 = __importDefault(require("typescript"));
39
10
  const rule = {
40
11
  meta: {
41
12
  type: 'suggestion',
42
- docs: { description: '禁止对字面量枚举进行 if 判断并使用 else,应该使用 switch 来穷尽所有情况.' },
43
- messages: { useSwitchForEnumLiteral: '检测到对字面量枚举的 if-else 判断,建议改用 switch 来穷尽所有情况.' },
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
- // 仅在 if 有 else 分支时检查
120
- if (node.alternate === null) {
21
+ // 检查是否有最终的 else 分支(递归检查)
22
+ const hasFinalElse = hasFinalElseBranch(node);
23
+ if (!hasFinalElse)
121
24
  return;
122
- }
123
- // 检查是否是等于/不等于比较
124
- const operands = getEqualityOperands(node.test);
125
- if (!operands) {
126
- return;
127
- }
128
- const [left, right] = operands;
129
- // 尝试从两边获取类型,判断是否是字面量枚举
130
- const leftType = getComparisonVariableType(left);
131
- const rightType = getComparisonVariableType(right);
132
- if (leftType && isLiteralUnionType(leftType)) {
133
- context.report({ node: node.alternate, messageId: 'useSwitchForEnumLiteral' });
134
- return;
135
- }
136
- if (rightType && isLiteralUnionType(rightType)) {
137
- context.report({ node: node.alternate, messageId: 'useSwitchForEnumLiteral' });
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsby/eslint-plugin",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [