diff --git a/docs/rules/no-unnecessary-state-wrap.md b/docs/rules/no-unnecessary-state-wrap.md index 3ee458dab..ec56bca9f 100644 --- a/docs/rules/no-unnecessary-state-wrap.md +++ b/docs/rules/no-unnecessary-state-wrap.md @@ -66,14 +66,17 @@ Therefore, wrapping them with `$state` is unnecessary and can lead to confusion. "error", { "additionalReactiveClasses": [], - "allowReassign": false + "allowReassign": ["SvelteSet"] } ] } ``` - `additionalReactiveClasses` ... An array of class names that should also be considered reactive. This is useful when you have custom classes that are inherently reactive. Default is `[]`. -- `allowReassign` ... If `true`, allows `$state` wrapping of reactive classes when the variable is reassigned. Default is `false`. +- `allowReassign` ... Can be either a boolean or an array of class names: + - If `true`, allows `$state` wrapping of any reactive classes when the variable is reassigned. + - If an array, allows `$state` wrapping for the specified reactive classes when the variable is reassigned. + - Default is `["SvelteSet"]`. ### Examples with Options @@ -97,13 +100,36 @@ Therefore, wrapping them with `$state` is unnecessary and can lead to confusion. ```svelte +``` + +#### `allowReassign` as array + +```svelte + diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 7b5cf238c..f5d448b8a 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -526,7 +526,7 @@ type SvelteNoUnknownStyleDirectiveProperty = []|[{ // ----- svelte/no-unnecessary-state-wrap ----- type SvelteNoUnnecessaryStateWrap = []|[{ additionalReactiveClasses?: string[] - allowReassign?: boolean + allowReassign?: (boolean | string[]) }] // ----- svelte/no-unused-class-name ----- type SvelteNoUnusedClassName = []|[{ diff --git a/packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts b/packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts index f89f52765..4177bf599 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts @@ -30,15 +30,28 @@ export default createRule('no-unnecessary-state-wrap', { uniqueItems: true }, allowReassign: { - type: 'boolean' + oneOf: [ + { + type: 'boolean' + }, + { + type: 'array', + items: { + type: 'string' + }, + uniqueItems: true + } + ] } }, additionalProperties: false } ], messages: { - unnecessaryStateWrap: '{{className}} is already reactive, $state wrapping is unnecessary.', - suggestRemoveStateWrap: 'Remove unnecessary $state wrapping' + unnecessaryStateWrap: + '{{className}} is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()).', + suggestRemoveStateWrap: + 'Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()).' }, type: 'suggestion', hasSuggestions: true, @@ -52,7 +65,7 @@ export default createRule('no-unnecessary-state-wrap', { create(context) { const options = context.options[0] ?? {}; const additionalReactiveClasses = options.additionalReactiveClasses ?? []; - const allowReassign = options.allowReassign ?? false; + const allowReassign = options.allowReassign ?? ['SvelteSet']; const { globalScope } = context.sourceCode.scopeManager; if (globalScope == null) { return {}; @@ -91,13 +104,27 @@ export default createRule('no-unnecessary-state-wrap', { }); } + function isAllowedReassign(className: string, identifier?: TSESTree.Identifier): boolean { + if (!identifier) return false; + + if (typeof allowReassign === 'boolean') { + return allowReassign && isReassigned(identifier); + } + + return ( + Array.isArray(allowReassign) && + allowReassign.includes(className) && + isReassigned(identifier) + ); + } + function reportUnnecessaryStateWrap( stateNode: TSESTree.Node, targetNode: TSESTree.Node, className: string, identifier?: TSESTree.Identifier ) { - if (allowReassign && identifier && isReassigned(identifier)) { + if (isAllowedReassign(className, identifier)) { return; } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/additional-class-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/additional-class-errors.yaml index 30518c2f7..1e33fb711 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/additional-class-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/additional-class-errors.yaml @@ -1,8 +1,8 @@ -- message: CustomReactiveClass1 is already reactive, $state wrapping is unnecessary. +- message: CustomReactiveClass1 is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 5 column: 25 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: CustomReactiveClass2 is already reactive, $state wrapping is unnecessary. +- message: CustomReactiveClass2 is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 6 column: 25 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-array-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-array-input.svelte new file mode 100644 index 000000000..9e01a1286 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-array-input.svelte @@ -0,0 +1,13 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-errors.yaml index 536b5f9c6..2f81e698c 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unnecessary-state-wrap/invalid/allow-reassign-errors.yaml @@ -1,8 +1,8 @@ -- message: SvelteSet is already reactive, $state wrapping is unnecessary. +- message: SvelteSet is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 6 column: 21 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteMap is already reactive, $state wrapping is unnecessary. +- message: SvelteMap is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 7 column: 19 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteMap is already reactive, $state wrapping is unnecessary. +- message: SvelteMap is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 13 column: 21 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteURL is already reactive, $state wrapping is unnecessary. +- message: SvelteURL is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 14 column: 21 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteURLSearchParams is already reactive, $state wrapping is unnecessary. +- message: SvelteURLSearchParams is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 15 column: 24 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteDate is already reactive, $state wrapping is unnecessary. +- message: SvelteDate is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 16 column: 22 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: MediaQuery is already reactive, $state wrapping is unnecessary. +- message: MediaQuery is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 17 column: 28 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: | -- message: SvelteMap is already reactive, $state wrapping is unnecessary. +- message: SvelteMap is already reactive. `$state` wrapping is unnecessary. Update it with mutations (e.g., .add(), .delete(), .push(), .splice()). line: 6 column: 21 suggestions: - - desc: Remove unnecessary $state wrapping + - desc: Remove the unnecessary `$state` and update the value via mutations (e.g., .add(), .delete(), .push(), .splice()). messageId: suggestRemoveStateWrap output: >