diff --git a/lib/rules/prefer-exact-props.js b/lib/rules/prefer-exact-props.js index 044311643d..c7c991df06 100644 --- a/lib/rules/prefer-exact-props.js +++ b/lib/rules/prefer-exact-props.js @@ -28,7 +28,9 @@ module.exports = { }, create: Components.detect((context, components, utils) => { + const typeAliases = {}; const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context); + const sourceCode = context.getSourceCode(); function getPropTypesErrorMessage() { const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers); @@ -71,23 +73,46 @@ module.exports = { ); } + function isNonExactPropWrapperFunction(node) { + return ( + node && + node.type === 'CallExpression' && + !propWrapperUtil.isExactPropWrapperFunction(context, sourceCode.getText(node.callee)) + ); + } + + function reportPropTypesError(node) { + context.report({ + node: node, + message: PROP_TYPES_MESSAGE, + data: getPropTypesErrorMessage() + }); + } + + function reportFlowError(node) { + context.report({ + node: node, + message: FLOW_MESSAGE + }); + } + return { + TypeAlias: function(node) { + // working around an issue with eslint@3 and babel-eslint not finding the TypeAlias in scope + typeAliases[node.id.name] = node; + }, + ClassProperty: function(node) { if (!propsUtil.isPropTypesDeclaration(node)) { return; } if (hasNonExactObjectTypeAnnotation(node)) { - context.report({ - node: node, - message: FLOW_MESSAGE - }); - } else if (isNonEmptyObjectExpression(node.value) && exactWrappers.size > 0) { - context.report({ - node: node, - message: PROP_TYPES_MESSAGE, - data: getPropTypesErrorMessage() - }); + reportFlowError(node); + } else if (exactWrappers.size > 0 && isNonEmptyObjectExpression(node.value)) { + reportPropTypesError(node); + } else if (exactWrappers.size > 0 && isNonExactPropWrapperFunction(node.value)) { + reportPropTypesError(node); } }, @@ -97,18 +122,13 @@ module.exports = { } if (hasNonExactObjectTypeAnnotation(node)) { - context.report({ - node: node, - message: FLOW_MESSAGE - }); + reportFlowError(node); } else if (hasGenericTypeAnnotation(node)) { const identifier = node.typeAnnotation.typeAnnotation.id.name; - const propsDefinition = variableUtil.findVariableByName(context, identifier); + const typeAlias = typeAliases[identifier]; + const propsDefinition = typeAlias ? typeAlias.right : null; if (isNonExactObjectTypeAnnotation(propsDefinition)) { - context.report({ - node: node, - message: FLOW_MESSAGE - }); + reportFlowError(node); } } }, @@ -120,20 +140,16 @@ module.exports = { const right = node.parent.right; if (isNonEmptyObjectExpression(right)) { - context.report({ - node: node, - message: PROP_TYPES_MESSAGE, - data: getPropTypesErrorMessage() - }); + reportPropTypesError(node); + } else if (isNonExactPropWrapperFunction(right)) { + reportPropTypesError(node); } else if (right.type === 'Identifier') { const identifier = right.name; const propsDefinition = variableUtil.findVariableByName(context, identifier); if (isNonEmptyObjectExpression(propsDefinition)) { - context.report({ - node: node, - message: PROP_TYPES_MESSAGE, - data: getPropTypesErrorMessage() - }); + reportPropTypesError(node); + } else if (isNonExactPropWrapperFunction(propsDefinition)) { + reportPropTypesError(node); } } } diff --git a/tests/lib/rules/prefer-exact-props.js b/tests/lib/rules/prefer-exact-props.js index c159b4b3e5..60bea7af30 100644 --- a/tests/lib/rules/prefer-exact-props.js +++ b/tests/lib/rules/prefer-exact-props.js @@ -186,6 +186,39 @@ ruleTester.run('prefer-exact-props', rule, { bar: PropTypes.string, }; ` + }, { + code: ` + import somethingElse from "something-else"; + const props = { + foo: PropTypes.string, + bar: PropTypes.shape({ + baz: PropTypes.string + }) + }; + class Component extends React.Component { + render() { + return
; + } + } + Component.propTypes = somethingElse(props); + ` + }, { + code: ` + import somethingElse from "something-else"; + const props = + class Component extends React.Component { + static propTypes = somethingElse({ + foo: PropTypes.string, + bar: PropTypes.shape({ + baz: PropTypes.string + }) + }); + render() { + return
; + } + } + `, + parser: 'babel-eslint' }], invalid: [{ code: ` @@ -291,5 +324,77 @@ ruleTester.run('prefer-exact-props', rule, { ] }, errors: [{message: 'Component propTypes should be exact by using one of \'exact\', \'forbidExtraProps\'.'}] + }, { + code: ` + const props = { + foo: PropTypes.string, + bar: PropTypes.shape({ + baz: PropTypes.string + }) + }; + class Component extends React.Component { + render() { + return
; + } + } + Component.propTypes = props; + `, + settings: { + propWrapperFunctions: [ + {property: 'exact', exact: true}, + {property: 'forbidExtraProps', exact: true} + ] + }, + errors: [{message: 'Component propTypes should be exact by using one of \'exact\', \'forbidExtraProps\'.'}] + }, { + code: ` + import somethingElse from "something-else"; + function Component({ foo, bar }) { + return
{foo}{bar}
; + } + Component.propTypes = somethingElse({ + foo: PropTypes.string, + bar: PropTypes.string, + }); + `, + settings: settings, + errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}] + }, { + code: ` + import somethingElse from "something-else"; + const props = { + foo: PropTypes.string, + bar: PropTypes.shape({ + baz: PropTypes.string + }) + }; + class Component extends React.Component { + render() { + return
; + } + } + Component.propTypes = somethingElse(props); + `, + settings: settings, + errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}] + }, { + code: ` + import somethingElse from "something-else"; + const props = + class Component extends React.Component { + static propTypes = somethingElse({ + foo: PropTypes.string, + bar: PropTypes.shape({ + baz: PropTypes.string + }) + }); + render() { + return
; + } + } + `, + settings: settings, + parser: 'babel-eslint', + errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}] }] });