From d8004616023a6ebb8b1c4aa8e67238a0bfe28e72 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Tue, 21 Feb 2017 09:02:20 -0800 Subject: [PATCH 1/8] Add new rule 'no-invalid-default-props' Fixes #1022 --- docs/rules/no-invalid-default-props.md | 192 +++ index.js | 1 + lib/rules/no-invalid-default-props.js | 628 ++++++++ tests/lib/rules/no-invalid-default-props.js | 1448 +++++++++++++++++++ 4 files changed, 2269 insertions(+) create mode 100644 docs/rules/no-invalid-default-props.md create mode 100644 lib/rules/no-invalid-default-props.js create mode 100644 tests/lib/rules/no-invalid-default-props.js diff --git a/docs/rules/no-invalid-default-props.md b/docs/rules/no-invalid-default-props.md new file mode 100644 index 0000000000..20327269fa --- /dev/null +++ b/docs/rules/no-invalid-default-props.md @@ -0,0 +1,192 @@ +# Enforce all defaultProps have a corresponding non-required PropType (no-invalid-default-props) + +This rule aims to ensure that any `defaultProp` has a non-required `PropType` declaration. + +Having `defaultProps` for non-existent `propTypes` is likely the result of errors in refactoring +or a sign of a missing `propType`. Having a `defaultProp` for a required property similarly +indicates a possible refactoring problem. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +function MyStatelessComponent({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string +}; + +MyStatelessComponent.defaultProps = { + foo: "foo" +}; +``` + +```jsx +var Greeting = React.createClass({ + render: function() { + return
Hello {this.props.foo} {this.props.bar}
; + }, + + propTypes: { + foo: React.PropTypes.string, + bar: React.PropTypes.string + }, + + getDefaultProps: function() { + return { + baz: "baz" + }; + } +}); +``` + +```jsx +class Greeting extends React.Component { + render() { + return ( +

Hello, {this.props.foo} {this.props.bar}

+ ); + } +} + +Greeting.propTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string +}; + +Greeting.defaultProps = { + foo: "foo" +}; +``` + +```jsx +class Greeting extends React.Component { + render() { + return ( +

Hello, {this.props.foo} {this.props.bar}

+ ); + } + + static propTypes = { + foo: React.PropTypes.string, + bar: React.PropTypes.string.isRequired + }; + + static defaultProps = { + baz: "baz" + }; +} +``` + +```jsx +type Props = { + foo: string, + bar?: string +}; + +function MyStatelessComponent(props: Props) { + return
Hello {props.foo} {props.bar}
; +} + +MyStatelessComponent.defaultProps = { + foo: "foo", + bar: "bar" +} +``` + +The following patterns are not considered warnings: + +```jsx +function MyStatelessComponent({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: React.PropTypes.string, + bar: React.PropTypes.string.isRequired +}; +``` + +```jsx +function MyStatelessComponent({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string +}; + +MyStatelessComponent.defaultProps = { + bar: 'some default' +}; +``` + +```jsx +type Props = { + foo: string, + bar?: string +}; + +function MyStatelessComponent(props: Props) { + return
Hello {props.foo} {props.bar}
; +} + +MyStatelessComponent.defaultProps = { + bar: 'some default' +}; +``` + +```js +function NotAComponent({ foo, bar }) {} + +NotAComponent.propTypes = { + foo: React.PropTypes.string, + bar: React.PropTypes.string.isRequired +}; +``` + +## Rule Options + +```js +... +"no-invalid-default-props": [, { "allowRequiredDefaults": }] +... +``` + +### `allowRequiredDefaults` + +When `true` the rule will ignore `defaultProps` for `isRequired` `propTypes`. + +The following patterns are considered okay and do not cause warnings: + +```jsx +function MyStatelessComponent({ foo, bar }) { + return
{foo}{bar}
; +} + +MyStatelessComponent.propTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string +}; + +MyStatelessComponent.defaultProps = { + foo: "foo" +}; +``` + +## When Not To Use It + +If you don't care about stray `defaultsProps` in your components, you can disable this rule. + +## Related rules + +- [require-default-props](./require-default-props.md) + +# Resources +- [Official React documentation on defaultProps](https://facebook.github.io/react/docs/typechecking-with-proptypes.html#default-prop-values) + diff --git a/index.js b/index.js index 3226b9bc46..d5afd75779 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ var allRules = { 'jsx-no-bind': require('./lib/rules/jsx-no-bind'), 'jsx-no-undef': require('./lib/rules/jsx-no-undef'), 'no-unknown-property': require('./lib/rules/no-unknown-property'), + 'no-invalid-default-props': require('./lib/rules/no-invalid-default-props'), 'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), 'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'), 'jsx-sort-props': require('./lib/rules/jsx-sort-props'), diff --git a/lib/rules/no-invalid-default-props.js b/lib/rules/no-invalid-default-props.js new file mode 100644 index 0000000000..3ea443ab77 --- /dev/null +++ b/lib/rules/no-invalid-default-props.js @@ -0,0 +1,628 @@ +/** + * @fileOverview Enforce all defaultProps are defined in propTypes + * @author Vitor Balocco + * @author Roy Sutton + */ +'use strict'; + +var has = require('has'); +var find = require('array.prototype.find'); +var Components = require('../util/Components'); +var variableUtil = require('../util/variable'); +var annotations = require('../util/annotations'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforce all defaultProps are defined and not required in propTypes.', + category: 'Best Practices' + }, + + schema: [{ + type: 'object', + properties: { + allowRequiredDefaults: { + default: false, + type: 'boolean' + } + }, + additionalProperties: false + }] + }, + + create: Components.detect(function(context, components, utils) { + + var configuration = context.options[0] || {}; + var allowRequiredDefaults = configuration.allowRequiredDefaults || false; + + /** + * Get properties name + * @param {Object} node - Property. + * @returns {String} Property name. + */ + function getPropertyName(node) { + if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) { + return node.key.name; + } else if (node.type === 'MemberExpression') { + return node.property.name; + // Special case for class properties + // (babel-eslint@5 does not expose property name so we have to rely on tokens) + } else if (node.type === 'ClassProperty') { + var tokens = context.getFirstTokens(node, 2); + return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; + } + return ''; + } + + /** + * Checks if the Identifier node passed in looks like a propTypes declaration. + * @param {ASTNode} node The node to check. Must be an Identifier node. + * @returns {Boolean} `true` if the node is a propTypes declaration, `false` if not + */ + function isPropTypesDeclaration(node) { + return getPropertyName(node) === 'propTypes'; + } + + /** + * Checks if the Identifier node passed in looks like a defaultProps declaration. + * @param {ASTNode} node The node to check. Must be an Identifier node. + * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not + */ + function isDefaultPropsDeclaration(node) { + return (getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps'); + } + + /** + * Checks if the PropTypes MemberExpression node passed in declares a required propType. + * @param {ASTNode} propTypeExpression node to check. Must be a `PropTypes` MemberExpression. + * @returns {Boolean} `true` if this PropType is required, `false` if not. + */ + function isRequiredPropType(propTypeExpression) { + return propTypeExpression.type === 'MemberExpression' && propTypeExpression.property.name === 'isRequired'; + } + + /** + * Find a variable by name in the current scope. + * @param {string} name Name of the variable to look for. + * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. + */ + function findVariableByName(name) { + var variable = find(variableUtil.variablesInScope(context), function(item) { + return item.name === name; + }); + + if (!variable || !variable.defs[0] || !variable.defs[0].node) { + return null; + } + + if (variable.defs[0].node.type === 'TypeAlias') { + return variable.defs[0].node.right; + } + + return variable.defs[0].node.init; + } + + /** + * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not + * an Identifier, then the node is simply returned. + * @param {ASTNode} node The node to resolve. + * @returns {ASTNode|null} Return null if the value could not be resolved, ASTNode otherwise. + */ + function resolveNodeValue(node) { + if (node.type === 'Identifier') { + return findVariableByName(node.name); + } + + return node; + } + + /** + * Tries to find the definition of a GenericTypeAnnotation in the current scope. + * @param {ASTNode} node The node GenericTypeAnnotation node to resolve. + * @return {ASTNode|null} Return null if definition cannot be found, ASTNode otherwise. + */ + function resolveGenericTypeAnnotation(node) { + if (node.type !== 'GenericTypeAnnotation' || node.id.type !== 'Identifier') { + return null; + } + + return findVariableByName(node.id.name); + } + + function resolveUnionTypeAnnotation(node) { + // Go through all the union and resolve any generic types. + return node.types.map(function(annotation) { + if (annotation.type === 'GenericTypeAnnotation') { + return resolveGenericTypeAnnotation(annotation); + } + + return annotation; + }); + } + + /** + * Extracts a PropType from an ObjectExpression node. + * @param {ASTNode} objectExpression ObjectExpression node. + * @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`. + */ + function getPropTypesFromObjectExpression(objectExpression) { + var props = objectExpression.properties.filter(function(property) { + return property.type !== 'ExperimentalSpreadProperty'; + }); + + return props.map(function(property) { + return { + name: property.key.name, + isRequired: isRequiredPropType(property.value), + node: property + }; + }); + } + + /** + * Extracts a PropType from a TypeAnnotation node. + * @param {ASTNode} node TypeAnnotation node. + * @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`. + */ + function getPropTypesFromTypeAnnotation(node) { + var properties; + + switch (node.typeAnnotation.type) { + case 'GenericTypeAnnotation': + var annotation = resolveGenericTypeAnnotation(node.typeAnnotation); + + if (annotation && annotation.id) { + annotation = findVariableByName(annotation.id.name); + } + + properties = annotation ? (annotation.properties || []) : []; + break; + + case 'UnionTypeAnnotation': + var union = resolveUnionTypeAnnotation(node.typeAnnotation); + properties = union.reduce(function(acc, curr) { + if (!curr) { + return acc; + } + + return acc.concat(curr.properties); + }, []); + break; + + case 'ObjectTypeAnnotation': + properties = node.typeAnnotation.properties; + break; + + default: + properties = []; + break; + } + + var props = properties.filter(function(property) { + return property.type === 'ObjectTypeProperty'; + }); + + return props.map(function(property) { + // the `key` property is not present in ObjectTypeProperty nodes, so we need to get the key name manually. + var tokens = context.getFirstTokens(property, 1); + var name = tokens[0].value; + + return { + name: name, + isRequired: !property.optional, + node: property + }; + }); + } + + /** + * Extracts a DefaultProp from an ObjectExpression node. + * @param {ASTNode} objectExpression ObjectExpression node. + * @returns {Object|string} Object representation of a defaultProp, to be consumed by + * `addDefaultPropsToComponent`, or string "unresolved", if the defaultProps + * from this ObjectExpression can't be resolved. + */ + function getDefaultPropsFromObjectExpression(objectExpression) { + var hasSpread = find(objectExpression.properties, function(property) { + return property.type === 'ExperimentalSpreadProperty'; + }); + + if (hasSpread) { + return 'unresolved'; + } + + return objectExpression.properties.map(function(defaultProp) { + return { + name: defaultProp.key.name, + node: defaultProp + }; + }); + } + + /** + * Marks a component's DefaultProps declaration as "unresolved". A component's DefaultProps is + * marked as "unresolved" if we cannot safely infer the values of its defaultProps declarations + * without risking false negatives. + * @param {Object} component The component to mark. + * @returns {void} + */ + function markDefaultPropsAsUnresolved(component) { + components.set(component.node, { + defaultProps: 'unresolved' + }); + } + + /** + * Adds propTypes to the component passed in. + * @param {ASTNode} component The component to add the propTypes to. + * @param {Object[]} propTypes propTypes to add to the component. + * @returns {void} + */ + function addPropTypesToComponent(component, propTypes) { + var props = component.propTypes || []; + + components.set(component.node, { + propTypes: props.concat(propTypes) + }); + } + + /** + * Adds defaultProps to the component passed in. + * @param {ASTNode} component The component to add the defaultProps to. + * @param {String[]|String} defaultProps defaultProps to add to the component or the string "unresolved" + * if this component has defaultProps that can't be resolved. + * @returns {void} + */ + function addDefaultPropsToComponent(component, defaultProps) { + // Early return if this component's defaultProps is already marked as "unresolved". + if (component.defaultProps === 'unresolved') { + return; + } + + if (defaultProps === 'unresolved') { + markDefaultPropsAsUnresolved(component); + return; + } + + var defaults = component.defaultProps || []; + + components.set(component.node, { + defaultProps: defaults.concat(defaultProps) + }); + } + + /** + * Tries to find a props type annotation in a stateless component. + * @param {ASTNode} node The AST node to look for a props type annotation. + * @return {void} + */ + function handleStatelessComponent(node) { + if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) { + return; + } + + // find component this props annotation belongs to + var component = components.get(utils.getParentStatelessComponent()); + if (!component) { + return; + } + + addPropTypesToComponent(component, getPropTypesFromTypeAnnotation(node.params[0].typeAnnotation, context)); + } + + function handlePropTypeAnnotationClassProperty(node) { + // find component this props annotation belongs to + var component = components.get(utils.getParentES6Component()); + if (!component) { + return; + } + + addPropTypesToComponent(component, getPropTypesFromTypeAnnotation(node.typeAnnotation, context)); + } + + function isPropTypeAnnotation(node) { + return (getPropertyName(node) === 'props' && !!node.typeAnnotation); + } + + function propFromName(propTypes, name) { + return propTypes.find(function (prop) { + return prop.name === name; + }); + } + + /** + * Reports all defaultProps passed in that don't have an appropriate propTypes counterpart. + * @param {Object[]} propTypes Array of propTypes to check. + * @param {Object} defaultProps Object of defaultProps to check. Keys are the props names. + * @return {void} + */ + function reportInvalidDefaultProps(propTypes, defaultProps) { + // If this defaultProps is "unresolved" or the propTypes is undefined, then we should ignore + // this component and not report any errors for it, to avoid false-positives with e.g. + // external defaultProps/propTypes declarations or spread operators. + if (defaultProps === 'unresolved' || !propTypes) { + return; + } + + defaultProps.forEach(function(defaultProp) { + var prop = propFromName(propTypes, defaultProp.name); + + if (prop && (allowRequiredDefaults || !prop.isRequired)) { + return; + } + + if (prop) { + context.report( + defaultProp.node, + 'defaultProp "{{name}}" defined for isRequired propType.', + {name: defaultProp.name} + ); + } else { + context.report( + defaultProp.node, + 'defaultProp "{{name}}" has no corresponding propTypes declaration.', + {name: defaultProp.name} + ); + } + }); + } + + // -------------------------------------------------------------------------- + // Public API + // -------------------------------------------------------------------------- + + return { + MemberExpression: function(node) { + var isPropType = isPropTypesDeclaration(node); + var isDefaultProp = isDefaultPropsDeclaration(node); + + if (!isPropType && !isDefaultProp) { + return; + } + + // find component this propTypes/defaultProps belongs to + var component = utils.getRelatedComponent(node); + if (!component) { + return; + } + + // e.g.: + // MyComponent.propTypes = { + // foo: React.PropTypes.string.isRequired, + // bar: React.PropTypes.string + // }; + // + // or: + // + // MyComponent.propTypes = myPropTypes; + if (node.parent.type === 'AssignmentExpression') { + + var expression = resolveNodeValue(node.parent.right); + if (!expression || expression.type !== 'ObjectExpression') { + // If a value can't be found, we mark the defaultProps declaration as "unresolved", because + // we should ignore this component and not report any errors for it, to avoid false-positives + // with e.g. external defaultProps declarations. + if (isDefaultProp) { + markDefaultPropsAsUnresolved(component); + } + + return; + } + + if (isPropType) { + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); + } else { + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + } + + return; + } + + // e.g.: + // MyComponent.propTypes.baz = React.PropTypes.string; + if (node.parent.type === 'MemberExpression' && node.parent.parent.type === 'AssignmentExpression') { + + if (isPropType) { + addPropTypesToComponent(component, [{ + name: node.parent.property.name, + isRequired: isRequiredPropType(node.parent.parent.right), + node: node.parent.parent + }]); + } else { + addDefaultPropsToComponent(component, [{ + name: node.parent.property.name, + node: node.parent.parent + }]); + } + + return; + } + }, + + // e.g.: + // class Hello extends React.Component { + // static get propTypes() { + // return { + // name: React.PropTypes.string + // }; + // } + // static get defaultProps() { + // return { + // name: 'Dean' + // }; + // } + // render() { + // return
Hello {this.props.name}
; + // } + // } + MethodDefinition: function(node) { + if (!node.static || node.kind !== 'get') { + return; + } + + var isPropType = isPropTypesDeclaration(node); + var isDefaultProp = isDefaultPropsDeclaration(node); + + if (!isPropType && !isDefaultProp) { + return; + } + + // find component this propTypes/defaultProps belongs to + var component = components.get(utils.getParentES6Component()); + if (!component) { + return; + } + + var returnStatement = utils.findReturnStatement(node); + if (!returnStatement) { + return; + } + + var expression = resolveNodeValue(returnStatement.argument); + if (!expression || expression.type !== 'ObjectExpression') { + return; + } + + if (isPropType) { + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); + } else { + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + } + }, + + // e.g.: + // class Greeting extends React.Component { + // render() { + // return ( + //

Hello, {this.props.foo} {this.props.bar}

+ // ); + // } + // static propTypes = { + // foo: React.PropTypes.string, + // bar: React.PropTypes.string.isRequired + // }; + // } + ClassProperty: function(node) { + if (isPropTypeAnnotation(node)) { + handlePropTypeAnnotationClassProperty(node); + return; + } + + if (!node.static) { + return; + } + + if (!node.value) { + return; + } + + var isPropType = getPropertyName(node) === 'propTypes'; + var isDefaultProp = getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps'; + + if (!isPropType && !isDefaultProp) { + return; + } + + // find component this propTypes/defaultProps belongs to + var component = components.get(utils.getParentES6Component()); + if (!component) { + return; + } + + var expression = resolveNodeValue(node.value); + if (!expression || expression.type !== 'ObjectExpression') { + return; + } + + if (isPropType) { + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); + } else { + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + } + }, + + // e.g.: + // React.createClass({ + // render: function() { + // return
{this.props.foo}
; + // }, + // propTypes: { + // foo: React.PropTypes.string.isRequired, + // }, + // getDefaultProps: function() { + // return { + // foo: 'default' + // }; + // } + // }); + ObjectExpression: function(node) { + // find component this propTypes/defaultProps belongs to + var component = utils.isES5Component(node) && components.get(node); + if (!component) { + return; + } + + // Search for the proptypes declaration + node.properties.forEach(function(property) { + if (property.type === 'ExperimentalSpreadProperty') { + return; + } + + var isPropType = isPropTypesDeclaration(property); + var isDefaultProp = isDefaultPropsDeclaration(property); + + if (!isPropType && !isDefaultProp) { + return; + } + + if (isPropType && property.value.type === 'ObjectExpression') { + addPropTypesToComponent(component, getPropTypesFromObjectExpression(property.value)); + return; + } + + if (isDefaultProp && property.value.type === 'ObjectExpression') { + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(property.value)); + } + + if (isDefaultProp && property.value.type === 'FunctionExpression') { + var returnStatement = utils.findReturnStatement(property); + if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') { + return; + } + + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(returnStatement.argument)); + } + }); + }, + + // Check for type annotations in stateless components + FunctionDeclaration: handleStatelessComponent, + ArrowFunctionExpression: handleStatelessComponent, + FunctionExpression: handleStatelessComponent, + + 'Program:exit': function() { + var list = components.list(); + + for (var component in list) { + if (!has(list, component)) { + continue; + } + + // If no defaultProps could be found, we don't report anything. + if (!list[component].defaultProps) { + return; + } + + reportInvalidDefaultProps( + list[component].propTypes, + list[component].defaultProps || {} + ); + } + } + }; + }) +}; diff --git a/tests/lib/rules/no-invalid-default-props.js b/tests/lib/rules/no-invalid-default-props.js new file mode 100644 index 0000000000..c22c18c450 --- /dev/null +++ b/tests/lib/rules/no-invalid-default-props.js @@ -0,0 +1,1448 @@ +/** + * @fileoverview Enforce all defaultProps are declared and non-required propTypes + * @author Vitor Balocco + * @author Roy Sutton + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-invalid-default-props'); +var RuleTester = require('eslint').RuleTester; +var assign = require('object.assign'); + +require('babel-eslint'); + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true + } +}; + +var ruleTester = new RuleTester({parserOptions: parserOptions}); + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +ruleTester.run('no-invalid-default-props', rule, { + + valid: [ + // + // stateless components + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string.isRequired', + '};' + ].join('\n') + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n') + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.propTypes.foo = React.PropTypes.string;', + 'MyStatelessComponent.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' bar: "bar"', + '};' + ].join('\n'), + options: [{ + allowRequiredDefaults: true + }] + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.propTypes.foo = React.PropTypes.string;', + 'MyStatelessComponent.defaultProps = {};', + 'MyStatelessComponent.defaultProps.foo = "foo";' + ].join('\n') + }, + { + code: [ + 'function MyStatelessComponent({ foo }) {', + ' return
{foo}
;', + '}', + 'MyStatelessComponent.propTypes = {};', + 'MyStatelessComponent.propTypes.foo = React.PropTypes.string;', + 'MyStatelessComponent.defaultProps = {};', + 'MyStatelessComponent.defaultProps.foo = "foo";' + ].join('\n') + }, + { + code: [ + 'const types = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'const defaults = {', + ' foo: "foo"', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n') + }, + { + code: [ + 'const defaults = {', + ' foo: "foo"', + '};', + 'const types = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n') + }, + + // + // React.createClass components + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string.isRequired', + ' }', + '});' + ].join('\n') + }, + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' foo: "foo"', + ' };', + ' }', + '});' + ].join('\n') + }, + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' foo: "foo",', + ' bar: "bar"', + ' };', + ' }', + '});' + ].join('\n') + }, + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' }', + '});' + ].join('\n') + }, + + // + // ES6 class component + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.propTypes.foo = React.PropTypes.string;', + 'Greeting.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.propTypes.foo = React.PropTypes.string;', + 'Greeting.defaultProps = {};', + 'Greeting.defaultProps.foo = "foo";' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {};', + 'Greeting.propTypes.foo = React.PropTypes.string;', + 'Greeting.defaultProps = {};', + 'Greeting.defaultProps.foo = "foo";' + ].join('\n') + }, + + // + // edge cases + + // not a react component + { + code: [ + 'function NotAComponent({ foo, bar }) {}', + 'NotAComponent.defaultProps = {', + ' bar: "bar"', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.defaulProps = {', + ' bar: "bar"', + '};' + ].join('\n') + }, + // external references + { + code: [ + 'const defaults = require("./defaults");', + 'const types = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n') + }, + { + code: [ + 'const defaults = {', + ' foo: "foo"', + '};', + 'const types = require("./propTypes");', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n') + }, + { + code: [ + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = require("./defaults").foo;', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n') + }, + { + code: [ + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = require("./defaults").foo;', + 'MyStatelessComponent.defaultProps.bar = "bar";', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n') + }, + { + code: [ + 'import defaults from "./defaults";', + + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = defaults;', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n'), + parserOptions: assign({sourceType: 'module'}, parserOptions) + }, + { + code: [ + 'import { foo } from "./defaults";', + + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = foo;', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n'), + parserOptions: assign({sourceType: 'module'}, parserOptions) + }, + // using spread operator + { + code: [ + 'const component = rowsOfType(GuestlistEntry, (rowData, ownProps) => ({', + ' ...rowData,', + ' onPress: () => ownProps.onPress(rowData.id),', + '}));' + ].join('\n') + }, + { + code: [ + 'MyStatelessComponent.propTypes = {', + ' ...stuff,', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = {', + ' foo: "foo"', + '};', + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n') + }, + { + code: [ + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = {', + ' ...defaults,', + '};', + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' ...someProps,', + ' bar: React.PropTypes.string.isRequired', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' ...defaults,', + ' bar: "bar"', + '};' + ].join('\n') + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' ...defaults,', + ' bar: "bar"', + '};' + ].join('\n') + }, + + // + // with Flow annotations + { + code: [ + 'type Props = {', + ' foo: string', + '};', + + 'class Hello extends React.Component {', + ' props: Props;', + + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'type Props = {', + ' foo: string,', + ' bar?: string', + '};', + + 'class Hello extends React.Component {', + ' props: Props;', + + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}', + + 'Hello.defaultProps = {', + ' bar: "bar"', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends React.Component {', + ' props: {', + ' foo: string,', + ' bar?: string', + ' };', + + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}', + + 'Hello.defaultProps = {', + ' bar: "bar"', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends React.Component {', + ' props: {', + ' foo: string', + ' };', + + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'function Hello(props: { foo?: string }) {', + ' return
Hello {props.foo}
;', + '}', + + 'Hello.defaultProps = { foo: "foo" };' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'function Hello(props: { foo: string }) {', + ' return
Hello {foo}
;', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'const Hello = (props: { foo?: string }) => {', + ' return
Hello {props.foo}
;', + '};', + + 'Hello.defaultProps = { foo: "foo" };' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'const Hello = (props: { foo: string }) => {', + ' return
Hello {foo}
;', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'const Hello = function(props: { foo?: string }) {', + ' return
Hello {props.foo}
;', + '};', + + 'Hello.defaultProps = { foo: "foo" };' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'const Hello = function(props: { foo: string }) {', + ' return
Hello {foo}
;', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'type Props = {', + ' foo: string,', + ' bar?: string', + '};', + + 'type Props2 = {', + ' foo: string,', + ' baz?: string', + '}', + + 'function Hello(props: Props | Props2) {', + ' return
Hello {props.foo}
;', + '}', + + 'Hello.defaultProps = {', + ' bar: "bar",', + ' baz: "baz"', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'import type Props from "fake";', + 'class Hello extends React.Component {', + ' props: Props;', + ' render () {', + ' return
Hello {this.props.name.firstname}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'type Props = any;', + + 'const Hello = function({ foo }: Props) {', + ' return
Hello {foo}
;', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'import type ImportedProps from "fake";', + 'type Props = ImportedProps;', + 'function Hello(props: Props) {', + ' return
Hello {props.name.firstname}
;', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + // don't error when variable is not in scope + { + code: [ + 'import type { ImportedType } from "fake";', + 'type Props = ImportedType;', + 'function Hello(props: Props) {', + ' return
Hello {props.name.firstname}
;', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, + // make sure error is not thrown with multiple assignments + { + code: [ + 'import type ImportedProps from "fake";', + 'type NestedProps = ImportedProps;', + 'type Props = NestedProps;', + 'function Hello(props: Props) {', + ' return
Hello {props.name.firstname}
;', + '}' + ].join('\n'), + parser: 'babel-eslint' + } + ], + + invalid: [ + // + // stateless components + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' baz: "baz"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 9, + column: 3 + }] + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' baz: "baz"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 9, + column: 3 + }], + options: [{ + allowRequiredDefaults: true + }] + }, + { + code: [ + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'MyStatelessComponent.defaultProps = {', + ' bar: "bar"', + '};', + 'MyStatelessComponent.defaultProps.baz = "baz";' + ].join('\n'), + errors: [ + { + message: 'defaultProp "bar" defined for isRequired propType.', + line: 9, + column: 3 + }, + { + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 11, + column: 1 + } + ] + }, + { + code: [ + 'const types = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = {', + ' bar: "bar"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "bar" defined for isRequired propType.', + line: 10, + column: 3 + }] + }, + { + code: [ + 'const defaults = {', + ' foo: "foo"', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string', + '};', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 2, + column: 3 + }] + }, + { + code: [ + 'const defaults = {', + ' foo: "foo"', + '};', + 'const types = {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string', + '};', + + 'function MyStatelessComponent({ foo, bar }) {', + ' return
{foo}{bar}
;', + '}', + 'MyStatelessComponent.propTypes = types;', + 'MyStatelessComponent.defaultProps = defaults;' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 2, + column: 3 + }] + }, + + // + // React.createClass components + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + ' },', + ' defaultProps: {', + ' baz: "baz"', + ' }', + '});' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 10, + column: 5 + }] + }, + { + code: [ + 'var Greeting = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.foo} {this.props.bar}
;', + ' },', + ' propTypes: {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' foo: "foo"', + ' };', + ' }', + '});' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 11, + column: 7 + }] + }, + + // + // ES6 class component + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.defaultProps = {', + ' baz: "baz"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 13, + column: 3 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' foo: React.PropTypes.string.isRequired,', + ' bar: React.PropTypes.string', + '};', + 'Greeting.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 13, + column: 3 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.propTypes.foo = React.PropTypes.string.isRequired;', + 'Greeting.defaultProps = {};', + 'Greeting.defaultProps.foo = "foo";' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 13, + column: 1 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {', + ' bar: React.PropTypes.string', + '};', + 'Greeting.propTypes.foo = React.PropTypes.string;', + 'Greeting.defaultProps = {};', + 'Greeting.defaultProps.baz = "baz";' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 13, + column: 1 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'Greeting.propTypes = {};', + 'Greeting.propTypes.foo = React.PropTypes.string.isRequired;', + 'Greeting.defaultProps = {};', + 'Greeting.defaultProps.foo = "foo";' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 11, + column: 1 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'const props = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'Greeting.propTypes = props;', + 'const defaults = {', + ' bar: "bar"', + '};', + 'Greeting.defaultProps = defaults;' + ].join('\n'), + errors: [{ + message: 'defaultProp "bar" defined for isRequired propType.', + line: 14, + column: 3 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + '}', + 'const props = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + '};', + 'const defaults = {', + ' baz: "baz"', + '};', + 'Greeting.propTypes = props;', + 'Greeting.defaultProps = defaults;' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 13, + column: 3 + }] + }, + + // + // ES6 classes with static getter methods + { + code: [ + 'class Hello extends React.Component {', + ' static get propTypes() {', + ' return {', + ' name: React.PropTypes.string.isRequired', + ' };', + ' }', + ' static get defaultProps() {', + ' return {', + ' name: "name"', + ' };', + ' }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + errors: [{ + message: 'defaultProp "name" defined for isRequired propType.', + line: 9, + column: 7 + }] + }, + { + code: [ + 'class Hello extends React.Component {', + ' static get propTypes() {', + ' return {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + ' };', + ' }', + ' static get defaultProps() {', + ' return {', + ' baz: "world"', + ' };', + ' }', + ' render() {', + ' return
Hello {this.props.bar}
;', + ' }', + '}' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 10, + column: 7 + }] + }, + { + code: [ + 'const props = {', + ' foo: React.PropTypes.string', + '};', + 'const defaults = {', + ' baz: "baz"', + '};', + + 'class Hello extends React.Component {', + ' static get propTypes() {', + ' return props;', + ' }', + ' static get defaultProps() {', + ' return defaults;', + ' }', + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}' + ].join('\n'), + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 5, + column: 3 + }] + }, + { + code: [ + 'const defaults = {', + ' bar: "world"', + '};', + + 'class Hello extends React.Component {', + ' static get propTypes() {', + ' return {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + ' };', + ' }', + ' static get defaultProps() {', + ' return defaults;', + ' }', + ' render() {', + ' return
Hello {this.props.bar}
;', + ' }', + '}' + ].join('\n'), + errors: [{ + message: 'defaultProp "bar" defined for isRequired propType.', + line: 2, + column: 3 + }] + }, + + // + // ES6 classes with property initializers + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + ' static propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + ' };', + ' static defaultProps = {', + ' bar: "bar"', + ' };', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "bar" defined for isRequired propType.', + line: 12, + column: 5 + }] + }, + { + code: [ + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + ' static propTypes = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + ' };', + ' static defaultProps = {', + ' baz: "baz"', + ' };', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 12, + column: 5 + }] + }, + { + code: [ + 'const props = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string.isRequired', + '};', + 'const defaults = {', + ' bar: "bar"', + '};', + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + ' static propTypes = props;', + ' static defaultProps = defaults;', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "bar" defined for isRequired propType.', + line: 6, + column: 3 + }] + }, + { + code: [ + 'const props = {', + ' foo: React.PropTypes.string,', + ' bar: React.PropTypes.string', + '};', + 'const defaults = {', + ' baz: "baz"', + '};', + 'class Greeting extends React.Component {', + ' render() {', + ' return (', + '

Hello, {this.props.foo} {this.props.bar}

', + ' );', + ' }', + ' static propTypes = props;', + ' static defaultProps = defaults;', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "baz" has no corresponding propTypes declaration.', + line: 6, + column: 3 + }] + }, + + // + // edge cases + { + code: [ + 'let Greetings = {};', + 'Greetings.Hello = class extends React.Component {', + ' render () {', + ' return
Hello {this.props.foo}
;', + ' }', + '}', + 'Greetings.Hello.propTypes = {', + ' foo: React.PropTypes.string.isRequired', + '};', + 'Greetings.Hello.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 11, + column: 3 + }] + }, + { + code: [ + 'var Greetings = ({ foo = "foo" }) => {', + ' return
Hello {this.props.foo}
;', + '}', + 'Greetings.propTypes = {', + ' foo: React.PropTypes.string.isRequired', + '};', + 'Greetings.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n'), + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 8, + column: 3 + }] + }, + + // + // with Flow annotations + { + code: [ + 'class Hello extends React.Component {', + ' props: {', + ' foo: string,', + ' bar?: string', + ' };', + + ' render() {', + ' return
Hello {this.props.foo}
;', + ' }', + '}', + + 'Hello.defaultProps = {', + ' foo: "foo"', + '};' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 11, + column: 3 + }] + }, + // Investigate why this test fails. Flow type not finding foo? + { + code: [ + 'function Hello(props: { foo: string }) {', + ' return
Hello {props.foo}
;', + '}', + 'Hello.defaultProps = {', + ' foo: "foo"', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 5, + column: 3 + }] + }, + { + code: [ + 'type Props = {', + ' foo: string', + '};', + + 'function Hello(props: Props) {', + ' return
Hello {props.foo}
;', + '}', + 'Hello.defaultProps = {', + ' foo: "foo"', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 8, + column: 3 + }] + }, + { + code: [ + 'const Hello = (props: { foo: string, bar?: string }) => {', + ' return
Hello {props.foo}
;', + '};', + 'Hello.defaultProps = { foo: "foo", bar: "bar" };' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'defaultProp "foo" defined for isRequired propType.', + line: 4, + column: 24 + }] + }, + { + code: [ + 'type Props = {', + ' foo: string,', + ' bar?: string', + '};', + + 'type Props2 = {', + ' foo: string,', + ' baz?: string', + '}', + + 'function Hello(props: Props | Props2) {', + ' return
Hello {props.foo}
;', + '}', + 'Hello.defaultProps = { foo: "foo", frob: "frob" };' + ].join('\n'), + parser: 'babel-eslint', + errors: [ + { + message: 'defaultProp "foo" defined for isRequired propType.', + line: 12, + column: 24 + }, + { + message: 'defaultProp "frob" has no corresponding propTypes declaration.', + line: 12, + column: 36 + } + ] + } + ] +}); From e1055b158b7f88afb70c0f198d200b97c187a873 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Thu, 23 Feb 2017 10:24:21 -0800 Subject: [PATCH 2/8] Find es5 --- lib/rules/no-invalid-default-props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-invalid-default-props.js b/lib/rules/no-invalid-default-props.js index 3ea443ab77..a1e1e52f63 100644 --- a/lib/rules/no-invalid-default-props.js +++ b/lib/rules/no-invalid-default-props.js @@ -329,7 +329,7 @@ module.exports = { } function propFromName(propTypes, name) { - return propTypes.find(function (prop) { + return find(propTypes, function (prop) { return prop.name === name; }); } From cbd90a37300e2afcd15a1880971a6e306d273af0 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 24 Feb 2017 03:05:01 -0800 Subject: [PATCH 3/8] Correct React.createClass defaultProps --- lib/rules/no-invalid-default-props.js | 4 ---- tests/lib/rules/no-invalid-default-props.js | 10 ++++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/rules/no-invalid-default-props.js b/lib/rules/no-invalid-default-props.js index a1e1e52f63..0c1b934fae 100644 --- a/lib/rules/no-invalid-default-props.js +++ b/lib/rules/no-invalid-default-props.js @@ -584,10 +584,6 @@ module.exports = { return; } - if (isDefaultProp && property.value.type === 'ObjectExpression') { - addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(property.value)); - } - if (isDefaultProp && property.value.type === 'FunctionExpression') { var returnStatement = utils.findReturnStatement(property); if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') { diff --git a/tests/lib/rules/no-invalid-default-props.js b/tests/lib/rules/no-invalid-default-props.js index c22c18c450..21f7d07b1e 100644 --- a/tests/lib/rules/no-invalid-default-props.js +++ b/tests/lib/rules/no-invalid-default-props.js @@ -887,15 +887,17 @@ ruleTester.run('no-invalid-default-props', rule, { ' foo: React.PropTypes.string,', ' bar: React.PropTypes.string.isRequired', ' },', - ' defaultProps: {', - ' baz: "baz"', + ' getDefaultProps: function() {', + ' return {', + ' baz: "baz"', + ' };', ' }', '});' ].join('\n'), errors: [{ message: 'defaultProp "baz" has no corresponding propTypes declaration.', - line: 10, - column: 5 + line: 11, + column: 7 }] }, { From e05f47a9adb9c9ffd3ba7a87ecda3509b7176feb Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 24 Feb 2017 13:51:36 -0800 Subject: [PATCH 4/8] Address review feedback --- lib/rules/no-invalid-default-props.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-invalid-default-props.js b/lib/rules/no-invalid-default-props.js index 0c1b934fae..48825d4a33 100644 --- a/lib/rules/no-invalid-default-props.js +++ b/lib/rules/no-invalid-default-props.js @@ -73,7 +73,8 @@ module.exports = { * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not */ function isDefaultPropsDeclaration(node) { - return (getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps'); + var propName = getPropertyName(node); + return (propName === 'defaultProps' || propName === 'getDefaultProps'); } /** @@ -424,7 +425,8 @@ module.exports = { // e.g.: // MyComponent.propTypes.baz = React.PropTypes.string; - if (node.parent.type === 'MemberExpression' && node.parent.parent.type === 'AssignmentExpression') { + if (node.parent.type === 'MemberExpression' && node.parent.parent && + node.parent.parent.type === 'AssignmentExpression') { if (isPropType) { addPropTypesToComponent(component, [{ @@ -520,8 +522,9 @@ module.exports = { return; } - var isPropType = getPropertyName(node) === 'propTypes'; - var isDefaultProp = getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps'; + var propName = getPropertyName(node); + var isPropType = propName === 'propTypes'; + var isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps'; if (!isPropType && !isDefaultProp) { return; From d4b6709b45584f59fc36a569e7c42693bb159cea Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Sat, 29 Apr 2017 22:39:14 -0400 Subject: [PATCH 5/8] Update name of rule --- ...lid-default-props.md => default-props-match-prop-types.md} | 4 ++-- index.js | 2 +- ...lid-default-props.js => default-props-match-prop-types.js} | 2 +- ...lid-default-props.js => default-props-match-prop-types.js} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename docs/rules/{no-invalid-default-props.md => default-props-match-prop-types.md} (96%) rename lib/rules/{no-invalid-default-props.js => default-props-match-prop-types.js} (99%) rename tests/lib/rules/{no-invalid-default-props.js => default-props-match-prop-types.js} (99%) diff --git a/docs/rules/no-invalid-default-props.md b/docs/rules/default-props-match-prop-types.md similarity index 96% rename from docs/rules/no-invalid-default-props.md rename to docs/rules/default-props-match-prop-types.md index 20327269fa..d3ead48146 100644 --- a/docs/rules/no-invalid-default-props.md +++ b/docs/rules/default-props-match-prop-types.md @@ -1,4 +1,4 @@ -# Enforce all defaultProps have a corresponding non-required PropType (no-invalid-default-props) +# Enforce all defaultProps have a corresponding non-required PropType (default-props-match-prop-types) This rule aims to ensure that any `defaultProp` has a non-required `PropType` declaration. @@ -154,7 +154,7 @@ NotAComponent.propTypes = { ```js ... -"no-invalid-default-props": [, { "allowRequiredDefaults": }] +"default-props-match-prop-types": [, { "allowRequiredDefaults": }] ... ``` diff --git a/index.js b/index.js index d5afd75779..85385275df 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,7 @@ var allRules = { 'jsx-no-bind': require('./lib/rules/jsx-no-bind'), 'jsx-no-undef': require('./lib/rules/jsx-no-undef'), 'no-unknown-property': require('./lib/rules/no-unknown-property'), - 'no-invalid-default-props': require('./lib/rules/no-invalid-default-props'), + 'default-props-match-prop-types': require('./lib/rules/default-props-match-prop-types'), 'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), 'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'), 'jsx-sort-props': require('./lib/rules/jsx-sort-props'), diff --git a/lib/rules/no-invalid-default-props.js b/lib/rules/default-props-match-prop-types.js similarity index 99% rename from lib/rules/no-invalid-default-props.js rename to lib/rules/default-props-match-prop-types.js index 48825d4a33..fa6e8bcfcb 100644 --- a/lib/rules/no-invalid-default-props.js +++ b/lib/rules/default-props-match-prop-types.js @@ -18,7 +18,7 @@ var annotations = require('../util/annotations'); module.exports = { meta: { docs: { - description: 'Enforce all defaultProps are defined and not required in propTypes.', + description: 'Enforce all defaultProps are defined and not "required" in propTypes.', category: 'Best Practices' }, diff --git a/tests/lib/rules/no-invalid-default-props.js b/tests/lib/rules/default-props-match-prop-types.js similarity index 99% rename from tests/lib/rules/no-invalid-default-props.js rename to tests/lib/rules/default-props-match-prop-types.js index 21f7d07b1e..a9c050ab42 100644 --- a/tests/lib/rules/no-invalid-default-props.js +++ b/tests/lib/rules/default-props-match-prop-types.js @@ -9,7 +9,7 @@ // Requirements // ------------------------------------------------------------------------------ -var rule = require('../../../lib/rules/no-invalid-default-props'); +var rule = require('../../../lib/rules/default-props-match-prop-types'); var RuleTester = require('eslint').RuleTester; var assign = require('object.assign'); @@ -29,7 +29,7 @@ var ruleTester = new RuleTester({parserOptions: parserOptions}); // Tests // ------------------------------------------------------------------------------ -ruleTester.run('no-invalid-default-props', rule, { +ruleTester.run('default-props-match-prop-types', rule, { valid: [ // From 0b2c3e292abef4f54dff79055a29b839d26faa06 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Sat, 29 Apr 2017 23:02:28 -0400 Subject: [PATCH 6/8] Update to createReactClass --- .../lib/rules/default-props-match-prop-types.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/lib/rules/default-props-match-prop-types.js b/tests/lib/rules/default-props-match-prop-types.js index a9c050ab42..4e5835709e 100644 --- a/tests/lib/rules/default-props-match-prop-types.js +++ b/tests/lib/rules/default-props-match-prop-types.js @@ -171,10 +171,10 @@ ruleTester.run('default-props-match-prop-types', rule, { }, // - // React.createClass components + // createReactClass components { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' },', @@ -187,7 +187,7 @@ ruleTester.run('default-props-match-prop-types', rule, { }, { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' },', @@ -205,7 +205,7 @@ ruleTester.run('default-props-match-prop-types', rule, { }, { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' },', @@ -224,7 +224,7 @@ ruleTester.run('default-props-match-prop-types', rule, { }, { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' }', @@ -876,10 +876,10 @@ ruleTester.run('default-props-match-prop-types', rule, { }, // - // React.createClass components + // createReactClass components { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' },', @@ -902,7 +902,7 @@ ruleTester.run('default-props-match-prop-types', rule, { }, { code: [ - 'var Greeting = React.createClass({', + 'var Greeting = createReactClass({', ' render: function() {', ' return
Hello {this.props.foo} {this.props.bar}
;', ' },', From b69abae2dd01d7cede039be7cc45c33068520414 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Mon, 1 May 2017 16:02:15 -0400 Subject: [PATCH 7/8] Update for Node 4 Enact-DCO-1.0-Signed-off-by: Roy Sutton roy.sutton@lge.com --- lib/rules/default-props-match-prop-types.js | 93 ++++++++++----------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/lib/rules/default-props-match-prop-types.js b/lib/rules/default-props-match-prop-types.js index fa6e8bcfcb..3e95fbc7b8 100644 --- a/lib/rules/default-props-match-prop-types.js +++ b/lib/rules/default-props-match-prop-types.js @@ -5,11 +5,10 @@ */ 'use strict'; -var has = require('has'); -var find = require('array.prototype.find'); -var Components = require('../util/Components'); -var variableUtil = require('../util/variable'); -var annotations = require('../util/annotations'); +const has = require('has'); +const Components = require('../util/Components'); +const variableUtil = require('../util/variable'); +const annotations = require('../util/annotations'); // ------------------------------------------------------------------------------ // Rule Definition @@ -36,8 +35,8 @@ module.exports = { create: Components.detect(function(context, components, utils) { - var configuration = context.options[0] || {}; - var allowRequiredDefaults = configuration.allowRequiredDefaults || false; + const configuration = context.options[0] || {}; + const allowRequiredDefaults = configuration.allowRequiredDefaults || false; /** * Get properties name @@ -52,7 +51,7 @@ module.exports = { // Special case for class properties // (babel-eslint@5 does not expose property name so we have to rely on tokens) } else if (node.type === 'ClassProperty') { - var tokens = context.getFirstTokens(node, 2); + const tokens = context.getFirstTokens(node, 2); return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; } return ''; @@ -73,7 +72,7 @@ module.exports = { * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not */ function isDefaultPropsDeclaration(node) { - var propName = getPropertyName(node); + const propName = getPropertyName(node); return (propName === 'defaultProps' || propName === 'getDefaultProps'); } @@ -92,9 +91,7 @@ module.exports = { * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. */ function findVariableByName(name) { - var variable = find(variableUtil.variablesInScope(context), function(item) { - return item.name === name; - }); + const variable = variableUtil.variablesInScope(context).find((item) => item.name === name); if (!variable || !variable.defs[0] || !variable.defs[0].node) { return null; @@ -151,7 +148,7 @@ module.exports = { * @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`. */ function getPropTypesFromObjectExpression(objectExpression) { - var props = objectExpression.properties.filter(function(property) { + const props = objectExpression.properties.filter(function(property) { return property.type !== 'ExperimentalSpreadProperty'; }); @@ -170,11 +167,11 @@ module.exports = { * @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`. */ function getPropTypesFromTypeAnnotation(node) { - var properties; + let properties; switch (node.typeAnnotation.type) { case 'GenericTypeAnnotation': - var annotation = resolveGenericTypeAnnotation(node.typeAnnotation); + let annotation = resolveGenericTypeAnnotation(node.typeAnnotation); if (annotation && annotation.id) { annotation = findVariableByName(annotation.id.name); @@ -184,7 +181,7 @@ module.exports = { break; case 'UnionTypeAnnotation': - var union = resolveUnionTypeAnnotation(node.typeAnnotation); + const union = resolveUnionTypeAnnotation(node.typeAnnotation); properties = union.reduce(function(acc, curr) { if (!curr) { return acc; @@ -203,14 +200,14 @@ module.exports = { break; } - var props = properties.filter(function(property) { + const props = properties.filter(function(property) { return property.type === 'ObjectTypeProperty'; }); return props.map(function(property) { // the `key` property is not present in ObjectTypeProperty nodes, so we need to get the key name manually. - var tokens = context.getFirstTokens(property, 1); - var name = tokens[0].value; + const tokens = context.getFirstTokens(property, 1); + const name = tokens[0].value; return { name: name, @@ -228,9 +225,7 @@ module.exports = { * from this ObjectExpression can't be resolved. */ function getDefaultPropsFromObjectExpression(objectExpression) { - var hasSpread = find(objectExpression.properties, function(property) { - return property.type === 'ExperimentalSpreadProperty'; - }); + const hasSpread = objectExpression.properties.find(property => property.type === 'ExperimentalSpreadProperty'); if (hasSpread) { return 'unresolved'; @@ -264,7 +259,7 @@ module.exports = { * @returns {void} */ function addPropTypesToComponent(component, propTypes) { - var props = component.propTypes || []; + const props = component.propTypes || []; components.set(component.node, { propTypes: props.concat(propTypes) @@ -289,7 +284,7 @@ module.exports = { return; } - var defaults = component.defaultProps || []; + const defaults = component.defaultProps || []; components.set(component.node, { defaultProps: defaults.concat(defaultProps) @@ -307,7 +302,7 @@ module.exports = { } // find component this props annotation belongs to - var component = components.get(utils.getParentStatelessComponent()); + const component = components.get(utils.getParentStatelessComponent()); if (!component) { return; } @@ -317,7 +312,7 @@ module.exports = { function handlePropTypeAnnotationClassProperty(node) { // find component this props annotation belongs to - var component = components.get(utils.getParentES6Component()); + const component = components.get(utils.getParentES6Component()); if (!component) { return; } @@ -330,9 +325,7 @@ module.exports = { } function propFromName(propTypes, name) { - return find(propTypes, function (prop) { - return prop.name === name; - }); + return propTypes.find(prop => prop.name === name); } /** @@ -350,7 +343,7 @@ module.exports = { } defaultProps.forEach(function(defaultProp) { - var prop = propFromName(propTypes, defaultProp.name); + const prop = propFromName(propTypes, defaultProp.name); if (prop && (allowRequiredDefaults || !prop.isRequired)) { return; @@ -378,15 +371,15 @@ module.exports = { return { MemberExpression: function(node) { - var isPropType = isPropTypesDeclaration(node); - var isDefaultProp = isDefaultPropsDeclaration(node); + const isPropType = isPropTypesDeclaration(node); + const isDefaultProp = isDefaultPropsDeclaration(node); if (!isPropType && !isDefaultProp) { return; } // find component this propTypes/defaultProps belongs to - var component = utils.getRelatedComponent(node); + const component = utils.getRelatedComponent(node); if (!component) { return; } @@ -402,7 +395,7 @@ module.exports = { // MyComponent.propTypes = myPropTypes; if (node.parent.type === 'AssignmentExpression') { - var expression = resolveNodeValue(node.parent.right); + const expression = resolveNodeValue(node.parent.right); if (!expression || expression.type !== 'ObjectExpression') { // If a value can't be found, we mark the defaultProps declaration as "unresolved", because // we should ignore this component and not report any errors for it, to avoid false-positives @@ -466,25 +459,25 @@ module.exports = { return; } - var isPropType = isPropTypesDeclaration(node); - var isDefaultProp = isDefaultPropsDeclaration(node); + const isPropType = isPropTypesDeclaration(node); + const isDefaultProp = isDefaultPropsDeclaration(node); if (!isPropType && !isDefaultProp) { return; } // find component this propTypes/defaultProps belongs to - var component = components.get(utils.getParentES6Component()); + const component = components.get(utils.getParentES6Component()); if (!component) { return; } - var returnStatement = utils.findReturnStatement(node); + const returnStatement = utils.findReturnStatement(node); if (!returnStatement) { return; } - var expression = resolveNodeValue(returnStatement.argument); + const expression = resolveNodeValue(returnStatement.argument); if (!expression || expression.type !== 'ObjectExpression') { return; } @@ -522,21 +515,21 @@ module.exports = { return; } - var propName = getPropertyName(node); - var isPropType = propName === 'propTypes'; - var isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps'; + const propName = getPropertyName(node); + const isPropType = propName === 'propTypes'; + const isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps'; if (!isPropType && !isDefaultProp) { return; } // find component this propTypes/defaultProps belongs to - var component = components.get(utils.getParentES6Component()); + const component = components.get(utils.getParentES6Component()); if (!component) { return; } - var expression = resolveNodeValue(node.value); + const expression = resolveNodeValue(node.value); if (!expression || expression.type !== 'ObjectExpression') { return; } @@ -564,7 +557,7 @@ module.exports = { // }); ObjectExpression: function(node) { // find component this propTypes/defaultProps belongs to - var component = utils.isES5Component(node) && components.get(node); + const component = utils.isES5Component(node) && components.get(node); if (!component) { return; } @@ -575,8 +568,8 @@ module.exports = { return; } - var isPropType = isPropTypesDeclaration(property); - var isDefaultProp = isDefaultPropsDeclaration(property); + const isPropType = isPropTypesDeclaration(property); + const isDefaultProp = isDefaultPropsDeclaration(property); if (!isPropType && !isDefaultProp) { return; @@ -588,7 +581,7 @@ module.exports = { } if (isDefaultProp && property.value.type === 'FunctionExpression') { - var returnStatement = utils.findReturnStatement(property); + const returnStatement = utils.findReturnStatement(property); if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') { return; } @@ -604,9 +597,9 @@ module.exports = { FunctionExpression: handleStatelessComponent, 'Program:exit': function() { - var list = components.list(); + const list = components.list(); - for (var component in list) { + for (let component in list) { if (!has(list, component)) { continue; } From 7ec5eb34ced3440ca70fb0ffcdb10a6dda185995 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Tue, 2 May 2017 09:31:34 -0400 Subject: [PATCH 8/8] Remove object.assign require --- tests/lib/rules/default-props-match-prop-types.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/lib/rules/default-props-match-prop-types.js b/tests/lib/rules/default-props-match-prop-types.js index 4e5835709e..97aa952e43 100644 --- a/tests/lib/rules/default-props-match-prop-types.js +++ b/tests/lib/rules/default-props-match-prop-types.js @@ -11,7 +11,6 @@ var rule = require('../../../lib/rules/default-props-match-prop-types'); var RuleTester = require('eslint').RuleTester; -var assign = require('object.assign'); require('babel-eslint'); @@ -426,7 +425,7 @@ ruleTester.run('default-props-match-prop-types', rule, { ' return
{foo}{bar}
;', '}' ].join('\n'), - parserOptions: assign({sourceType: 'module'}, parserOptions) + parserOptions: Object.assign({sourceType: 'module'}, parserOptions) }, { code: [ @@ -441,7 +440,7 @@ ruleTester.run('default-props-match-prop-types', rule, { ' return
{foo}{bar}
;', '}' ].join('\n'), - parserOptions: assign({sourceType: 'module'}, parserOptions) + parserOptions: Object.assign({sourceType: 'module'}, parserOptions) }, // using spread operator {