diff --git a/.changeset/rich-colts-nail.md b/.changeset/rich-colts-nail.md new file mode 100644 index 000000000..51c8eea60 --- /dev/null +++ b/.changeset/rich-colts-nail.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: added the `prefer-svelte-reactivity` rule diff --git a/README.md b/README.md index db731dd26..4a048d6e1 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-shorthand-style-property-overrides](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: | | [svelte/no-store-async](https://sveltejs.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 | :star: | | [svelte/no-unknown-style-directive-property](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: | +| [svelte/prefer-svelte-reactivity](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-svelte-reactivity/) | disallow using built-in classes where a reactive alternative is provided by svelte/reactivity | :star: | | [svelte/require-store-callbacks-use-set-param](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](https://sveltejs.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. | :star::wrench: | | [svelte/valid-compile](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | | diff --git a/docs/rules.md b/docs/rules.md index 58d896b10..cea6c075a 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -29,6 +29,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: | | [svelte/no-store-async](./rules/no-store-async.md) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | :star: | | [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: | +| [svelte/prefer-svelte-reactivity](./rules/prefer-svelte-reactivity.md) | disallow using built-in classes where a reactive alternative is provided by svelte/reactivity | :star: | | [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](./rules/require-store-reactive-access.md) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :star::wrench: | | [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | | diff --git a/docs/rules/prefer-svelte-reactivity.md b/docs/rules/prefer-svelte-reactivity.md new file mode 100644 index 000000000..9688d00e5 --- /dev/null +++ b/docs/rules/prefer-svelte-reactivity.md @@ -0,0 +1,68 @@ +--- +pageClass: 'rule-details' +sidebarDepth: 0 +title: 'svelte/prefer-svelte-reactivity' +description: 'disallow using built-in classes where a reactive alternative is provided by svelte/reactivity' +--- + +# svelte/prefer-svelte-reactivity + +> disallow using built-in classes where a reactive alternative is provided by svelte/reactivity + +- :exclamation: **_This rule has not been released yet._** +- :gear: This rule is included in `"plugin:svelte/recommended"`. + +## :book: Rule Details + +The built-in `Date`, `Map`, `Set`, `URL` and `URLSearchParams` classes are often used in frontend code, however, their properties and methods are not reactive. Because of that, Svelte provides reactive versions of these 5 builtins as part of the "svelte/reactivity" package. This rule reports usage of the built-in versions in Svelte code. + + + +```svelte + +``` + +## :wrench: Options + +Nothing. + +## :books: Further Reading + +- [svelte/reactivity documentation](https://svelte.dev/docs/svelte/svelte-reactivity) + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/prefer-svelte-reactivity.ts) diff --git a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts index b1b2d51ad..81c2b7dbc 100644 --- a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts +++ b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts @@ -37,6 +37,7 @@ const config: Linter.Config[] = [ 'svelte/no-unused-svelte-ignore': 'error', 'svelte/no-useless-children-snippet': 'error', 'svelte/no-useless-mustaches': 'error', + 'svelte/prefer-svelte-reactivity': 'error', 'svelte/require-each-key': 'error', 'svelte/require-event-dispatcher-types': 'error', 'svelte/require-store-reactive-access': 'error', diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 19e30a345..39e216f70 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -306,6 +306,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/ */ 'svelte/prefer-style-directive'?: Linter.RuleEntry<[]> + /** + * disallow using built-in classes where a reactive alternative is provided by svelte/reactivity + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-svelte-reactivity/ + */ + 'svelte/prefer-svelte-reactivity'?: Linter.RuleEntry<[]> /** * require keyed `{#each}` block * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-each-key/ diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts new file mode 100644 index 000000000..b8b83c90a --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -0,0 +1,72 @@ +import { ReferenceTracker } from '@eslint-community/eslint-utils'; +import { getSourceCode } from '../utils/compat.js'; +import { createRule } from '../utils/index.js'; + +export default createRule('prefer-svelte-reactivity', { + meta: { + docs: { + description: + 'disallow using built-in classes where a reactive alternative is provided by svelte/reactivity', + category: 'Possible Errors', + recommended: true + }, + schema: [], + messages: { + dateUsed: 'Found a usage of the built-in Date class. Use a SvelteDate instead.', + mapUsed: 'Found a usage of the built-in Map class. Use a SvelteMap instead.', + setUsed: 'Found a usage of the built-in Set class. Use a SvelteSet instead.', + urlUsed: 'Found a usage of the built-in URL class. Use a SvelteURL instead.', + urlSearchParamsUsed: + 'Found a usage of the built-in URLSearchParams class. Use a SvelteURLSearchParams instead.' + }, + type: 'problem', // 'problem', or 'layout', + conditions: [ + { + svelteVersions: ['5'], + svelteFileTypes: ['.svelte', '.svelte.[js|ts]'] + } + ] + }, + create(context) { + return { + Program() { + const referenceTracker = new ReferenceTracker( + getSourceCode(context).scopeManager.globalScope! + ); + for (const { node, path } of referenceTracker.iterateGlobalReferences({ + Date: { + [ReferenceTracker.CONSTRUCT]: true + }, + Map: { + [ReferenceTracker.CONSTRUCT]: true + }, + Set: { + [ReferenceTracker.CONSTRUCT]: true + }, + URL: { + [ReferenceTracker.CALL]: true, + [ReferenceTracker.CONSTRUCT]: true, + [ReferenceTracker.READ]: true + }, + URLSearchParams: { + [ReferenceTracker.CALL]: true, + [ReferenceTracker.CONSTRUCT]: true, + [ReferenceTracker.READ]: true + } + })) { + const typeToMessageId: Record = { + Date: 'dateUsed', + Map: 'mapUsed', + Set: 'setUsed', + URL: 'urlUsed', + URLSearchParams: 'urlSearchParamsUsed' + }; + context.report({ + messageId: typeToMessageId[path[0]], + node + }); + } + } + }; + } +}); diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts index 3151d17f1..f437d0d82 100644 --- a/packages/eslint-plugin-svelte/src/utils/rules.ts +++ b/packages/eslint-plugin-svelte/src/utils/rules.ts @@ -60,6 +60,7 @@ import preferClassDirective from '../rules/prefer-class-directive.js'; import preferConst from '../rules/prefer-const.js'; import preferDestructuredStoreProps from '../rules/prefer-destructured-store-props.js'; import preferStyleDirective from '../rules/prefer-style-directive.js'; +import preferSvelteReactivity from '../rules/prefer-svelte-reactivity.js'; import requireEachKey from '../rules/require-each-key.js'; import requireEventDispatcherTypes from '../rules/require-event-dispatcher-types.js'; import requireOptimizedStyleAttribute from '../rules/require-optimized-style-attribute.js'; @@ -135,6 +136,7 @@ export const rules = [ preferConst, preferDestructuredStoreProps, preferStyleDirective, + preferSvelteReactivity, requireEachKey, requireEventDispatcherTypes, requireOptimizedStyleAttribute, diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/date01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/date01-input.svelte new file mode 100644 index 000000000..3d0b6ce89 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/date01-input.svelte @@ -0,0 +1,5 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/map01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/map01-input.svelte new file mode 100644 index 000000000..5403fc3ea --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/map01-input.svelte @@ -0,0 +1,5 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/set01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/set01-input.svelte new file mode 100644 index 000000000..7a1ad366b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/set01-input.svelte @@ -0,0 +1,5 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url-search-params01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url-search-params01-input.svelte new file mode 100644 index 000000000..2423d7960 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url-search-params01-input.svelte @@ -0,0 +1,5 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url01-input.svelte new file mode 100644 index 000000000..9851e3103 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/url01-input.svelte @@ -0,0 +1,5 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-date01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-date01-input.svelte new file mode 100644 index 000000000..0ab09e823 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-date01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-map01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-map01-input.svelte new file mode 100644 index 000000000..5f431b8b1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-map01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-set01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-set01-input.svelte new file mode 100644 index 000000000..bf3379c3c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-set01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url-search-params01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url-search-params01-input.svelte new file mode 100644 index 000000000..8d3d4d2d2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url-search-params01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url01-input.svelte new file mode 100644 index 000000000..ea960e9fd --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/aliased-url01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-date01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-date01-input.svelte new file mode 100644 index 000000000..911cb5461 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-date01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-map01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-map01-input.svelte new file mode 100644 index 000000000..43ff00a06 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-map01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-set01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-set01-input.svelte new file mode 100644 index 000000000..6c6728c53 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-set01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url-search-params01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url-search-params01-input.svelte new file mode 100644 index 000000000..0f66db767 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url-search-params01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url01-input.svelte new file mode 100644 index 000000000..014046937 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/svelte-url01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-date01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-date01-input.svelte new file mode 100644 index 000000000..851db5e63 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-date01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-map01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-map01-input.svelte new file mode 100644 index 000000000..32eab3dc7 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-map01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-set01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-set01-input.svelte new file mode 100644 index 000000000..fc378f046 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-set01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url-search-params01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url-search-params01-input.svelte new file mode 100644 index 000000000..de69ce09e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url-search-params01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url01-input.svelte new file mode 100644 index 000000000..eab451693 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/unrelated-url01-input.svelte @@ -0,0 +1,7 @@ + + +{variable} diff --git a/packages/eslint-plugin-svelte/tests/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/tests/src/rules/prefer-svelte-reactivity.ts new file mode 100644 index 000000000..7517fdc3a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/prefer-svelte-reactivity.ts @@ -0,0 +1,16 @@ +import { RuleTester } from '../../utils/eslint-compat.js'; +import rule from '../../../src/rules/prefer-svelte-reactivity.js'; +import { loadTestCases } from '../../utils/utils.js'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + globals: { + URL: "readonly", + URLSearchParams: "readonly", + }, + sourceType: 'module' + } +}); + +tester.run('prefer-svelte-reactivity', rule as any, loadTestCases('prefer-svelte-reactivity'));