eslint-plugin-svelte 3.19.0 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/main.d.ts +1 -1
- package/lib/meta.d.ts +1 -1
- package/lib/meta.js +1 -1
- package/lib/rule-types.d.ts +11 -1
- package/lib/rules/no-at-const-tags.d.ts +2 -0
- package/lib/rules/no-at-const-tags.js +67 -0
- package/lib/rules/no-bind-value-on-checkable-inputs.d.ts +2 -0
- package/lib/rules/no-bind-value-on-checkable-inputs.js +73 -0
- package/lib/rules/valid-prop-names-in-kit-pages.js +21 -9
- package/lib/utils/rules.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -296,6 +296,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
|
|
|
296
296
|
| Rule ID | Description | |
|
|
297
297
|
|:--------|:------------|:---|
|
|
298
298
|
| [svelte/infinite-reactive-loop](https://sveltejs.github.io/eslint-plugin-svelte/rules/infinite-reactive-loop/) | Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. | :star: |
|
|
299
|
+
| [svelte/no-bind-value-on-checkable-inputs](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-bind-value-on-checkable-inputs/) | disallow useless `bind:value` on `<input type="checkbox">` and `<input type="radio">` | :bulb: |
|
|
299
300
|
| [svelte/no-dom-manipulating](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/) | disallow DOM manipulating | :star: |
|
|
300
301
|
| [svelte/no-dupe-else-if-blocks](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
|
|
301
302
|
| [svelte/no-dupe-on-directives](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-on-directives/) | disallow duplicate `on:` directives | :star: |
|
|
@@ -334,6 +335,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|
|
|
334
335
|
| [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: |
|
|
335
336
|
| [svelte/button-has-type](https://sveltejs.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
|
|
336
337
|
| [svelte/no-add-event-listener](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-add-event-listener/) | Warns against the use of `addEventListener` | :bulb: |
|
|
338
|
+
| [svelte/no-at-const-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-const-tags/) | disallow the use of `{@const}` in favor of `{const ...}` declaration tags | :wrench: |
|
|
337
339
|
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star::bulb: |
|
|
338
340
|
| [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. | |
|
|
339
341
|
| [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: |
|
|
@@ -403,7 +405,7 @@ These rules relate to SvelteKit and its best Practices.
|
|
|
403
405
|
|:--------|:------------|:---|
|
|
404
406
|
| [svelte/no-export-load-in-svelte-module-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in SvelteKit page components. | :star: |
|
|
405
407
|
| [svelte/no-navigation-without-resolve](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-resolve/) | disallow internal navigation (links, `goto()`, `pushState()`, `replaceState()`) without a `resolve()` | :star: |
|
|
406
|
-
| [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props
|
|
408
|
+
| [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow invalid props in SvelteKit route components. | :star: |
|
|
407
409
|
|
|
408
410
|
## Experimental
|
|
409
411
|
|
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.21.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
1
|
export declare const name: "eslint-plugin-svelte";
|
|
2
|
-
export declare const version: "3.
|
|
2
|
+
export declare const version: "3.21.0";
|
package/lib/meta.js
CHANGED
package/lib/rule-types.d.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface RuleOptions {
|
|
|
100
100
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-add-event-listener/
|
|
101
101
|
*/
|
|
102
102
|
'svelte/no-add-event-listener'?: Linter.RuleEntry<[]>;
|
|
103
|
+
/**
|
|
104
|
+
* disallow the use of `{@const}` in favor of `{const ...}` declaration tags
|
|
105
|
+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-const-tags/
|
|
106
|
+
*/
|
|
107
|
+
'svelte/no-at-const-tags'?: Linter.RuleEntry<[]>;
|
|
103
108
|
/**
|
|
104
109
|
* disallow the use of `{@debug}`
|
|
105
110
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/
|
|
@@ -110,6 +115,11 @@ export interface RuleOptions {
|
|
|
110
115
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-html-tags/
|
|
111
116
|
*/
|
|
112
117
|
'svelte/no-at-html-tags'?: Linter.RuleEntry<[]>;
|
|
118
|
+
/**
|
|
119
|
+
* disallow useless `bind:value` on `<input type="checkbox">` and `<input type="radio">`
|
|
120
|
+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-bind-value-on-checkable-inputs/
|
|
121
|
+
*/
|
|
122
|
+
'svelte/no-bind-value-on-checkable-inputs'?: Linter.RuleEntry<[]>;
|
|
113
123
|
/**
|
|
114
124
|
* disallow DOM manipulating
|
|
115
125
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/
|
|
@@ -409,7 +419,7 @@ export interface RuleOptions {
|
|
|
409
419
|
*/
|
|
410
420
|
'svelte/valid-each-key'?: Linter.RuleEntry<[]>;
|
|
411
421
|
/**
|
|
412
|
-
* disallow props
|
|
422
|
+
* disallow invalid props in SvelteKit route components.
|
|
413
423
|
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/
|
|
414
424
|
*/
|
|
415
425
|
'svelte/valid-prop-names-in-kit-pages'?: Linter.RuleEntry<[]>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createRule } from '../utils/index.js';
|
|
2
|
+
import { getSvelteContext } from '../utils/svelte-context.js';
|
|
3
|
+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
const shouldRun = semver.satisfies(SVELTE_VERSION, '>=5.56.0');
|
|
6
|
+
export default createRule('no-at-const-tags', {
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'disallow the use of `{@const}` in favor of `{const ...}` declaration tags',
|
|
10
|
+
category: 'Best Practices',
|
|
11
|
+
recommended: false
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
unexpected: 'Use `{const ...}` declaration tag instead of legacy `{@const ...}`.'
|
|
17
|
+
},
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
conditions: [
|
|
20
|
+
{
|
|
21
|
+
svelteVersions: ['5']
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
if (!shouldRun) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
const sourceCode = context.sourceCode;
|
|
30
|
+
const runes = getSvelteContext(context)?.runes;
|
|
31
|
+
// Only report and fix in runes mode, since preserving reactivity requires
|
|
32
|
+
// `$derived(...)`, which is unavailable outside runes mode.
|
|
33
|
+
if (runes !== true) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
SvelteConstTag(node) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: 'unexpected',
|
|
41
|
+
*fix(fixer) {
|
|
42
|
+
const text = sourceCode.getText(node);
|
|
43
|
+
const match = /^\{(\s*)@const\b/u.exec(text);
|
|
44
|
+
if (!match) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const atOffset = node.range[0] + 1 + match[1].length;
|
|
48
|
+
yield fixer.removeRange([atOffset, atOffset + 1]);
|
|
49
|
+
const init = node.declarations[0].init;
|
|
50
|
+
if (init == null) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Preserve the reactivity of legacy `{@const}` by wrapping the
|
|
54
|
+
// initializer in `$derived(...)`. Skip when already wrapped.
|
|
55
|
+
if (init.type === 'CallExpression' &&
|
|
56
|
+
init.callee.type === 'Identifier' &&
|
|
57
|
+
init.callee.name === '$derived') {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
yield fixer.insertTextBefore(init, '$derived(');
|
|
61
|
+
yield fixer.insertTextAfter(init, ')');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getStaticValue } from '@eslint-community/eslint-utils';
|
|
2
|
+
import { getScope } from '../utils/ast-utils.js';
|
|
3
|
+
import { createRule } from '../utils/index.js';
|
|
4
|
+
export default createRule('no-bind-value-on-checkable-inputs', {
|
|
5
|
+
meta: {
|
|
6
|
+
hasSuggestions: true,
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'disallow useless `bind:value` on `<input type="checkbox">` and `<input type="radio">`',
|
|
9
|
+
category: 'Possible Errors',
|
|
10
|
+
recommended: false
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
type: 'problem',
|
|
14
|
+
messages: {
|
|
15
|
+
checkboxValueBinding: '`bind:value` does not work on checkbox inputs. Did you mean `bind:checked` or `bind:group`?',
|
|
16
|
+
radioValueBinding: '`bind:value` does not work on radio inputs. Did you mean `bind:group`?', // svelte compiler disallows `bind:checked` on radios
|
|
17
|
+
checkedSuggestion: 'Change `bind:value` to `bind:checked`.',
|
|
18
|
+
groupSuggestion: 'Change `bind:value` to `bind:group`.'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
"SvelteElement[name.name='input']"(node) {
|
|
24
|
+
function getType() {
|
|
25
|
+
const typeAttr = node.startTag?.attributes.find((attr) => attr.type === 'SvelteAttribute' && attr.key.name.toLowerCase() === 'type');
|
|
26
|
+
if (!typeAttr)
|
|
27
|
+
return null;
|
|
28
|
+
if (typeAttr.value.length !== 1)
|
|
29
|
+
return null;
|
|
30
|
+
const typeValue = typeAttr.value[0];
|
|
31
|
+
if (typeValue.type === 'SvelteLiteral') {
|
|
32
|
+
// <input type="checkbox" />
|
|
33
|
+
return typeValue.value.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
if (typeValue.type === 'SvelteMustacheTag') {
|
|
36
|
+
const staticValue = getStaticValue(typeValue.expression, getScope(context, typeValue.expression));
|
|
37
|
+
if (typeof staticValue?.value !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
// <input type={"checkbox"} />
|
|
40
|
+
return staticValue.value.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const type = getType();
|
|
45
|
+
if (!type || (type !== 'checkbox' && type !== 'radio'))
|
|
46
|
+
return;
|
|
47
|
+
const bindValue = node.startTag?.attributes.find((attr) => attr.type === 'SvelteDirective' &&
|
|
48
|
+
attr.kind === 'Binding' &&
|
|
49
|
+
attr.key.name.name === 'value');
|
|
50
|
+
if (!bindValue)
|
|
51
|
+
return;
|
|
52
|
+
function suggestion(suggestionType, bindValue // make typescript happy
|
|
53
|
+
) {
|
|
54
|
+
return {
|
|
55
|
+
messageId: `${suggestionType}Suggestion`,
|
|
56
|
+
fix(fixer) {
|
|
57
|
+
if (bindValue.shorthand)
|
|
58
|
+
return fixer.replaceText(bindValue, `bind:${suggestionType}={value}`);
|
|
59
|
+
return fixer.replaceText(bindValue.key, `bind:${suggestionType}`);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
context.report({
|
|
64
|
+
node: bindValue,
|
|
65
|
+
messageId: `${type}ValueBinding`,
|
|
66
|
+
suggest: type === 'checkbox'
|
|
67
|
+
? [suggestion('checked', bindValue), suggestion('group', bindValue)]
|
|
68
|
+
: [suggestion('group', bindValue)]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createRule } from '../utils/index.js';
|
|
2
|
-
import { getSvelteVersion } from '../utils/svelte-context.js';
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
import { getSvelteContext, getSvelteVersion } from '../utils/svelte-context.js';
|
|
3
|
+
const PAGE_PROP_NAMES = ['data', 'form', 'params', 'snapshot'];
|
|
4
|
+
const LEGACY_PAGE_PROP_NAMES = [...PAGE_PROP_NAMES, 'errors'];
|
|
5
|
+
const LAYOUT_PROP_NAMES = [...PAGE_PROP_NAMES, 'children'];
|
|
6
|
+
const ERROR_PROP_NAMES = ['error'];
|
|
5
7
|
function checkProp(node, context, expectedPropNames) {
|
|
6
8
|
if (node.id.type !== 'ObjectPattern')
|
|
7
9
|
return;
|
|
@@ -32,25 +34,35 @@ function isModuleScript(node) {
|
|
|
32
34
|
export default createRule('valid-prop-names-in-kit-pages', {
|
|
33
35
|
meta: {
|
|
34
36
|
docs: {
|
|
35
|
-
description: 'disallow props
|
|
37
|
+
description: 'disallow invalid props in SvelteKit route components.',
|
|
36
38
|
category: 'SvelteKit',
|
|
37
39
|
recommended: true
|
|
38
40
|
},
|
|
39
41
|
schema: [],
|
|
40
42
|
messages: {
|
|
41
|
-
unexpected: 'disallow props
|
|
43
|
+
unexpected: 'disallow invalid props in SvelteKit route components.'
|
|
42
44
|
},
|
|
43
45
|
type: 'problem',
|
|
44
46
|
conditions: [
|
|
45
47
|
{
|
|
46
|
-
svelteKitFileTypes: ['+page.svelte', '+
|
|
48
|
+
svelteKitFileTypes: ['+page.svelte', '+layout.svelte', '+error.svelte']
|
|
47
49
|
}
|
|
48
50
|
]
|
|
49
51
|
},
|
|
50
52
|
create(context) {
|
|
51
53
|
let isScript = false;
|
|
52
54
|
const isSvelte5 = getSvelteVersion() === '5';
|
|
53
|
-
const
|
|
55
|
+
const svelteContext = getSvelteContext(context);
|
|
56
|
+
const fileType = svelteContext?.svelteKitFileType;
|
|
57
|
+
let expectedPropNames = PAGE_PROP_NAMES;
|
|
58
|
+
if (isSvelte5) {
|
|
59
|
+
if (fileType === '+layout.svelte') {
|
|
60
|
+
expectedPropNames = LAYOUT_PROP_NAMES;
|
|
61
|
+
}
|
|
62
|
+
else if (fileType === '+error.svelte') {
|
|
63
|
+
expectedPropNames = ERROR_PROP_NAMES;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
54
66
|
return {
|
|
55
67
|
// <script>
|
|
56
68
|
'Program > SvelteScriptElement > SvelteStartTag': (node) => {
|
|
@@ -67,7 +79,7 @@ export default createRule('valid-prop-names-in-kit-pages', {
|
|
|
67
79
|
return;
|
|
68
80
|
// export let foo
|
|
69
81
|
if (node.id.type === 'Identifier') {
|
|
70
|
-
if (!
|
|
82
|
+
if (!LEGACY_PAGE_PROP_NAMES.includes(node.id.name)) {
|
|
71
83
|
context.report({
|
|
72
84
|
node,
|
|
73
85
|
loc: node.loc,
|
|
@@ -77,7 +89,7 @@ export default createRule('valid-prop-names-in-kit-pages', {
|
|
|
77
89
|
return;
|
|
78
90
|
}
|
|
79
91
|
// export let { xxx, yyy } = zzz
|
|
80
|
-
checkProp(node, context,
|
|
92
|
+
checkProp(node, context, LEGACY_PAGE_PROP_NAMES);
|
|
81
93
|
},
|
|
82
94
|
// Svelte5
|
|
83
95
|
// let { foo, bar } = $props();
|
package/lib/utils/rules.js
CHANGED
|
@@ -17,8 +17,10 @@ import maxAttributesPerLine from '../rules/max-attributes-per-line.js';
|
|
|
17
17
|
import maxLinesPerBlock from '../rules/max-lines-per-block.js';
|
|
18
18
|
import mustacheSpacing from '../rules/mustache-spacing.js';
|
|
19
19
|
import noAddEventListener from '../rules/no-add-event-listener.js';
|
|
20
|
+
import noAtConstTags from '../rules/no-at-const-tags.js';
|
|
20
21
|
import noAtDebugTags from '../rules/no-at-debug-tags.js';
|
|
21
22
|
import noAtHtmlTags from '../rules/no-at-html-tags.js';
|
|
23
|
+
import noBindValueOnCheckableInputs from '../rules/no-bind-value-on-checkable-inputs.js';
|
|
22
24
|
import noDomManipulating from '../rules/no-dom-manipulating.js';
|
|
23
25
|
import noDupeElseIfBlocks from '../rules/no-dupe-else-if-blocks.js';
|
|
24
26
|
import noDupeOnDirectives from '../rules/no-dupe-on-directives.js';
|
|
@@ -100,8 +102,10 @@ export const rules = [
|
|
|
100
102
|
maxLinesPerBlock,
|
|
101
103
|
mustacheSpacing,
|
|
102
104
|
noAddEventListener,
|
|
105
|
+
noAtConstTags,
|
|
103
106
|
noAtDebugTags,
|
|
104
107
|
noAtHtmlTags,
|
|
108
|
+
noBindValueOnCheckableInputs,
|
|
105
109
|
noDomManipulating,
|
|
106
110
|
noDupeElseIfBlocks,
|
|
107
111
|
noDupeOnDirectives,
|