From d0f2b5a4b91a934b4f18735d2a57ee4af82bc773 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:57:37 +0000 Subject: [PATCH 01/10] feat: add fixer for `derived-has-same-inputs-outputs` Simply replaces the param names with the correct ones since we're already aware of what they should be. --- .../rules/derived-has-same-inputs-outputs.ts | 7 +++++-- .../invalid/test01-output.js | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 3ea90c277..d9fd1e3ce 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -11,6 +11,7 @@ export default createRule('derived-has-same-inputs-outputs', { recommended: false, conflictWithPrettier: false }, + fixable: 'code', schema: [], messages: { unexpected: "The argument name should be '{{name}}'." @@ -53,7 +54,8 @@ export default createRule('derived-has-same-inputs-outputs', { node: fn, loc: fnParam.loc, messageId: 'unexpected', - data: { name: expectedName } + data: { name: expectedName }, + fix: (fixer) => fixer.replaceText(fnParam, expectedName) }); } } @@ -81,7 +83,8 @@ export default createRule('derived-has-same-inputs-outputs', { node: fn, loc: element.loc, messageId: 'unexpected', - data: { name: expectedName } + data: { name: expectedName }, + fix: (fixer) => fixer.replaceText(element, expectedName) }); } } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js new file mode 100644 index 000000000..2b3bf071f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js @@ -0,0 +1,20 @@ +import { derived } from 'svelte/store'; + +derived(a, ($a) => { + /** do nothing */ +}); +derived(c, ($c, set) => { + /** do nothing */ +}); +derived([e, f], ([$e, $f]) => { + /** do nothing */ +}); +derived([i, j], ([$i, $j], set) => { + /** do nothing */ +}); +derived([null, l], ([$m, $l]) => { + /** do nothing */ +}); +derived([o, null], ([$o, $q]) => { + /** do nothing */ +}); From 6a4bfc3d089f423752df3088f040b9f72d336f94 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:20:21 +0000 Subject: [PATCH 02/10] fix: rename variable references within the current scope --- .../rules/derived-has-same-inputs-outputs.ts | 31 ++++++++++++++-- packages/eslint-plugin-svelte/src/types.ts | 2 ++ .../invalid/test01-errors.yaml | 24 +++++++++++++ .../invalid/test01-input.js | 36 +++++++++++++++++++ .../invalid/test01-output.js | 36 +++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index d9fd1e3ce..734ffc080 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -2,6 +2,7 @@ import type { TSESTree } from '@typescript-eslint/types'; import { createRule } from '../utils/index.js'; import type { RuleContext } from '../types.js'; import { extractStoreReferences } from './reference-helpers/svelte-store.js'; +import { getSourceCode } from '../utils/compat.js'; export default createRule('derived-has-same-inputs-outputs', { meta: { @@ -55,7 +56,19 @@ export default createRule('derived-has-same-inputs-outputs', { loc: fnParam.loc, messageId: 'unexpected', data: { name: expectedName }, - fix: (fixer) => fixer.replaceText(fnParam, expectedName) + fix: (fixer) => { + const scope = getSourceCode(context).getScope(fn.body); + const variable = scope.variables.find((variable) => variable.name === fnParam.name); + + if (!variable) { + return fixer.replaceText(fnParam, expectedName); + } + + return [ + fixer.replaceText(fnParam, expectedName), + ...variable.references.map((ref) => fixer.replaceText(ref.identifier, expectedName)) + ]; + } }); } } @@ -84,7 +97,21 @@ export default createRule('derived-has-same-inputs-outputs', { loc: element.loc, messageId: 'unexpected', data: { name: expectedName }, - fix: (fixer) => fixer.replaceText(element, expectedName) + fix: (fixer) => { + const scope = getSourceCode(context).getScope(fn.body); + const variable = scope.variables.find((variable) => variable.name === element.name); + + if (!variable) { + return fixer.replaceText(element, expectedName); + } + + return [ + fixer.replaceText(element, expectedName), + ...variable.references.map((ref) => + fixer.replaceText(ref.identifier, expectedName) + ) + ]; + } }); } } diff --git a/packages/eslint-plugin-svelte/src/types.ts b/packages/eslint-plugin-svelte/src/types.ts index c7deb2756..1f74b81d4 100644 --- a/packages/eslint-plugin-svelte/src/types.ts +++ b/packages/eslint-plugin-svelte/src/types.ts @@ -366,4 +366,6 @@ export interface SourceCode { getCommentsAfter(nodeOrToken: NodeOrToken | AST.Token): AST.Comment[]; getCommentsInside(node: NodeOrToken): AST.Comment[]; + + getScope(node: NodeOrToken): Scope; } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml index 9ce09c034..4f2e36d10 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml @@ -30,3 +30,27 @@ line: 18 column: 22 suggestions: null +- message: The argument name should be '$a'. + line: 21 + column: 13 + suggestions: null +- message: The argument name should be '$a'. + line: 28 + column: 13 + suggestions: null +- message: The argument name should be '$e'. + line: 37 + column: 19 + suggestions: null +- message: The argument name should be '$f'. + line: 37 + column: 22 + suggestions: null +- message: The argument name should be '$e'. + line: 46 + column: 19 + suggestions: null +- message: The argument name should be '$f'. + line: 46 + column: 22 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js index 98d579bcc..9bff5cdc1 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js @@ -18,3 +18,39 @@ derived([null, l], ([$m, $n]) => { derived([o, null], ([$p, $q]) => { /** do nothing */ }); +derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); +}); +derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); +}); +derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); +}); +derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); +}); diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js index 2b3bf071f..d33b8eef8 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js @@ -18,3 +18,39 @@ derived([null, l], ([$m, $l]) => { derived([o, null], ([$o, $q]) => { /** do nothing */ }); +derived(a, ($a) => { + doSomethingWith($a); + + somethingWithACallback(() => { + $a; + }); +}); +derived(a, ($a) => { + $a; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); +}); +derived([e, f], ([$e, h]) => { + $e; + h; + + somethingWithACallback(() => { + $e; + h; + }); +}); +derived([e, f], ([$e, h]) => { + $e; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); +}); From 4014919402da03bca6f609e33da6f3044b89baa9 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 12:07:36 +0000 Subject: [PATCH 03/10] fix: try use a generator instead Still figuring out what eslint is playing at. --- .../rules/derived-has-same-inputs-outputs.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 734ffc080..6bae0b81d 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -97,20 +97,17 @@ export default createRule('derived-has-same-inputs-outputs', { loc: element.loc, messageId: 'unexpected', data: { name: expectedName }, - fix: (fixer) => { + *fix(fixer) { const scope = getSourceCode(context).getScope(fn.body); const variable = scope.variables.find((variable) => variable.name === element.name); - if (!variable) { - return fixer.replaceText(element, expectedName); - } + yield fixer.replaceText(element, expectedName); - return [ - fixer.replaceText(element, expectedName), - ...variable.references.map((ref) => - fixer.replaceText(ref.identifier, expectedName) - ) - ]; + if (variable) { + for (const ref of variable.references) { + yield fixer.replaceText(ref.identifier, expectedName); + } + } } }); } From 84055878de43ea6d05c4b53e87022df54284fb8b Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:05:41 +0000 Subject: [PATCH 04/10] chore: rework to be a suggestion --- .../rules/derived-has-same-inputs-outputs.ts | 101 +- .../invalid/test01-errors.yaml | 926 +++++++++++++++++- .../invalid/test01-input.js | 5 + .../invalid/test01-output.js | 56 -- 4 files changed, 994 insertions(+), 94 deletions(-) delete mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 6bae0b81d..81d3dcc42 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -1,9 +1,36 @@ import type { TSESTree } from '@typescript-eslint/types'; +import type { Variable } from '@typescript-eslint/scope-manager'; import { createRule } from '../utils/index.js'; import type { RuleContext } from '../types.js'; import { extractStoreReferences } from './reference-helpers/svelte-store.js'; import { getSourceCode } from '../utils/compat.js'; +function findVariableForName( + context: RuleContext, + node: TSESTree.Node, + name: string, + expectedName: string +): { hasConflict: boolean; variable: Variable | null } { + const scope = getSourceCode(context).getScope(node); + let hasConflict = false; + let variable: Variable | null = null; + + for (const v of scope.variables) { + if (hasConflict && variable) { + break; + } + if (v.name === expectedName) { + hasConflict = true; + continue; + } + if (v.name === name) { + variable = v; + } + } + + return { hasConflict, variable }; +} + export default createRule('derived-has-same-inputs-outputs', { meta: { docs: { @@ -12,10 +39,11 @@ export default createRule('derived-has-same-inputs-outputs', { recommended: false, conflictWithPrettier: false }, - fixable: 'code', + hasSuggestions: true, schema: [], messages: { - unexpected: "The argument name should be '{{name}}'." + unexpected: "The argument name should be '{{name}}'.", + renameParam: 'Rename the parameter from {{oldName}} to {{newName}}.' }, type: 'suggestion' }, @@ -51,24 +79,35 @@ export default createRule('derived-has-same-inputs-outputs', { if (fnParam.type !== 'Identifier') return; const expectedName = `$${args.name}`; if (expectedName !== fnParam.name) { + const { hasConflict, variable } = findVariableForName( + context, + fn.body, + fnParam.name, + expectedName + ); + context.report({ node: fn, loc: fnParam.loc, messageId: 'unexpected', data: { name: expectedName }, - fix: (fixer) => { - const scope = getSourceCode(context).getScope(fn.body); - const variable = scope.variables.find((variable) => variable.name === fnParam.name); + suggest: hasConflict + ? undefined + : [ + { + messageId: 'renameParam', + data: { oldName: fnParam.name, newName: expectedName }, + *fix(fixer) { + yield fixer.replaceText(fnParam, expectedName); - if (!variable) { - return fixer.replaceText(fnParam, expectedName); - } - - return [ - fixer.replaceText(fnParam, expectedName), - ...variable.references.map((ref) => fixer.replaceText(ref.identifier, expectedName)) - ]; - } + if (variable) { + for (const ref of variable.references) { + yield fixer.replaceText(ref.identifier, expectedName); + } + } + } + } + ] }); } } @@ -92,23 +131,35 @@ export default createRule('derived-has-same-inputs-outputs', { if (element && element.type === 'Identifier' && argName) { const expectedName = `$${argName}`; if (expectedName !== element.name) { + const { hasConflict, variable } = findVariableForName( + context, + fn.body, + element.name, + expectedName + ); + context.report({ node: fn, loc: element.loc, messageId: 'unexpected', data: { name: expectedName }, - *fix(fixer) { - const scope = getSourceCode(context).getScope(fn.body); - const variable = scope.variables.find((variable) => variable.name === element.name); - - yield fixer.replaceText(element, expectedName); + suggest: hasConflict + ? undefined + : [ + { + messageId: 'renameParam', + data: { oldName: element.name, newName: expectedName }, + *fix(fixer) { + yield fixer.replaceText(element, expectedName); - if (variable) { - for (const ref of variable.references) { - yield fixer.replaceText(ref.identifier, expectedName); - } - } - } + if (variable) { + for (const ref of variable.references) { + yield fixer.replaceText(ref.identifier, expectedName); + } + } + } + } + ] }); } } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml index 4f2e36d10..5949ea561 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml @@ -1,56 +1,956 @@ - message: The argument name should be '$a'. line: 3 column: 13 - suggestions: null + suggestions: + - desc: Rename the parameter from b to $a. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, ($a) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$c'. line: 6 column: 13 - suggestions: null + suggestions: + - desc: Rename the parameter from d to $c. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, ($c, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$e'. line: 9 column: 19 - suggestions: null + suggestions: + - desc: Rename the parameter from g to $e. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([$e, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$f'. line: 9 column: 22 - suggestions: null + suggestions: + - desc: Rename the parameter from h to $f. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, $f]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$i'. line: 12 column: 19 - suggestions: null + suggestions: + - desc: Rename the parameter from k to $i. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([$i, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$j'. line: 12 column: 22 - suggestions: null + suggestions: + - desc: Rename the parameter from l to $j. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, $j], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$l'. line: 15 column: 26 - suggestions: null + suggestions: + - desc: Rename the parameter from $n to $l. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $l]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$o'. line: 18 column: 22 - suggestions: null + suggestions: + - desc: Rename the parameter from $p to $o. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$o, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$a'. line: 21 column: 13 - suggestions: null + suggestions: + - desc: Rename the parameter from b to $a. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, ($a) => { + doSomethingWith($a); + + somethingWithACallback(() => { + $a; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$a'. line: 28 column: 13 - suggestions: null + suggestions: + - desc: Rename the parameter from b to $a. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, ($a) => { + $a; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$e'. line: 37 column: 19 - suggestions: null + suggestions: + - desc: Rename the parameter from g to $e. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([$e, h]) => { + $e; + h; + + somethingWithACallback(() => { + $e; + h; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$f'. line: 37 column: 22 - suggestions: null + suggestions: + - desc: Rename the parameter from h to $f. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, $f]) => { + g; + $f; + + somethingWithACallback(() => { + g; + $f; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$e'. line: 46 column: 19 - suggestions: null + suggestions: + - desc: Rename the parameter from g to $e. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([$e, h]) => { + $e; + h; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); - message: The argument name should be '$f'. line: 46 column: 22 + suggestions: + - desc: Rename the parameter from h to $f. + messageId: renameParam + output: | + import { derived } from 'svelte/store'; + + derived(a, (b) => { + /** do nothing */ + }); + derived(c, (d, set) => { + /** do nothing */ + }); + derived([e, f], ([g, h]) => { + /** do nothing */ + }); + derived([i, j], ([k, l], set) => { + /** do nothing */ + }); + derived([null, l], ([$m, $n]) => { + /** do nothing */ + }); + derived([o, null], ([$p, $q]) => { + /** do nothing */ + }); + derived(a, (b) => { + doSomethingWith(b); + + somethingWithACallback(() => { + b; + }); + }); + derived(a, (b) => { + b; + + somethingWithACallback(() => { + // purposely shadow the var, this should not be updated + const b = 303; + b; + }); + }); + derived([e, f], ([g, h]) => { + g; + h; + + somethingWithACallback(() => { + g; + h; + }); + }); + derived([e, f], ([g, $f]) => { + g; + $f; + + somethingWithACallback(() => { + const g = 303; + const h = 808; + g; + h; + }); + }); + derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; + }); +- message: The argument name should be '$a'. + line: 57 + column: 13 suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js index 9bff5cdc1..745ba1564 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js @@ -54,3 +54,8 @@ derived([e, f], ([g, h]) => { h; }); }); +derived(a, (b) => { + // cause a conflict in names so the suggestion can't work + const $a = 303; + $a; +}); diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js deleted file mode 100644 index d33b8eef8..000000000 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-output.js +++ /dev/null @@ -1,56 +0,0 @@ -import { derived } from 'svelte/store'; - -derived(a, ($a) => { - /** do nothing */ -}); -derived(c, ($c, set) => { - /** do nothing */ -}); -derived([e, f], ([$e, $f]) => { - /** do nothing */ -}); -derived([i, j], ([$i, $j], set) => { - /** do nothing */ -}); -derived([null, l], ([$m, $l]) => { - /** do nothing */ -}); -derived([o, null], ([$o, $q]) => { - /** do nothing */ -}); -derived(a, ($a) => { - doSomethingWith($a); - - somethingWithACallback(() => { - $a; - }); -}); -derived(a, ($a) => { - $a; - - somethingWithACallback(() => { - // purposely shadow the var, this should not be updated - const b = 303; - b; - }); -}); -derived([e, f], ([$e, h]) => { - $e; - h; - - somethingWithACallback(() => { - $e; - h; - }); -}); -derived([e, f], ([$e, h]) => { - $e; - h; - - somethingWithACallback(() => { - const g = 303; - const h = 808; - g; - h; - }); -}); From b0ad83c29158ffa33de0eaa12a420004374dea38 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:18:41 +0000 Subject: [PATCH 05/10] fix: use getScope util --- .../src/rules/derived-has-same-inputs-outputs.ts | 4 ++-- packages/eslint-plugin-svelte/src/types.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 81d3dcc42..9049bf780 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -3,7 +3,7 @@ import type { Variable } from '@typescript-eslint/scope-manager'; import { createRule } from '../utils/index.js'; import type { RuleContext } from '../types.js'; import { extractStoreReferences } from './reference-helpers/svelte-store.js'; -import { getSourceCode } from '../utils/compat.js'; +import { getScope } from '../utils/ast-utils.js'; function findVariableForName( context: RuleContext, @@ -11,7 +11,7 @@ function findVariableForName( name: string, expectedName: string ): { hasConflict: boolean; variable: Variable | null } { - const scope = getSourceCode(context).getScope(node); + const scope = getScope(context, node); let hasConflict = false; let variable: Variable | null = null; diff --git a/packages/eslint-plugin-svelte/src/types.ts b/packages/eslint-plugin-svelte/src/types.ts index 1f74b81d4..c7deb2756 100644 --- a/packages/eslint-plugin-svelte/src/types.ts +++ b/packages/eslint-plugin-svelte/src/types.ts @@ -366,6 +366,4 @@ export interface SourceCode { getCommentsAfter(nodeOrToken: NodeOrToken | AST.Token): AST.Comment[]; getCommentsInside(node: NodeOrToken): AST.Comment[]; - - getScope(node: NodeOrToken): Scope; } From b9a161ca7b2966cc7fad565e173177cd008af9e6 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:25:50 +0000 Subject: [PATCH 06/10] chore: update docs --- README.md | 2 +- docs/rules.md | 2 +- docs/rules/derived-has-same-inputs-outputs.md | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db731dd26..735d4279d 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | Rule ID | Description | | |:--------|:------------|:---| | [svelte/consistent-selector-style](https://sveltejs.github.io/eslint-plugin-svelte/rules/consistent-selector-style/) | enforce a consistent style for CSS selectors | | -| [svelte/derived-has-same-inputs-outputs](https://sveltejs.github.io/eslint-plugin-svelte/rules/derived-has-same-inputs-outputs/) | derived store should use same variable names between values and callback | | +| [svelte/derived-has-same-inputs-outputs](https://sveltejs.github.io/eslint-plugin-svelte/rules/derived-has-same-inputs-outputs/) | derived store should use same variable names between values and callback | :bulb: | | [svelte/first-attribute-linebreak](https://sveltejs.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak/) | enforce the location of first attribute | :wrench: | | [svelte/html-closing-bracket-new-line](https://sveltejs.github.io/eslint-plugin-svelte/rules/html-closing-bracket-new-line/) | Require or disallow a line break before tag's closing brackets | :wrench: | | [svelte/html-closing-bracket-spacing](https://sveltejs.github.io/eslint-plugin-svelte/rules/html-closing-bracket-spacing/) | require or disallow a space before tag's closing brackets | :wrench: | diff --git a/docs/rules.md b/docs/rules.md index 58d896b10..55cfee95f 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -80,7 +80,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | Rule ID | Description | | | :------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------- | | [svelte/consistent-selector-style](./rules/consistent-selector-style.md) | enforce a consistent style for CSS selectors | | -| [svelte/derived-has-same-inputs-outputs](./rules/derived-has-same-inputs-outputs.md) | derived store should use same variable names between values and callback | | +| [svelte/derived-has-same-inputs-outputs](./rules/derived-has-same-inputs-outputs.md) | derived store should use same variable names between values and callback | :bulb: | | [svelte/first-attribute-linebreak](./rules/first-attribute-linebreak.md) | enforce the location of first attribute | :wrench: | | [svelte/html-closing-bracket-new-line](./rules/html-closing-bracket-new-line.md) | Require or disallow a line break before tag's closing brackets | :wrench: | | [svelte/html-closing-bracket-spacing](./rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets | :wrench: | diff --git a/docs/rules/derived-has-same-inputs-outputs.md b/docs/rules/derived-has-same-inputs-outputs.md index b059bc655..f8c6568e8 100644 --- a/docs/rules/derived-has-same-inputs-outputs.md +++ b/docs/rules/derived-has-same-inputs-outputs.md @@ -10,6 +10,8 @@ since: 'v2.8.0' > derived store should use same variable names between values and callback +- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + ## :book: Rule Details This rule reports where variable names and callback function's argument names are different. From b12da198bec3b4d21b9b18ceb0f0465a2f48817d Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:53:35 +0000 Subject: [PATCH 07/10] chore: add changeset --- .changeset/neat-dots-grin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-dots-grin.md diff --git a/.changeset/neat-dots-grin.md b/.changeset/neat-dots-grin.md new file mode 100644 index 000000000..9a77b5022 --- /dev/null +++ b/.changeset/neat-dots-grin.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +Adds a suggestion to the `derived-has-same-inputs-outputs` rule which renames the outputs. From 30418af5dd38ed13514cc4dea21c8e3f4aec4ed3 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 15:32:18 +0000 Subject: [PATCH 08/10] chore: extract fixer to factory --- .../rules/derived-has-same-inputs-outputs.ts | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 9049bf780..47ea34d05 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -1,7 +1,7 @@ import type { TSESTree } from '@typescript-eslint/types'; import type { Variable } from '@typescript-eslint/scope-manager'; import { createRule } from '../utils/index.js'; -import type { RuleContext } from '../types.js'; +import type { RuleContext, RuleFixer } from '../types.js'; import { extractStoreReferences } from './reference-helpers/svelte-store.js'; import { getScope } from '../utils/ast-utils.js'; @@ -31,6 +31,18 @@ function findVariableForName( return { hasConflict, variable }; } +function createFixer(node: TSESTree.Node, variable: Variable | null, name: string) { + return function* fix(fixer: RuleFixer) { + yield fixer.replaceText(node, name); + + if (variable) { + for (const ref of variable.references) { + yield fixer.replaceText(ref.identifier, name); + } + } + }; +} + export default createRule('derived-has-same-inputs-outputs', { meta: { docs: { @@ -97,15 +109,7 @@ export default createRule('derived-has-same-inputs-outputs', { { messageId: 'renameParam', data: { oldName: fnParam.name, newName: expectedName }, - *fix(fixer) { - yield fixer.replaceText(fnParam, expectedName); - - if (variable) { - for (const ref of variable.references) { - yield fixer.replaceText(ref.identifier, expectedName); - } - } - } + fix: createFixer(fnParam, variable, expectedName) } ] }); @@ -149,15 +153,7 @@ export default createRule('derived-has-same-inputs-outputs', { { messageId: 'renameParam', data: { oldName: element.name, newName: expectedName }, - *fix(fixer) { - yield fixer.replaceText(element, expectedName); - - if (variable) { - for (const ref of variable.references) { - yield fixer.replaceText(ref.identifier, expectedName); - } - } - } + fix: createFixer(element, variable, expectedName) } ] }); From 1710e44ccd201e61f6f74ceac2deb9079e7a6ec4 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 15:44:59 +0000 Subject: [PATCH 09/10] fix: do not fix when there are parent conflicts --- .../rules/derived-has-same-inputs-outputs.ts | 25 ++++-- .../invalid/test01-errors.yaml | 88 +++++++++++++++++++ .../invalid/test01-input.js | 6 ++ 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 47ea34d05..3e48bf668 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -15,16 +15,25 @@ function findVariableForName( let hasConflict = false; let variable: Variable | null = null; - for (const v of scope.variables) { - if (hasConflict && variable) { - break; - } - if (v.name === expectedName) { + for (const ref of scope.references) { + if (ref.identifier.name === expectedName) { hasConflict = true; - continue; + break; } - if (v.name === name) { - variable = v; + } + + if (!hasConflict) { + for (const v of scope.variables) { + if (hasConflict && variable) { + break; + } + if (v.name === expectedName) { + hasConflict = true; + continue; + } + if (v.name === name) { + variable = v; + } } } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml index 5949ea561..2107d3072 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-errors.yaml @@ -66,6 +66,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$c'. line: 6 column: 13 @@ -134,6 +140,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$e'. line: 9 column: 19 @@ -202,6 +214,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$f'. line: 9 column: 22 @@ -270,6 +288,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$i'. line: 12 column: 19 @@ -338,6 +362,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$j'. line: 12 column: 22 @@ -406,6 +436,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$l'. line: 15 column: 26 @@ -474,6 +510,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$o'. line: 18 column: 22 @@ -542,6 +584,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$a'. line: 21 column: 13 @@ -610,6 +658,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$a'. line: 28 column: 13 @@ -678,6 +732,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$e'. line: 37 column: 19 @@ -746,6 +806,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$f'. line: 37 column: 22 @@ -814,6 +880,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$e'. line: 46 column: 19 @@ -882,6 +954,12 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$f'. line: 46 column: 22 @@ -950,7 +1028,17 @@ const $a = 303; $a; }); + somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); + }); - message: The argument name should be '$a'. line: 57 column: 13 suggestions: null +- message: The argument name should be '$a'. + line: 64 + column: 14 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js index 745ba1564..0cda15103 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/derived-has-same-inputs-outputs/invalid/test01-input.js @@ -59,3 +59,9 @@ derived(a, (b) => { const $a = 303; $a; }); +somethingWithACallback(() => { + const $a = 303; + derived(a, (b) => { + $a; + }); +}); From 5b85027125295407daf1b8f0ba2c3d79d4c9d5e6 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 29 Mar 2025 16:51:25 +0000 Subject: [PATCH 10/10] chore: rework with an early return --- .../rules/derived-has-same-inputs-outputs.ts | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts index 3e48bf668..30f86aa31 100644 --- a/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts +++ b/packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts @@ -12,32 +12,24 @@ function findVariableForName( expectedName: string ): { hasConflict: boolean; variable: Variable | null } { const scope = getScope(context, node); - let hasConflict = false; let variable: Variable | null = null; for (const ref of scope.references) { if (ref.identifier.name === expectedName) { - hasConflict = true; - break; + return { hasConflict: true, variable: null }; } } - if (!hasConflict) { - for (const v of scope.variables) { - if (hasConflict && variable) { - break; - } - if (v.name === expectedName) { - hasConflict = true; - continue; - } - if (v.name === name) { - variable = v; - } + for (const v of scope.variables) { + if (v.name === expectedName) { + return { hasConflict: true, variable: null }; + } + if (v.name === name) { + variable = v; } } - return { hasConflict, variable }; + return { hasConflict: false, variable }; } function createFixer(node: TSESTree.Node, variable: Variable | null, name: string) {