eslint-plugin-svelte 3.5.0 → 3.6.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 +10 -7
- package/lib/configs/flat/recommended.js +1 -0
- package/lib/main.d.ts +1 -1
- package/lib/meta.d.ts +2 -2
- package/lib/meta.js +1 -1
- package/lib/rule-types.d.ts +20 -0
- package/lib/rules/consistent-selector-style.js +64 -17
- package/lib/rules/no-add-event-listener.d.ts +2 -0
- package/lib/rules/no-add-event-listener.js +58 -0
- package/lib/rules/no-navigation-without-base.js +3 -51
- package/lib/rules/no-unused-svelte-ignore.js +10 -0
- package/lib/rules/prefer-writable-derived.d.ts +2 -0
- package/lib/rules/prefer-writable-derived.js +107 -0
- package/lib/rules/require-event-prefix.d.ts +2 -0
- package/lib/rules/require-event-prefix.js +93 -0
- package/lib/utils/expression-affixes.d.ts +6 -0
- package/lib/utils/expression-affixes.js +138 -0
- package/lib/utils/rules.js +6 -0
- package/lib/utils/ts-utils/index.d.ts +24 -0
- package/lib/utils/ts-utils/index.js +36 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
|
|
31
31
|
## Introduction
|
|
32
32
|
|
|
33
|
-
`eslint-plugin-svelte` is the official [ESLint](https://eslint.org/) plugin for [Svelte](https://svelte.dev/)
|
|
34
|
-
It leverages the AST generated by [svelte-eslint-parser](https://github.com/sveltejs/svelte-eslint-parser) to provide custom linting for Svelte
|
|
33
|
+
`eslint-plugin-svelte` is the official [ESLint](https://eslint.org/) plugin for [Svelte](https://svelte.dev/).\
|
|
34
|
+
It leverages the AST generated by [svelte-eslint-parser](https://github.com/sveltejs/svelte-eslint-parser) to provide custom linting for Svelte.\
|
|
35
35
|
Note that `eslint-plugin-svelte` and `svelte-eslint-parser` cannot be used alongside [eslint-plugin-svelte3](https://github.com/sveltejs/eslint-plugin-svelte3).
|
|
36
36
|
|
|
37
37
|
<!--USAGE_SECTION_START-->
|
|
@@ -219,8 +219,8 @@ export default [
|
|
|
219
219
|
|
|
220
220
|
## Editor Integrations
|
|
221
221
|
|
|
222
|
-
**Visual Studio Code
|
|
223
|
-
Install [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
|
|
222
|
+
**Visual Studio Code**\
|
|
223
|
+
Install [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint).\
|
|
224
224
|
Configure `.svelte` files in `.vscode/settings.json`:
|
|
225
225
|
|
|
226
226
|
```json
|
|
@@ -247,8 +247,8 @@ This project follows [Semantic Versioning](https://semver.org/). Unlike [ESLint
|
|
|
247
247
|
<!-- prettier-ignore-start -->
|
|
248
248
|
<!--RULES_SECTION_START-->
|
|
249
249
|
|
|
250
|
-
:wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems
|
|
251
|
-
:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions)
|
|
250
|
+
:wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.\
|
|
251
|
+
:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\
|
|
252
252
|
:star: Indicates that the rule is included in the `plugin:svelte/recommended` config.
|
|
253
253
|
|
|
254
254
|
<!--RULES_TABLE_START-->
|
|
@@ -294,6 +294,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|
|
|
294
294
|
|:--------|:------------|:---|
|
|
295
295
|
| [svelte/block-lang](https://sveltejs.github.io/eslint-plugin-svelte/rules/block-lang/) | disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks. | :bulb: |
|
|
296
296
|
| [svelte/button-has-type](https://sveltejs.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
|
|
297
|
+
| [svelte/no-add-event-listener](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-add-event-listener/) | Warns against the use of `addEventListener` | :bulb: |
|
|
297
298
|
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star::bulb: |
|
|
298
299
|
| [svelte/no-ignored-unsubscribe](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-ignored-unsubscribe/) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
|
|
299
300
|
| [svelte/no-immutable-reactive-statements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-immutable-reactive-statements/) | disallow reactive statements that don't reference reactive values. | :star: |
|
|
@@ -310,6 +311,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|
|
|
310
311
|
| [svelte/no-useless-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :star::wrench: |
|
|
311
312
|
| [svelte/prefer-const](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-const/) | Require `const` declarations for variables that are never reassigned after declared | :wrench: |
|
|
312
313
|
| [svelte/prefer-destructured-store-props](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
|
|
314
|
+
| [svelte/prefer-writable-derived](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-writable-derived/) | Prefer using writable $derived instead of $state and $effect | :star::bulb: |
|
|
313
315
|
| [svelte/require-each-key](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-each-key/) | require keyed `{#each}` block | :star: |
|
|
314
316
|
| [svelte/require-event-dispatcher-types](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for `createEventDispatcher` | :star: |
|
|
315
317
|
| [svelte/require-optimized-style-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
|
|
@@ -337,6 +339,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
|
|
|
337
339
|
| [svelte/no-spaces-around-equal-signs-in-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-spaces-around-equal-signs-in-attribute/) | disallow spaces around equal signs in attribute | :wrench: |
|
|
338
340
|
| [svelte/prefer-class-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
|
|
339
341
|
| [svelte/prefer-style-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
|
|
342
|
+
| [svelte/require-event-prefix](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-prefix/) | require component event names to start with "on" | |
|
|
340
343
|
| [svelte/shorthand-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
|
|
341
344
|
| [svelte/shorthand-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/) | enforce use of shorthand syntax in directives | :wrench: |
|
|
342
345
|
| [svelte/sort-attributes](https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/) | enforce order of attributes | :wrench: |
|
|
@@ -398,7 +401,7 @@ These rules relate to this plugin works:
|
|
|
398
401
|
|
|
399
402
|
## Contributing
|
|
400
403
|
|
|
401
|
-
Contributions are welcome! Please open an issue or submit a PR. For more details, see [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
404
|
+
Contributions are welcome! Please open an issue or submit a PR. For more details, see [CONTRIBUTING.md](./CONTRIBUTING.md).\
|
|
402
405
|
Refer to [svelte-eslint-parser](https://github.com/sveltejs/svelte-eslint-parser) for AST details.
|
|
403
406
|
|
|
404
407
|
<!--DOCS_IGNORE_END-->
|
|
@@ -33,6 +33,7 @@ const config = [
|
|
|
33
33
|
'svelte/no-unused-svelte-ignore': 'error',
|
|
34
34
|
'svelte/no-useless-children-snippet': 'error',
|
|
35
35
|
'svelte/no-useless-mustaches': 'error',
|
|
36
|
+
'svelte/prefer-writable-derived': 'error',
|
|
36
37
|
'svelte/require-each-key': 'error',
|
|
37
38
|
'svelte/require-event-dispatcher-types': 'error',
|
|
38
39
|
'svelte/require-store-reactive-access': 'error',
|
package/lib/main.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const configs: {
|
|
|
14
14
|
export declare const rules: Record<string, Rule.RuleModule>;
|
|
15
15
|
export declare const meta: {
|
|
16
16
|
name: "eslint-plugin-svelte";
|
|
17
|
-
version: "3.
|
|
17
|
+
version: "3.6.0";
|
|
18
18
|
};
|
|
19
19
|
export declare const processors: {
|
|
20
20
|
'.svelte': typeof processor;
|
package/lib/meta.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const name
|
|
2
|
-
export declare const version
|
|
1
|
+
export declare const name: "eslint-plugin-svelte";
|
|
2
|
+
export declare const version: "3.6.0";
|
package/lib/meta.js
CHANGED
package/lib/rule-types.d.ts
CHANGED
|
@@ -92,6 +92,11 @@ export interface RuleOptions {
|
|
|
92
92
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/mustache-spacing/
|
|
93
93
|
*/
|
|
94
94
|
'svelte/mustache-spacing'?: Linter.RuleEntry<SvelteMustacheSpacing>;
|
|
95
|
+
/**
|
|
96
|
+
* Warns against the use of `addEventListener`
|
|
97
|
+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-add-event-listener/
|
|
98
|
+
*/
|
|
99
|
+
'svelte/no-add-event-listener'?: Linter.RuleEntry<[]>;
|
|
95
100
|
/**
|
|
96
101
|
* disallow the use of `{@debug}`
|
|
97
102
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/
|
|
@@ -299,6 +304,11 @@ export interface RuleOptions {
|
|
|
299
304
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/
|
|
300
305
|
*/
|
|
301
306
|
'svelte/prefer-style-directive'?: Linter.RuleEntry<[]>;
|
|
307
|
+
/**
|
|
308
|
+
* Prefer using writable $derived instead of $state and $effect
|
|
309
|
+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-writable-derived/
|
|
310
|
+
*/
|
|
311
|
+
'svelte/prefer-writable-derived'?: Linter.RuleEntry<[]>;
|
|
302
312
|
/**
|
|
303
313
|
* require keyed `{#each}` block
|
|
304
314
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-each-key/
|
|
@@ -309,6 +319,11 @@ export interface RuleOptions {
|
|
|
309
319
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/
|
|
310
320
|
*/
|
|
311
321
|
'svelte/require-event-dispatcher-types'?: Linter.RuleEntry<[]>;
|
|
322
|
+
/**
|
|
323
|
+
* require component event names to start with "on"
|
|
324
|
+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-prefix/
|
|
325
|
+
*/
|
|
326
|
+
'svelte/require-event-prefix'?: Linter.RuleEntry<SvelteRequireEventPrefix>;
|
|
312
327
|
/**
|
|
313
328
|
* require style attributes that can be optimized
|
|
314
329
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/
|
|
@@ -568,6 +583,11 @@ type SveltePreferConst = [] | [
|
|
|
568
583
|
excludedRunes?: string[];
|
|
569
584
|
}
|
|
570
585
|
];
|
|
586
|
+
type SvelteRequireEventPrefix = [] | [
|
|
587
|
+
{
|
|
588
|
+
checkAsyncFunctions?: boolean;
|
|
589
|
+
}
|
|
590
|
+
];
|
|
571
591
|
type SvelteShorthandAttribute = [] | [
|
|
572
592
|
{
|
|
573
593
|
prefer?: ("always" | "never");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findClassesInAttribute } from '../utils/ast-utils.js';
|
|
2
|
+
import { extractExpressionPrefixLiteral, extractExpressionSuffixLiteral } from '../utils/expression-affixes.js';
|
|
2
3
|
import { createRule } from '../utils/index.js';
|
|
3
4
|
export default createRule('consistent-selector-style', {
|
|
4
5
|
meta: {
|
|
@@ -49,9 +50,19 @@ export default createRule('consistent-selector-style', {
|
|
|
49
50
|
const checkGlobal = context.options[0]?.checkGlobal ?? false;
|
|
50
51
|
const style = context.options[0]?.style ?? ['type', 'id', 'class'];
|
|
51
52
|
const whitelistedClasses = [];
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const selections = {
|
|
54
|
+
class: {
|
|
55
|
+
exact: new Map(),
|
|
56
|
+
affixes: new Map(),
|
|
57
|
+
universalSelector: false
|
|
58
|
+
},
|
|
59
|
+
id: {
|
|
60
|
+
exact: new Map(),
|
|
61
|
+
affixes: new Map(),
|
|
62
|
+
universalSelector: false
|
|
63
|
+
},
|
|
64
|
+
type: new Map()
|
|
65
|
+
};
|
|
55
66
|
/**
|
|
56
67
|
* Checks selectors in a given PostCSS node
|
|
57
68
|
*/
|
|
@@ -89,10 +100,10 @@ export default createRule('consistent-selector-style', {
|
|
|
89
100
|
* Checks a class selector
|
|
90
101
|
*/
|
|
91
102
|
function checkClassSelector(node) {
|
|
92
|
-
if (whitelistedClasses.includes(node.value)) {
|
|
103
|
+
if (selections.class.universalSelector || whitelistedClasses.includes(node.value)) {
|
|
93
104
|
return;
|
|
94
105
|
}
|
|
95
|
-
const selection =
|
|
106
|
+
const selection = matchSelection(selections.class, node.value);
|
|
96
107
|
for (const styleValue of style) {
|
|
97
108
|
if (styleValue === 'class') {
|
|
98
109
|
return;
|
|
@@ -104,7 +115,7 @@ export default createRule('consistent-selector-style', {
|
|
|
104
115
|
});
|
|
105
116
|
return;
|
|
106
117
|
}
|
|
107
|
-
if (styleValue === 'type' && canUseTypeSelector(selection,
|
|
118
|
+
if (styleValue === 'type' && canUseTypeSelector(selection, selections.type)) {
|
|
108
119
|
context.report({
|
|
109
120
|
messageId: 'classShouldBeType',
|
|
110
121
|
loc: styleSelectorNodeLoc(node)
|
|
@@ -117,7 +128,10 @@ export default createRule('consistent-selector-style', {
|
|
|
117
128
|
* Checks an ID selector
|
|
118
129
|
*/
|
|
119
130
|
function checkIdSelector(node) {
|
|
120
|
-
|
|
131
|
+
if (selections.id.universalSelector) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const selection = matchSelection(selections.id, node.value);
|
|
121
135
|
for (const styleValue of style) {
|
|
122
136
|
if (styleValue === 'class') {
|
|
123
137
|
context.report({
|
|
@@ -129,7 +143,7 @@ export default createRule('consistent-selector-style', {
|
|
|
129
143
|
if (styleValue === 'id') {
|
|
130
144
|
return;
|
|
131
145
|
}
|
|
132
|
-
if (styleValue === 'type' && canUseTypeSelector(selection,
|
|
146
|
+
if (styleValue === 'type' && canUseTypeSelector(selection, selections.type)) {
|
|
133
147
|
context.report({
|
|
134
148
|
messageId: 'idShouldBeType',
|
|
135
149
|
loc: styleSelectorNodeLoc(node)
|
|
@@ -142,7 +156,7 @@ export default createRule('consistent-selector-style', {
|
|
|
142
156
|
* Checks a type selector
|
|
143
157
|
*/
|
|
144
158
|
function checkTypeSelector(node) {
|
|
145
|
-
const selection =
|
|
159
|
+
const selection = selections.type.get(node.value) ?? [];
|
|
146
160
|
for (const styleValue of style) {
|
|
147
161
|
if (styleValue === 'class') {
|
|
148
162
|
context.report({
|
|
@@ -168,21 +182,42 @@ export default createRule('consistent-selector-style', {
|
|
|
168
182
|
if (node.kind !== 'html') {
|
|
169
183
|
return;
|
|
170
184
|
}
|
|
171
|
-
addToArrayMap(
|
|
172
|
-
const classes = node.startTag.attributes.flatMap(findClassesInAttribute);
|
|
173
|
-
for (const className of classes) {
|
|
174
|
-
addToArrayMap(classSelections, className, node);
|
|
175
|
-
}
|
|
185
|
+
addToArrayMap(selections.type, node.name.name, node);
|
|
176
186
|
for (const attribute of node.startTag.attributes) {
|
|
177
187
|
if (attribute.type === 'SvelteDirective' && attribute.kind === 'Class') {
|
|
178
188
|
whitelistedClasses.push(attribute.key.name.name);
|
|
179
189
|
}
|
|
180
|
-
|
|
190
|
+
for (const className of findClassesInAttribute(attribute)) {
|
|
191
|
+
addToArrayMap(selections.class.exact, className, node);
|
|
192
|
+
}
|
|
193
|
+
if (attribute.type !== 'SvelteAttribute') {
|
|
181
194
|
continue;
|
|
182
195
|
}
|
|
183
196
|
for (const value of attribute.value) {
|
|
184
|
-
if (value.type === '
|
|
185
|
-
|
|
197
|
+
if (attribute.key.name === 'class' && value.type === 'SvelteMustacheTag') {
|
|
198
|
+
const prefix = extractExpressionPrefixLiteral(context, value.expression);
|
|
199
|
+
const suffix = extractExpressionSuffixLiteral(context, value.expression);
|
|
200
|
+
if (prefix === null && suffix === null) {
|
|
201
|
+
selections.class.universalSelector = true;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
addToArrayMap(selections.class.affixes, [prefix, suffix], node);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (attribute.key.name === 'id') {
|
|
208
|
+
if (value.type === 'SvelteLiteral') {
|
|
209
|
+
addToArrayMap(selections.id.exact, value.value, node);
|
|
210
|
+
}
|
|
211
|
+
else if (value.type === 'SvelteMustacheTag') {
|
|
212
|
+
const prefix = extractExpressionPrefixLiteral(context, value.expression);
|
|
213
|
+
const suffix = extractExpressionSuffixLiteral(context, value.expression);
|
|
214
|
+
if (prefix === null && suffix === null) {
|
|
215
|
+
selections.id.universalSelector = true;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
addToArrayMap(selections.id.affixes, [prefix, suffix], node);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
186
221
|
}
|
|
187
222
|
}
|
|
188
223
|
}
|
|
@@ -204,6 +239,18 @@ export default createRule('consistent-selector-style', {
|
|
|
204
239
|
function addToArrayMap(map, key, value) {
|
|
205
240
|
map.set(key, (map.get(key) ?? []).concat(value));
|
|
206
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Finds all nodes in selections that could be matched by key
|
|
244
|
+
*/
|
|
245
|
+
function matchSelection(selections, key) {
|
|
246
|
+
const selection = selections.exact.get(key) ?? [];
|
|
247
|
+
selections.affixes.forEach((nodes, [prefix, suffix]) => {
|
|
248
|
+
if ((prefix === null || key.startsWith(prefix)) && (suffix === null || key.endsWith(suffix))) {
|
|
249
|
+
selection.push(...nodes);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return selection;
|
|
253
|
+
}
|
|
207
254
|
/**
|
|
208
255
|
* Checks whether a given selection could be obtained using an ID selector
|
|
209
256
|
*/
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRule } from '../utils/index.js';
|
|
2
|
+
export default createRule('no-add-event-listener', {
|
|
3
|
+
meta: {
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Warns against the use of `addEventListener`',
|
|
6
|
+
category: 'Best Practices',
|
|
7
|
+
recommended: false
|
|
8
|
+
},
|
|
9
|
+
hasSuggestions: true,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
unexpected: 'Do not use `addEventListener`. Use the `on` function from `svelte/events` instead.'
|
|
13
|
+
},
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
conditions: [
|
|
16
|
+
{
|
|
17
|
+
svelteVersions: ['5']
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
CallExpression(node) {
|
|
24
|
+
const { callee } = node;
|
|
25
|
+
let target = null;
|
|
26
|
+
if (callee.type === 'MemberExpression' &&
|
|
27
|
+
callee.property.type === 'Identifier' &&
|
|
28
|
+
callee.property.name === 'addEventListener') {
|
|
29
|
+
target = context.sourceCode.getText(callee.object);
|
|
30
|
+
}
|
|
31
|
+
else if (callee.type === 'Identifier' && callee.name === 'addEventListener') {
|
|
32
|
+
target = 'window';
|
|
33
|
+
}
|
|
34
|
+
if (target === null) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const openParen = context.sourceCode.getTokenAfter(callee);
|
|
38
|
+
const suggest = [];
|
|
39
|
+
if (openParen !== null) {
|
|
40
|
+
suggest.push({
|
|
41
|
+
desc: 'Use `on` from `svelte/events` instead',
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
return [
|
|
44
|
+
fixer.replaceText(callee, 'on'),
|
|
45
|
+
fixer.insertTextAfter(openParen, `${target}, `)
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'unexpected',
|
|
53
|
+
suggest
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRule } from '../utils/index.js';
|
|
2
2
|
import { ReferenceTracker } from '@eslint-community/eslint-utils';
|
|
3
3
|
import { findVariable } from '../utils/ast-utils.js';
|
|
4
|
+
import { extractExpressionPrefixVariable } from '../utils/expression-affixes.js';
|
|
4
5
|
export default createRule('no-navigation-without-base', {
|
|
5
6
|
meta: {
|
|
6
7
|
docs: {
|
|
@@ -163,57 +164,8 @@ function checkShallowNavigationCall(context, call, basePathNames, messageId) {
|
|
|
163
164
|
}
|
|
164
165
|
// Helper functions
|
|
165
166
|
function expressionStartsWithBase(context, url, basePathNames) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return binaryExpressionStartsWithBase(context, url, basePathNames);
|
|
169
|
-
case 'Identifier':
|
|
170
|
-
return variableStartsWithBase(context, url, basePathNames);
|
|
171
|
-
case 'MemberExpression':
|
|
172
|
-
return memberExpressionStartsWithBase(url, basePathNames);
|
|
173
|
-
case 'TemplateLiteral':
|
|
174
|
-
return templateLiteralStartsWithBase(context, url, basePathNames);
|
|
175
|
-
default:
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
function binaryExpressionStartsWithBase(context, url, basePathNames) {
|
|
180
|
-
return (url.left.type !== 'PrivateIdentifier' &&
|
|
181
|
-
expressionStartsWithBase(context, url.left, basePathNames));
|
|
182
|
-
}
|
|
183
|
-
function memberExpressionStartsWithBase(url, basePathNames) {
|
|
184
|
-
return url.property.type === 'Identifier' && basePathNames.has(url.property);
|
|
185
|
-
}
|
|
186
|
-
function variableStartsWithBase(context, url, basePathNames) {
|
|
187
|
-
if (basePathNames.has(url)) {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
const variable = findVariable(context, url);
|
|
191
|
-
if (variable === null ||
|
|
192
|
-
variable.identifiers.length !== 1 ||
|
|
193
|
-
variable.identifiers[0].parent.type !== 'VariableDeclarator' ||
|
|
194
|
-
variable.identifiers[0].parent.init === null) {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
return expressionStartsWithBase(context, variable.identifiers[0].parent.init, basePathNames);
|
|
198
|
-
}
|
|
199
|
-
function templateLiteralStartsWithBase(context, url, basePathNames) {
|
|
200
|
-
const startingIdentifier = extractLiteralStartingExpression(url);
|
|
201
|
-
return (startingIdentifier !== undefined &&
|
|
202
|
-
expressionStartsWithBase(context, startingIdentifier, basePathNames));
|
|
203
|
-
}
|
|
204
|
-
function extractLiteralStartingExpression(templateLiteral) {
|
|
205
|
-
const literalParts = [...templateLiteral.expressions, ...templateLiteral.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1);
|
|
206
|
-
for (const part of literalParts) {
|
|
207
|
-
if (part.type === 'TemplateElement' && part.value.raw === '') {
|
|
208
|
-
// Skip empty quasi in the begining
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (part.type !== 'TemplateElement') {
|
|
212
|
-
return part;
|
|
213
|
-
}
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
return undefined;
|
|
167
|
+
const prefixVariable = extractExpressionPrefixVariable(context, url);
|
|
168
|
+
return prefixVariable !== null && basePathNames.has(prefixVariable);
|
|
217
169
|
}
|
|
218
170
|
function expressionIsEmpty(url) {
|
|
219
171
|
return ((url.type === 'Literal' && url.value === '') ||
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getSvelteCompileWarnings } from '../shared/svelte-compile-warns/index.js';
|
|
2
2
|
import { createRule } from '../utils/index.js';
|
|
3
3
|
import { getSvelteIgnoreItems } from '../shared/svelte-compile-warns/ignore-comment.js';
|
|
4
|
+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
|
|
5
|
+
import semver from 'semver';
|
|
4
6
|
export default createRule('no-unused-svelte-ignore', {
|
|
5
7
|
meta: {
|
|
6
8
|
docs: {
|
|
@@ -40,6 +42,14 @@ export default createRule('no-unused-svelte-ignore', {
|
|
|
40
42
|
return {};
|
|
41
43
|
}
|
|
42
44
|
for (const unused of warnings.unusedIgnores) {
|
|
45
|
+
if (unused.code === 'reactive-component' && semver.satisfies(SVELTE_VERSION, '<5')) {
|
|
46
|
+
// Svelte v4 `reactive-component` warnings are not emitted
|
|
47
|
+
// when we use the `generate: false` compiler option.
|
|
48
|
+
// This is probably not the intended behavior of Svelte v4, but it's not going to be fixed,
|
|
49
|
+
// so as a workaround we'll ignore the `reactive-component` warnings.
|
|
50
|
+
// See https://github.com/sveltejs/eslint-plugin-svelte/issues/1192
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
43
53
|
context.report({
|
|
44
54
|
loc: {
|
|
45
55
|
start: sourceCode.getLocFromIndex(unused.range[0]),
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createRule } from '../utils/index.js';
|
|
2
|
+
import { getScope } from '../utils/ast-utils.js';
|
|
3
|
+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
// Writable derived were introduced in Svelte 5.25.0
|
|
6
|
+
const shouldRun = semver.satisfies(SVELTE_VERSION, '>=5.25.0');
|
|
7
|
+
function isEffectOrEffectPre(node) {
|
|
8
|
+
if (node.callee.type === 'Identifier') {
|
|
9
|
+
return node.callee.name === '$effect';
|
|
10
|
+
}
|
|
11
|
+
if (node.callee.type === 'MemberExpression') {
|
|
12
|
+
return (node.callee.object.type === 'Identifier' &&
|
|
13
|
+
node.callee.object.name === '$effect' &&
|
|
14
|
+
node.callee.property.type === 'Identifier' &&
|
|
15
|
+
node.callee.property.name === 'pre');
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function isValidFunctionArgument(argument) {
|
|
20
|
+
if ((argument.type !== 'FunctionExpression' && argument.type !== 'ArrowFunctionExpression') ||
|
|
21
|
+
argument.params.length !== 0) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (argument.body.type !== 'BlockStatement') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return argument.body.body.length === 1;
|
|
28
|
+
}
|
|
29
|
+
function isValidAssignment(statement) {
|
|
30
|
+
if (statement.type !== 'ExpressionStatement')
|
|
31
|
+
return false;
|
|
32
|
+
const { expression } = statement;
|
|
33
|
+
return (expression.type === 'AssignmentExpression' &&
|
|
34
|
+
expression.operator === '=' &&
|
|
35
|
+
expression.left.type === 'Identifier');
|
|
36
|
+
}
|
|
37
|
+
function isStateVariable(init) {
|
|
38
|
+
return (init?.type === 'CallExpression' &&
|
|
39
|
+
init.callee.type === 'Identifier' &&
|
|
40
|
+
init.callee.name === '$state');
|
|
41
|
+
}
|
|
42
|
+
export default createRule('prefer-writable-derived', {
|
|
43
|
+
meta: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Prefer using writable $derived instead of $state and $effect',
|
|
46
|
+
category: 'Best Practices',
|
|
47
|
+
recommended: true
|
|
48
|
+
},
|
|
49
|
+
schema: [],
|
|
50
|
+
messages: {
|
|
51
|
+
unexpected: 'Prefer using writable $derived instead of $state and $effect',
|
|
52
|
+
suggestRewrite: 'Rewrite $state and $effect to $derived'
|
|
53
|
+
},
|
|
54
|
+
type: 'suggestion',
|
|
55
|
+
conditions: [
|
|
56
|
+
{
|
|
57
|
+
svelteVersions: ['5'],
|
|
58
|
+
runes: [true, 'undetermined']
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
hasSuggestions: true
|
|
62
|
+
},
|
|
63
|
+
create(context) {
|
|
64
|
+
if (!shouldRun) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
CallExpression: (node) => {
|
|
69
|
+
if (!isEffectOrEffectPre(node) || node.arguments.length !== 1) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const argument = node.arguments[0];
|
|
73
|
+
if (!isValidFunctionArgument(argument)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const statement = argument.body.body[0];
|
|
77
|
+
if (!isValidAssignment(statement)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const { left, right } = statement.expression;
|
|
81
|
+
const scope = getScope(context, statement);
|
|
82
|
+
const reference = scope.references.find((ref) => ref.identifier.type === 'Identifier' && ref.identifier.name === left.name);
|
|
83
|
+
const def = reference?.resolved?.defs?.[0];
|
|
84
|
+
if (!def || def.type !== 'Variable' || def.node.type !== 'VariableDeclarator') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { init } = def.node;
|
|
88
|
+
if (!isStateVariable(init)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
context.report({
|
|
92
|
+
node: def.node,
|
|
93
|
+
messageId: 'unexpected',
|
|
94
|
+
suggest: [
|
|
95
|
+
{
|
|
96
|
+
messageId: 'suggestRewrite',
|
|
97
|
+
fix: (fixer) => {
|
|
98
|
+
const rightCode = context.sourceCode.getText(right);
|
|
99
|
+
return [fixer.replaceText(init, `$derived(${rightCode})`), fixer.remove(node)];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createRule } from '../utils/index.js';
|
|
2
|
+
import { getTypeScriptTools, isMethodSymbol, isPropertySignatureKind, isFunctionTypeKind, isMethodSignatureKind, isTypeReferenceKind, isIdentifierKind } from '../utils/ts-utils/index.js';
|
|
3
|
+
export default createRule('require-event-prefix', {
|
|
4
|
+
meta: {
|
|
5
|
+
docs: {
|
|
6
|
+
description: 'require component event names to start with "on"',
|
|
7
|
+
category: 'Stylistic Issues',
|
|
8
|
+
conflictWithPrettier: false,
|
|
9
|
+
recommended: false
|
|
10
|
+
},
|
|
11
|
+
schema: [
|
|
12
|
+
{
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
checkAsyncFunctions: {
|
|
16
|
+
type: 'boolean'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
additionalProperties: false
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
messages: {
|
|
23
|
+
nonPrefixedFunction: 'Component event name must start with "on".'
|
|
24
|
+
},
|
|
25
|
+
type: 'suggestion',
|
|
26
|
+
conditions: [
|
|
27
|
+
{
|
|
28
|
+
svelteVersions: ['5'],
|
|
29
|
+
svelteFileTypes: ['.svelte']
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
const tsTools = getTypeScriptTools(context);
|
|
35
|
+
if (!tsTools) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const checkAsyncFunctions = context.options[0]?.checkAsyncFunctions ?? false;
|
|
39
|
+
return {
|
|
40
|
+
CallExpression(node) {
|
|
41
|
+
const propsType = getPropsType(node, tsTools);
|
|
42
|
+
if (propsType === undefined) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const property of propsType.getProperties()) {
|
|
46
|
+
if (isFunctionLike(property, tsTools) &&
|
|
47
|
+
!property.getName().startsWith('on') &&
|
|
48
|
+
(checkAsyncFunctions || !isFunctionAsync(property, tsTools))) {
|
|
49
|
+
const declarationTsNode = property.getDeclarations()?.[0];
|
|
50
|
+
const declarationEstreeNode = declarationTsNode !== undefined
|
|
51
|
+
? tsTools.service.tsNodeToESTreeNodeMap.get(declarationTsNode)
|
|
52
|
+
: undefined;
|
|
53
|
+
context.report({
|
|
54
|
+
node: declarationEstreeNode ?? node,
|
|
55
|
+
messageId: 'nonPrefixedFunction'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
function getPropsType(node, tsTools) {
|
|
64
|
+
if (node.callee.type !== 'Identifier' ||
|
|
65
|
+
node.callee.name !== '$props' ||
|
|
66
|
+
node.parent.type !== 'VariableDeclarator') {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const tsNode = tsTools.service.esTreeNodeToTSNodeMap.get(node.parent.id);
|
|
70
|
+
if (tsNode === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return tsTools.service.program.getTypeChecker().getTypeAtLocation(tsNode);
|
|
74
|
+
}
|
|
75
|
+
function isFunctionLike(functionSymbol, tsTools) {
|
|
76
|
+
return (isMethodSymbol(functionSymbol, tsTools.ts) ||
|
|
77
|
+
(functionSymbol.valueDeclaration !== undefined &&
|
|
78
|
+
isPropertySignatureKind(functionSymbol.valueDeclaration, tsTools.ts) &&
|
|
79
|
+
functionSymbol.valueDeclaration.type !== undefined &&
|
|
80
|
+
isFunctionTypeKind(functionSymbol.valueDeclaration.type, tsTools.ts)));
|
|
81
|
+
}
|
|
82
|
+
function isFunctionAsync(functionSymbol, tsTools) {
|
|
83
|
+
return (functionSymbol.getDeclarations()?.some((declaration) => {
|
|
84
|
+
if (!isMethodSignatureKind(declaration, tsTools.ts)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (declaration.type === undefined || !isTypeReferenceKind(declaration.type, tsTools.ts)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return (isIdentifierKind(declaration.type.typeName, tsTools.ts) &&
|
|
91
|
+
declaration.type.typeName.escapedText === 'Promise');
|
|
92
|
+
}) ?? false);
|
|
93
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TSESTree } from '@typescript-eslint/types';
|
|
2
|
+
import type { RuleContext } from '../types.js';
|
|
3
|
+
import type { AST } from 'svelte-eslint-parser';
|
|
4
|
+
export declare function extractExpressionPrefixVariable(context: RuleContext, expression: TSESTree.Expression): TSESTree.Identifier | null;
|
|
5
|
+
export declare function extractExpressionPrefixLiteral(context: RuleContext, expression: AST.SvelteLiteral | TSESTree.Node): string | null;
|
|
6
|
+
export declare function extractExpressionSuffixLiteral(context: RuleContext, expression: AST.SvelteLiteral | TSESTree.Node): string | null;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { findVariable } from './ast-utils.js';
|
|
2
|
+
// Variable prefix extraction
|
|
3
|
+
export function extractExpressionPrefixVariable(context, expression) {
|
|
4
|
+
switch (expression.type) {
|
|
5
|
+
case 'BinaryExpression':
|
|
6
|
+
return extractBinaryExpressionPrefixVariable(context, expression);
|
|
7
|
+
case 'Identifier':
|
|
8
|
+
return extractVariablePrefixVariable(context, expression);
|
|
9
|
+
case 'MemberExpression':
|
|
10
|
+
return extractMemberExpressionPrefixVariable(expression);
|
|
11
|
+
case 'TemplateLiteral':
|
|
12
|
+
return extractTemplateLiteralPrefixVariable(context, expression);
|
|
13
|
+
default:
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function extractBinaryExpressionPrefixVariable(context, expression) {
|
|
18
|
+
return expression.left.type !== 'PrivateIdentifier'
|
|
19
|
+
? extractExpressionPrefixVariable(context, expression.left)
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
function extractVariablePrefixVariable(context, expression) {
|
|
23
|
+
const variable = findVariable(context, expression);
|
|
24
|
+
if (variable === null ||
|
|
25
|
+
variable.identifiers.length !== 1 ||
|
|
26
|
+
variable.identifiers[0].parent.type !== 'VariableDeclarator' ||
|
|
27
|
+
variable.identifiers[0].parent.init === null) {
|
|
28
|
+
return expression;
|
|
29
|
+
}
|
|
30
|
+
return (extractExpressionPrefixVariable(context, variable.identifiers[0].parent.init) ?? expression);
|
|
31
|
+
}
|
|
32
|
+
function extractMemberExpressionPrefixVariable(expression) {
|
|
33
|
+
return expression.property.type === 'Identifier' ? expression.property : null;
|
|
34
|
+
}
|
|
35
|
+
function extractTemplateLiteralPrefixVariable(context, expression) {
|
|
36
|
+
const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1);
|
|
37
|
+
for (const part of literalParts) {
|
|
38
|
+
if (part.type === 'TemplateElement' && part.value.raw === '') {
|
|
39
|
+
// Skip empty quasi in the begining
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (part.type !== 'TemplateElement') {
|
|
43
|
+
return extractExpressionPrefixVariable(context, part);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// Literal prefix extraction
|
|
50
|
+
export function extractExpressionPrefixLiteral(context, expression) {
|
|
51
|
+
switch (expression.type) {
|
|
52
|
+
case 'BinaryExpression':
|
|
53
|
+
return extractBinaryExpressionPrefixLiteral(context, expression);
|
|
54
|
+
case 'Identifier':
|
|
55
|
+
return extractVariablePrefixLiteral(context, expression);
|
|
56
|
+
case 'Literal':
|
|
57
|
+
return typeof expression.value === 'string' ? expression.value : null;
|
|
58
|
+
case 'SvelteLiteral':
|
|
59
|
+
return expression.value;
|
|
60
|
+
case 'TemplateLiteral':
|
|
61
|
+
return extractTemplateLiteralPrefixLiteral(context, expression);
|
|
62
|
+
default:
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function extractBinaryExpressionPrefixLiteral(context, expression) {
|
|
67
|
+
return expression.left.type !== 'PrivateIdentifier'
|
|
68
|
+
? extractExpressionPrefixLiteral(context, expression.left)
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
function extractVariablePrefixLiteral(context, expression) {
|
|
72
|
+
const variable = findVariable(context, expression);
|
|
73
|
+
if (variable === null ||
|
|
74
|
+
variable.identifiers.length !== 1 ||
|
|
75
|
+
variable.identifiers[0].parent.type !== 'VariableDeclarator' ||
|
|
76
|
+
variable.identifiers[0].parent.init === null) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return extractExpressionPrefixLiteral(context, variable.identifiers[0].parent.init);
|
|
80
|
+
}
|
|
81
|
+
function extractTemplateLiteralPrefixLiteral(context, expression) {
|
|
82
|
+
const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1);
|
|
83
|
+
for (const part of literalParts) {
|
|
84
|
+
if (part.type === 'TemplateElement') {
|
|
85
|
+
if (part.value.raw === '') {
|
|
86
|
+
// Skip empty quasi
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
return part.value.raw;
|
|
90
|
+
}
|
|
91
|
+
return extractExpressionPrefixLiteral(context, part);
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
// Literal suffix extraction
|
|
96
|
+
export function extractExpressionSuffixLiteral(context, expression) {
|
|
97
|
+
switch (expression.type) {
|
|
98
|
+
case 'BinaryExpression':
|
|
99
|
+
return extractBinaryExpressionSuffixLiteral(context, expression);
|
|
100
|
+
case 'Identifier':
|
|
101
|
+
return extractVariableSuffixLiteral(context, expression);
|
|
102
|
+
case 'Literal':
|
|
103
|
+
return typeof expression.value === 'string' ? expression.value : null;
|
|
104
|
+
case 'SvelteLiteral':
|
|
105
|
+
return expression.value;
|
|
106
|
+
case 'TemplateLiteral':
|
|
107
|
+
return extractTemplateLiteralSuffixLiteral(context, expression);
|
|
108
|
+
default:
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function extractBinaryExpressionSuffixLiteral(context, expression) {
|
|
113
|
+
return extractExpressionSuffixLiteral(context, expression.right);
|
|
114
|
+
}
|
|
115
|
+
function extractVariableSuffixLiteral(context, expression) {
|
|
116
|
+
const variable = findVariable(context, expression);
|
|
117
|
+
if (variable === null ||
|
|
118
|
+
variable.identifiers.length !== 1 ||
|
|
119
|
+
variable.identifiers[0].parent.type !== 'VariableDeclarator' ||
|
|
120
|
+
variable.identifiers[0].parent.init === null) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return extractExpressionSuffixLiteral(context, variable.identifiers[0].parent.init);
|
|
124
|
+
}
|
|
125
|
+
function extractTemplateLiteralSuffixLiteral(context, expression) {
|
|
126
|
+
const literalParts = [...expression.expressions, ...expression.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1);
|
|
127
|
+
for (const part of literalParts.reverse()) {
|
|
128
|
+
if (part.type === 'TemplateElement') {
|
|
129
|
+
if (part.value.raw === '') {
|
|
130
|
+
// Skip empty quasi
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
return part.value.raw;
|
|
134
|
+
}
|
|
135
|
+
return extractExpressionSuffixLiteral(context, part);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
package/lib/utils/rules.js
CHANGED
|
@@ -15,6 +15,7 @@ import indent from '../rules/indent.js';
|
|
|
15
15
|
import infiniteReactiveLoop from '../rules/infinite-reactive-loop.js';
|
|
16
16
|
import maxAttributesPerLine from '../rules/max-attributes-per-line.js';
|
|
17
17
|
import mustacheSpacing from '../rules/mustache-spacing.js';
|
|
18
|
+
import noAddEventListener from '../rules/no-add-event-listener.js';
|
|
18
19
|
import noAtDebugTags from '../rules/no-at-debug-tags.js';
|
|
19
20
|
import noAtHtmlTags from '../rules/no-at-html-tags.js';
|
|
20
21
|
import noDomManipulating from '../rules/no-dom-manipulating.js';
|
|
@@ -56,8 +57,10 @@ import preferClassDirective from '../rules/prefer-class-directive.js';
|
|
|
56
57
|
import preferConst from '../rules/prefer-const.js';
|
|
57
58
|
import preferDestructuredStoreProps from '../rules/prefer-destructured-store-props.js';
|
|
58
59
|
import preferStyleDirective from '../rules/prefer-style-directive.js';
|
|
60
|
+
import preferWritableDerived from '../rules/prefer-writable-derived.js';
|
|
59
61
|
import requireEachKey from '../rules/require-each-key.js';
|
|
60
62
|
import requireEventDispatcherTypes from '../rules/require-event-dispatcher-types.js';
|
|
63
|
+
import requireEventPrefix from '../rules/require-event-prefix.js';
|
|
61
64
|
import requireOptimizedStyleAttribute from '../rules/require-optimized-style-attribute.js';
|
|
62
65
|
import requireStoreCallbacksUseSetParam from '../rules/require-store-callbacks-use-set-param.js';
|
|
63
66
|
import requireStoreReactiveAccess from '../rules/require-store-reactive-access.js';
|
|
@@ -89,6 +92,7 @@ export const rules = [
|
|
|
89
92
|
infiniteReactiveLoop,
|
|
90
93
|
maxAttributesPerLine,
|
|
91
94
|
mustacheSpacing,
|
|
95
|
+
noAddEventListener,
|
|
92
96
|
noAtDebugTags,
|
|
93
97
|
noAtHtmlTags,
|
|
94
98
|
noDomManipulating,
|
|
@@ -130,8 +134,10 @@ export const rules = [
|
|
|
130
134
|
preferConst,
|
|
131
135
|
preferDestructuredStoreProps,
|
|
132
136
|
preferStyleDirective,
|
|
137
|
+
preferWritableDerived,
|
|
133
138
|
requireEachKey,
|
|
134
139
|
requireEventDispatcherTypes,
|
|
140
|
+
requireEventPrefix,
|
|
135
141
|
requireOptimizedStyleAttribute,
|
|
136
142
|
requireStoreCallbacksUseSetParam,
|
|
137
143
|
requireStoreReactiveAccess,
|
|
@@ -112,3 +112,27 @@ export declare function getTypeName(type: TS.Type, tsTools: TSTools): string;
|
|
|
112
112
|
* Return the type of the given property in the given type, or undefined if no such property exists
|
|
113
113
|
*/
|
|
114
114
|
export declare function getTypeOfPropertyOfType(type: TS.Type, name: string, checker: TS.TypeChecker): TS.Type | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Check whether the given symbol is a method type or not.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isMethodSymbol(type: TS.Symbol, ts: TypeScript): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Check whether the given node is a property signature kind or not.
|
|
121
|
+
*/
|
|
122
|
+
export declare function isPropertySignatureKind(node: TS.Node, ts: TypeScript): node is TS.PropertySignature;
|
|
123
|
+
/**
|
|
124
|
+
* Check whether the given node is a function type kind or not.
|
|
125
|
+
*/
|
|
126
|
+
export declare function isFunctionTypeKind(node: TS.Node, ts: TypeScript): node is TS.FunctionTypeNode;
|
|
127
|
+
/**
|
|
128
|
+
* Check whether the given node is a method signature kind or not.
|
|
129
|
+
*/
|
|
130
|
+
export declare function isMethodSignatureKind(node: TS.Node, ts: TypeScript): node is TS.MethodSignature;
|
|
131
|
+
/**
|
|
132
|
+
* Check whether the given node is a type reference kind or not.
|
|
133
|
+
*/
|
|
134
|
+
export declare function isTypeReferenceKind(node: TS.Node, ts: TypeScript): node is TS.TypeReferenceNode;
|
|
135
|
+
/**
|
|
136
|
+
* Check whether the given node is an identifier kind or not.
|
|
137
|
+
*/
|
|
138
|
+
export declare function isIdentifierKind(node: TS.Node, ts: TypeScript): node is TS.Identifier;
|
|
@@ -260,3 +260,39 @@ export function getTypeOfPropertyOfType(type, name, checker) {
|
|
|
260
260
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- getTypeOfPropertyOfType is an internal API of TS.
|
|
261
261
|
return checker.getTypeOfPropertyOfType(type, name);
|
|
262
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Check whether the given symbol is a method type or not.
|
|
265
|
+
*/
|
|
266
|
+
export function isMethodSymbol(type, ts) {
|
|
267
|
+
return (type.getFlags() & ts.SymbolFlags.Method) !== 0;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Check whether the given node is a property signature kind or not.
|
|
271
|
+
*/
|
|
272
|
+
export function isPropertySignatureKind(node, ts) {
|
|
273
|
+
return node.kind === ts.SyntaxKind.PropertySignature;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check whether the given node is a function type kind or not.
|
|
277
|
+
*/
|
|
278
|
+
export function isFunctionTypeKind(node, ts) {
|
|
279
|
+
return node.kind === ts.SyntaxKind.FunctionType;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Check whether the given node is a method signature kind or not.
|
|
283
|
+
*/
|
|
284
|
+
export function isMethodSignatureKind(node, ts) {
|
|
285
|
+
return node.kind === ts.SyntaxKind.MethodSignature;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Check whether the given node is a type reference kind or not.
|
|
289
|
+
*/
|
|
290
|
+
export function isTypeReferenceKind(node, ts) {
|
|
291
|
+
return node.kind === ts.SyntaxKind.TypeReference;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Check whether the given node is an identifier kind or not.
|
|
295
|
+
*/
|
|
296
|
+
export function isIdentifierKind(node, ts) {
|
|
297
|
+
return node.kind === ts.SyntaxKind.Identifier;
|
|
298
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-svelte",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "ESLint plugin for Svelte using AST",
|
|
5
5
|
"repository": "git+https://github.com/sveltejs/eslint-plugin-svelte.git",
|
|
6
6
|
"homepage": "https://sveltejs.github.io/eslint-plugin-svelte",
|
|
@@ -34,9 +34,8 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@eslint-community/eslint-utils": "^4.4.1",
|
|
36
36
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
|
37
|
-
"eslint-compat-utils": "^0.6.4",
|
|
38
37
|
"esutils": "^2.0.3",
|
|
39
|
-
"known-css-properties": "^0.
|
|
38
|
+
"known-css-properties": "^0.36.0",
|
|
40
39
|
"postcss": "^8.4.49",
|
|
41
40
|
"postcss-load-config": "^3.1.4",
|
|
42
41
|
"postcss-safe-parser": "^7.0.0",
|