Skip to content

Commit d9b8604

Browse files
authored
feat: add fixer for derived-has-same-inputs-outputs (#1163)
1 parent 29b1315 commit d9b8604

File tree

7 files changed

+1154
-13
lines changed

7 files changed

+1154
-13
lines changed

.changeset/neat-dots-grin.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
Adds a suggestion to the `derived-has-same-inputs-outputs` rule which renames the outputs.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
323323
| Rule ID | Description | |
324324
|:--------|:------------|:---|
325325
| [svelte/consistent-selector-style](https://sveltejs.github.io/eslint-plugin-svelte/rules/consistent-selector-style/) | enforce a consistent style for CSS selectors | |
326-
| [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 | |
326+
| [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: |
327327
| [svelte/first-attribute-linebreak](https://sveltejs.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak/) | enforce the location of first attribute | :wrench: |
328328
| [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: |
329329
| [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: |

docs/rules.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
8080
| Rule ID | Description | |
8181
| :------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------- |
8282
| [svelte/consistent-selector-style](./rules/consistent-selector-style.md) | enforce a consistent style for CSS selectors | |
83-
| [svelte/derived-has-same-inputs-outputs](./rules/derived-has-same-inputs-outputs.md) | derived store should use same variable names between values and callback | |
83+
| [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: |
8484
| [svelte/first-attribute-linebreak](./rules/first-attribute-linebreak.md) | enforce the location of first attribute | :wrench: |
8585
| [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: |
8686
| [svelte/html-closing-bracket-spacing](./rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets | :wrench: |

docs/rules/derived-has-same-inputs-outputs.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ since: 'v2.8.0'
1010

1111
> derived store should use same variable names between values and callback
1212
13+
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
14+
1315
## :book: Rule Details
1416

1517
This rule reports where variable names and callback function's argument names are different.

packages/eslint-plugin-svelte/src/rules/derived-has-same-inputs-outputs.ts

+79-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,48 @@
11
import type { TSESTree } from '@typescript-eslint/types';
2+
import type { Variable } from '@typescript-eslint/scope-manager';
23
import { createRule } from '../utils/index.js';
3-
import type { RuleContext } from '../types.js';
4+
import type { RuleContext, RuleFixer } from '../types.js';
45
import { extractStoreReferences } from './reference-helpers/svelte-store.js';
6+
import { getScope } from '../utils/ast-utils.js';
7+
8+
function findVariableForName(
9+
context: RuleContext,
10+
node: TSESTree.Node,
11+
name: string,
12+
expectedName: string
13+
): { hasConflict: boolean; variable: Variable | null } {
14+
const scope = getScope(context, node);
15+
let variable: Variable | null = null;
16+
17+
for (const ref of scope.references) {
18+
if (ref.identifier.name === expectedName) {
19+
return { hasConflict: true, variable: null };
20+
}
21+
}
22+
23+
for (const v of scope.variables) {
24+
if (v.name === expectedName) {
25+
return { hasConflict: true, variable: null };
26+
}
27+
if (v.name === name) {
28+
variable = v;
29+
}
30+
}
31+
32+
return { hasConflict: false, variable };
33+
}
34+
35+
function createFixer(node: TSESTree.Node, variable: Variable | null, name: string) {
36+
return function* fix(fixer: RuleFixer) {
37+
yield fixer.replaceText(node, name);
38+
39+
if (variable) {
40+
for (const ref of variable.references) {
41+
yield fixer.replaceText(ref.identifier, name);
42+
}
43+
}
44+
};
45+
}
546

647
export default createRule('derived-has-same-inputs-outputs', {
748
meta: {
@@ -11,9 +52,11 @@ export default createRule('derived-has-same-inputs-outputs', {
1152
recommended: false,
1253
conflictWithPrettier: false
1354
},
55+
hasSuggestions: true,
1456
schema: [],
1557
messages: {
16-
unexpected: "The argument name should be '{{name}}'."
58+
unexpected: "The argument name should be '{{name}}'.",
59+
renameParam: 'Rename the parameter from {{oldName}} to {{newName}}.'
1760
},
1861
type: 'suggestion'
1962
},
@@ -49,11 +92,27 @@ export default createRule('derived-has-same-inputs-outputs', {
4992
if (fnParam.type !== 'Identifier') return;
5093
const expectedName = `$${args.name}`;
5194
if (expectedName !== fnParam.name) {
95+
const { hasConflict, variable } = findVariableForName(
96+
context,
97+
fn.body,
98+
fnParam.name,
99+
expectedName
100+
);
101+
52102
context.report({
53103
node: fn,
54104
loc: fnParam.loc,
55105
messageId: 'unexpected',
56-
data: { name: expectedName }
106+
data: { name: expectedName },
107+
suggest: hasConflict
108+
? undefined
109+
: [
110+
{
111+
messageId: 'renameParam',
112+
data: { oldName: fnParam.name, newName: expectedName },
113+
fix: createFixer(fnParam, variable, expectedName)
114+
}
115+
]
57116
});
58117
}
59118
}
@@ -77,11 +136,27 @@ export default createRule('derived-has-same-inputs-outputs', {
77136
if (element && element.type === 'Identifier' && argName) {
78137
const expectedName = `$${argName}`;
79138
if (expectedName !== element.name) {
139+
const { hasConflict, variable } = findVariableForName(
140+
context,
141+
fn.body,
142+
element.name,
143+
expectedName
144+
);
145+
80146
context.report({
81147
node: fn,
82148
loc: element.loc,
83149
messageId: 'unexpected',
84-
data: { name: expectedName }
150+
data: { name: expectedName },
151+
suggest: hasConflict
152+
? undefined
153+
: [
154+
{
155+
messageId: 'renameParam',
156+
data: { oldName: element.name, newName: expectedName },
157+
fix: createFixer(element, variable, expectedName)
158+
}
159+
]
85160
});
86161
}
87162
}

0 commit comments

Comments
 (0)