@lsby/eslint-plugin 0.0.26 → 0.0.27

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,16 +4,16 @@ 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_else_on_equality_1 = __importDefault(require("./lib/rules/no-else-on-equality"));
8
7
  const no_negation_1 = __importDefault(require("./lib/rules/no-negation"));
9
8
  const no_switch_default_1 = __importDefault(require("./lib/rules/no-switch-default"));
10
9
  const prefer_let_1 = __importDefault(require("./lib/rules/prefer-let"));
10
+ const prefer_switch_for_literal_enum_1 = __importDefault(require("./lib/rules/prefer-switch-for-literal-enum"));
11
11
  module.exports = {
12
12
  rules: {
13
13
  'prefer-let': prefer_let_1.default,
14
14
  'no-negation': no_negation_1.default,
15
15
  'no-definite-assignment-assertion': no_definite_assignment_assertion_1.default,
16
- 'no-else-on-equality': no_else_on_equality_1.default,
16
+ 'prefer-switch-for-literal-enum': prefer_switch_for_literal_enum_1.default,
17
17
  'no-switch-default': no_switch_default_1.default,
18
18
  },
19
19
  };
@@ -1,17 +1,26 @@
1
1
  "use strict";
2
2
  // 永远使用let, 拒绝var和const, 并自动修复
3
- // 原因:
4
- // 1 抽象泄漏
5
- // const只约束原语值和指针不变, 不约束引用值本身不变
3
+ //
4
+ // # 原因:
5
+ // ## 抽象泄漏
6
+ // 使用const时, 通常我们**实际**想表达的意图是"该值不可变", 但const没有完全兑现这个意图
7
+ // 设计上, const只约束原语值和指针不变, 不约束引用值本身不变
6
8
  // 使用者要搞懂什么可变什么不可变, 必须理解原语值和引用值的区别, 以及变量在内存上的机制
7
9
  // 这种"必须搞懂背后的原理才能正常使用"的情况是显然的抽象泄漏
8
- // 2 误导
10
+ // ## 误导
9
11
  // 对于新人, 很容易被误解, 导致引用值被意外修改
10
- // 3 心智成本
12
+ // ## 心智成本
11
13
  // 实际使用时, 即使是const声明值, 也必须确认其是否为引用类型, 若是则还是需要确认所有可能的修改
12
14
  // 这与let无异, 反而增加了心智成本
13
- // **错误的信息比没有信息更糟糕**
14
- // 替代:
15
+ // ## 滑坡
16
+ // **错误的信息比没有信息更糟糕, 不完全的限制比没有限制更糟糕**
17
+ // 没有信息时人会自己去查, 而错误的信息会带来误导
18
+ // 没有限制时人会考虑边界, 不完全的限制则导致要么完全不考虑边界(因为认为有兜底), 要么更小心的考虑边界(区分哪些是兜底, 哪些是没有兜底的)
19
+ // 这不是加强培训能解决的, 人是不可信任和漏洞百出的, 必须通过形式约束才能让我们建立对代码的信心
20
+ // 让我们想想墨菲定律: 任何可能出错的事情最终一定会出错
21
+ // 如果我们放任使用这种很容易被误解的特性, 在大型项目中, 在足够长的时间后, 一定会出现我们不想看到的那种写法
22
+ //
23
+ // # 替代:
15
24
  // 如果真的需要表达不变, 应该使用类型等级的递归只读, 建模隐藏等方法
16
25
  // 这虽然复杂度更高, 但可以真正保证安全
17
26
  // 这也迫使程序员思考是否真的有必要这样设计, 而不是"随手一用", 提供一个"虚假的安全感"
@@ -1,3 +1,3 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- declare const rule: TSESLint.RuleModule<'noElse', []>;
2
+ declare const rule: TSESLint.RuleModule<'useSwitchForEnumLiteral', []>;
3
3
  export default rule;
@@ -0,0 +1,143 @@
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
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const ts = __importStar(require("typescript"));
39
+ const rule = {
40
+ meta: {
41
+ type: 'suggestion',
42
+ docs: { description: '禁止对字面量枚举进行 if 判断并使用 else,应该使用 switch 来穷尽所有情况.' },
43
+ messages: { useSwitchForEnumLiteral: '检测到对字面量枚举的 if-else 判断,建议改用 switch 来穷尽所有情况.' },
44
+ schema: [],
45
+ },
46
+ defaultOptions: [],
47
+ 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
+ return {
118
+ IfStatement(node) {
119
+ // 仅在 if 有 else 分支时检查
120
+ if (node.alternate === null) {
121
+ 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' });
138
+ }
139
+ },
140
+ };
141
+ },
142
+ };
143
+ exports.default = rule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsby/eslint-plugin",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -1,178 +0,0 @@
1
- "use strict";
2
- // 禁止对于等于/不等于条件的 else 处理多种状态
3
- // 因为对于这些条件, else 表示"除给定状态外的所有可能"
4
- // 对于状态小于等于两个的条件, else 只会兜底一种情况, 这是正确的
5
- // 但如果状态继续增加, else 兜底必然匹配一个以上种状态
6
- // 此时 else 部分会默默吃掉新增状态却无任何提示, 这很容易造成意外的状态逻辑遗漏
7
- // 这种情况下应当使用提早返回(early return)或 switch 穷尽
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- Object.defineProperty(exports, "__esModule", { value: true });
42
- const ts = __importStar(require("typescript"));
43
- const rule = {
44
- meta: {
45
- type: 'problem',
46
- docs: {
47
- description: '禁止在非二值状态的等于/不等于条件中使用 else. 当状态扩张时, else 会隐匿地处理新状态, 容易造成状态逻辑遗漏. ',
48
- },
49
- messages: {
50
- noElse: '禁止在非二值状态的等于/不等于条件中使用 else. 若状态在语义上是二值的(例如"是否为空"), 请先通过辅助变量表示为布尔值;若为多状态逻辑, 请使用提早返回(early return)或 switch 穷尽. ',
51
- },
52
- schema: [],
53
- },
54
- defaultOptions: [],
55
- create(context) {
56
- const parserServices = context.parserServices;
57
- const typeChecker = parserServices?.program?.getTypeChecker();
58
- /**
59
- * 计算类型中的字面量值个数
60
- */
61
- function countLiteralVariants(type) {
62
- // 先检查联合类型, 因为它可能包含其他标志
63
- if ((type.flags & ts.TypeFlags.Union) !== 0) {
64
- const union = type;
65
- // Count distinct types in union
66
- let count = 0;
67
- for (const t of union.types) {
68
- count += countLiteralVariants(t);
69
- }
70
- return count;
71
- }
72
- // 布尔类型有 2 个值:true 和 false
73
- if ((type.flags & ts.TypeFlags.Boolean) !== 0) {
74
- return 2;
75
- }
76
- // 布尔字面量类型(true 或 false)
77
- if ((type.flags & ts.TypeFlags.BooleanLiteral) !== 0) {
78
- return 1;
79
- }
80
- // null 类型(1 个值)
81
- if ((type.flags & ts.TypeFlags.Null) !== 0) {
82
- return 1;
83
- }
84
- // undefined 类型(1 个值)
85
- if ((type.flags & ts.TypeFlags.Undefined) !== 0) {
86
- return 1;
87
- }
88
- // 字面量类型
89
- if ((type.flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral)) !== 0) {
90
- return 1;
91
- }
92
- // 其他类型(如 number、string 等)认为是无限的
93
- return Infinity;
94
- }
95
- /**
96
- * 检查条件中是否包含等于或不等于操作符, 以及被比较的值
97
- * 返回 [operand1, operand2] 或 null
98
- */
99
- function getEqualityOperands(node) {
100
- if (node.type === 'BinaryExpression') {
101
- const binExpr = node;
102
- if (['===', '!==', '==', '!='].includes(binExpr.operator)) {
103
- return [binExpr.left, binExpr.right];
104
- }
105
- return null;
106
- }
107
- if (node.type === 'LogicalExpression') {
108
- const left = getEqualityOperands(node.left);
109
- if (left)
110
- return left;
111
- return getEqualityOperands(node.right);
112
- }
113
- return null;
114
- }
115
- /**
116
- * 检查是否应该允许 else(状态数 ≤ 2)
117
- * 返回 true 表示允许 else, false 表示不允许
118
- */
119
- function shouldAllowElse(leftExpr, rightExpr) {
120
- if (!typeChecker || !parserServices?.esTreeNodeToTSNodeMap) {
121
- // 无法获取类型信息时, 按原始规则:不允许 else
122
- return false;
123
- }
124
- try {
125
- // 获取 TypeScript 节点
126
- const leftNode = parserServices.esTreeNodeToTSNodeMap.get(leftExpr);
127
- const rightNode = parserServices.esTreeNodeToTSNodeMap.get(rightExpr);
128
- if (!leftNode || !rightNode) {
129
- // 无法映射节点时, 按原始规则:不允许 else
130
- return false;
131
- }
132
- // 获取两个操作数的类型
133
- let leftType = typeChecker.getTypeAtLocation(leftNode);
134
- let rightType = typeChecker.getTypeAtLocation(rightNode);
135
- // 对于标识符, 尝试获取声明的类型
136
- if (leftExpr.type === 'Identifier') {
137
- const leftSymbol = typeChecker.getSymbolAtLocation(leftNode);
138
- if (leftSymbol && leftSymbol.valueDeclaration) {
139
- leftType = typeChecker.getTypeOfSymbolAtLocation(leftSymbol, leftSymbol.valueDeclaration);
140
- }
141
- }
142
- if (rightExpr.type === 'Identifier') {
143
- const rightSymbol = typeChecker.getSymbolAtLocation(rightNode);
144
- if (rightSymbol && rightSymbol.valueDeclaration) {
145
- rightType = typeChecker.getTypeOfSymbolAtLocation(rightSymbol, rightSymbol.valueDeclaration);
146
- }
147
- }
148
- // 计算每边的字面量值个数
149
- const leftCount = countLiteralVariants(leftType);
150
- const rightCount = countLiteralVariants(rightType);
151
- // 只有当两边都是有限的且都 ≤ 2 个状态时, 才允许 else
152
- const bothFinite = isFinite(leftCount) && isFinite(rightCount);
153
- const allow = bothFinite && leftCount <= 2 && rightCount <= 2;
154
- return allow;
155
- }
156
- catch {
157
- // 如果出错, 按原始规则:不允许 else
158
- return false;
159
- }
160
- }
161
- return {
162
- IfStatement(node) {
163
- // 仅在 if 条件是等于或不等于的情况下, 禁止 else
164
- if (node.alternate !== null) {
165
- const operands = getEqualityOperands(node.test);
166
- if (operands) {
167
- const [left, right] = operands;
168
- // 检查是否应该允许 else(基于类型的字面量值个数)
169
- if (!shouldAllowElse(left, right)) {
170
- context.report({ node: node.alternate, messageId: 'noElse' });
171
- }
172
- }
173
- }
174
- },
175
- };
176
- },
177
- };
178
- exports.default = rule;