Skip to content

Commit 0e0de41

Browse files
committed
[Fix]: use variable value in prop type fields defined by variables
1 parent c8915b1 commit 0e0de41

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

lib/util/propTypes.js

+50
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,27 @@ module.exports = function propTypesInstructions(context, components, utils) {
390390
});
391391
}
392392

393+
/**
394+
* Resolve node of type Identifier when building declaration types.
395+
* @param {ASTNode} node
396+
* @param {Function} callback called with the resolved value only if resolved.
397+
*/
398+
function resolveValueForIdentifierNode(node, callback) {
399+
if (
400+
node
401+
&& node.type === 'Identifier'
402+
) {
403+
const scope = context.getScope();
404+
const identVariable = scope.variableScope.variables.find(
405+
(variable) => variable.name === node.name
406+
);
407+
if (identVariable) {
408+
const definition = identVariable.defs[identVariable.defs.length - 1];
409+
callback(definition.node.init);
410+
}
411+
}
412+
}
413+
393414
/**
394415
* Creates the representation of the React propTypes for the component.
395416
* The representation is used to verify nested used properties.
@@ -408,6 +429,23 @@ module.exports = function propTypesInstructions(context, components, utils) {
408429
return {};
409430
}
410431

432+
let identNodeResolved = false;
433+
// Resolve identifier node for cases where isRequired is set in
434+
// the variable declaration or not at all.
435+
// const variableType = PropTypes.shape({ foo: ... }).isRequired
436+
// propTypes = {
437+
// example: variableType
438+
// }
439+
// --------
440+
// const variableType = PropTypes.shape({ foo: ... })
441+
// propTypes = {
442+
// example: variableType
443+
// }
444+
resolveValueForIdentifierNode(value, (newValue) => {
445+
identNodeResolved = true;
446+
value = newValue;
447+
});
448+
411449
if (
412450
value
413451
&& value.type === 'MemberExpression'
@@ -418,6 +456,18 @@ module.exports = function propTypesInstructions(context, components, utils) {
418456
value = value.object;
419457
}
420458

459+
// Resolve identifier node for cases where isRequired is set in
460+
// the prop types.
461+
// const variableType = PropTypes.shape({ foo: ... })
462+
// propTypes = {
463+
// example: variableType.isRequired
464+
// }
465+
if (!identNodeResolved) {
466+
resolveValueForIdentifierNode(value, (newValue) => {
467+
value = newValue;
468+
});
469+
}
470+
421471
// Verify PropTypes that are functions
422472
if (
423473
value

tests/lib/rules/prop-types.js

+132
Original file line numberDiff line numberDiff line change
@@ -4949,6 +4949,138 @@ ruleTester.run('prop-types', rule, {
49494949
{
49504950
message: '\'ordering\' is missing in props validation'
49514951
}]
4952+
},
4953+
{
4954+
code: `
4955+
const firstType = PropTypes.shape({
4956+
id: PropTypes.number,
4957+
});
4958+
class ComponentX extends React.Component {
4959+
static propTypes = {
4960+
first: firstType.isRequired,
4961+
};
4962+
render() {
4963+
return (
4964+
<div>
4965+
<div>Counter = {this.props.first.name}</div>
4966+
</div>
4967+
);
4968+
}
4969+
}
4970+
`,
4971+
parser: parsers.BABEL_ESLINT,
4972+
errors: [{
4973+
message: "'first.name' is missing in props validation"
4974+
}]
4975+
},
4976+
{
4977+
code: `
4978+
const firstType = PropTypes.shape({
4979+
id: PropTypes.number,
4980+
}).isRequired;
4981+
class ComponentX extends React.Component {
4982+
static propTypes = {
4983+
first: firstType,
4984+
};
4985+
render() {
4986+
return (
4987+
<div>
4988+
<div>Counter = {this.props.first.name}</div>
4989+
</div>
4990+
);
4991+
}
4992+
}
4993+
`,
4994+
parser: parsers.BABEL_ESLINT,
4995+
errors: [{
4996+
message: "'first.name' is missing in props validation"
4997+
}]
4998+
},
4999+
{
5000+
code: `
5001+
const firstType = PropTypes.shape({
5002+
id: PropTypes.number,
5003+
});
5004+
class ComponentX extends React.Component {
5005+
static propTypes = {
5006+
first: firstType,
5007+
};
5008+
render() {
5009+
return (
5010+
<div>
5011+
<div>Counter = {this.props.first.name}</div>
5012+
</div>
5013+
);
5014+
}
5015+
}
5016+
`,
5017+
parser: parsers.BABEL_ESLINT,
5018+
errors: [{
5019+
message: "'first.name' is missing in props validation"
5020+
}]
5021+
},
5022+
{
5023+
code: `
5024+
function Foo({
5025+
foo: {
5026+
bar: foo,
5027+
baz
5028+
},
5029+
}) {
5030+
return <p>{foo.reduce(() => 5)}</p>;
5031+
}
5032+
const fooType = PropTypes.shape({
5033+
bar: PropTypes.arrayOf(PropTypes.string).isRequired,
5034+
}).isRequired
5035+
Foo.propTypes = {
5036+
foo: fooType,
5037+
};
5038+
`,
5039+
errors: [{
5040+
message: '\'foo.baz\' is missing in props validation'
5041+
}]
5042+
},
5043+
{
5044+
code: `
5045+
function Foo({
5046+
foo: {
5047+
bar: foo,
5048+
baz
5049+
},
5050+
}) {
5051+
return <p>{foo.reduce(() => 5)}</p>;
5052+
}
5053+
const fooType = PropTypes.shape({
5054+
bar: PropTypes.arrayOf(PropTypes.string).isRequired,
5055+
})
5056+
Foo.propTypes = {
5057+
foo: fooType.isRequired,
5058+
};
5059+
`,
5060+
errors: [{
5061+
message: '\'foo.baz\' is missing in props validation'
5062+
}]
5063+
},
5064+
{
5065+
code: `
5066+
function Foo({
5067+
foo: {
5068+
bar: foo,
5069+
baz
5070+
},
5071+
}) {
5072+
return <p>{foo.reduce(() => 5)}</p>;
5073+
}
5074+
const fooType = PropTypes.shape({
5075+
bar: PropTypes.arrayOf(PropTypes.string).isRequired,
5076+
})
5077+
Foo.propTypes = {
5078+
foo: fooType,
5079+
};
5080+
`,
5081+
errors: [{
5082+
message: '\'foo.baz\' is missing in props validation'
5083+
}]
49525084
}
49535085
]
49545086
});

0 commit comments

Comments
 (0)