diff --git a/docs/rules/hook-use-state.md b/docs/rules/hook-use-state.md index 17946a0ec2..1d4fa7f9ce 100644 --- a/docs/rules/hook-use-state.md +++ b/docs/rules/hook-use-state.md @@ -9,15 +9,29 @@ This rule checks whether the value and setter variables destructured from a `Rea Examples of **incorrect** code for this rule: ```js +import React from 'react'; const useStateResult = React.useState(); ``` ```js +import { useState } from 'react'; +const useStateResult = useState(); +``` + +```js +import React from 'react'; const [color, updateColor] = React.useState(); ``` Examples of **correct** code for this rule: + +```js +import { useState } from 'react'; +const [color, setColor] = useState(); +``` + ```js +import React from 'react'; const [color, setColor] = React.useState(); ``` diff --git a/lib/rules/hook-use-state.js b/lib/rules/hook-use-state.js index 1587535a60..fdd6247f29 100644 --- a/lib/rules/hook-use-state.js +++ b/lib/rules/hook-use-state.js @@ -32,10 +32,14 @@ module.exports = { }, create(context) { + let isReactImported = false; + let reactUseStateLocal; + return { CallExpression(node) { const isReactUseStateCall = ( - node.callee.type === 'MemberExpression' + isReactImported + && node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'React' && node.callee.property.type === 'Identifier' @@ -43,8 +47,9 @@ module.exports = { ); const isUseStateCall = ( - node.callee.type === 'Identifier' - && node.callee.name === 'useState' + reactUseStateLocal + && node.callee.type === 'Identifier' + && node.callee.name === reactUseStateLocal ); // Ignore unless this is a useState() or React.useState() call. @@ -95,6 +100,21 @@ module.exports = { ) : undefined }); } + }, + ImportDeclaration(node) { + isReactImported = node.source.type === 'Literal' && node.source.value === 'react'; + const reactUseStateSpecifier = isReactImported + ? node.specifiers.find( + (specifier) => ( + specifier.type === 'ImportSpecifier' + && specifier.imported.name === 'useState' + ) + ) + : undefined; + + reactUseStateLocal = reactUseStateSpecifier + ? reactUseStateSpecifier.local.name + : undefined; } }; } diff --git a/tests/lib/rules/hook-use-state.js b/tests/lib/rules/hook-use-state.js index 101c05f4ee..3b4ca5200a 100644 --- a/tests/lib/rules/hook-use-state.js +++ b/tests/lib/rules/hook-use-state.js @@ -27,142 +27,190 @@ const ruleTester = new RuleTester({ const tests = { valid: [ { - code: 'const [color, setColor] = useState()' + code: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [color, setColor] = useState(\'#ffffff\')' + code: `import { useState } from 'react'; + const [color, setColor] = useState('#ffffff')` }, { - code: 'const [color, setColor] = React.useState()' + code: `import { useState } from 'react'; + const [color, setColor] = React.useState()` }, { - code: 'const [color1, setColor1] = useState()' + code: `import { useState } from 'react'; + const [color1, setColor1] = useState()` }, { - code: 'const [color, setColor] = useState()', + code: 'const result = useState()' + }, + { + code: `import { useRef } from 'react'; + const result = useState()` + }, + { + code: 'const result = React.useState()' + }, + { + code: `import { useState } from 'react'; + const [color, setColor] = useState()`, parser: parsers.TYPESCRIPT_ESLINT }, { - code: 'const [color, setColor] = useState(\'#ffffff\')', + code: `import { useState } from 'react'; + const [color, setColor] = useState('#ffffff')`, parser: parsers.TYPESCRIPT_ESLINT } ].concat(parsers.TS([ { - code: 'const [color, setColor] = useState()', + code: `import { useState } from 'react'; + const [color, setColor] = useState()`, parser: parsers['@TYPESCRIPT_ESLINT'] }, { - code: 'const [color, setColor] = useState(\'#ffffff\')', + code: `import { useState } from 'react'; + const [color, setColor] = useState('#ffffff')`, parser: parsers['@TYPESCRIPT_ESLINT'] } ]) ), invalid: [ { - code: 'useState()', + code: `import { useState } from 'react'; + useState()`, + errors: [{ + message: 'setState call is not destructured into value + setter pair' + }] + }, + { + code: `import { useState as useStateAlternativeName } from 'react'; + useStateAlternativeName()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const result = useState()', + code: `import { useState } from 'react'; + const result = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const result = React.useState()', + code: `import { useState } from 'react'; + const result = React.useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [, , extra1] = useState()', + code: `import { useState } from 'react'; + const [, , extra1] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [, setColor] = useState()', + code: `import { useState } from 'react'; + const [, setColor] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const { color } = useState()', + code: `import { useState } from 'react'; + const { color } = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [] = useState()', + code: `import { useState } from 'react'; + const [] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [, , , ,] = useState()', + code: `import { useState } from 'react'; + const [, , , ,] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [color] = useState()', + code: `import { useState } from 'react'; + const [color] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()' + output: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [color, , extra1] = useState()', + code: `import { useState } from 'react'; + const [color, , extra1] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()' + output: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [color, setColor, extra1, extra2, extra3] = useState()', + code: `import { useState } from 'react'; + const [color, setColor, extra1, extra2, extra3] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()' + output: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [, makeColor] = useState()', + code: `import { useState } from 'react'; + const [, makeColor] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }] }, { - code: 'const [color, setFlavor, extraneous] = useState()', + code: `import { useState } from 'react'; + const [color, setFlavor, extraneous] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()' + output: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [color, setFlavor] = useState()', + code: `import { useState } from 'react'; + const [color, setFlavor] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()' + output: `import { useState } from 'react'; + const [color, setColor] = useState()` }, { - code: 'const [color, setFlavor] = useState()', + code: `import { useState } from 'react'; + const [color, setFlavor] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()', + output: `import { useState } from 'react'; + const [color, setColor] = useState()`, parser: parsers.TYPESCRIPT_ESLINT } ].concat( parsers.TS([ { - code: 'const [color, setFlavor] = useState()', + code: `import { useState } from 'react'; + const [color, setFlavor] = useState()`, errors: [{ message: 'setState call is not destructured into value + setter pair' }], - output: 'const [color, setColor] = useState()', + output: `import { useState } from 'react'; + const [color, setColor] = useState()`, parser: parsers['@TYPESCRIPT_ESLINT'] } ])