Skip to content

feat: add fixer for derived-has-same-inputs-outputs #1163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 29, 2025
5 changes: 5 additions & 0 deletions .changeset/neat-dots-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-svelte': minor
---

Adds a suggestion to the `derived-has-same-inputs-outputs` rule which renames the outputs.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/derived-has-same-inputs-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
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';

function findVariableForName(
context: RuleContext,
node: TSESTree.Node,
name: string,
expectedName: string
): { hasConflict: boolean; variable: Variable | null } {
const scope = getScope(context, node);
let variable: Variable | null = null;

for (const ref of scope.references) {
if (ref.identifier.name === expectedName) {
return { hasConflict: true, variable: null };
}
}

for (const v of scope.variables) {
if (v.name === expectedName) {
return { hasConflict: true, variable: null };
}
if (v.name === name) {
variable = v;
}
}

return { hasConflict: false, 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: {
Expand All @@ -11,9 +52,11 @@ export default createRule('derived-has-same-inputs-outputs', {
recommended: false,
conflictWithPrettier: false
},
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'
},
Expand Down Expand Up @@ -49,11 +92,27 @@ 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 }
data: { name: expectedName },
suggest: hasConflict
? undefined
: [
{
messageId: 'renameParam',
data: { oldName: fnParam.name, newName: expectedName },
fix: createFixer(fnParam, variable, expectedName)
}
]
});
}
}
Expand All @@ -77,11 +136,27 @@ 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 }
data: { name: expectedName },
suggest: hasConflict
? undefined
: [
{
messageId: 'renameParam',
data: { oldName: element.name, newName: expectedName },
fix: createFixer(element, variable, expectedName)
}
]
});
}
}
Expand Down
Loading
Loading