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 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 other than data or errors in SvelteKit page components. | :star: |
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.19.0";
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.19.0";
2
+ export declare const version: "3.21.0";
package/lib/meta.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // This file has been automatically generated,
3
3
  // in order to update its content execute "pnpm run update"
4
4
  export const name = 'eslint-plugin-svelte';
5
- export const version = '3.19.0';
5
+ export const version = '3.21.0';
@@ -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 other than data or errors in SvelteKit page components.
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,2 @@
1
+ declare const _default: import("../types.js").RuleModule;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: import("src/types.js").RuleModule;
2
+ export default _default;
@@ -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 EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'params', 'snapshot'];
4
- const EXPECTED_PROP_NAMES_SVELTE5 = [...EXPECTED_PROP_NAMES, 'children'];
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 other than data or errors in SvelteKit page components.',
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 other than data or errors in SvelteKit page components.'
43
+ unexpected: 'disallow invalid props in SvelteKit route components.'
42
44
  },
43
45
  type: 'problem',
44
46
  conditions: [
45
47
  {
46
- svelteKitFileTypes: ['+page.svelte', '+error.svelte', '+layout.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 expectedPropNames = isSvelte5 ? EXPECTED_PROP_NAMES_SVELTE5 : EXPECTED_PROP_NAMES;
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 (!expectedPropNames.includes(node.id.name)) {
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, expectedPropNames);
92
+ checkProp(node, context, LEGACY_PAGE_PROP_NAMES);
81
93
  },
82
94
  // Svelte5
83
95
  // let { foo, bar } = $props();
@@ -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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "3.19.0",
3
+ "version": "3.21.0",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": {
6
6
  "type": "git",