Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] Symmetric useState hook variable names
Ensure two symmetrically-named variables are destructured from useState hook calls
- Loading branch information
1 parent
05d35ad
commit 01e6a4d
Showing
5 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Ensure destructuring and symmetric naming of useState hook value and setter variables (react/hook-use-state) | ||
|
||
**Fixable:** In some cases, this rule is automatically fixable using the `--fix` flag on the command line. | ||
|
||
## Rule Details | ||
|
||
This rule checks whether the value and setter variables destructured from a `React.useState()` call are named symmetrically. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
const useStateResult = React.useState(); | ||
``` | ||
|
||
```js | ||
const [color, updateColor] = React.useState(); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
const [color, setColor] = React.useState(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* @fileoverview Ensure symmetric naming of useState hook value and setter variables | ||
* @author Duncan Beevers | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const docsUrl = require('../util/docsUrl'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
const USE_STATE_ERROR_MESSAGE = 'useStateErrorMessage'; | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'Ensure symmetric naming of useState hook value and setter variables', | ||
category: 'Best Practices', | ||
recommended: false, | ||
url: docsUrl('hook-use-state') | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
[USE_STATE_ERROR_MESSAGE]: 'setState call is not destructured into value + setter pair' | ||
}, | ||
schema: [{ | ||
type: 'object', | ||
additionalProperties: false | ||
}] | ||
}, | ||
|
||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
const isReactUseStateCall = ( | ||
node.callee.type === 'MemberExpression' | ||
&& node.callee.object.type === 'Identifier' | ||
&& node.callee.object.name === 'React' | ||
&& node.callee.property.type === 'Identifier' | ||
&& node.callee.property.name === 'useState' | ||
); | ||
|
||
const isUseStateCall = ( | ||
node.callee.type === 'Identifier' | ||
&& node.callee.name === 'useState' | ||
); | ||
|
||
// Ignore unless this is a useState() or React.useState() call. | ||
if (!isReactUseStateCall && !isUseStateCall) { | ||
return; | ||
} | ||
|
||
const isDestructuringDeclarator = ( | ||
node.parent.type === 'VariableDeclarator' | ||
&& node.parent.id.type === 'ArrayPattern' | ||
); | ||
|
||
if (!isDestructuringDeclarator) { | ||
context.report({node, messageId: USE_STATE_ERROR_MESSAGE}); | ||
return; | ||
} | ||
|
||
const variableNodes = node.parent.id.elements; | ||
const valueVariable = variableNodes[0]; | ||
const setterVariable = variableNodes[1]; | ||
|
||
const valueVariableName = valueVariable | ||
? valueVariable.name | ||
: undefined; | ||
|
||
const setterVariableName = setterVariable | ||
? setterVariable.name | ||
: undefined; | ||
|
||
const expectedSetterVariableName = valueVariableName ? ( | ||
`set${ | ||
valueVariableName.charAt(0).toUpperCase() | ||
}${valueVariableName.slice(1)}` | ||
) : undefined; | ||
|
||
if ( | ||
!valueVariable | ||
|| !setterVariable | ||
|| setterVariableName !== expectedSetterVariableName | ||
|| variableNodes.length !== 2 | ||
) { | ||
context.report({ | ||
node: node.parent.id, | ||
messageId: USE_STATE_ERROR_MESSAGE, | ||
fix: valueVariableName ? (fixer) => fixer.replaceTextRange( | ||
[node.parent.id.range[0], node.parent.id.range[1]], | ||
`[${valueVariableName}, ${expectedSetterVariableName}]` | ||
) : undefined | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/** | ||
* @fileoverview Ensure symmetric naming of setState hook value and setter variables | ||
* @author Duncan Beevers | ||
*/ | ||
|
||
'use strict'; | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const RuleTester = require('eslint').RuleTester; | ||
const rule = require('../../../lib/rules/hook-use-state'); | ||
const parsers = require('../../helpers/parsers'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Tests | ||
// ------------------------------------------------------------------------------ | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module' | ||
} | ||
}); | ||
|
||
const tests = { | ||
valid: [ | ||
{ | ||
code: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, setColor] = useState(\'#ffffff\')' | ||
}, | ||
{ | ||
code: 'const [color, setColor] = React.useState()' | ||
}, | ||
{ | ||
code: 'const [color1, setColor1] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, setColor] = useState<string>()', | ||
parser: parsers.TYPESCRIPT_ESLINT | ||
}, | ||
{ | ||
code: 'const [color, setColor] = useState<string>(\'#ffffff\')', | ||
parser: parsers.TYPESCRIPT_ESLINT | ||
} | ||
].concat(parsers.TS([ | ||
{ | ||
code: 'const [color, setColor] = useState<string>()', | ||
parser: parsers['@TYPESCRIPT_ESLINT'] | ||
}, | ||
{ | ||
code: 'const [color, setColor] = useState<string>(\'#ffffff\')', | ||
parser: parsers['@TYPESCRIPT_ESLINT'] | ||
} | ||
]) | ||
), | ||
invalid: [ | ||
{ | ||
code: 'useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const result = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const result = React.useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [, , extra1] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [, setColor] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const { color } = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [, , , ,] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [color] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, , extra1] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, setColor, extra1, extra2, extra3] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [, makeColor] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}] | ||
}, | ||
{ | ||
code: 'const [color, setFlavor, extraneous] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, setFlavor] = useState()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState()' | ||
}, | ||
{ | ||
code: 'const [color, setFlavor] = useState<string>()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState<string>()', | ||
parser: parsers.TYPESCRIPT_ESLINT | ||
} | ||
].concat( | ||
parsers.TS([ | ||
{ | ||
code: 'const [color, setFlavor] = useState<string>()', | ||
errors: [{ | ||
message: 'setState call is not destructured into value + setter pair' | ||
}], | ||
output: 'const [color, setColor] = useState<string>()', | ||
parser: parsers['@TYPESCRIPT_ESLINT'] | ||
} | ||
]) | ||
) | ||
}; | ||
|
||
ruleTester.run('hook-set-state-names', rule, tests); |