eslint-plugin-svelte 2.8.0 → 2.9.0
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/README.md +3 -2
- package/lib/rules/@typescript-eslint/no-unnecessary-condition.d.ts +2 -0
- package/lib/rules/@typescript-eslint/no-unnecessary-condition.js +448 -0
- package/lib/rules/system.js +1 -1
- package/lib/types.d.ts +8 -2
- package/lib/utils/rules.js +2 -0
- package/lib/utils/ts-utils/index.d.ts +35 -0
- package/lib/utils/ts-utils/index.js +205 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ This plugin provides configs:
|
|
|
89
89
|
|
|
90
90
|
- `plugin:svelte/base` ... Configuration to enable correct Svelte parsing.
|
|
91
91
|
- `plugin:svelte/recommended` ... Above, plus rules to prevent errors or unintended behavior.
|
|
92
|
-
- `plugin:svelte/prettier` ...
|
|
92
|
+
- `plugin:svelte/prettier` ... Turns off rules that may conflict with [Prettier](https://prettier.io/) (You still need to configure prettier to work with svelte yourself, for example by using [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte).).
|
|
93
93
|
|
|
94
94
|
See [the rule list](https://ota-meshi.github.io/eslint-plugin-svelte/rules/) to get the `rules` that this plugin provides.
|
|
95
95
|
|
|
@@ -322,10 +322,11 @@ These rules relate to style guidelines, and are therefore quite subjective:
|
|
|
322
322
|
|
|
323
323
|
## Extension Rules
|
|
324
324
|
|
|
325
|
-
These rules extend the rules provided by ESLint itself to work well in Svelte:
|
|
325
|
+
These rules extend the rules provided by ESLint itself, or other plugins to work well in Svelte:
|
|
326
326
|
|
|
327
327
|
| Rule ID | Description | |
|
|
328
328
|
|:--------|:------------|:---|
|
|
329
|
+
| [svelte/@typescript-eslint/no-unnecessary-condition](https://ota-meshi.github.io/eslint-plugin-svelte/rules/@typescript-eslint/no-unnecessary-condition/) | disallow conditionals where the type is always truthy or always falsy | :wrench: |
|
|
329
330
|
| [svelte/no-inner-declarations](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-inner-declarations/) | disallow variable or `function` declarations in nested blocks | :star: |
|
|
330
331
|
| [svelte/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-trailing-spaces/) | disallow trailing whitespace at the end of lines | :wrench: |
|
|
331
332
|
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../../utils");
|
|
4
|
+
const ts_utils_1 = require("../../utils/ts-utils");
|
|
5
|
+
function unionTypeParts(type) {
|
|
6
|
+
return [...iterate(type)];
|
|
7
|
+
function* iterate(t) {
|
|
8
|
+
if (t.isUnion()) {
|
|
9
|
+
for (const type of t.types) {
|
|
10
|
+
yield* iterate(type);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
yield t;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function isPossiblyFalsy(type, tsTools) {
|
|
19
|
+
return (unionTypeParts(type)
|
|
20
|
+
.filter((t) => !(0, ts_utils_1.isTruthyLiteral)(t, tsTools))
|
|
21
|
+
.some((type) => (0, ts_utils_1.isPossiblyFalsyType)(type, tsTools.ts)));
|
|
22
|
+
}
|
|
23
|
+
function isPossiblyTruthy(type, tsTools) {
|
|
24
|
+
return unionTypeParts(type).some((type) => !(0, ts_utils_1.isFalsyType)(type, tsTools));
|
|
25
|
+
}
|
|
26
|
+
function isPossiblyNullish(type, tsTools) {
|
|
27
|
+
return (0, ts_utils_1.isNullableType)(type, tsTools.ts);
|
|
28
|
+
}
|
|
29
|
+
function isAlwaysNullish(type, tsTools) {
|
|
30
|
+
return (0, ts_utils_1.isNullishType)(type, tsTools.ts);
|
|
31
|
+
}
|
|
32
|
+
function isLiteral(type, tsTools) {
|
|
33
|
+
return ((0, ts_utils_1.isBooleanLiteralType)(type, tsTools.ts) ||
|
|
34
|
+
(0, ts_utils_1.isNullishType)(type, tsTools.ts) ||
|
|
35
|
+
type.isLiteral());
|
|
36
|
+
}
|
|
37
|
+
exports.default = (0, utils_1.createRule)("@typescript-eslint/no-unnecessary-condition", {
|
|
38
|
+
meta: {
|
|
39
|
+
docs: {
|
|
40
|
+
description: "disallow conditionals where the type is always truthy or always falsy",
|
|
41
|
+
category: "Extension Rules",
|
|
42
|
+
recommended: false,
|
|
43
|
+
extensionRule: {
|
|
44
|
+
plugin: "@typescript-eslint/eslint-plugin",
|
|
45
|
+
url: "https://typescript-eslint.io/rules/no-unnecessary-condition/",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
schema: [
|
|
49
|
+
{
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
allowConstantLoopConditions: {
|
|
53
|
+
description: "Whether to ignore constant loop conditions, such as `while (true)`.",
|
|
54
|
+
type: "boolean",
|
|
55
|
+
},
|
|
56
|
+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: {
|
|
57
|
+
description: "Whether to not error when running with a tsconfig that has strictNullChecks turned.",
|
|
58
|
+
type: "boolean",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
fixable: "code",
|
|
65
|
+
messages: {
|
|
66
|
+
alwaysTruthy: "Unnecessary conditional, value is always truthy.",
|
|
67
|
+
alwaysFalsy: "Unnecessary conditional, value is always falsy.",
|
|
68
|
+
alwaysTruthyFunc: "This callback should return a conditional, but return is always truthy.",
|
|
69
|
+
alwaysFalsyFunc: "This callback should return a conditional, but return is always falsy.",
|
|
70
|
+
neverNullish: "Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.",
|
|
71
|
+
alwaysNullish: "Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`.",
|
|
72
|
+
literalBooleanExpression: "Unnecessary conditional, both sides of the expression are literal values.",
|
|
73
|
+
noOverlapBooleanExpression: "Unnecessary conditional, the types have no overlap.",
|
|
74
|
+
never: "Unnecessary conditional, value is `never`.",
|
|
75
|
+
neverOptionalChain: "Unnecessary optional chain on a non-nullish value.",
|
|
76
|
+
noStrictNullCheck: "This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.",
|
|
77
|
+
},
|
|
78
|
+
type: "suggestion",
|
|
79
|
+
},
|
|
80
|
+
create(context) {
|
|
81
|
+
const { allowConstantLoopConditions = false, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false, } = (context.options[0] || {});
|
|
82
|
+
const tools = (0, ts_utils_1.getTypeScriptTools)(context);
|
|
83
|
+
if (!tools) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
const { service, ts } = tools;
|
|
87
|
+
const checker = service.program.getTypeChecker();
|
|
88
|
+
const sourceCode = context.getSourceCode();
|
|
89
|
+
const compilerOptions = service.program.getCompilerOptions();
|
|
90
|
+
const isStrictNullChecks = compilerOptions.strict
|
|
91
|
+
? compilerOptions.strictNullChecks !== false
|
|
92
|
+
: compilerOptions.strictNullChecks;
|
|
93
|
+
if (!isStrictNullChecks &&
|
|
94
|
+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true) {
|
|
95
|
+
context.report({
|
|
96
|
+
loc: {
|
|
97
|
+
start: { line: 0, column: 0 },
|
|
98
|
+
end: { line: 0, column: 0 },
|
|
99
|
+
},
|
|
100
|
+
messageId: "noStrictNullCheck",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const mutableVarReferenceIds = [];
|
|
104
|
+
const scriptElements = [];
|
|
105
|
+
let inSvelteReactiveStatement = false;
|
|
106
|
+
for (const scope of [
|
|
107
|
+
sourceCode.scopeManager.globalScope,
|
|
108
|
+
sourceCode.scopeManager.globalScope?.childScopes.find((scope) => scope.type === "module"),
|
|
109
|
+
]) {
|
|
110
|
+
if (!scope)
|
|
111
|
+
continue;
|
|
112
|
+
for (const variable of scope.variables) {
|
|
113
|
+
if (variable.defs.some((def) => def.type === "Variable" &&
|
|
114
|
+
(def.parent.kind === "var" || def.parent.kind === "let"))) {
|
|
115
|
+
for (const reference of variable.references) {
|
|
116
|
+
mutableVarReferenceIds.push(reference.identifier);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const body of sourceCode.ast.body) {
|
|
122
|
+
if (body.type === "SvelteScriptElement") {
|
|
123
|
+
scriptElements.push(body);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function hasSvelteReactiveVar(node) {
|
|
127
|
+
const inReactiveScope = inSvelteReactiveStatement ||
|
|
128
|
+
(scriptElements.length &&
|
|
129
|
+
scriptElements.every((elem) => node.range[1] <= elem.range[0] || elem.range[1] <= node.range[0]));
|
|
130
|
+
if (!inReactiveScope) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return mutableVarReferenceIds.some((id) => node.range[0] <= id.range[0] && id.range[1] <= node.range[1]);
|
|
134
|
+
}
|
|
135
|
+
function getNodeType(node) {
|
|
136
|
+
const tsNode = service.esTreeNodeToTSNodeMap.get(node);
|
|
137
|
+
return tsNode && (0, ts_utils_1.getConstrainedTypeAtLocation)(checker, tsNode);
|
|
138
|
+
}
|
|
139
|
+
function nodeIsArrayType(node) {
|
|
140
|
+
const nodeType = getNodeType(node);
|
|
141
|
+
if (!nodeType) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return checker.isArrayType(nodeType);
|
|
145
|
+
}
|
|
146
|
+
function nodeIsTupleType(node) {
|
|
147
|
+
const nodeType = getNodeType(node);
|
|
148
|
+
return Boolean(nodeType && (0, ts_utils_1.isTupleType)(nodeType, ts));
|
|
149
|
+
}
|
|
150
|
+
function isArrayIndexExpression(node) {
|
|
151
|
+
return (node.type === "MemberExpression" &&
|
|
152
|
+
node.computed &&
|
|
153
|
+
(nodeIsArrayType(node.object) ||
|
|
154
|
+
(nodeIsTupleType(node.object) &&
|
|
155
|
+
node.property.type !== "Literal")));
|
|
156
|
+
}
|
|
157
|
+
function checkNode(node, isUnaryNotArgument = false) {
|
|
158
|
+
if (hasSvelteReactiveVar(node)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (node.type === "UnaryExpression" && node.operator === "!") {
|
|
162
|
+
checkNode(node.argument, true);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (isArrayIndexExpression(node)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (node.type === "LogicalExpression" && node.operator !== "??") {
|
|
169
|
+
checkNode(node.right);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const type = getNodeType(node);
|
|
173
|
+
if (!type ||
|
|
174
|
+
unionTypeParts(type).some((part) => (0, ts_utils_1.isAnyType)(part, ts) ||
|
|
175
|
+
(0, ts_utils_1.isUnknownType)(part, ts) ||
|
|
176
|
+
part.isTypeParameter())) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
let messageId = null;
|
|
180
|
+
if (unionTypeParts(type).some((part) => (0, ts_utils_1.isNeverType)(part, ts))) {
|
|
181
|
+
messageId = "never";
|
|
182
|
+
}
|
|
183
|
+
else if (!isPossiblyTruthy(type, tools)) {
|
|
184
|
+
messageId = !isUnaryNotArgument ? "alwaysFalsy" : "alwaysTruthy";
|
|
185
|
+
}
|
|
186
|
+
else if (!isPossiblyFalsy(type, tools)) {
|
|
187
|
+
messageId = !isUnaryNotArgument ? "alwaysTruthy" : "alwaysFalsy";
|
|
188
|
+
}
|
|
189
|
+
if (messageId) {
|
|
190
|
+
context.report({ node, messageId });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function checkNodeForNullish(node) {
|
|
194
|
+
if (hasSvelteReactiveVar(node)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const type = getNodeType(node);
|
|
198
|
+
if (!type || (0, ts_utils_1.isAnyType)(type, ts) || (0, ts_utils_1.isUnknownType)(type, ts)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let messageId = null;
|
|
202
|
+
if (unionTypeParts(type).some((part) => (0, ts_utils_1.isNeverType)(part, ts))) {
|
|
203
|
+
messageId = "never";
|
|
204
|
+
}
|
|
205
|
+
else if (!isPossiblyNullish(type, tools)) {
|
|
206
|
+
if (!isArrayIndexExpression(node) &&
|
|
207
|
+
!(node.type === "ChainExpression" &&
|
|
208
|
+
node.expression.type !== "TSNonNullExpression" &&
|
|
209
|
+
optionChainContainsOptionArrayIndex(node.expression))) {
|
|
210
|
+
messageId = "neverNullish";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (isAlwaysNullish(type, tools)) {
|
|
214
|
+
messageId = "alwaysNullish";
|
|
215
|
+
}
|
|
216
|
+
if (messageId) {
|
|
217
|
+
context.report({ node, messageId });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const BOOL_OPERATORS = new Set([
|
|
221
|
+
"<",
|
|
222
|
+
">",
|
|
223
|
+
"<=",
|
|
224
|
+
">=",
|
|
225
|
+
"==",
|
|
226
|
+
"===",
|
|
227
|
+
"!=",
|
|
228
|
+
"!==",
|
|
229
|
+
]);
|
|
230
|
+
function checkIfBinaryExpressionIsNecessaryConditional(node) {
|
|
231
|
+
if (hasSvelteReactiveVar(node)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!BOOL_OPERATORS.has(node.operator)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const leftType = getNodeType(node.left);
|
|
238
|
+
const rightType = getNodeType(node.right);
|
|
239
|
+
if (!leftType || !rightType) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (isLiteral(leftType, tools) && isLiteral(rightType, tools)) {
|
|
243
|
+
context.report({ node, messageId: "literalBooleanExpression" });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (isStrictNullChecks) {
|
|
247
|
+
const UNDEFINED = ts.TypeFlags.Undefined;
|
|
248
|
+
const NULL = ts.TypeFlags.Null;
|
|
249
|
+
const isComparable = (type, f) => {
|
|
250
|
+
let flag = f;
|
|
251
|
+
flag |=
|
|
252
|
+
ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter;
|
|
253
|
+
if (node.operator === "==" || node.operator === "!=") {
|
|
254
|
+
flag |= NULL | UNDEFINED;
|
|
255
|
+
}
|
|
256
|
+
return unionTypeParts(type).some((t) => (t.flags & flag) !== 0);
|
|
257
|
+
};
|
|
258
|
+
if ((leftType.flags === UNDEFINED &&
|
|
259
|
+
!isComparable(rightType, UNDEFINED)) ||
|
|
260
|
+
(rightType.flags === UNDEFINED &&
|
|
261
|
+
!isComparable(leftType, UNDEFINED)) ||
|
|
262
|
+
(leftType.flags === NULL && !isComparable(rightType, NULL)) ||
|
|
263
|
+
(rightType.flags === NULL && !isComparable(leftType, NULL))) {
|
|
264
|
+
context.report({ node, messageId: "noOverlapBooleanExpression" });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function checkLogicalExpressionForUnnecessaryConditionals(node) {
|
|
269
|
+
if (node.operator === "??") {
|
|
270
|
+
checkNodeForNullish(node.left);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
checkNode(node.left);
|
|
274
|
+
}
|
|
275
|
+
function checkIfLoopIsNecessaryConditional(node) {
|
|
276
|
+
if (node.test === null) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (allowConstantLoopConditions) {
|
|
280
|
+
const nodeType = getNodeType(node.test);
|
|
281
|
+
if (nodeType &&
|
|
282
|
+
(0, ts_utils_1.isBooleanLiteralType)(nodeType, ts) &&
|
|
283
|
+
checker.typeToString(nodeType) === "true")
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
checkNode(node.test);
|
|
287
|
+
}
|
|
288
|
+
const ARRAY_PREDICATE_FUNCTIONS = new Set([
|
|
289
|
+
"filter",
|
|
290
|
+
"find",
|
|
291
|
+
"some",
|
|
292
|
+
"every",
|
|
293
|
+
]);
|
|
294
|
+
function isArrayPredicateFunction(node) {
|
|
295
|
+
const { callee } = node;
|
|
296
|
+
return (callee.type === "MemberExpression" &&
|
|
297
|
+
callee.property.type === "Identifier" &&
|
|
298
|
+
ARRAY_PREDICATE_FUNCTIONS.has(callee.property.name) &&
|
|
299
|
+
(nodeIsArrayType(callee.object) || nodeIsTupleType(callee.object)));
|
|
300
|
+
}
|
|
301
|
+
function checkCallExpression(node) {
|
|
302
|
+
if (isArrayPredicateFunction(node) && node.arguments.length) {
|
|
303
|
+
const callback = node.arguments[0];
|
|
304
|
+
if ((callback.type === "ArrowFunctionExpression" ||
|
|
305
|
+
callback.type === "FunctionExpression") &&
|
|
306
|
+
callback.body) {
|
|
307
|
+
if (callback.body.type !== "BlockStatement") {
|
|
308
|
+
checkNode(callback.body);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const callbackBody = callback.body.body;
|
|
312
|
+
if (callbackBody.length === 1 &&
|
|
313
|
+
callbackBody[0].type === "ReturnStatement" &&
|
|
314
|
+
callbackBody[0].argument) {
|
|
315
|
+
checkNode(callbackBody[0].argument);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const nodeType = getNodeType(callback);
|
|
320
|
+
if (!nodeType) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const returnTypes = (0, ts_utils_1.getCallSignaturesOfType)(nodeType).map((sig) => sig.getReturnType());
|
|
324
|
+
if (returnTypes.length === 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (returnTypes.some((t) => (0, ts_utils_1.isAnyType)(t, ts) || (0, ts_utils_1.isUnknownType)(t, ts))) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (!returnTypes.some((t) => isPossiblyFalsy(t, tools))) {
|
|
331
|
+
context.report({
|
|
332
|
+
node: callback,
|
|
333
|
+
messageId: "alwaysTruthyFunc",
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!returnTypes.some((t) => isPossiblyTruthy(t, tools))) {
|
|
338
|
+
context.report({
|
|
339
|
+
node: callback,
|
|
340
|
+
messageId: "alwaysFalsyFunc",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function optionChainContainsOptionArrayIndex(node) {
|
|
346
|
+
const lhsNode = node.type === "CallExpression" ? node.callee : node.object;
|
|
347
|
+
if (node.optional && isArrayIndexExpression(lhsNode)) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
if (lhsNode.type === "MemberExpression" ||
|
|
351
|
+
lhsNode.type === "CallExpression") {
|
|
352
|
+
return optionChainContainsOptionArrayIndex(lhsNode);
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
function isNullablePropertyType(objType, propertyType) {
|
|
357
|
+
if (propertyType.isUnion()) {
|
|
358
|
+
return propertyType.types.some((type) => isNullablePropertyType(objType, type));
|
|
359
|
+
}
|
|
360
|
+
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
|
|
361
|
+
const propType = (0, ts_utils_1.getTypeOfPropertyOfType)(objType, propertyType.value.toString(), checker);
|
|
362
|
+
if (propType) {
|
|
363
|
+
return (0, ts_utils_1.isNullableType)(propType, ts);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const typeName = (0, ts_utils_1.getTypeName)(propertyType, tools);
|
|
367
|
+
return Boolean((typeName === "string" &&
|
|
368
|
+
checker.getIndexInfoOfType(objType, ts.IndexKind.String)) ||
|
|
369
|
+
(typeName === "number" &&
|
|
370
|
+
checker.getIndexInfoOfType(objType, ts.IndexKind.Number)));
|
|
371
|
+
}
|
|
372
|
+
function isNullableOriginFromPrev(node) {
|
|
373
|
+
const prevType = getNodeType(node.object);
|
|
374
|
+
const property = node.property;
|
|
375
|
+
if (prevType && prevType.isUnion() && property.type === "Identifier") {
|
|
376
|
+
const isOwnNullable = prevType.types.some((type) => {
|
|
377
|
+
if (node.computed) {
|
|
378
|
+
const propertyType = getNodeType(node.property);
|
|
379
|
+
return Boolean(propertyType && isNullablePropertyType(type, propertyType));
|
|
380
|
+
}
|
|
381
|
+
const propType = (0, ts_utils_1.getTypeOfPropertyOfType)(type, property.name, checker);
|
|
382
|
+
return propType && (0, ts_utils_1.isNullableType)(propType, ts);
|
|
383
|
+
});
|
|
384
|
+
return !isOwnNullable && (0, ts_utils_1.isNullableType)(prevType, ts);
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
function isOptionableExpression(node) {
|
|
389
|
+
const type = getNodeType(node);
|
|
390
|
+
if (!type) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
const isOwnNullable = node.type === "MemberExpression"
|
|
394
|
+
? !isNullableOriginFromPrev(node)
|
|
395
|
+
: true;
|
|
396
|
+
return ((0, ts_utils_1.isAnyType)(type, ts) ||
|
|
397
|
+
(0, ts_utils_1.isUnknownType)(type, ts) ||
|
|
398
|
+
((0, ts_utils_1.isNullableType)(type, ts) && isOwnNullable));
|
|
399
|
+
}
|
|
400
|
+
function checkOptionalChain(node, beforeOperator, fix) {
|
|
401
|
+
if (!node.optional) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (optionChainContainsOptionArrayIndex(node)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const nodeToCheck = node.type === "CallExpression" ? node.callee : node.object;
|
|
408
|
+
if (hasSvelteReactiveVar(nodeToCheck)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (isOptionableExpression(nodeToCheck)) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const questionDotOperator = sourceCode.getTokenAfter(beforeOperator, {
|
|
415
|
+
includeComments: false,
|
|
416
|
+
filter: (token) => token.type === "Punctuator" && token.value === "?.",
|
|
417
|
+
});
|
|
418
|
+
context.report({
|
|
419
|
+
node,
|
|
420
|
+
loc: questionDotOperator.loc,
|
|
421
|
+
messageId: "neverOptionalChain",
|
|
422
|
+
fix(fixer) {
|
|
423
|
+
return fixer.replaceText(questionDotOperator, fix);
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function checkOptionalMemberExpression(node) {
|
|
428
|
+
checkOptionalChain(node, node.object, node.computed ? "" : ".");
|
|
429
|
+
}
|
|
430
|
+
function checkOptionalCallExpression(node) {
|
|
431
|
+
checkOptionalChain(node, node.callee, "");
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
SvelteReactiveStatement: () => (inSvelteReactiveStatement = true),
|
|
435
|
+
"SvelteReactiveStatement:exit": () => (inSvelteReactiveStatement = false),
|
|
436
|
+
BinaryExpression: checkIfBinaryExpressionIsNecessaryConditional,
|
|
437
|
+
CallExpression: checkCallExpression,
|
|
438
|
+
ConditionalExpression: (node) => checkNode(node.test),
|
|
439
|
+
DoWhileStatement: checkIfLoopIsNecessaryConditional,
|
|
440
|
+
ForStatement: checkIfLoopIsNecessaryConditional,
|
|
441
|
+
IfStatement: (node) => checkNode(node.test),
|
|
442
|
+
LogicalExpression: checkLogicalExpressionForUnnecessaryConditionals,
|
|
443
|
+
WhileStatement: checkIfLoopIsNecessaryConditional,
|
|
444
|
+
"MemberExpression[optional = true]": checkOptionalMemberExpression,
|
|
445
|
+
"CallExpression[optional = true]": checkOptionalCallExpression,
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
});
|
package/lib/rules/system.js
CHANGED
|
@@ -60,7 +60,7 @@ exports.default = (0, utils_1.createRule)("system", {
|
|
|
60
60
|
loc: node.startTag.loc.end,
|
|
61
61
|
});
|
|
62
62
|
if (node.endTag) {
|
|
63
|
-
directives.
|
|
63
|
+
directives.disableBlock(node.endTag.loc.start, isIgnoreRule, {
|
|
64
64
|
loc: node.endTag.loc.start,
|
|
65
65
|
});
|
|
66
66
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -22,7 +22,10 @@ export interface RuleMetaData {
|
|
|
22
22
|
description: string;
|
|
23
23
|
category: RuleCategory;
|
|
24
24
|
recommended: boolean | "base";
|
|
25
|
-
extensionRule?: string
|
|
25
|
+
extensionRule?: string | {
|
|
26
|
+
plugin: string;
|
|
27
|
+
url: string;
|
|
28
|
+
};
|
|
26
29
|
url: string;
|
|
27
30
|
ruleId: string;
|
|
28
31
|
ruleName: string;
|
|
@@ -47,7 +50,10 @@ export interface PartialRuleMetaData {
|
|
|
47
50
|
docs: {
|
|
48
51
|
description: string;
|
|
49
52
|
recommended: boolean | "base";
|
|
50
|
-
extensionRule?: string
|
|
53
|
+
extensionRule?: string | {
|
|
54
|
+
plugin: string;
|
|
55
|
+
url: string;
|
|
56
|
+
};
|
|
51
57
|
default?: "error" | "warn";
|
|
52
58
|
} & ({
|
|
53
59
|
category: Exclude<RuleCategory, "Stylistic Issues">;
|
package/lib/utils/rules.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.rules = void 0;
|
|
7
|
+
const no_unnecessary_condition_1 = __importDefault(require("../rules/@typescript-eslint/no-unnecessary-condition"));
|
|
7
8
|
const button_has_type_1 = __importDefault(require("../rules/button-has-type"));
|
|
8
9
|
const comment_directive_1 = __importDefault(require("../rules/comment-directive"));
|
|
9
10
|
const derived_has_same_inputs_outputs_1 = __importDefault(require("../rules/derived-has-same-inputs-outputs"));
|
|
@@ -44,6 +45,7 @@ const spaced_html_comment_1 = __importDefault(require("../rules/spaced-html-comm
|
|
|
44
45
|
const system_1 = __importDefault(require("../rules/system"));
|
|
45
46
|
const valid_compile_1 = __importDefault(require("../rules/valid-compile"));
|
|
46
47
|
exports.rules = [
|
|
48
|
+
no_unnecessary_condition_1.default,
|
|
47
49
|
button_has_type_1.default,
|
|
48
50
|
comment_directive_1.default,
|
|
49
51
|
derived_has_same_inputs_outputs_1.default,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RuleContext, ASTNode } from "../../types";
|
|
2
|
+
import type * as TS from "typescript";
|
|
3
|
+
export declare type TypeScript = typeof TS;
|
|
4
|
+
export type { TS };
|
|
5
|
+
export declare type TSTools = {
|
|
6
|
+
service: {
|
|
7
|
+
esTreeNodeToTSNodeMap: ReadonlyMap<unknown, TS.Node>;
|
|
8
|
+
tsNodeToESTreeNodeMap: ReadonlyMap<TS.Node, ASTNode>;
|
|
9
|
+
program: TS.Program;
|
|
10
|
+
hasFullTypeInformation: boolean;
|
|
11
|
+
};
|
|
12
|
+
ts: TypeScript;
|
|
13
|
+
};
|
|
14
|
+
export declare function getTypeScriptTools(context: RuleContext): TSTools | null;
|
|
15
|
+
export declare function getTypeScript(context: RuleContext): TypeScript | undefined;
|
|
16
|
+
export declare function isTruthyLiteral(type: TS.Type, tsTools: TSTools): boolean;
|
|
17
|
+
export declare function isFalsyType(type: TS.Type, tsTools: TSTools): boolean;
|
|
18
|
+
export declare function isNullishType(type: TS.Type, ts: TypeScript): boolean;
|
|
19
|
+
export declare function isNullableType(type: TS.Type, ts: TypeScript): boolean;
|
|
20
|
+
export declare function isBooleanLiteralType(type: TS.Type, ts: TypeScript): boolean;
|
|
21
|
+
export declare function isObjectType(type: TS.Type, ts: TypeScript): type is TS.ObjectType;
|
|
22
|
+
export declare function isReferenceObjectType(type: TS.Type, ts: TypeScript): type is TS.TypeReference;
|
|
23
|
+
export declare function isTupleObjectType(type: TS.Type, ts: TypeScript): type is TS.TupleType;
|
|
24
|
+
export declare function isTupleType(type: TS.Type, ts: TypeScript): boolean;
|
|
25
|
+
export declare function isAnyType(type: TS.Type, ts: TypeScript): boolean;
|
|
26
|
+
export declare function isUnknownType(type: TS.Type, ts: TypeScript): boolean;
|
|
27
|
+
export declare function isNeverType(type: TS.Type, ts: TypeScript): boolean;
|
|
28
|
+
export declare function isUndefinedType(type: TS.Type, ts: TypeScript): boolean;
|
|
29
|
+
export declare function isVoidType(type: TS.Type, ts: TypeScript): boolean;
|
|
30
|
+
export declare function isNullType(type: TS.Type, ts: TypeScript): boolean;
|
|
31
|
+
export declare function isPossiblyFalsyType(type: TS.Type, ts: TypeScript): boolean;
|
|
32
|
+
export declare function getCallSignaturesOfType(type: TS.Type): readonly TS.Signature[];
|
|
33
|
+
export declare function getConstrainedTypeAtLocation(checker: TS.TypeChecker, node: TS.Node): TS.Type;
|
|
34
|
+
export declare function getTypeName(type: TS.Type, tsTools: TSTools): string;
|
|
35
|
+
export declare function getTypeOfPropertyOfType(type: TS.Type, name: string, checker: TS.TypeChecker): TS.Type | undefined;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getTypeOfPropertyOfType = exports.getTypeName = exports.getConstrainedTypeAtLocation = exports.getCallSignaturesOfType = exports.isPossiblyFalsyType = exports.isNullType = exports.isVoidType = exports.isUndefinedType = exports.isNeverType = exports.isUnknownType = exports.isAnyType = exports.isTupleType = exports.isTupleObjectType = exports.isReferenceObjectType = exports.isObjectType = exports.isBooleanLiteralType = exports.isNullableType = exports.isNullishType = exports.isFalsyType = exports.isTruthyLiteral = exports.getTypeScript = exports.getTypeScriptTools = void 0;
|
|
7
|
+
const module_1 = __importDefault(require("module"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function getTypeScriptTools(context) {
|
|
10
|
+
const ts = getTypeScript(context);
|
|
11
|
+
if (!ts) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const { program, esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = context.parserServices;
|
|
15
|
+
if (!program || !esTreeNodeToTSNodeMap || !tsNodeToESTreeNodeMap) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const hasFullTypeInformation = context.parserServices.hasFullTypeInformation ?? true;
|
|
19
|
+
if (!hasFullTypeInformation) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
service: {
|
|
24
|
+
esTreeNodeToTSNodeMap,
|
|
25
|
+
tsNodeToESTreeNodeMap,
|
|
26
|
+
hasFullTypeInformation,
|
|
27
|
+
program,
|
|
28
|
+
},
|
|
29
|
+
ts,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
exports.getTypeScriptTools = getTypeScriptTools;
|
|
33
|
+
let cacheTypeScript;
|
|
34
|
+
function getTypeScript(context) {
|
|
35
|
+
if (cacheTypeScript) {
|
|
36
|
+
return cacheTypeScript;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const cwd = context.getCwd?.() ?? process.cwd();
|
|
40
|
+
const relativeTo = path_1.default.join(cwd, "__placeholder__.js");
|
|
41
|
+
cacheTypeScript = module_1.default.createRequire(relativeTo)("typescript");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
}
|
|
45
|
+
if (cacheTypeScript) {
|
|
46
|
+
return cacheTypeScript;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
cacheTypeScript ?? (cacheTypeScript = require("typescript"));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
}
|
|
53
|
+
return cacheTypeScript;
|
|
54
|
+
}
|
|
55
|
+
exports.getTypeScript = getTypeScript;
|
|
56
|
+
function isTruthyLiteral(type, tsTools) {
|
|
57
|
+
if (type.isUnion()) {
|
|
58
|
+
return type.types.every((t) => isTruthyLiteral(t, tsTools));
|
|
59
|
+
}
|
|
60
|
+
return ((isBooleanLiteralType(type, tsTools.ts) &&
|
|
61
|
+
tsTools.service.program.getTypeChecker().typeToString(type) === "true") ||
|
|
62
|
+
(type.isLiteral() && Boolean(type.value)));
|
|
63
|
+
}
|
|
64
|
+
exports.isTruthyLiteral = isTruthyLiteral;
|
|
65
|
+
function isFalsyType(type, tsTools) {
|
|
66
|
+
if (type.isUnion()) {
|
|
67
|
+
return type.types.every((t) => isFalsyType(t, tsTools));
|
|
68
|
+
}
|
|
69
|
+
if (isUndefinedType(type, tsTools.ts) ||
|
|
70
|
+
isNullType(type, tsTools.ts) ||
|
|
71
|
+
isVoidType(type, tsTools.ts))
|
|
72
|
+
return true;
|
|
73
|
+
if (type.isLiteral())
|
|
74
|
+
return !type.value;
|
|
75
|
+
return (isBooleanLiteralType(type, tsTools.ts) &&
|
|
76
|
+
tsTools.service.program.getTypeChecker().typeToString(type) === "false");
|
|
77
|
+
}
|
|
78
|
+
exports.isFalsyType = isFalsyType;
|
|
79
|
+
function isNullishType(type, ts) {
|
|
80
|
+
if (type.isUnion()) {
|
|
81
|
+
return type.types.every((t) => isNullishType(t, ts));
|
|
82
|
+
}
|
|
83
|
+
return isNullType(type, ts) || isUndefinedType(type, ts);
|
|
84
|
+
}
|
|
85
|
+
exports.isNullishType = isNullishType;
|
|
86
|
+
function isNullableType(type, ts) {
|
|
87
|
+
if (type.isUnion()) {
|
|
88
|
+
return type.types.some((t) => isNullableType(t, ts));
|
|
89
|
+
}
|
|
90
|
+
return isNullType(type, ts) || isUndefinedType(type, ts);
|
|
91
|
+
}
|
|
92
|
+
exports.isNullableType = isNullableType;
|
|
93
|
+
function isBooleanLiteralType(type, ts) {
|
|
94
|
+
return (type.flags & ts.TypeFlags.BooleanLiteral) !== 0;
|
|
95
|
+
}
|
|
96
|
+
exports.isBooleanLiteralType = isBooleanLiteralType;
|
|
97
|
+
function isObjectType(type, ts) {
|
|
98
|
+
return (type.flags & ts.TypeFlags.Object) !== 0;
|
|
99
|
+
}
|
|
100
|
+
exports.isObjectType = isObjectType;
|
|
101
|
+
function isReferenceObjectType(type, ts) {
|
|
102
|
+
return (isObjectType(type, ts) &&
|
|
103
|
+
(type.objectFlags & ts.ObjectFlags.Reference) !== 0);
|
|
104
|
+
}
|
|
105
|
+
exports.isReferenceObjectType = isReferenceObjectType;
|
|
106
|
+
function isTupleObjectType(type, ts) {
|
|
107
|
+
return (isObjectType(type, ts) && (type.objectFlags & ts.ObjectFlags.Tuple) !== 0);
|
|
108
|
+
}
|
|
109
|
+
exports.isTupleObjectType = isTupleObjectType;
|
|
110
|
+
function isTupleType(type, ts) {
|
|
111
|
+
return (isTupleObjectType(type, ts) ||
|
|
112
|
+
(isReferenceObjectType(type, ts) && isTupleObjectType(type.target, ts)));
|
|
113
|
+
}
|
|
114
|
+
exports.isTupleType = isTupleType;
|
|
115
|
+
function isAnyType(type, ts) {
|
|
116
|
+
return (type.flags & ts.TypeFlags.Any) !== 0;
|
|
117
|
+
}
|
|
118
|
+
exports.isAnyType = isAnyType;
|
|
119
|
+
function isUnknownType(type, ts) {
|
|
120
|
+
return (type.flags & ts.TypeFlags.Unknown) !== 0;
|
|
121
|
+
}
|
|
122
|
+
exports.isUnknownType = isUnknownType;
|
|
123
|
+
function isNeverType(type, ts) {
|
|
124
|
+
return (type.flags & ts.TypeFlags.Never) !== 0;
|
|
125
|
+
}
|
|
126
|
+
exports.isNeverType = isNeverType;
|
|
127
|
+
function isUndefinedType(type, ts) {
|
|
128
|
+
return (type.flags & ts.TypeFlags.Undefined) !== 0;
|
|
129
|
+
}
|
|
130
|
+
exports.isUndefinedType = isUndefinedType;
|
|
131
|
+
function isVoidType(type, ts) {
|
|
132
|
+
return (type.flags & ts.TypeFlags.Void) !== 0;
|
|
133
|
+
}
|
|
134
|
+
exports.isVoidType = isVoidType;
|
|
135
|
+
function isNullType(type, ts) {
|
|
136
|
+
return (type.flags & ts.TypeFlags.Null) !== 0;
|
|
137
|
+
}
|
|
138
|
+
exports.isNullType = isNullType;
|
|
139
|
+
function isPossiblyFalsyType(type, ts) {
|
|
140
|
+
if (type.isUnion()) {
|
|
141
|
+
return type.types.some((t) => isPossiblyFalsyType(t, ts));
|
|
142
|
+
}
|
|
143
|
+
return (type.flags & ts.TypeFlags.PossiblyFalsy) !== 0;
|
|
144
|
+
}
|
|
145
|
+
exports.isPossiblyFalsyType = isPossiblyFalsyType;
|
|
146
|
+
function getCallSignaturesOfType(type) {
|
|
147
|
+
if (type.isUnion()) {
|
|
148
|
+
return type.types.flatMap((t) => getCallSignaturesOfType(t));
|
|
149
|
+
}
|
|
150
|
+
if (type.isIntersection()) {
|
|
151
|
+
let signatures = [];
|
|
152
|
+
for (const t of type.types) {
|
|
153
|
+
const sig = getCallSignaturesOfType(t);
|
|
154
|
+
if (sig.length !== 0) {
|
|
155
|
+
if (signatures.length) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
signatures = sig;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return signatures;
|
|
162
|
+
}
|
|
163
|
+
return type.getCallSignatures();
|
|
164
|
+
}
|
|
165
|
+
exports.getCallSignaturesOfType = getCallSignaturesOfType;
|
|
166
|
+
function getConstrainedTypeAtLocation(checker, node) {
|
|
167
|
+
const nodeType = checker.getTypeAtLocation(node);
|
|
168
|
+
const constrained = checker.getBaseConstraintOfType(nodeType);
|
|
169
|
+
return constrained ?? nodeType;
|
|
170
|
+
}
|
|
171
|
+
exports.getConstrainedTypeAtLocation = getConstrainedTypeAtLocation;
|
|
172
|
+
function getTypeName(type, tsTools) {
|
|
173
|
+
const { ts } = tsTools;
|
|
174
|
+
if ((type.flags & ts.TypeFlags.StringLike) !== 0) {
|
|
175
|
+
return "string";
|
|
176
|
+
}
|
|
177
|
+
const typeChecker = tsTools.service.program.getTypeChecker();
|
|
178
|
+
if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) {
|
|
179
|
+
const symbol = type.getSymbol();
|
|
180
|
+
const decls = symbol?.getDeclarations();
|
|
181
|
+
const typeParamDecl = decls?.[0];
|
|
182
|
+
if (ts.isTypeParameterDeclaration(typeParamDecl) &&
|
|
183
|
+
typeParamDecl.constraint != null) {
|
|
184
|
+
return getTypeName(typeChecker.getTypeFromTypeNode(typeParamDecl.constraint), tsTools);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (type.isUnion() &&
|
|
188
|
+
type.types
|
|
189
|
+
.map((value) => getTypeName(value, tsTools))
|
|
190
|
+
.every((t) => t === "string")) {
|
|
191
|
+
return "string";
|
|
192
|
+
}
|
|
193
|
+
if (type.isIntersection() &&
|
|
194
|
+
type.types
|
|
195
|
+
.map((value) => getTypeName(value, tsTools))
|
|
196
|
+
.some((t) => t === "string")) {
|
|
197
|
+
return "string";
|
|
198
|
+
}
|
|
199
|
+
return typeChecker.typeToString(type);
|
|
200
|
+
}
|
|
201
|
+
exports.getTypeName = getTypeName;
|
|
202
|
+
function getTypeOfPropertyOfType(type, name, checker) {
|
|
203
|
+
return checker.getTypeOfPropertyOfType(type, name);
|
|
204
|
+
}
|
|
205
|
+
exports.getTypeOfPropertyOfType = getTypeOfPropertyOfType;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-svelte",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "ESLint plugin for Svelte using AST",
|
|
5
5
|
"repository": "git+https://github.com/ota-meshi/eslint-plugin-svelte.git",
|
|
6
6
|
"homepage": "https://ota-meshi.github.io/eslint-plugin-svelte",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
|
113
113
|
"@typescript-eslint/parser": "^5.4.1-0",
|
|
114
114
|
"@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@4",
|
|
115
|
+
"@typescript/vfs": "^1.4.0",
|
|
115
116
|
"assert": "^2.0.0",
|
|
116
117
|
"commitlint": "^17.0.3",
|
|
117
118
|
"env-cmd": "^10.1.0",
|