-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Extract duplicate code between prop-types and no-unused-prop-types #1 #1536
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
Changes from all commits
6907ae6
e9862d1
13222de
db0d571
2fcbfbe
14742f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ const variable = require('../util/variable'); | |
const annotations = require('../util/annotations'); | ||
const versionUtil = require('../util/version'); | ||
const propsUtil = require('../util/props'); | ||
const astUtil = require('../util/ast'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Constants | ||
|
@@ -21,7 +22,6 @@ const propsUtil = require('../util/props'); | |
const DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/; | ||
const DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/; | ||
const DIRECT_PREV_PROPS_REGEX = /^prevProps\s*(\.|\[)/; | ||
const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate']; | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
|
@@ -82,24 +82,6 @@ module.exports = { | |
return value; | ||
} | ||
|
||
/** | ||
* Check if we are in a lifecycle method | ||
* @return {boolean} true if we are in a class constructor, false if not | ||
**/ | ||
function inLifeCycleMethod() { | ||
let scope = context.getScope(); | ||
while (scope) { | ||
if ( | ||
scope.block && scope.block.parent && | ||
scope.block.parent.key && | ||
LIFE_CYCLE_METHODS.indexOf(scope.block.parent.key.name) >= 0 | ||
) { | ||
return true; | ||
} | ||
scope = scope.upper; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Check if the current node is in a setState updater method | ||
|
@@ -155,7 +137,7 @@ module.exports = { | |
|| isPropArgumentInSetStateUpdater(node)) | ||
); | ||
const isStatelessFunctionUsage = node.object.name === 'props'; | ||
return isClassUsage || isStatelessFunctionUsage || inLifeCycleMethod(); | ||
return isClassUsage || isStatelessFunctionUsage || astUtil.inLifeCycleMethod(context); | ||
} | ||
|
||
/** | ||
|
@@ -241,40 +223,6 @@ module.exports = { | |
); | ||
} | ||
|
||
/** | ||
* Returns true if the given node is a React Component lifecycle method | ||
* @param {ASTNode} node The AST node being checked. | ||
* @return {Boolean} True if the node is a lifecycle method | ||
*/ | ||
function isNodeALifeCycleMethod(node) { | ||
const nodeKeyName = (node.key || {}).name; | ||
return ( | ||
node.kind === 'constructor' || | ||
nodeKeyName === 'componentWillReceiveProps' || | ||
nodeKeyName === 'shouldComponentUpdate' || | ||
nodeKeyName === 'componentWillUpdate' || | ||
nodeKeyName === 'componentDidUpdate' | ||
); | ||
} | ||
|
||
/** | ||
* Returns true if the given node is inside a React Component lifecycle | ||
* method. | ||
* @param {ASTNode} node The AST node being checked. | ||
* @return {Boolean} True if the node is inside a lifecycle method | ||
*/ | ||
function isInLifeCycleMethod(node) { | ||
if (node.type === 'MethodDefinition' && isNodeALifeCycleMethod(node)) { | ||
return true; | ||
} | ||
|
||
if (node.parent) { | ||
return isInLifeCycleMethod(node.parent); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Checks if a prop init name matches common naming patterns | ||
* @param {ASTNode} node The AST node being checked. | ||
|
@@ -553,21 +501,6 @@ module.exports = { | |
} | ||
} | ||
|
||
/** | ||
* Check if we are in a class constructor | ||
* @return {boolean} true if we are in a class constructor, false if not | ||
*/ | ||
function inConstructor() { | ||
let scope = context.getScope(); | ||
while (scope) { | ||
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') { | ||
return true; | ||
} | ||
scope = scope.upper; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Retrieve the name of a property node | ||
* @param {ASTNode} node The AST node with the property. | ||
|
@@ -579,10 +512,11 @@ module.exports = { | |
const isDirectPrevProp = DIRECT_PREV_PROPS_REGEX.test(sourceCode.getText(node)); | ||
const isDirectSetStateProp = isPropArgumentInSetStateUpdater(node); | ||
const isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component(); | ||
const isNotInConstructor = !inConstructor(node); | ||
const isNotInLifeCycleMethod = !inLifeCycleMethod(); | ||
const isNotInLifeCycleMethod = !astUtil.inLifeCycleMethod(context); | ||
const isNotInConstructor = !astUtil.inConstructor(context); | ||
const isNotInSetStateUpdater = !inSetStateUpdater(); | ||
if ((isDirectProp || isDirectNextProp || isDirectPrevProp || isDirectSetStateProp) | ||
if ( | ||
(isDirectProp || isDirectNextProp || isDirectPrevProp || isDirectSetStateProp) | ||
&& isInClassComponent | ||
&& isNotInConstructor | ||
&& isNotInLifeCycleMethod | ||
|
@@ -668,10 +602,12 @@ module.exports = { | |
) | ||
); | ||
// let {firstname} = props | ||
const genericDestructuring = isPropAttributeName(node) && ( | ||
utils.getParentStatelessComponent() || | ||
isInLifeCycleMethod(node) | ||
); | ||
const genericDestructuring = | ||
isPropAttributeName(node) && ( | ||
utils.getParentStatelessComponent() || | ||
astUtil.inConstructor(context) || | ||
astUtil.inLifeCycleMethod(context) | ||
); | ||
|
||
if (thisDestructuring) { | ||
properties = node.id.properties[i].value.properties; | ||
|
@@ -992,7 +928,8 @@ module.exports = { | |
// let {firstname} = props | ||
const statelessDestructuring = destructuring && isPropAttributeName(node) && ( | ||
utils.getParentStatelessComponent() || | ||
isInLifeCycleMethod(node) | ||
astUtil.inConstructor(context) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also here, which test case exposes the need for this check? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above: Note that the old However, this case seems to be untested. 😞 In master we can remove this whole block and the tests still pass.
|
||
astUtil.inLifeCycleMethod(context) | ||
); | ||
|
||
if (!thisDestructuring && !statelessDestructuring) { | ||
|
@@ -1058,7 +995,7 @@ module.exports = { | |
ObjectPattern: function(node) { | ||
// If the object pattern is a destructured props object in a lifecycle | ||
// method -- mark it for used props. | ||
if (isNodeALifeCycleMethod(node.parent.parent)) { | ||
if (astUtil.inConstructor(context) || astUtil.inLifeCycleMethod(context)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. Fails when removing inConstructor: When removing the I suppose that we can also test https://reactjs.org/docs/react-component.html#componentwillupdate which also has |
||
node.properties.forEach((property, i) => { | ||
if (i === 0) { | ||
markPropTypesAsUsed(node.parent); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ const variable = require('../util/variable'); | |
const annotations = require('../util/annotations'); | ||
const versionUtil = require('../util/version'); | ||
const propsUtil = require('../util/props'); | ||
const astUtil = require('../util/ast'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Constants | ||
|
@@ -86,39 +87,6 @@ module.exports = { | |
return value; | ||
} | ||
|
||
/** | ||
* Check if we are in a class constructor | ||
* @return {boolean} true if we are in a class constructor, false if not | ||
*/ | ||
function inConstructor() { | ||
let scope = context.getScope(); | ||
while (scope) { | ||
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') { | ||
return true; | ||
} | ||
scope = scope.upper; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Check if we are in a class constructor | ||
* @return {boolean} true if we are in a class constructor, false if not | ||
*/ | ||
function inComponentWillReceiveProps() { | ||
let scope = context.getScope(); | ||
while (scope) { | ||
if ( | ||
scope.block && scope.block.parent && | ||
scope.block.parent.key && scope.block.parent.key.name === 'componentWillReceiveProps' | ||
) { | ||
return true; | ||
} | ||
scope = scope.upper; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Checks if a prop is being assigned a value props.bar = 'bar' | ||
* @param {ASTNode} node The AST node being checked. | ||
|
@@ -144,7 +112,7 @@ module.exports = { | |
node.object.type === 'ThisExpression' && node.property.name === 'props' | ||
); | ||
const isStatelessFunctionUsage = node.object.name === 'props' && !isAssignmentToProp(node); | ||
const isNextPropsUsage = node.object.name === 'nextProps' && inComponentWillReceiveProps(); | ||
const isNextPropsUsage = node.object.name === 'nextProps' && astUtil.inLifeCycleMethod(context); | ||
return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage; | ||
} | ||
|
||
|
@@ -534,9 +502,14 @@ module.exports = { | |
function getPropertyName(node) { | ||
const isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node)); | ||
const isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component(); | ||
const isNotInConstructor = !inConstructor(); | ||
const isNotInComponentWillReceiveProps = !inComponentWillReceiveProps(); | ||
if (isDirectProp && isInClassComponent && isNotInConstructor && isNotInComponentWillReceiveProps) { | ||
const isNotInConstructor = !astUtil.inConstructor(context); | ||
const isNotInLifecycleMethod = !astUtil.inLifeCycleMethod(context); | ||
if ( | ||
isDirectProp && | ||
isInClassComponent && | ||
isNotInConstructor && | ||
isNotInLifecycleMethod | ||
) { | ||
return void 0; | ||
} | ||
if (!isDirectProp) { | ||
|
@@ -614,9 +587,11 @@ module.exports = { | |
); | ||
// let {firstname} = props | ||
const directDestructuring = | ||
PROPS_REGEX.test(node.init.name) && | ||
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps()) | ||
; | ||
PROPS_REGEX.test(node.init.name) && ( | ||
utils.getParentStatelessComponent() || | ||
astUtil.inConstructor(context) || | ||
astUtil.inLifeCycleMethod(context) | ||
); | ||
|
||
if (thisDestructuring) { | ||
properties = node.id.properties[i].value.properties; | ||
|
@@ -649,7 +624,7 @@ module.exports = { | |
name: name, | ||
allNames: allNames, | ||
node: ( | ||
!isDirectProp && !inConstructor() && !inComponentWillReceiveProps() ? | ||
!isDirectProp && !astUtil.inConstructor(context) && !astUtil.inLifeCycleMethod(context) ? | ||
node.parent.property : | ||
node.property | ||
) | ||
|
@@ -967,7 +942,7 @@ module.exports = { | |
const directDestructuring = | ||
destructuring && | ||
PROPS_REGEX.test(node.init.name) && | ||
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, which test cases warrant this change from just checking willReceiveProps, to checking all the lifecycle methods? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is missing tests! Indeed we will now check more cases than just |
||
(utils.getParentStatelessComponent() || astUtil.inConstructor(context) || astUtil.inLifeCycleMethod(context)) | ||
; | ||
|
||
if (!thisDestructuring && !directDestructuring) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which test case exposes this missing inConstructor check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the old
isInLifeCycleMethod
function already was checking whether we are in constructor:node.kind === 'constructor'
. Now it is split intoinConstructor
andinLifecycleMethod
so the code is the same between this rule andprop-types
.In this case, the following two tests will fail when removing this line: