eslint-plugin-svelte 2.11.0 → 2.13.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 +46 -6
- package/lib/rules/html-self-closing.js +12 -6
- package/lib/rules/indent-helpers/ast.d.ts +3 -3
- package/lib/rules/indent-helpers/es.js +7 -9
- package/lib/rules/no-dom-manipulating.d.ts +2 -0
- package/lib/rules/no-dom-manipulating.js +109 -0
- package/lib/rules/no-export-load-in-svelte-module-in-kit-pages.d.ts +2 -0
- package/lib/rules/no-export-load-in-svelte-module-in-kit-pages.js +41 -0
- package/lib/rules/prefer-destructured-store-props.js +4 -2
- package/lib/rules/reference-helpers/svelte-store.d.ts +6 -2
- package/lib/rules/reference-helpers/svelte-store.js +99 -1
- package/lib/rules/require-store-callbacks-use-set-param.d.ts +2 -0
- package/lib/rules/require-store-callbacks-use-set-param.js +43 -0
- package/lib/rules/require-store-reactive-access.d.ts +2 -0
- package/lib/rules/require-store-reactive-access.js +205 -0
- package/lib/rules/valid-prop-names-in-kit-pages.d.ts +2 -0
- package/lib/rules/valid-prop-names-in-kit-pages.js +61 -0
- package/lib/types-for-node.d.ts +72 -73
- package/lib/types.d.ts +12 -6
- package/lib/utils/ast-utils.d.ts +6 -6
- package/lib/utils/ast-utils.js +11 -9
- package/lib/utils/cache.d.ts +4 -0
- package/lib/utils/cache.js +32 -0
- package/lib/utils/css-utils/style-attribute.d.ts +4 -4
- package/lib/utils/eslint-core.d.ts +3 -3
- package/lib/utils/get-package-json.d.ts +5 -0
- package/lib/utils/get-package-json.js +55 -0
- package/lib/utils/rules.js +10 -0
- package/lib/utils/svelte-kit.d.ts +2 -0
- package/lib/utils/svelte-kit.js +48 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -188,12 +188,6 @@ See also <https://github.com/ota-meshi/svelte-eslint-parser#readme>.
|
|
|
188
188
|
|
|
189
189
|
You can change the behavior of this plugin with some settings.
|
|
190
190
|
|
|
191
|
-
- `ignoreWarnings` (optional) ... Specifies an array of rules that ignore reports in the template.
|
|
192
|
-
For example, set rules on the template that cannot avoid false positives.
|
|
193
|
-
- `compileOptions` (optional) ... Specifies options for Svelte compile. Effects rules that use Svelte compile. The target rules are [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) and [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/). **Note that it has no effect on ESLint's custom parser**.
|
|
194
|
-
- `postcss` (optional) ... Specifies options related to PostCSS. You can disable the PostCSS process by specifying `false`.
|
|
195
|
-
- `configFilePath` (optional) ... Specifies the path of the directory containing the PostCSS configuration.
|
|
196
|
-
|
|
197
191
|
e.g.
|
|
198
192
|
|
|
199
193
|
```js
|
|
@@ -210,6 +204,47 @@ module.exports = {
|
|
|
210
204
|
configFilePath: "./path/to/my/postcss.config.js",
|
|
211
205
|
},
|
|
212
206
|
},
|
|
207
|
+
kit: {
|
|
208
|
+
files: {
|
|
209
|
+
routes: "src/routes",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
// ...
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### settings.svelte.ignoreWarnings
|
|
219
|
+
|
|
220
|
+
Specifies an array of rules that ignore reports in the template.
|
|
221
|
+
For example, set rules on the template that cannot avoid false positives.
|
|
222
|
+
|
|
223
|
+
#### settings.svelte.compileOptions
|
|
224
|
+
|
|
225
|
+
Specifies options for Svelte compile. Effects rules that use Svelte compile. The target rules are [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) and [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/). **Note that it has no effect on ESLint's custom parser**.
|
|
226
|
+
|
|
227
|
+
- `postcss` ... Specifies options related to PostCSS. You can disable the PostCSS process by specifying `false`.
|
|
228
|
+
- `configFilePath` ... Specifies the path of the directory containing the PostCSS configuration.
|
|
229
|
+
|
|
230
|
+
#### settings.svelte.kit
|
|
231
|
+
|
|
232
|
+
If you use SvelteKit with not default configuration, you need to set below configurations.
|
|
233
|
+
The schema is subset of SvelteKit's configuration.
|
|
234
|
+
Therefore please check [SvelteKit docs](https://kit.svelte.dev/docs/configuration) for more details.
|
|
235
|
+
|
|
236
|
+
e.g.
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
module.exports = {
|
|
240
|
+
// ...
|
|
241
|
+
settings: {
|
|
242
|
+
svelte: {
|
|
243
|
+
kit: {
|
|
244
|
+
files: {
|
|
245
|
+
routes: "src/routes",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
213
248
|
},
|
|
214
249
|
},
|
|
215
250
|
// ...
|
|
@@ -263,15 +298,20 @@ These rules relate to possible syntax or logic errors in Svelte code:
|
|
|
263
298
|
|
|
264
299
|
| Rule ID | Description | |
|
|
265
300
|
|:--------|:------------|:---|
|
|
301
|
+
| [svelte/no-dom-manipulating](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/) | disallow DOM manipulating | |
|
|
266
302
|
| [svelte/no-dupe-else-if-blocks](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
|
|
267
303
|
| [svelte/no-dupe-style-properties](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-style-properties/) | disallow duplicate style properties | :star: |
|
|
268
304
|
| [svelte/no-dynamic-slot-name](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
|
|
305
|
+
| [svelte/no-export-load-in-svelte-module-in-kit-pages](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in Svelte Kit page components. | |
|
|
269
306
|
| [svelte/no-not-function-handler](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: |
|
|
270
307
|
| [svelte/no-object-in-text-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: |
|
|
271
308
|
| [svelte/no-shorthand-style-property-overrides](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: |
|
|
272
309
|
| [svelte/no-store-async](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-store-async/) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | |
|
|
273
310
|
| [svelte/no-unknown-style-directive-property](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: |
|
|
311
|
+
| [svelte/require-store-callbacks-use-set-param](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param | |
|
|
312
|
+
| [svelte/require-store-reactive-access](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: |
|
|
274
313
|
| [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
|
|
314
|
+
| [svelte/valid-prop-names-in-kit-pages](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props other than data or errors in Svelte Kit page components. | |
|
|
275
315
|
|
|
276
316
|
## Security Vulnerability
|
|
277
317
|
|
|
@@ -49,14 +49,14 @@ exports.default = (0, utils_1.createRule)("html-self-closing", {
|
|
|
49
49
|
},
|
|
50
50
|
],
|
|
51
51
|
},
|
|
52
|
-
create(
|
|
52
|
+
create(context) {
|
|
53
53
|
let options = {
|
|
54
54
|
void: "always",
|
|
55
55
|
normal: "always",
|
|
56
56
|
component: "always",
|
|
57
57
|
svelte: "always",
|
|
58
58
|
};
|
|
59
|
-
const option =
|
|
59
|
+
const option = context.options?.[0];
|
|
60
60
|
switch (option) {
|
|
61
61
|
case "none":
|
|
62
62
|
options = {
|
|
@@ -103,16 +103,22 @@ exports.default = (0, utils_1.createRule)("html-self-closing", {
|
|
|
103
103
|
}
|
|
104
104
|
return true;
|
|
105
105
|
}
|
|
106
|
-
function report(node,
|
|
106
|
+
function report(node, shouldBeClosed) {
|
|
107
107
|
const elementType = getElementType(node);
|
|
108
|
-
|
|
108
|
+
context.report({
|
|
109
109
|
node,
|
|
110
|
-
|
|
110
|
+
loc: {
|
|
111
|
+
start: context
|
|
112
|
+
.getSourceCode()
|
|
113
|
+
.getLocFromIndex(node.startTag.range[1] - (node.startTag.selfClosing ? 2 : 1)),
|
|
114
|
+
end: node.loc.end,
|
|
115
|
+
},
|
|
116
|
+
messageId: shouldBeClosed ? "requireClosing" : "disallowClosing",
|
|
111
117
|
data: {
|
|
112
118
|
type: TYPE_MESSAGES[elementType],
|
|
113
119
|
},
|
|
114
120
|
*fix(fixer) {
|
|
115
|
-
if (
|
|
121
|
+
if (shouldBeClosed) {
|
|
116
122
|
for (const child of node.children) {
|
|
117
123
|
yield fixer.removeRange(child.range);
|
|
118
124
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AST } from "svelte-eslint-parser";
|
|
2
|
-
import type
|
|
2
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
3
3
|
declare type AnyToken = AST.Token | AST.Comment;
|
|
4
|
-
export declare function isWhitespace(token: AnyToken |
|
|
5
|
-
export declare function isNotWhitespace(token: AnyToken |
|
|
4
|
+
export declare function isWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
|
|
5
|
+
export declare function isNotWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
|
|
6
6
|
export {};
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.defineVisitor = void 0;
|
|
4
4
|
const commons_1 = require("./commons");
|
|
5
5
|
const eslint_utils_1 = require("eslint-utils");
|
|
6
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
6
7
|
function defineVisitor(context) {
|
|
7
8
|
const { sourceCode, offsets, options } = context;
|
|
8
9
|
function getRootLeft(node) {
|
|
9
10
|
let target = node;
|
|
10
|
-
let parent = getParent(target);
|
|
11
|
+
let parent = (0, ast_utils_1.getParent)(target);
|
|
11
12
|
while (parent &&
|
|
12
13
|
(parent.type === "AssignmentExpression" ||
|
|
13
14
|
parent.type === "AssignmentPattern" ||
|
|
@@ -18,7 +19,7 @@ function defineVisitor(context) {
|
|
|
18
19
|
break;
|
|
19
20
|
}
|
|
20
21
|
target = parent;
|
|
21
|
-
parent = getParent(target);
|
|
22
|
+
parent = (0, ast_utils_1.getParent)(target);
|
|
22
23
|
}
|
|
23
24
|
return target.left;
|
|
24
25
|
}
|
|
@@ -161,12 +162,12 @@ function defineVisitor(context) {
|
|
|
161
162
|
});
|
|
162
163
|
const alternateToken = sourceCode.getTokenAfter(colonToken);
|
|
163
164
|
let baseNode = node;
|
|
164
|
-
let parent = getParent(baseNode);
|
|
165
|
+
let parent = (0, ast_utils_1.getParent)(baseNode);
|
|
165
166
|
while (parent &&
|
|
166
167
|
parent.type === "ConditionalExpression" &&
|
|
167
168
|
parent.alternate === baseNode) {
|
|
168
169
|
baseNode = parent;
|
|
169
|
-
parent = getParent(baseNode);
|
|
170
|
+
parent = (0, ast_utils_1.getParent)(baseNode);
|
|
170
171
|
}
|
|
171
172
|
const baseToken = sourceCode.getFirstToken(baseNode);
|
|
172
173
|
offsets.setOffsetToken([questionToken, colonToken], 1, baseToken);
|
|
@@ -335,9 +336,9 @@ function defineVisitor(context) {
|
|
|
335
336
|
filter: eslint_utils_1.isOpeningParenToken,
|
|
336
337
|
includeComments: false,
|
|
337
338
|
});
|
|
338
|
-
let bodyBaseToken;
|
|
339
|
+
let bodyBaseToken = null;
|
|
339
340
|
if (firstToken.type === "Punctuator") {
|
|
340
|
-
bodyBaseToken = sourceCode.getFirstToken(getParent(node));
|
|
341
|
+
bodyBaseToken = sourceCode.getFirstToken((0, ast_utils_1.getParent)(node));
|
|
341
342
|
}
|
|
342
343
|
else {
|
|
343
344
|
let tokenOffset = 0;
|
|
@@ -735,9 +736,6 @@ function defineVisitor(context) {
|
|
|
735
736
|
};
|
|
736
737
|
}
|
|
737
738
|
exports.defineVisitor = defineVisitor;
|
|
738
|
-
function getParent(node) {
|
|
739
|
-
return node.parent || null;
|
|
740
|
-
}
|
|
741
739
|
function isOptionalToken(token) {
|
|
742
740
|
return token.type === "Punctuator" && token.value === "?.";
|
|
743
741
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const ast_utils_1 = require("../utils/ast-utils");
|
|
5
|
+
const eslint_utils_1 = require("eslint-utils");
|
|
6
|
+
const DOM_MANIPULATING_METHODS = new Set([
|
|
7
|
+
"appendChild",
|
|
8
|
+
"insertBefore",
|
|
9
|
+
"normalize",
|
|
10
|
+
"removeChild",
|
|
11
|
+
"replaceChild",
|
|
12
|
+
"after",
|
|
13
|
+
"append",
|
|
14
|
+
"before",
|
|
15
|
+
"insertAdjacentElement",
|
|
16
|
+
"insertAdjacentHTML",
|
|
17
|
+
"insertAdjacentText",
|
|
18
|
+
"prepend",
|
|
19
|
+
"remove",
|
|
20
|
+
"replaceChildren",
|
|
21
|
+
"replaceWith",
|
|
22
|
+
]);
|
|
23
|
+
const DOM_MANIPULATING_PROPERTIES = new Set([
|
|
24
|
+
"textContent",
|
|
25
|
+
"innerHTML",
|
|
26
|
+
"outerHTML",
|
|
27
|
+
"innerText",
|
|
28
|
+
"outerText",
|
|
29
|
+
]);
|
|
30
|
+
exports.default = (0, utils_1.createRule)("no-dom-manipulating", {
|
|
31
|
+
meta: {
|
|
32
|
+
docs: {
|
|
33
|
+
description: "disallow DOM manipulating",
|
|
34
|
+
category: "Possible Errors",
|
|
35
|
+
recommended: false,
|
|
36
|
+
},
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: {
|
|
39
|
+
disallowManipulateDOM: "Don't manipulate the DOM directly. The Svelte runtime can get confused if there is a difference between the actual DOM and the DOM expected by the Svelte runtime.",
|
|
40
|
+
},
|
|
41
|
+
type: "problem",
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
const domVariables = new Set();
|
|
45
|
+
function verifyIdentifier(node) {
|
|
46
|
+
const member = node.parent;
|
|
47
|
+
if (member?.type !== "MemberExpression" || member.object !== node) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const name = (0, eslint_utils_1.getPropertyName)(member);
|
|
51
|
+
if (!name) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let target = member;
|
|
55
|
+
let parent = target.parent;
|
|
56
|
+
while (parent?.type === "ChainExpression") {
|
|
57
|
+
target = parent;
|
|
58
|
+
parent = parent.parent;
|
|
59
|
+
}
|
|
60
|
+
if (!parent) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (parent.type === "CallExpression") {
|
|
64
|
+
if (parent.callee !== target || !DOM_MANIPULATING_METHODS.has(name)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (parent.type === "AssignmentExpression") {
|
|
69
|
+
if (parent.left !== target || !DOM_MANIPULATING_PROPERTIES.has(name)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
context.report({
|
|
74
|
+
node: member,
|
|
75
|
+
messageId: "disallowManipulateDOM",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
"SvelteDirective[kind='Binding']"(node) {
|
|
80
|
+
if (node.key.name.name !== "this" ||
|
|
81
|
+
!node.expression ||
|
|
82
|
+
node.expression.type !== "Identifier") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const element = node.parent.parent;
|
|
86
|
+
if (element.type !== "SvelteElement" || !isHTMLElement(element)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const variable = (0, ast_utils_1.findVariable)(context, node.expression);
|
|
90
|
+
if (!variable ||
|
|
91
|
+
(variable.scope.type !== "module" && variable.scope.type !== "global")) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
domVariables.add(variable);
|
|
95
|
+
},
|
|
96
|
+
"Program:exit"() {
|
|
97
|
+
for (const variable of domVariables) {
|
|
98
|
+
for (const reference of variable.references) {
|
|
99
|
+
verifyIdentifier(reference.identifier);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
function isHTMLElement(node) {
|
|
105
|
+
return (node.kind === "html" ||
|
|
106
|
+
(node.kind === "special" && (0, ast_utils_1.getNodeName)(node) === "svelte:element"));
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const svelte_kit_1 = require("../utils/svelte-kit");
|
|
5
|
+
exports.default = (0, utils_1.createRule)("no-export-load-in-svelte-module-in-kit-pages", {
|
|
6
|
+
meta: {
|
|
7
|
+
docs: {
|
|
8
|
+
description: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
|
|
9
|
+
category: "Possible Errors",
|
|
10
|
+
recommended: false,
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
unexpected: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
|
|
15
|
+
},
|
|
16
|
+
type: "problem",
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
if (!(0, svelte_kit_1.isKitPageComponent)(context)) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
let isModule = false;
|
|
23
|
+
return {
|
|
24
|
+
[`Program > SvelteScriptElement > SvelteStartTag > SvelteAttribute[key.name="context"] > SvelteLiteral[value="module"]`]: () => {
|
|
25
|
+
isModule = true;
|
|
26
|
+
},
|
|
27
|
+
"Program > SvelteScriptElement:exit": () => {
|
|
28
|
+
isModule = false;
|
|
29
|
+
},
|
|
30
|
+
[`:matches(ExportNamedDeclaration > FunctionDeclaration, ExportNamedDeclaration > VariableDeclaration > VariableDeclarator) > Identifier.id[name="load"]`]: (node) => {
|
|
31
|
+
if (!isModule)
|
|
32
|
+
return {};
|
|
33
|
+
return context.report({
|
|
34
|
+
node,
|
|
35
|
+
loc: node.loc,
|
|
36
|
+
messageId: "unexpected",
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -47,7 +47,8 @@ exports.default = (0, utils_1.createRule)("prefer-destructured-store-props", {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
function isReactiveVariableDefinitionWithMemberExpression(node) {
|
|
50
|
-
return (node.
|
|
50
|
+
return (node.type === "Identifier" &&
|
|
51
|
+
node.parent?.type === "MemberExpression" &&
|
|
51
52
|
node.parent.object === node &&
|
|
52
53
|
(0, eslint_utils_1.getPropertyName)(node.parent) === propName &&
|
|
53
54
|
node.parent.parent?.type === "AssignmentExpression" &&
|
|
@@ -58,7 +59,8 @@ exports.default = (0, utils_1.createRule)("prefer-destructured-store-props", {
|
|
|
58
59
|
.parent?.type === "SvelteReactiveStatement");
|
|
59
60
|
}
|
|
60
61
|
function isReactiveVariableDefinitionWithDestructuring(node) {
|
|
61
|
-
return (node.
|
|
62
|
+
return (node.type === "Identifier" &&
|
|
63
|
+
node.parent?.type === "AssignmentExpression" &&
|
|
62
64
|
node.parent.right === node &&
|
|
63
65
|
node.parent.left.type === "ObjectPattern" &&
|
|
64
66
|
node.parent.parent?.type === "ExpressionStatement" &&
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
2
|
import type { RuleContext } from "../../types";
|
|
3
3
|
declare type StoreName = "writable" | "readable" | "derived";
|
|
4
4
|
export declare function extractStoreReferences(context: RuleContext, storeNames?: StoreName[]): Generator<{
|
|
5
|
-
node:
|
|
5
|
+
node: TSESTree.CallExpression;
|
|
6
6
|
name: string;
|
|
7
7
|
}, void>;
|
|
8
|
+
export declare type StoreChecker = (node: TSESTree.Expression, options?: {
|
|
9
|
+
consistent?: boolean;
|
|
10
|
+
}) => boolean;
|
|
11
|
+
export declare function createStoreChecker(context: RuleContext): StoreChecker;
|
|
8
12
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractStoreReferences = void 0;
|
|
3
|
+
exports.createStoreChecker = exports.extractStoreReferences = void 0;
|
|
4
4
|
const eslint_utils_1 = require("eslint-utils");
|
|
5
|
+
const ts_utils_1 = require("../../utils/ts-utils");
|
|
6
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
5
7
|
function* extractStoreReferences(context, storeNames = ["writable", "readable", "derived"]) {
|
|
6
8
|
const referenceTracker = new eslint_utils_1.ReferenceTracker(context.getScope());
|
|
7
9
|
for (const { node, path } of referenceTracker.iterateEsmReferences({
|
|
@@ -25,3 +27,99 @@ function* extractStoreReferences(context, storeNames = ["writable", "readable",
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
exports.extractStoreReferences = extractStoreReferences;
|
|
30
|
+
function createStoreChecker(context) {
|
|
31
|
+
const tools = (0, ts_utils_1.getTypeScriptTools)(context);
|
|
32
|
+
const checker = tools
|
|
33
|
+
? createStoreCheckerForTS(tools)
|
|
34
|
+
: createStoreCheckerForES(context);
|
|
35
|
+
return (node, options) => checker(node, {
|
|
36
|
+
consistent: options?.consistent ?? false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
exports.createStoreChecker = createStoreChecker;
|
|
40
|
+
function createStoreCheckerForES(context) {
|
|
41
|
+
const storeVariables = new Map();
|
|
42
|
+
for (const { node } of extractStoreReferences(context)) {
|
|
43
|
+
const parent = (0, ast_utils_1.getParent)(node);
|
|
44
|
+
if (!parent ||
|
|
45
|
+
parent.type !== "VariableDeclarator" ||
|
|
46
|
+
parent.id.type !== "Identifier") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const decl = (0, ast_utils_1.getParent)(parent);
|
|
50
|
+
if (!decl || decl.type !== "VariableDeclaration") {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const variable = (0, ast_utils_1.findVariable)(context, parent.id);
|
|
54
|
+
if (variable) {
|
|
55
|
+
storeVariables.set(variable, { const: decl.kind === "const" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return (node, options) => {
|
|
59
|
+
if (node.type !== "Identifier" || node.name.startsWith("$")) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const variable = (0, ast_utils_1.findVariable)(context, node);
|
|
63
|
+
if (!variable) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const info = storeVariables.get(variable);
|
|
67
|
+
if (!info) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return options.consistent ? info.const : true;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function createStoreCheckerForTS(tools) {
|
|
74
|
+
const { service } = tools;
|
|
75
|
+
const checker = service.program.getTypeChecker();
|
|
76
|
+
const tsNodeMap = service.esTreeNodeToTSNodeMap;
|
|
77
|
+
return (node, options) => {
|
|
78
|
+
const tsNode = tsNodeMap.get(node);
|
|
79
|
+
if (!tsNode) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
83
|
+
return isStoreType(checker.getApparentType(type));
|
|
84
|
+
function isStoreType(type) {
|
|
85
|
+
return eachTypeCheck(type, options, (type) => {
|
|
86
|
+
const subscribe = type.getProperty("subscribe");
|
|
87
|
+
if (!subscribe) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const subscribeType = checker.getTypeOfSymbolAtLocation(subscribe, tsNode);
|
|
91
|
+
return isStoreSubscribeSignatureType(subscribeType);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function isStoreSubscribeSignatureType(type) {
|
|
95
|
+
return eachTypeCheck(type, options, (type) => {
|
|
96
|
+
for (const signature of type.getCallSignatures()) {
|
|
97
|
+
if (signature.parameters.length >= 2 &&
|
|
98
|
+
maybeFunctionSymbol(signature.parameters[0]) &&
|
|
99
|
+
maybeFunctionSymbol(signature.parameters[1])) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function maybeFunctionSymbol(param) {
|
|
107
|
+
const type = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, tsNode));
|
|
108
|
+
return maybeFunctionType(type);
|
|
109
|
+
}
|
|
110
|
+
function maybeFunctionType(type) {
|
|
111
|
+
return eachTypeCheck(type, { consistent: false }, (type) => {
|
|
112
|
+
return type.getCallSignatures().length > 0;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function eachTypeCheck(type, options, check) {
|
|
118
|
+
if (type.isUnion()) {
|
|
119
|
+
if (options.consistent) {
|
|
120
|
+
return type.types.every((t) => eachTypeCheck(t, options, check));
|
|
121
|
+
}
|
|
122
|
+
return type.types.some((t) => eachTypeCheck(t, options, check));
|
|
123
|
+
}
|
|
124
|
+
return check(type);
|
|
125
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const svelte_store_1 = require("./reference-helpers/svelte-store");
|
|
5
|
+
exports.default = (0, utils_1.createRule)("require-store-callbacks-use-set-param", {
|
|
6
|
+
meta: {
|
|
7
|
+
docs: {
|
|
8
|
+
description: "store callbacks must use `set` param",
|
|
9
|
+
category: "Possible Errors",
|
|
10
|
+
recommended: false,
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
unexpected: "Store callbacks must use `set` param.",
|
|
15
|
+
},
|
|
16
|
+
type: "suggestion",
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
Program() {
|
|
21
|
+
for (const { node } of (0, svelte_store_1.extractStoreReferences)(context, [
|
|
22
|
+
"readable",
|
|
23
|
+
"writable",
|
|
24
|
+
])) {
|
|
25
|
+
const [_, fn] = node.arguments;
|
|
26
|
+
if (!fn ||
|
|
27
|
+
(fn.type !== "ArrowFunctionExpression" &&
|
|
28
|
+
fn.type !== "FunctionExpression")) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const param = fn.params[0];
|
|
32
|
+
if (!param || (param.type === "Identifier" && param.name !== "set")) {
|
|
33
|
+
context.report({
|
|
34
|
+
node: fn,
|
|
35
|
+
loc: fn.loc,
|
|
36
|
+
messageId: "unexpected",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|