Skip to content

Commit 6168ac5

Browse files
committed
Fix prop-types for namespaced components (fixes #621)
1 parent 16e7019 commit 6168ac5

File tree

2 files changed

+103
-9
lines changed

2 files changed

+103
-9
lines changed

lib/util/Components.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ function componentRule(rule, context) {
332332
var j;
333333
var k;
334334
var l;
335+
var componentName;
336+
var componentNode;
335337
// Get the component path
336338
var componentPath = [];
337339
while (node) {
@@ -344,6 +346,7 @@ function componentRule(rule, context) {
344346
node = node.object;
345347
}
346348
componentPath.reverse();
349+
componentName = componentPath.slice(0, componentPath.length - 1).join('.');
347350

348351
// Find the variable in the current scope
349352
var variableName = componentPath.shift();
@@ -362,7 +365,31 @@ function componentRule(rule, context) {
362365
return null;
363366
}
364367

365-
// Find the variable declaration
368+
// Try to find the component using variable references
369+
var refs = variableInScope.references;
370+
var refId;
371+
for (i = 0, j = refs.length; i < j; i++) {
372+
refId = refs[i].identifier;
373+
if (refId.parent && refId.parent.type === 'MemberExpression') {
374+
refId = refId.parent;
375+
}
376+
if (sourceCode.getText(refId) !== componentName) {
377+
continue;
378+
}
379+
if (refId.type === 'MemberExpression') {
380+
componentNode = refId.parent.right;
381+
} else if (refId.parent && refId.parent.type === 'VariableDeclarator') {
382+
componentNode = refId.parent.init;
383+
}
384+
break;
385+
}
386+
387+
if (componentNode) {
388+
// Return the component
389+
return components.add(componentNode, 1);
390+
}
391+
392+
// Try to find the component using variable declarations
366393
var defInScope;
367394
var defs = variableInScope.defs;
368395
for (i = 0, j = defs.length; i < j; i++) {
@@ -374,27 +401,27 @@ function componentRule(rule, context) {
374401
if (!defInScope || !defInScope.node) {
375402
return null;
376403
}
377-
node = defInScope.node.init || defInScope.node;
404+
componentNode = defInScope.node.init || defInScope.node;
378405

379406
// Traverse the node properties to the component declaration
380407
for (i = 0, j = componentPath.length; i < j; i++) {
381-
if (!node.properties) {
408+
if (!componentNode.properties) {
382409
continue;
383410
}
384-
for (k = 0, l = node.properties.length; k < l; k++) {
385-
if (node.properties[k].key.name === componentPath[i]) {
386-
node = node.properties[k];
411+
for (k = 0, l = componentNode.properties.length; k < l; k++) {
412+
if (componentNode.properties[k].key.name === componentPath[i]) {
413+
componentNode = componentNode.properties[k];
387414
break;
388415
}
389416
}
390-
if (!node || !node.value) {
417+
if (!componentNode || !componentNode.value) {
391418
return null;
392419
}
393-
node = node.value;
420+
componentNode = componentNode.value;
394421
}
395422

396423
// Return the component
397-
return components.add(node, 1);
424+
return components.add(componentNode, 1);
398425
}
399426
};
400427

tests/lib/rules/prop-types.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,45 @@ ruleTester.run('prop-types', rule, {
12321232
'}'
12331233
].join('\n'),
12341234
parserOptions: parserOptions
1235+
}, {
1236+
code: [
1237+
'let Greetings = class extends React.Component {',
1238+
' render () {',
1239+
' return <div>Hello {this.props.name}</div>;',
1240+
' }',
1241+
'};',
1242+
'Greetings.propTypes = {',
1243+
' name: React.PropTypes.string',
1244+
'}'
1245+
].join('\n'),
1246+
parserOptions: parserOptions
1247+
}, {
1248+
code: [
1249+
'let Greetings = {',
1250+
' Hello: class extends React.Component {',
1251+
' render () {',
1252+
' return <div>Hello {this.props.name}</div>;',
1253+
' }',
1254+
' }',
1255+
'}',
1256+
'Greetings.Hello.propTypes = {',
1257+
' name: React.PropTypes.string',
1258+
'};'
1259+
].join('\n'),
1260+
parserOptions: parserOptions
1261+
}, {
1262+
code: [
1263+
'let Greetings = {};',
1264+
'Greetings.Hello = class extends React.Component {',
1265+
' render () {',
1266+
' return <div>Hello {this.props.name}</div>;',
1267+
' }',
1268+
'};',
1269+
'Greetings.Hello.propTypes = {',
1270+
' name: React.PropTypes.string',
1271+
'}'
1272+
].join('\n'),
1273+
parserOptions: parserOptions
12351274
}
12361275
],
12371276

@@ -2052,6 +2091,34 @@ ruleTester.run('prop-types', rule, {
20522091
errors: [
20532092
{message: '\'result.notok\' is missing in props validation'}
20542093
]
2094+
}, {
2095+
code: [
2096+
'let Greetings = class extends React.Component {',
2097+
' render () {',
2098+
' return <div>Hello {this.props.name}</div>;',
2099+
' }',
2100+
'}',
2101+
'Greetings.propTypes = {};'
2102+
].join('\n'),
2103+
parserOptions: parserOptions,
2104+
errors: [{
2105+
message: '\'name\' is missing in props validation'
2106+
}]
2107+
}, {
2108+
code: [
2109+
'let Greetings = {',
2110+
' Hello: class extends React.Component {',
2111+
' render () {',
2112+
' return <div>Hello {this.props.name}</div>;',
2113+
' }',
2114+
' }',
2115+
'}',
2116+
'Greetings.Hello.propTypes = {};'
2117+
].join('\n'),
2118+
parserOptions: parserOptions,
2119+
errors: [{
2120+
message: '\'name\' is missing in props validation'
2121+
}]
20552122
}, {
20562123
code: [
20572124
'let Greetings = {};',

0 commit comments

Comments
 (0)