Skip to content

Commit 7fd4c4a

Browse files
committed
[Refactor]: simplify utils/usedPropTypes.js
1 parent f7a9fb5 commit 7fd4c4a

File tree

1 file changed

+94
-122
lines changed

1 file changed

+94
-122
lines changed

lib/util/usedPropTypes.js

+94-122
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@ const ast = require('./ast');
1212
// Constants
1313
// ------------------------------------------------------------------------------
1414

15-
const DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
16-
const DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/;
17-
const DIRECT_PREV_PROPS_REGEX = /^prevProps\s*(\.|\[)/;
1815
const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate'];
1916
const ASYNC_SAFE_LIFE_CYCLE_METHODS = ['getDerivedStateFromProps', 'getSnapshotBeforeUpdate', 'UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillUpdate'];
2017

2118
/**
22-
* Checks if a prop init name matches common naming patterns
23-
* @param {ASTNode} node The AST node being checked.
19+
* Checks if the string is one of `props`, `nextProps`, or `prevProps`
20+
* @param {string} name The AST node being checked.
2421
* @returns {Boolean} True if the prop name matches
2522
*/
26-
function isPropAttributeName(node) {
27-
return (
28-
node.init.name === 'props' ||
29-
node.init.name === 'nextProps' ||
30-
node.init.name === 'prevProps'
31-
);
23+
function isCommonVariableNameForProps(name) {
24+
return name === 'props' || name === 'nextProps' || name === 'prevProps';
3225
}
3326

3427
/**
@@ -40,26 +33,6 @@ function mustBeValidated(component) {
4033
return !!(component && !component.ignorePropsValidation);
4134
}
4235

43-
/**
44-
* Check if we are in a class constructor
45-
* @return {boolean} true if we are in a class constructor, false if not
46-
*/
47-
function inComponentWillReceiveProps(context) {
48-
let scope = context.getScope();
49-
while (scope) {
50-
if (
51-
scope.block &&
52-
scope.block.parent &&
53-
scope.block.parent.key &&
54-
scope.block.parent.key.name === 'componentWillReceiveProps'
55-
) {
56-
return true;
57-
}
58-
scope = scope.upper;
59-
}
60-
return false;
61-
}
62-
6336
/**
6437
* Check if we are in a lifecycle method
6538
* @return {boolean} true if we are in a class constructor, false if not
@@ -143,7 +116,10 @@ function inSetStateUpdater(context) {
143116
return false;
144117
}
145118

146-
function isPropArgumentInSetStateUpdater(context, node) {
119+
function isPropArgumentInSetStateUpdater(context, name) {
120+
if (typeof name !== 'string') {
121+
return;
122+
}
147123
let scope = context.getScope();
148124
while (scope) {
149125
if (
@@ -156,13 +132,29 @@ function isPropArgumentInSetStateUpdater(context, node) {
156132
scope.block.parent.arguments[0].params &&
157133
scope.block.parent.arguments[0].params.length > 1
158134
) {
159-
return scope.block.parent.arguments[0].params[1].name === node.object.name;
135+
return scope.block.parent.arguments[0].params[1].name === name;
160136
}
161137
scope = scope.upper;
162138
}
163139
return false;
164140
}
165141

142+
function isInClassComponent(utils) {
143+
return utils.getParentES6Component() || utils.getParentES5Component();
144+
}
145+
146+
/**
147+
* Checks if the node is `this.props`
148+
* @param {ASTNode|undefined} node
149+
* @returns {boolean}
150+
*/
151+
function isThisDotProps(node) {
152+
return !!node &&
153+
node.type === 'MemberExpression' &&
154+
node.object.type === 'ThisExpression' &&
155+
node.property.name === 'props';
156+
}
157+
166158
/**
167159
* Checks if the prop has spread operator.
168160
* @param {ASTNode} node The AST node being marked.
@@ -178,27 +170,7 @@ function hasSpreadOperator(context, node) {
178170
* @param {ASTNode} node The AST node with the property.
179171
* @return {string|undefined} the name of the property or undefined if not found
180172
*/
181-
function getPropertyName(node, context, utils, checkAsyncSafeLifeCycles) {
182-
const sourceCode = context.getSourceCode();
183-
const isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node));
184-
const isDirectNextProp = DIRECT_NEXT_PROPS_REGEX.test(sourceCode.getText(node));
185-
const isDirectPrevProp = DIRECT_PREV_PROPS_REGEX.test(sourceCode.getText(node));
186-
const isDirectSetStateProp = isPropArgumentInSetStateUpdater(context, node);
187-
const isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component();
188-
const isNotInConstructor = !utils.inConstructor(node);
189-
const isNotInLifeCycleMethod = !inLifeCycleMethod(context, checkAsyncSafeLifeCycles);
190-
const isNotInSetStateUpdater = !inSetStateUpdater(context);
191-
if ((isDirectProp || isDirectNextProp || isDirectPrevProp || isDirectSetStateProp) &&
192-
isInClassComponent &&
193-
isNotInConstructor &&
194-
isNotInLifeCycleMethod &&
195-
isNotInSetStateUpdater
196-
) {
197-
return;
198-
}
199-
if (!isDirectProp && !isDirectNextProp && !isDirectPrevProp && !isDirectSetStateProp) {
200-
node = node.parent;
201-
}
173+
function getPropertyName(node) {
202174
const property = node.property;
203175
if (property) {
204176
switch (property.type) {
@@ -225,21 +197,34 @@ function getPropertyName(node, context, utils, checkAsyncSafeLifeCycles) {
225197
}
226198

227199
/**
228-
* Checks if we are using a prop
229-
* @param {ASTNode} node The AST node being checked.
230-
* @returns {Boolean} True if we are using a prop, false if not.
200+
* Checks if the node is a propTypes usage of the form `this.props.*`, `props.*`, `prevProps.*`, or `nextProps.*`.
201+
* @param {ASTNode} node
202+
* @param {Context} context
203+
* @param {Object} utils
204+
* @param {boolean} checkAsyncSafeLifeCycles
205+
* @returns {boolean}
231206
*/
232-
function isPropTypesUsage(node, context, utils, checkAsyncSafeLifeCycles) {
233-
const isThisPropsUsage = node.object.type === 'ThisExpression' && node.property.name === 'props';
234-
const isPropsUsage = isThisPropsUsage || node.object.name === 'nextProps' || node.object.name === 'prevProps';
235-
const isClassUsage = (
236-
(utils.getParentES6Component() || utils.getParentES5Component()) &&
237-
(isThisPropsUsage || isPropArgumentInSetStateUpdater(context, node))
238-
);
239-
const isStatelessFunctionUsage = node.object.name === 'props' && !ast.isAssignmentLHS(node);
240-
return isClassUsage ||
241-
isStatelessFunctionUsage ||
242-
(isPropsUsage && inLifeCycleMethod(context, checkAsyncSafeLifeCycles));
207+
function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafeLifeCycles) {
208+
if (isInClassComponent(utils)) {
209+
// this.props.*
210+
if (isThisDotProps(node.object)) {
211+
return true;
212+
}
213+
// props.* or prevProps.* or nextProps.*
214+
if (
215+
isCommonVariableNameForProps(node.object.name) &&
216+
(inLifeCycleMethod(context, checkAsyncSafeLifeCycles) || utils.inConstructor())
217+
) {
218+
return true;
219+
}
220+
// this.setState((_, props) => props.*))
221+
if (isPropArgumentInSetStateUpdater(context, node.object.name)) {
222+
return true;
223+
}
224+
return false;
225+
}
226+
// props.* in function component
227+
return node.object.name === 'props' && !ast.isAssignmentLHS(node);
243228
}
244229

245230
module.exports = function usedPropTypesInstructions(context, components, utils) {
@@ -258,7 +243,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
258243
let properties;
259244
switch (node.type) {
260245
case 'MemberExpression':
261-
name = getPropertyName(node, context, utils, checkAsyncSafeLifeCycles);
246+
name = getPropertyName(node);
262247
if (name) {
263248
allNames = parentNames.concat(name);
264249
if (
@@ -268,16 +253,16 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
268253
) {
269254
markPropTypesAsUsed(node.parent, allNames);
270255
}
256+
// Handle the destructuring part of `const {foo} = props.a.b`
257+
if (
258+
node.parent.type === 'VariableDeclarator' &&
259+
node.parent.id.type === 'ObjectPattern'
260+
) {
261+
node.parent.id.parent = node.parent; // patch for bug in eslint@4 in which ObjectPattern has no parent
262+
markPropTypesAsUsed(node.parent.id, allNames);
263+
}
271264
// Do not mark computed props as used.
272265
type = name !== '__COMPUTED_PROP__' ? 'direct' : null;
273-
} else if (
274-
node.parent.id &&
275-
node.parent.id.properties &&
276-
node.parent.id.properties.length &&
277-
ast.getKeyValue(context, node.parent.id.properties[0])
278-
) {
279-
type = 'destructuring';
280-
properties = node.parent.id.properties;
281266
}
282267
break;
283268
case 'ArrowFunctionExpression':
@@ -293,31 +278,9 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
293278
propParam.properties;
294279
break;
295280
}
296-
case 'VariableDeclarator':
297-
node.id.properties.some((property) => {
298-
// let {props: {firstname}} = this
299-
const thisDestructuring = (
300-
property.key && (
301-
(property.key.name === 'props' || property.key.value === 'props') &&
302-
property.value.type === 'ObjectPattern'
303-
)
304-
);
305-
// let {firstname} = props
306-
const genericDestructuring = isPropAttributeName(node) && (
307-
utils.getParentStatelessComponent() ||
308-
isInLifeCycleMethod(node, checkAsyncSafeLifeCycles)
309-
);
310-
311-
if (thisDestructuring) {
312-
properties = property.value.properties;
313-
} else if (genericDestructuring) {
314-
properties = node.id.properties;
315-
} else {
316-
return false;
317-
}
318-
type = 'destructuring';
319-
return true;
320-
});
281+
case 'ObjectPattern':
282+
type = 'destructuring';
283+
properties = node.properties;
321284
break;
322285
default:
323286
throw new Error(`${node.type} ASTNodes are not handled by markPropTypesAsUsed`);
@@ -334,15 +297,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
334297
break;
335298
}
336299

337-
const nodeSource = context.getSourceCode().getText(node);
338-
const isDirectProp = DIRECT_PROPS_REGEX.test(nodeSource) ||
339-
DIRECT_NEXT_PROPS_REGEX.test(nodeSource) ||
340-
DIRECT_PREV_PROPS_REGEX.test(nodeSource);
341-
const reportedNode = (
342-
!isDirectProp && !utils.inConstructor() && !inComponentWillReceiveProps(context) ?
343-
node.parent.property :
344-
node.property
345-
);
300+
const reportedNode = node.property;
346301
usedPropTypes.push({
347302
name,
348303
allNames,
@@ -358,7 +313,8 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
358313
}
359314
const propName = ast.getKeyValue(context, properties[k]);
360315

361-
let currentNode = node;
316+
// Get parent names in the right hand side of `const {foo} = props.a.b`
317+
let currentNode = (node.parent && node.parent.init) || {};
362318
allNames = [];
363319
while (currentNode.property && currentNode.property.name !== 'props') {
364320
allNames.unshift(currentNode.property.name);
@@ -436,19 +392,35 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
436392

437393
return {
438394
VariableDeclarator(node) {
439-
const destructuring = node.init && node.id && node.id.type === 'ObjectPattern';
395+
// Only handles destructuring
396+
if (node.id.type !== 'ObjectPattern') {
397+
return;
398+
}
399+
440400
// let {props: {firstname}} = this
441-
const thisDestructuring = destructuring && node.init.type === 'ThisExpression';
442-
// let {firstname} = props
443-
const statelessDestructuring = destructuring && isPropAttributeName(node) && (
444-
utils.getParentStatelessComponent() ||
445-
isInLifeCycleMethod(node, checkAsyncSafeLifeCycles)
446-
);
401+
const propsProperty = node.id.properties.find(property => (
402+
property.key &&
403+
(property.key.name === 'props' || property.key.value === 'props') &&
404+
property.value.type === 'ObjectPattern'
405+
));
406+
if (propsProperty && node.init.type === 'ThisExpression') {
407+
markPropTypesAsUsed(propsProperty.value);
408+
return;
409+
}
447410

448-
if (!thisDestructuring && !statelessDestructuring) {
411+
// let {firstname} = props
412+
if (
413+
isCommonVariableNameForProps(node.init.name) &&
414+
(utils.getParentStatelessComponent() || isInLifeCycleMethod(node, checkAsyncSafeLifeCycles))
415+
) {
416+
markPropTypesAsUsed(node.id);
449417
return;
450418
}
451-
markPropTypesAsUsed(node);
419+
420+
// let {firstname} = this.props
421+
if (isThisDotProps(node.init) && isInClassComponent(utils)) {
422+
markPropTypesAsUsed(node.id);
423+
}
452424
},
453425

454426
FunctionDeclaration: handleFunctionLikeExpressions,
@@ -465,7 +437,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
465437
},
466438

467439
MemberExpression(node) {
468-
if (isPropTypesUsage(node, context, utils, checkAsyncSafeLifeCycles)) {
440+
if (isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafeLifeCycles)) {
469441
markPropTypesAsUsed(node);
470442
}
471443
},

0 commit comments

Comments
 (0)