eslint-plugin-svelte 2.8.0 → 2.10.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 +4 -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/prefer-destructured-store-props.d.ts +2 -0
- package/lib/rules/prefer-destructured-store-props.js +182 -0
- package/lib/rules/system.js +1 -1
- package/lib/shared/svelte-compile-warns/transform/babel.js +1 -1
- package/lib/shared/svelte-compile-warns/transform/less.js +1 -1
- package/lib/shared/svelte-compile-warns/transform/postcss.js +1 -1
- package/lib/shared/svelte-compile-warns/transform/sass.js +1 -1
- package/lib/shared/svelte-compile-warns/transform/stylus.js +1 -1
- package/lib/types.d.ts +9 -3
- package/lib/utils/ast-utils.d.ts +4 -2
- package/lib/utils/ast-utils.js +46 -2
- package/lib/utils/rules.js +4 -0
- package/lib/utils/ts-utils/index.d.ts +35 -0
- package/lib/utils/ts-utils/index.js +205 -0
- package/package.json +7 -4
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
|
|
|
@@ -294,6 +294,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|
|
|
294
294
|
| [svelte/no-reactive-literals](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :bulb: |
|
|
295
295
|
| [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
|
|
296
296
|
| [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
|
|
297
|
+
| [svelte/prefer-destructured-store-props](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
|
|
297
298
|
| [svelte/require-optimized-style-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
|
|
298
299
|
| [svelte/require-stores-init](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | |
|
|
299
300
|
|
|
@@ -322,10 +323,11 @@ These rules relate to style guidelines, and are therefore quite subjective:
|
|
|
322
323
|
|
|
323
324
|
## Extension Rules
|
|
324
325
|
|
|
325
|
-
These rules extend the rules provided by ESLint itself to work well in Svelte:
|
|
326
|
+
These rules extend the rules provided by ESLint itself, or other plugins to work well in Svelte:
|
|
326
327
|
|
|
327
328
|
| Rule ID | Description | |
|
|
328
329
|
|:--------|:------------|:---|
|
|
330
|
+
| [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
331
|
| [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
332
|
| [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
333
|
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const eslint_utils_1 = require("eslint-utils");
|
|
4
|
+
const esutils_1 = require("esutils");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const ast_utils_1 = require("../utils/ast-utils");
|
|
7
|
+
exports.default = (0, utils_1.createRule)("prefer-destructured-store-props", {
|
|
8
|
+
meta: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: "destructure values from object stores for better change tracking & fewer redraws",
|
|
11
|
+
category: "Best Practices",
|
|
12
|
+
recommended: false,
|
|
13
|
+
},
|
|
14
|
+
hasSuggestions: true,
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
useDestructuring: `Destructure {{property}} from {{store}} for better change tracking & fewer redraws`,
|
|
18
|
+
fixUseDestructuring: `Using destructuring like $: ({ {{property}} } = {{store}}); will run faster`,
|
|
19
|
+
fixUseVariable: `Using the predefined reactive variable {{variable}}`,
|
|
20
|
+
},
|
|
21
|
+
type: "suggestion",
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
let mainScript = null;
|
|
25
|
+
const reports = [];
|
|
26
|
+
let inScriptElement = false;
|
|
27
|
+
const storeMemberAccessStack = [];
|
|
28
|
+
function* findReactiveVariable(object, propName) {
|
|
29
|
+
const storeVar = (0, ast_utils_1.findVariable)(context, object);
|
|
30
|
+
if (!storeVar) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const reference of storeVar.references) {
|
|
34
|
+
const id = reference.identifier;
|
|
35
|
+
if (id.name !== object.name)
|
|
36
|
+
continue;
|
|
37
|
+
if (isReactiveVariableDefinitionWithMemberExpression(id)) {
|
|
38
|
+
yield id.parent.parent.left;
|
|
39
|
+
}
|
|
40
|
+
else if (isReactiveVariableDefinitionWithDestructuring(id)) {
|
|
41
|
+
const prop = id.parent.left.properties.find((prop) => prop.type === "Property" &&
|
|
42
|
+
prop.value.type === "Identifier" &&
|
|
43
|
+
(0, eslint_utils_1.getPropertyName)(prop) === propName);
|
|
44
|
+
if (prop) {
|
|
45
|
+
yield prop.value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isReactiveVariableDefinitionWithMemberExpression(node) {
|
|
50
|
+
return (node.parent?.type === "MemberExpression" &&
|
|
51
|
+
node.parent.object === node &&
|
|
52
|
+
(0, eslint_utils_1.getPropertyName)(node.parent) === propName &&
|
|
53
|
+
node.parent.parent?.type === "AssignmentExpression" &&
|
|
54
|
+
node.parent.parent.right === node.parent &&
|
|
55
|
+
node.parent.parent.left.type === "Identifier" &&
|
|
56
|
+
node.parent.parent.parent?.type === "ExpressionStatement" &&
|
|
57
|
+
node.parent.parent.parent
|
|
58
|
+
.parent?.type === "SvelteReactiveStatement");
|
|
59
|
+
}
|
|
60
|
+
function isReactiveVariableDefinitionWithDestructuring(node) {
|
|
61
|
+
return (node.parent?.type === "AssignmentExpression" &&
|
|
62
|
+
node.parent.right === node &&
|
|
63
|
+
node.parent.left.type === "ObjectPattern" &&
|
|
64
|
+
node.parent.parent?.type === "ExpressionStatement" &&
|
|
65
|
+
node.parent.parent.parent
|
|
66
|
+
?.type === "SvelteReactiveStatement");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function hasTopLevelVariable(name) {
|
|
70
|
+
const scopeManager = context.getSourceCode().scopeManager;
|
|
71
|
+
if (scopeManager.globalScope?.set.has(name)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const moduleScope = scopeManager.globalScope?.childScopes.find((s) => s.type === "module");
|
|
75
|
+
return moduleScope?.set.has(name) || false;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
SvelteScriptElement(node) {
|
|
79
|
+
inScriptElement = true;
|
|
80
|
+
const scriptContext = (0, ast_utils_1.findAttribute)(node, "context");
|
|
81
|
+
const contextValue = scriptContext?.value.length === 1 && scriptContext.value[0];
|
|
82
|
+
if (contextValue &&
|
|
83
|
+
contextValue.type === "SvelteLiteral" &&
|
|
84
|
+
contextValue.value === "module") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
mainScript = node;
|
|
88
|
+
},
|
|
89
|
+
"SvelteScriptElement:exit"() {
|
|
90
|
+
inScriptElement = false;
|
|
91
|
+
},
|
|
92
|
+
"MemberExpression[object.type='Identifier'][object.name=/^\\$/]"(node) {
|
|
93
|
+
if (inScriptElement)
|
|
94
|
+
return;
|
|
95
|
+
storeMemberAccessStack.unshift({ node, identifiers: [] });
|
|
96
|
+
},
|
|
97
|
+
Identifier(node) {
|
|
98
|
+
storeMemberAccessStack[0]?.identifiers.push(node);
|
|
99
|
+
},
|
|
100
|
+
"MemberExpression[object.type='Identifier'][object.name=/^\\$/]:exit"(node) {
|
|
101
|
+
if (storeMemberAccessStack[0]?.node !== node)
|
|
102
|
+
return;
|
|
103
|
+
const { identifiers } = storeMemberAccessStack.shift();
|
|
104
|
+
for (const id of identifiers) {
|
|
105
|
+
if (!(0, ast_utils_1.isExpressionIdentifier)(id))
|
|
106
|
+
continue;
|
|
107
|
+
const variable = (0, ast_utils_1.findVariable)(context, id);
|
|
108
|
+
const isTopLevel = !variable ||
|
|
109
|
+
variable.scope.type === "module" ||
|
|
110
|
+
variable.scope.type === "global";
|
|
111
|
+
if (!isTopLevel) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
reports.push(node);
|
|
116
|
+
},
|
|
117
|
+
"Program:exit"() {
|
|
118
|
+
const scriptEndTag = mainScript && mainScript.endTag;
|
|
119
|
+
for (const node of reports) {
|
|
120
|
+
const store = node.object.name;
|
|
121
|
+
const suggest = [];
|
|
122
|
+
if (!node.computed) {
|
|
123
|
+
for (const variable of findReactiveVariable(node.object, node.property.name)) {
|
|
124
|
+
suggest.push({
|
|
125
|
+
messageId: "fixUseVariable",
|
|
126
|
+
data: {
|
|
127
|
+
variable: variable.name,
|
|
128
|
+
},
|
|
129
|
+
fix(fixer) {
|
|
130
|
+
return fixer.replaceText(node, variable.name);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (scriptEndTag) {
|
|
135
|
+
suggest.push({
|
|
136
|
+
messageId: "fixUseDestructuring",
|
|
137
|
+
data: {
|
|
138
|
+
store,
|
|
139
|
+
property: node.property.name,
|
|
140
|
+
},
|
|
141
|
+
fix(fixer) {
|
|
142
|
+
const propName = node.property.name;
|
|
143
|
+
let varName = propName;
|
|
144
|
+
if (varName.startsWith("$")) {
|
|
145
|
+
varName = varName.slice(1);
|
|
146
|
+
}
|
|
147
|
+
const baseName = varName;
|
|
148
|
+
let suffix = 0;
|
|
149
|
+
if (esutils_1.keyword.isReservedWordES6(varName, true) ||
|
|
150
|
+
esutils_1.keyword.isRestrictedWord(varName)) {
|
|
151
|
+
varName = `${baseName}${++suffix}`;
|
|
152
|
+
}
|
|
153
|
+
while (hasTopLevelVariable(varName)) {
|
|
154
|
+
varName = `${baseName}${++suffix}`;
|
|
155
|
+
}
|
|
156
|
+
return [
|
|
157
|
+
fixer.insertTextAfterRange([scriptEndTag.range[0], scriptEndTag.range[0]], `$: ({ ${propName}${propName !== varName ? `: ${varName}` : ""} } = ${store});\n`),
|
|
158
|
+
fixer.replaceText(node, varName),
|
|
159
|
+
];
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
context.report({
|
|
165
|
+
node,
|
|
166
|
+
messageId: "useDestructuring",
|
|
167
|
+
data: {
|
|
168
|
+
store,
|
|
169
|
+
property: !node.computed
|
|
170
|
+
? node.property.name
|
|
171
|
+
: context
|
|
172
|
+
.getSourceCode()
|
|
173
|
+
.getText(node.property)
|
|
174
|
+
.replace(/\s+/g, " "),
|
|
175
|
+
},
|
|
176
|
+
suggest,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
});
|
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">;
|
|
@@ -108,7 +114,7 @@ declare type SuggestionDescriptorMessage = {
|
|
|
108
114
|
} | {
|
|
109
115
|
messageId: string;
|
|
110
116
|
};
|
|
111
|
-
declare type SuggestionReportDescriptor = SuggestionDescriptorMessage & ReportDescriptorOptionsBase;
|
|
117
|
+
export declare type SuggestionReportDescriptor = SuggestionDescriptorMessage & ReportDescriptorOptionsBase;
|
|
112
118
|
interface ReportDescriptorOptions extends ReportDescriptorOptionsBase {
|
|
113
119
|
suggest?: SuggestionReportDescriptor[] | null;
|
|
114
120
|
}
|
package/lib/utils/ast-utils.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ASTNode, RuleContext, SourceCode } from "../types";
|
|
2
2
|
import type * as ESTree from "estree";
|
|
3
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
3
4
|
import type { AST as SvAST } from "svelte-eslint-parser";
|
|
4
5
|
import type { Scope } from "eslint";
|
|
5
6
|
export declare function equalTokens(left: ASTNode, right: ASTNode, sourceCode: SourceCode): boolean;
|
|
6
|
-
export declare function getStringIfConstant(node: ESTree.Expression): string | null;
|
|
7
|
+
export declare function getStringIfConstant(node: ESTree.Expression | TSESTree.Expression): string | null;
|
|
7
8
|
export declare function needParentheses(node: ESTree.Expression, kind: "not" | "logical"): boolean;
|
|
8
9
|
export declare function isHTMLElementLike(node: SvAST.SvelteElement | SvAST.SvelteScriptElement | SvAST.SvelteStyleElement): node is SvAST.SvelteHTMLElement | (SvAST.SvelteSpecialElement & {
|
|
9
10
|
name: SvAST.SvelteName & {
|
|
@@ -29,7 +30,7 @@ export declare function findBindDirective<N extends string>(node: SvAST.SvelteEl
|
|
|
29
30
|
}) | null;
|
|
30
31
|
export declare function getStaticAttributeValue(node: SvAST.SvelteAttribute): string | null;
|
|
31
32
|
export declare function getLangValue(node: SvAST.SvelteScriptElement | SvAST.SvelteStyleElement): string | null;
|
|
32
|
-
export declare function findVariable(context: RuleContext, node: ESTree.Identifier): Scope.Variable | null;
|
|
33
|
+
export declare function findVariable(context: RuleContext, node: ESTree.Identifier | TSESTree.Identifier): Scope.Variable | null;
|
|
33
34
|
export declare function getScope(context: RuleContext, currentNode: ESTree.Node): Scope.Scope;
|
|
34
35
|
export declare type QuoteAndRange = {
|
|
35
36
|
quote: "unquoted" | "double" | "single";
|
|
@@ -50,3 +51,4 @@ export declare function getAttributeKeyText(node: SvAST.SvelteAttribute | SvAST.
|
|
|
50
51
|
export declare function getDirectiveName(node: SvAST.SvelteDirective): string;
|
|
51
52
|
export declare function getNodeName(node: SvAST.SvelteElement): string;
|
|
52
53
|
export declare function isVoidHtmlElement(node: SvAST.SvelteElement): boolean;
|
|
54
|
+
export declare function isExpressionIdentifier(node: TSESTree.Identifier): boolean;
|
package/lib/utils/ast-utils.js
CHANGED
|
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.isVoidHtmlElement = exports.getNodeName = exports.getDirectiveName = exports.getAttributeKeyText = exports.getMustacheTokens = exports.getAttributeValueQuoteAndRange = exports.getScope = exports.findVariable = exports.getLangValue = exports.getStaticAttributeValue = exports.findBindDirective = exports.findShorthandAttribute = exports.findAttribute = exports.isHTMLElementLike = exports.needParentheses = exports.getStringIfConstant = exports.equalTokens = void 0;
|
|
29
|
+
exports.isExpressionIdentifier = exports.isVoidHtmlElement = exports.getNodeName = exports.getDirectiveName = exports.getAttributeKeyText = exports.getMustacheTokens = exports.getAttributeValueQuoteAndRange = exports.getScope = exports.findVariable = exports.getLangValue = exports.getStaticAttributeValue = exports.findBindDirective = exports.findShorthandAttribute = exports.findAttribute = exports.isHTMLElementLike = exports.needParentheses = exports.getStringIfConstant = exports.equalTokens = void 0;
|
|
30
30
|
const eslintUtils = __importStar(require("eslint-utils"));
|
|
31
31
|
const void_elements_1 = __importDefault(require("./void-elements"));
|
|
32
32
|
function equalTokens(left, right, sourceCode) {
|
|
@@ -168,7 +168,15 @@ function getLangValue(node) {
|
|
|
168
168
|
}
|
|
169
169
|
exports.getLangValue = getLangValue;
|
|
170
170
|
function findVariable(context, node) {
|
|
171
|
-
|
|
171
|
+
const initialScope = eslintUtils.getInnermostScope(getScope(context, node), node);
|
|
172
|
+
const variable = eslintUtils.findVariable(initialScope, node);
|
|
173
|
+
if (variable) {
|
|
174
|
+
return variable;
|
|
175
|
+
}
|
|
176
|
+
if (!node.name.startsWith("$")) {
|
|
177
|
+
return variable;
|
|
178
|
+
}
|
|
179
|
+
return eslintUtils.findVariable(initialScope, node.name.slice(1));
|
|
172
180
|
}
|
|
173
181
|
exports.findVariable = findVariable;
|
|
174
182
|
function getScope(context, currentNode) {
|
|
@@ -346,3 +354,39 @@ function isVoidHtmlElement(node) {
|
|
|
346
354
|
return void_elements_1.default.includes(getNodeName(node));
|
|
347
355
|
}
|
|
348
356
|
exports.isVoidHtmlElement = isVoidHtmlElement;
|
|
357
|
+
function isExpressionIdentifier(node) {
|
|
358
|
+
const parent = node.parent;
|
|
359
|
+
if (!parent) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (parent.type === "MemberExpression") {
|
|
363
|
+
return !parent.computed || parent.property !== node;
|
|
364
|
+
}
|
|
365
|
+
if (parent.type === "Property" ||
|
|
366
|
+
parent.type === "MethodDefinition" ||
|
|
367
|
+
parent.type === "PropertyDefinition") {
|
|
368
|
+
return !parent.computed || parent.key !== node;
|
|
369
|
+
}
|
|
370
|
+
if (parent.type === "FunctionDeclaration" ||
|
|
371
|
+
parent.type === "FunctionExpression" ||
|
|
372
|
+
parent.type === "ClassDeclaration" ||
|
|
373
|
+
parent.type === "ClassExpression") {
|
|
374
|
+
return parent.id !== node;
|
|
375
|
+
}
|
|
376
|
+
if (parent.type === "LabeledStatement" ||
|
|
377
|
+
parent.type === "BreakStatement" ||
|
|
378
|
+
parent.type === "ContinueStatement") {
|
|
379
|
+
return parent.label !== node;
|
|
380
|
+
}
|
|
381
|
+
if (parent.type === "MetaProperty") {
|
|
382
|
+
return parent.property !== node;
|
|
383
|
+
}
|
|
384
|
+
if (parent.type === "ImportSpecifier") {
|
|
385
|
+
return parent.imported !== node;
|
|
386
|
+
}
|
|
387
|
+
if (parent.type === "ExportSpecifier") {
|
|
388
|
+
return parent.exported !== node;
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
exports.isExpressionIdentifier = isExpressionIdentifier;
|
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"));
|
|
@@ -34,6 +35,7 @@ const no_unknown_style_directive_property_1 = __importDefault(require("../rules/
|
|
|
34
35
|
const no_unused_svelte_ignore_1 = __importDefault(require("../rules/no-unused-svelte-ignore"));
|
|
35
36
|
const no_useless_mustaches_1 = __importDefault(require("../rules/no-useless-mustaches"));
|
|
36
37
|
const prefer_class_directive_1 = __importDefault(require("../rules/prefer-class-directive"));
|
|
38
|
+
const prefer_destructured_store_props_1 = __importDefault(require("../rules/prefer-destructured-store-props"));
|
|
37
39
|
const prefer_style_directive_1 = __importDefault(require("../rules/prefer-style-directive"));
|
|
38
40
|
const require_optimized_style_attribute_1 = __importDefault(require("../rules/require-optimized-style-attribute"));
|
|
39
41
|
const require_stores_init_1 = __importDefault(require("../rules/require-stores-init"));
|
|
@@ -44,6 +46,7 @@ const spaced_html_comment_1 = __importDefault(require("../rules/spaced-html-comm
|
|
|
44
46
|
const system_1 = __importDefault(require("../rules/system"));
|
|
45
47
|
const valid_compile_1 = __importDefault(require("../rules/valid-compile"));
|
|
46
48
|
exports.rules = [
|
|
49
|
+
no_unnecessary_condition_1.default,
|
|
47
50
|
button_has_type_1.default,
|
|
48
51
|
comment_directive_1.default,
|
|
49
52
|
derived_has_same_inputs_outputs_1.default,
|
|
@@ -74,6 +77,7 @@ exports.rules = [
|
|
|
74
77
|
no_unused_svelte_ignore_1.default,
|
|
75
78
|
no_useless_mustaches_1.default,
|
|
76
79
|
prefer_class_directive_1.default,
|
|
80
|
+
prefer_destructured_store_props_1.default,
|
|
77
81
|
prefer_style_directive_1.default,
|
|
78
82
|
require_optimized_style_attribute_1.default,
|
|
79
83
|
require_stores_init_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.10.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",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"debug": "^4.3.1",
|
|
69
69
|
"eslint-utils": "^3.0.0",
|
|
70
|
+
"esutils": "^2.0.3",
|
|
70
71
|
"known-css-properties": "^0.25.0",
|
|
71
72
|
"postcss": "^8.4.5",
|
|
72
73
|
"postcss-load-config": "^3.1.4",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"@changesets/changelog-github": "^0.4.6",
|
|
89
90
|
"@changesets/cli": "^2.24.2",
|
|
90
91
|
"@fontsource/fira-mono": "^4.5.0",
|
|
91
|
-
"@ota-meshi/eslint-plugin": "^0.
|
|
92
|
+
"@ota-meshi/eslint-plugin": "^0.13.0",
|
|
92
93
|
"@sindresorhus/slugify": "^2.1.0",
|
|
93
94
|
"@sveltejs/adapter-static": "^1.0.0-next.40",
|
|
94
95
|
"@sveltejs/kit": "^1.0.0-next.456",
|
|
@@ -100,11 +101,12 @@
|
|
|
100
101
|
"@types/eslint-utils": "^3.0.1",
|
|
101
102
|
"@types/eslint-visitor-keys": "^1.0.0",
|
|
102
103
|
"@types/estree": "^1.0.0",
|
|
104
|
+
"@types/esutils": "^2.0.0",
|
|
103
105
|
"@types/less": "^3.0.3",
|
|
104
106
|
"@types/markdown-it": "^12.2.3",
|
|
105
107
|
"@types/markdown-it-container": "^2.0.5",
|
|
106
108
|
"@types/markdown-it-emoji": "^2.0.2",
|
|
107
|
-
"@types/mocha": "^
|
|
109
|
+
"@types/mocha": "^10.0.0",
|
|
108
110
|
"@types/node": "^16.0.0",
|
|
109
111
|
"@types/postcss-safe-parser": "^5.0.1",
|
|
110
112
|
"@types/prismjs": "^1.26.0",
|
|
@@ -112,6 +114,7 @@
|
|
|
112
114
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
|
113
115
|
"@typescript-eslint/parser": "^5.4.1-0",
|
|
114
116
|
"@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@4",
|
|
117
|
+
"@typescript/vfs": "^1.4.0",
|
|
115
118
|
"assert": "^2.0.0",
|
|
116
119
|
"commitlint": "^17.0.3",
|
|
117
120
|
"env-cmd": "^10.1.0",
|
|
@@ -171,7 +174,7 @@
|
|
|
171
174
|
"access": "public"
|
|
172
175
|
},
|
|
173
176
|
"typeCoverage": {
|
|
174
|
-
"atLeast": 98.
|
|
177
|
+
"atLeast": 98.71,
|
|
175
178
|
"cache": true,
|
|
176
179
|
"detail": true,
|
|
177
180
|
"ignoreAsAssertion": true,
|