eslint-plugin-th-rules 3.3.0 → 3.3.1
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-explicit-nil-check.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-explicit-nil-check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAWtF,QAAA,MAAM,sBAAsB;;
|
|
1
|
+
{"version":3,"file":"prefer-explicit-nil-check.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-explicit-nil-check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAWtF,QAAA,MAAM,sBAAsB;;CAoW1B,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
|
|
@@ -12,11 +12,11 @@ const preferExplicitNilCheck = createRule({
|
|
|
12
12
|
type: 'problem',
|
|
13
13
|
fixable: 'code',
|
|
14
14
|
docs: {
|
|
15
|
-
description: 'Disallow implicit truthy/falsy checks
|
|
15
|
+
description: 'Disallow implicit truthy/falsy checks in boolean-test positions. Prefer explicit _.isNil(value) or _.isEmpty(value) (depending on the value type).',
|
|
16
16
|
},
|
|
17
17
|
schema: [],
|
|
18
18
|
messages: {
|
|
19
|
-
useIsNil: 'Implicit truthy/falsy checks are not allowed. Use _.isNil(value)
|
|
19
|
+
useIsNil: 'Implicit truthy/falsy checks are not allowed. Use _.isNil(value) / !_.isNil(value) or _.isEmpty(value) / !_.isEmpty(value).',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
defaultOptions: [],
|
|
@@ -41,12 +41,83 @@ const preferExplicitNilCheck = createRule({
|
|
|
41
41
|
}
|
|
42
42
|
return fixer.insertTextBefore(firstNode, importText);
|
|
43
43
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
44
|
+
function unwrapChainExpression(node) {
|
|
45
|
+
if (node.type === AST_NODE_TYPES.ChainExpression)
|
|
46
|
+
return node.expression;
|
|
47
|
+
return node;
|
|
48
|
+
}
|
|
49
|
+
function getTsType(node) {
|
|
50
|
+
const unwrapped = unwrapChainExpression(node);
|
|
51
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(unwrapped);
|
|
46
52
|
if (_.isNil(tsNode))
|
|
53
|
+
return null;
|
|
54
|
+
return checker.getTypeAtLocation(tsNode);
|
|
55
|
+
}
|
|
56
|
+
function isNullableFlag(flags) {
|
|
57
|
+
return (flags & ts.TypeFlags.Null) !== 0 || (flags & ts.TypeFlags.Undefined) !== 0;
|
|
58
|
+
}
|
|
59
|
+
function isStringLikeFlag(flags) {
|
|
60
|
+
return (flags & ts.TypeFlags.String) !== 0 || (flags & ts.TypeFlags.StringLiteral) !== 0;
|
|
61
|
+
}
|
|
62
|
+
function isNumberLikeFlag(flags) {
|
|
63
|
+
return (flags & ts.TypeFlags.Number) !== 0 || (flags & ts.TypeFlags.NumberLiteral) !== 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns true iff the expression type is effectively:
|
|
67
|
+
* string | null | undefined
|
|
68
|
+
* (i.e., all non-nullish constituents are string/string-literal).
|
|
69
|
+
*/
|
|
70
|
+
function isStringByTS(node) {
|
|
71
|
+
const type = getTsType(node);
|
|
72
|
+
if (_.isNil(type))
|
|
47
73
|
return false;
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
if (!type.isUnion()) {
|
|
75
|
+
const flags = type.getFlags();
|
|
76
|
+
return isStringLikeFlag(flags);
|
|
77
|
+
}
|
|
78
|
+
let sawNonNullish = false;
|
|
79
|
+
for (const t of type.types) {
|
|
80
|
+
const flags = t.getFlags();
|
|
81
|
+
if (isNullableFlag(flags))
|
|
82
|
+
continue;
|
|
83
|
+
sawNonNullish = true;
|
|
84
|
+
if (!isStringLikeFlag(flags))
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return sawNonNullish;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns true iff the expression type is effectively:
|
|
91
|
+
* number | null | undefined
|
|
92
|
+
*
|
|
93
|
+
* We intentionally DO NOT auto-fix numeric truthy/falsy checks because
|
|
94
|
+
* converting `if (n)` into `!_.isNil(n)` changes semantics for `0`.
|
|
95
|
+
*/
|
|
96
|
+
function isNumberByTS(node) {
|
|
97
|
+
const type = getTsType(node);
|
|
98
|
+
if (_.isNil(type))
|
|
99
|
+
return false;
|
|
100
|
+
if (!type.isUnion()) {
|
|
101
|
+
const flags = type.getFlags();
|
|
102
|
+
return isNumberLikeFlag(flags);
|
|
103
|
+
}
|
|
104
|
+
let sawNonNullish = false;
|
|
105
|
+
for (const t of type.types) {
|
|
106
|
+
const flags = t.getFlags();
|
|
107
|
+
if (isNullableFlag(flags))
|
|
108
|
+
continue;
|
|
109
|
+
sawNonNullish = true;
|
|
110
|
+
if (!isNumberLikeFlag(flags))
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return sawNonNullish;
|
|
114
|
+
}
|
|
115
|
+
function isBooleanByTS(node) {
|
|
116
|
+
const type = getTsType(node);
|
|
117
|
+
if (_.isNil(type))
|
|
118
|
+
return false;
|
|
119
|
+
const flags = type.getFlags();
|
|
120
|
+
return (flags & ts.TypeFlags.Boolean) !== 0 || (flags & ts.TypeFlags.BooleanLiteral) !== 0;
|
|
50
121
|
}
|
|
51
122
|
function isAlreadyExplicitCheck(node) {
|
|
52
123
|
return (node.type === AST_NODE_TYPES.CallExpression &&
|
|
@@ -62,8 +133,7 @@ const preferExplicitNilCheck = createRule({
|
|
|
62
133
|
if (node.type === AST_NODE_TYPES.MemberExpression)
|
|
63
134
|
return true;
|
|
64
135
|
if (node.type === AST_NODE_TYPES.ChainExpression) {
|
|
65
|
-
|
|
66
|
-
return inner.type === AST_NODE_TYPES.MemberExpression;
|
|
136
|
+
return node.expression.type === AST_NODE_TYPES.MemberExpression;
|
|
67
137
|
}
|
|
68
138
|
return false;
|
|
69
139
|
}
|
|
@@ -84,12 +154,24 @@ const preferExplicitNilCheck = createRule({
|
|
|
84
154
|
});
|
|
85
155
|
}
|
|
86
156
|
function transformTruthy(node) {
|
|
157
|
+
if (isNumberByTS(node))
|
|
158
|
+
return;
|
|
87
159
|
const text = context.sourceCode.getText(node);
|
|
160
|
+
if (isStringByTS(node)) {
|
|
161
|
+
reportFull(node, `!${LODASH_IDENT}.isEmpty(${text})`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
88
164
|
reportFull(node, `!${LODASH_IDENT}.isNil(${text})`);
|
|
89
165
|
}
|
|
90
166
|
function transformFalsyUnary(node) {
|
|
91
167
|
const arg = node.argument;
|
|
168
|
+
if (isNumberByTS(arg))
|
|
169
|
+
return;
|
|
92
170
|
const text = context.sourceCode.getText(arg);
|
|
171
|
+
if (isStringByTS(arg)) {
|
|
172
|
+
reportFull(node, `${LODASH_IDENT}.isEmpty(${text})`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
93
175
|
reportFull(node, `${LODASH_IDENT}.isNil(${text})`);
|
|
94
176
|
}
|
|
95
177
|
function expressionHasSideEffects(node) {
|
|
@@ -175,6 +257,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
175
257
|
if (isImplicitOperand(arg)) {
|
|
176
258
|
if (isBooleanByTS(arg))
|
|
177
259
|
return;
|
|
260
|
+
if (isNumberByTS(arg))
|
|
261
|
+
return;
|
|
178
262
|
transformFalsyUnary(node);
|
|
179
263
|
return;
|
|
180
264
|
}
|
|
@@ -189,6 +273,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
189
273
|
if (mode === 'test' && isImplicitOperand(node)) {
|
|
190
274
|
if (isBooleanByTS(node))
|
|
191
275
|
return;
|
|
276
|
+
if (isNumberByTS(node))
|
|
277
|
+
return;
|
|
192
278
|
transformTruthy(node);
|
|
193
279
|
}
|
|
194
280
|
}
|