Skip to content

Commit

Permalink
fixup! [New] Symmetric useState hook variable names
Browse files Browse the repository at this point in the history
  • Loading branch information
duncanbeevers committed Dec 11, 2021
1 parent 7f188db commit 1d81d76
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 12 deletions.
83 changes: 71 additions & 12 deletions lib/rules/hook-use-state.js
Expand Up @@ -28,6 +28,8 @@ module.exports = {
fixable: 'code',
messages,
schema: [],
type: 'suggestion',
hasSuggestions: true,
},

create: Components.detect((context, components) => ({
Expand Down Expand Up @@ -134,18 +136,75 @@ module.exports = {
&& variableNodes.length === 2;

if (!isSymmetricGetterSetterPair) {
report(
context,
messages.useStateErrorMessage,
'useStateErrorMessage',
{
node: node.parent.id,
fix: valueVariableName ? (fixer) => fixer.replaceTextRange(
[node.parent.id.range[0], node.parent.id.range[1]],
`[${valueVariableName}, ${expectedSetterVariableName}]`
) : undefined,
}
);
const isSingleGetter = valueVariable && variableNodes.length === 1;
const isUseStateCalledWithSingleArgument = node.arguments.length === 1;
if (isSingleGetter && isUseStateCalledWithSingleArgument) {
const useMemoReactImportSpecifier = namedReactImports ? namedReactImports.find((specifier) => specifier.imported.name === 'useMemo') : undefined;
const sourceCode = context.getSourceCode();
const useStateArgumentSourceCode = sourceCode.getText(node.arguments[0]);

report(
context,
messages.useStateErrorMessage,
'useStateErrorMessage',
{
node: node.parent.id,
suggest: [
{
desc: 'Replace useState call with useMemo',
fix: (fixer) => {
const useMemoImportName = useMemoReactImportSpecifier && useMemoReactImportSpecifier.local.name;

const useMemoReference = useMemoImportName
|| (defaultReactImportName
&& `${defaultReactImportName}.useMemo`)
|| 'useMemo';

const fixes = [
// Add useMemo import, if necessary
useStateReactImportSpecifier
&& (!useMemoReactImportSpecifier || defaultReactImportName)
&& fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'),
// Convert single-value destructure to simple assignment
fixer.replaceTextRange(node.parent.id.range, valueVariableName),
// Convert useState call to useMemo + arrow function + dependency array
fixer.replaceTextRange(
node.range,
`${useMemoReference}(() => ${useStateArgumentSourceCode}, [])`
),
].filter(Boolean);

return fixes;
},
},
{
desc: 'Destructure useState call into value + setter pair',
fix: (fixer) => {
const fix = fixer.replaceTextRange(
node.parent.id.range,
`[${valueVariableName}, ${expectedSetterVariableName}]`
);

return fix;
},
},
].filter(Boolean),
}
);
} else {
report(
context,
messages.useStateErrorMessage,
'useStateErrorMessage',
{
node: node.parent.id,
fix: valueVariableName ? (fixer) => fixer.replaceTextRange(
[node.parent.id.range[0], node.parent.id.range[1]],
`[${valueVariableName}, ${expectedSetterVariableName}]`
) : undefined,
}
);
}
}
},
})),
Expand Down
75 changes: 75 additions & 0 deletions tests/lib/rules/hook-use-state.js
Expand Up @@ -236,6 +236,81 @@ const tests = {
const [color, setColor] = useState()
}`,
},
{
code: `import { useState } from 'react'
export default function useColor(initialColor) {
const [color] = useState(initialColor)
}`,
errors: [{
message: 'useState call is not destructured into value + setter pair',
suggestions: [
{
desc: 'Replace useState call with useMemo',
output: `import { useState, useMemo } from 'react'
export default function useColor(initialColor) {
const color = useMemo(() => initialColor, [])
}`,
},
{
desc: 'Destructure useState call into value + setter pair',
output: `import { useState } from 'react'
export default function useColor(initialColor) {
const [color, setColor] = useState(initialColor)
}`,
},
],
}],
},
{
code: `import { useState, useMemo as useMemoAlternative } from 'react'
export default function useColor(initialColor) {
const [color] = useState(initialColor)
}`,
errors: [{
message: 'useState call is not destructured into value + setter pair',
suggestions: [
{
desc: 'Replace useState call with useMemo',
output: `import { useState, useMemo as useMemoAlternative } from 'react'
export default function useColor(initialColor) {
const color = useMemoAlternative(() => initialColor, [])
}`,
},
{
desc: 'Destructure useState call into value + setter pair',
output: `import { useState, useMemo as useMemoAlternative } from 'react'
export default function useColor(initialColor) {
const [color, setColor] = useState(initialColor)
}`,
},
],
}],
},
{
code: `import React from 'react'
export default function useColor(initialColor) {
const [color] = React.useState(initialColor)
}`,
errors: [{
message: 'useState call is not destructured into value + setter pair',
suggestions: [
{
desc: 'Replace useState call with useMemo',
output: `import React from 'react'
export default function useColor(initialColor) {
const color = React.useMemo(() => initialColor, [])
}`,
},
{
desc: 'Destructure useState call into value + setter pair',
output: `import React from 'react'
export default function useColor(initialColor) {
const [color, setColor] = React.useState(initialColor)
}`,
},
],
}],
},
{
code: `import { useState } from 'react'
export default function useColor() {
Expand Down

0 comments on commit 1d81d76

Please sign in to comment.