From 4dc8c4d5b67acf8b21e4b4d3d269904c6f5e0bb7 Mon Sep 17 00:00:00 2001 From: Alex Zherdev Date: Thu, 28 Jun 2018 23:18:24 -0700 Subject: [PATCH] Add sort-default-props and deprecate jsx-sort-default-props Pt. 1 of #1834 --- README.md | 1 + docs/rules/jsx-sort-default-props.md | 2 + docs/rules/sort-default-props.md | 185 ++++++++ index.js | 1 + lib/rules/jsx-sort-default-props.js | 15 + lib/rules/sort-default-props.js | 166 +++++++ tests/lib/rules/sort-default-props.js | 612 ++++++++++++++++++++++++++ 7 files changed, 982 insertions(+) create mode 100644 docs/rules/sort-default-props.md create mode 100644 lib/rules/sort-default-props.js create mode 100644 tests/lib/rules/sort-default-props.js diff --git a/README.md b/README.md index 5b787b1afc..3a5fba4726 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ Enable the rules that you would like to use. * [react/require-render-return](docs/rules/require-render-return.md): Enforce ES5 or ES6 class for returning value in render function * [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable) * [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order (fixable) +* [react/sort-default-props](docs/rules/sort-default-props.md): Enforce default props alphabetical sorting * [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting * [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object * [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md): Prevent void DOM elements (e.g. ``, `
`) from receiving children diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index d4b48f8181..710220286c 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -1,5 +1,7 @@ # Enforce defaultProps declarations alphabetical sorting (react/jsx-sort-default-props) +**Deprecation notice**: This rule is deprecated. It has been renamed to [sort-default-props](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-default-props.md). + Some developers prefer to sort `defaultProps` declarations alphabetically to be able to find necessary declarations easier at a later time. Others feel that it adds complexity and becomes a burden to maintain. ## Rule Details diff --git a/docs/rules/sort-default-props.md b/docs/rules/sort-default-props.md new file mode 100644 index 0000000000..0d4b3d1faf --- /dev/null +++ b/docs/rules/sort-default-props.md @@ -0,0 +1,185 @@ +# Enforce defaultProps declarations alphabetical sorting (react/sort-default-props) + +Some developers prefer to sort `defaultProps` declarations alphabetically to be able to find necessary declarations easier at a later time. Others feel that it adds complexity and becomes a burden to maintain. + +## Rule Details + +This rule checks all components and verifies that all `defaultProps` declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive. + +The following patterns are considered warnings: + +```jsx +var Component = createReactClass({ +... + getDefaultProps: function() { + return { + z: "z", + a: "a", + b: "b" + }; + }, +... +}); + +class Component extends React.Component { + ... +} +Component.defaultProps = { + z: "z", + a: "a", + b: "b" +}; + +class Component extends React.Component { + static defaultProps = { + z: "z", + y: "y", + a: "a" + } + render() { + return
; + } +} + +const Component = (props) => (...); +Component.defaultProps = { + z: "z", + y: "y", + a: "a" +}; + +const defaults = { + b: "b" +}; +const types = { + a: PropTypes.string, + b: PropTypes.string, + c: PropTypes.string' +}; +function StatelessComponentWithSpreadInPropTypes({ a, b, c }) { + return
{a}{b}{c}
; +} +StatelessComponentWithSpreadInPropTypes.propTypes = types; +StatelessComponentWithSpreadInPropTypes.defaultProps = { + c: "c", + a: "a", + ...defaults, +}; + +export default class ClassWithSpreadInPropTypes extends BaseClass { + static propTypes = { + a: PropTypes.string, + b: PropTypes.string, + c: PropTypes.string, + d: PropTypes.string, + e: PropTypes.string, + f: PropTypes.string + } + static defaultProps = { + b: "b", + a: "a", + ...c.defaultProps, + f: "f", + e: "e", + ...d.defaultProps + } +} +``` + +The following patterns are considered okay and do **not** cause warnings: + +```jsx +var Component = createReactClass({ +... + getDefaultProps: function() { + return { + a: "a", + b: "b", + c: "c" + }; + }, +... +}); + +class Component extends React.Component { + ... +} +Component.defaultProps = { + a: "a", + b: "b", + c: "c" +}; + +class Component extends React.Component { + static defaultProps = { + a: PropTypes.any, + b: PropTypes.any, + c: PropTypes.any + } + render() { + return
; + } +} + +const Component = (props) => (...); +Component.defaultProps = { + a: "a", + y: "y", + z: "z" +}; + +const defaults = { + b: "b" +}; +const types = { + a: PropTypes.string, + b: PropTypes.string, + c: PropTypes.string' +}; +function StatelessComponentWithSpreadInPropTypes({ a, b, c }) { + return
{a}{b}{c}
; +} +StatelessComponentWithSpreadInPropTypes.propTypes = types; +StatelessComponentWithSpreadInPropTypes.defaultProps = { + a: "a", + c: "c", + ...defaults, +}; + +export default class ClassWithSpreadInPropTypes extends BaseClass { + static propTypes = { + a: PropTypes.string, + b: PropTypes.string, + c: PropTypes.string, + d: PropTypes.string, + e: PropTypes.string, + f: PropTypes.string + } + static defaultProps = { + a: "a", + b: "b", + ...c.defaultProps, + e: "e", + f: "f", + ...d.defaultProps + } +} +``` + +## Rule Options + +```js +... +"react/sort-default-props": [, { + "ignoreCase": , +}] +... +``` + +### `ignoreCase` + +When `true` the rule ignores the case-sensitivity of the declarations order. + +## When not to use + +This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing `defaultProps` declarations isn't a part of your coding standards, then you can leave this rule off. diff --git a/index.js b/index.js index 45a96d7da3..5bc0a1aa07 100644 --- a/index.js +++ b/index.js @@ -77,6 +77,7 @@ const allRules = { 'require-render-return': require('./lib/rules/require-render-return'), 'self-closing-comp': require('./lib/rules/self-closing-comp'), 'sort-comp': require('./lib/rules/sort-comp'), + 'sort-default-props': require('./lib/rules/sort-default-props'), 'sort-prop-types': require('./lib/rules/sort-prop-types'), 'style-prop-object': require('./lib/rules/style-prop-object'), 'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children') diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index 967e0e419f..8f49802f76 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -1,10 +1,14 @@ /** * @fileoverview Enforce default props alphabetical sorting * @author Vladimir Kattsov + * @deprecated */ 'use strict'; const variableUtil = require('../util/variable'); +const log = require('../util/log'); + +let isWarnedForDeprecation = false; // ------------------------------------------------------------------------------ // Rule Definition @@ -12,6 +16,7 @@ const variableUtil = require('../util/variable'); module.exports = { meta: { + deprecated: true, docs: { description: 'Enforce default props alphabetical sorting', category: 'Stylistic Issues', @@ -160,6 +165,16 @@ module.exports = { } checkNode(node.parent.right); + }, + + Program: function() { + if (isWarnedForDeprecation) { + return; + } + + log('The react/jsx-sort-default-props rule is deprecated. ' + + 'It has been renamed to react/sort-default-props.'); + isWarnedForDeprecation = true; } }; } diff --git a/lib/rules/sort-default-props.js b/lib/rules/sort-default-props.js new file mode 100644 index 0000000000..967e0e419f --- /dev/null +++ b/lib/rules/sort-default-props.js @@ -0,0 +1,166 @@ +/** + * @fileoverview Enforce default props alphabetical sorting + * @author Vladimir Kattsov + */ +'use strict'; + +const variableUtil = require('../util/variable'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforce default props alphabetical sorting', + category: 'Stylistic Issues', + recommended: false + }, + + schema: [{ + type: 'object', + properties: { + ignoreCase: { + type: 'boolean' + } + }, + additionalProperties: false + }] + }, + + create: function(context) { + const sourceCode = context.getSourceCode(); + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || false; + const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []); + + /** + * 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') { + const 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 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) { + const propName = getPropertyName(node); + return (propName === 'defaultProps' || propName === 'getDefaultProps'); + } + + function getKey(node) { + return sourceCode.getText(node.key || node.argument); + } + + /** + * 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) { + const variable = variableUtil.variablesInScope(context).find(item => 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; + } + + /** + * Checks if defaultProps declarations are sorted + * @param {Array} declarations The array of AST nodes being checked. + * @returns {void} + */ + function checkSorted(declarations) { + declarations.reduce((prev, curr, idx, decls) => { + if (/SpreadProperty$/.test(curr.type)) { + return decls[idx + 1]; + } + + let prevPropName = getKey(prev); + let currentPropName = getKey(curr); + + if (ignoreCase) { + prevPropName = prevPropName.toLowerCase(); + currentPropName = currentPropName.toLowerCase(); + } + + if (currentPropName < prevPropName) { + context.report({ + node: curr, + message: 'Default prop types declarations should be sorted alphabetically' + }); + + return prev; + } + + return curr; + }, declarations[0]); + } + + function checkNode(node) { + switch (node && node.type) { + case 'ObjectExpression': + checkSorted(node.properties); + break; + case 'Identifier': + const propTypesObject = findVariableByName(node.name); + if (propTypesObject && propTypesObject.properties) { + checkSorted(propTypesObject.properties); + } + break; + case 'CallExpression': + const innerNode = node.arguments && node.arguments[0]; + if (propWrapperFunctions.has(node.callee.name) && innerNode) { + checkNode(innerNode); + } + break; + default: + break; + } + } + + // -------------------------------------------------------------------------- + // Public API + // -------------------------------------------------------------------------- + + return { + ClassProperty: function(node) { + if (!isDefaultPropsDeclaration(node)) { + return; + } + + checkNode(node.value); + }, + + MemberExpression: function(node) { + if (!isDefaultPropsDeclaration(node)) { + return; + } + + checkNode(node.parent.right); + } + }; + } +}; diff --git a/tests/lib/rules/sort-default-props.js b/tests/lib/rules/sort-default-props.js new file mode 100644 index 0000000000..fbf381454c --- /dev/null +++ b/tests/lib/rules/sort-default-props.js @@ -0,0 +1,612 @@ +/** + * @fileoverview Tests for sort-default-props + * @author Vladimir Kattsov + */ +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const rule = require('../../../lib/rules/sort-default-props'); +const RuleTester = require('eslint').RuleTester; + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +require('babel-eslint'); + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ERROR_MESSAGE = 'Default prop types declarations should be sorted alphabetically'; + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('sort-default-props', rule, { + valid: [{ + code: [ + 'var First = createReactClass({', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n') + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' A: PropTypes.any,', + ' Z: PropTypes.string,', + ' a: PropTypes.any,', + ' z: PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' A: "A",', + ' Z: "Z",', + ' a: "a",', + ' z: "z"', + ' };', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n') + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.any,', + ' A: PropTypes.any,', + ' z: PropTypes.string,', + ' Z: PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' a: "a",', + ' A: "A",', + ' z: "z",', + ' Z: "Z"', + ' };', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + ignoreCase: true + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.any,', + ' z: PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' a: "a",', + ' z: "z"', + ' };', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});', + 'var Second = createReactClass({', + ' propTypes: {', + ' AA: PropTypes.any,', + ' ZZ: PropTypes.string', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' AA: "AA",', + ' ZZ: "ZZ"', + ' };', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n') + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: PropTypes.string,', + ' z: PropTypes.string', + '};', + 'First.propTypes.justforcheck = PropTypes.string;', + 'First.defaultProps = {', + ' a: a,', + ' z: z', + '};', + 'First.defaultProps.justforcheck = "justforcheck";' + ].join('\n') + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: PropTypes.any,', + ' A: PropTypes.any,', + ' z: PropTypes.string,', + ' Z: PropTypes.string', + '};', + 'First.defaultProps = {', + ' a: "a",', + ' A: "A",', + ' z: "z",', + ' Z: "Z"', + '};' + ].join('\n'), + options: [{ + ignoreCase: true + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.any,', + ' b: PropTypes.any,', + ' c: PropTypes.any', + ' };', + ' static defaultProps = {', + ' a: "a",', + ' b: "b",', + ' c: "c"', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "aria-controls": PropTypes.string', + '};', + 'Hello.defaultProps = {', + ' "aria-controls": "aria-controls"', + '};' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + ignoreCase: true + }] + }, { + // Invalid code, should not be validated + code: [ + 'class Component extends React.Component {', + ' propTypes: {', + ' a: PropTypes.any,', + ' c: PropTypes.any,', + ' b: PropTypes.any', + ' };', + ' defaultProps: {', + ' a: "a",', + ' c: "c",', + ' b: "b"', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'var Hello = createReactClass({', + ' render: function() {', + ' let { a, ...b } = obj;', + ' let c = { ...d };', + ' return
;', + ' }', + '});' + ].join('\n') + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' barRequired: PropTypes.func.isRequired,', + ' onBar: PropTypes.func,', + ' z: PropTypes.any', + ' },', + ' getDefaultProps: function() {', + ' return {', + ' barRequired: "barRequired",', + ' onBar: "onBar",', + ' z: "z"', + ' };', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n') + }, { + code: [ + 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + ' static propTypes = {', + ' b: PropTypes.string,', + ' ...c.propTypes,', + ' a: PropTypes.string', + ' }', + ' static defaultProps = {', + ' b: "b",', + ' ...c.defaultProps,', + ' a: "a"', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + ' static propTypes = {', + ' a: PropTypes.string,', + ' b: PropTypes.string,', + ' c: PropTypes.string,', + ' d: PropTypes.string,', + ' e: PropTypes.string,', + ' f: PropTypes.string', + ' }', + ' static defaultProps = {', + ' a: "a",', + ' b: "b",', + ' ...c.defaultProps,', + ' e: "e",', + ' f: "f",', + ' ...d.defaultProps', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'const defaults = {', + ' b: "b"', + '};', + 'const types = {', + ' a: PropTypes.string,', + ' b: PropTypes.string,', + ' c: PropTypes.string', + '};', + 'function StatelessComponentWithSpreadInPropTypes({ a, b, c }) {', + ' return
{a}{b}{c}
;', + '}', + 'StatelessComponentWithSpreadInPropTypes.propTypes = types;', + 'StatelessComponentWithSpreadInPropTypes.defaultProps = {', + ' c: "c",', + ' ...defaults,', + ' a: "a"', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + 'const propTypes = require(\'./externalPropTypes\')', + 'const defaultProps = require(\'./externalDefaultProps\')', + 'const TextFieldLabel = (props) => {', + ' return
;', + '};', + 'TextFieldLabel.propTypes = propTypes;', + 'TextFieldLabel.defaultProps = defaultProps;' + ].join('\n') + }, { + code: [ + 'const First = (props) =>
;', + 'export const propTypes = {', + ' a: PropTypes.any,', + ' z: PropTypes.string,', + '};', + 'export const defaultProps = {', + ' a: "a",', + ' z: "z",', + '};', + 'First.propTypes = propTypes;', + 'First.defaultProps = defaultProps;' + ].join('\n') + }], + + invalid: [{ + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.any,', + ' b: PropTypes.any,', + ' c: PropTypes.any', + ' };', + ' static defaultProps = {', + ' a: "a",', + ' c: "c",', + ' b: "b"', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 10, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.any,', + ' b: PropTypes.any,', + ' c: PropTypes.any', + ' };', + ' static defaultProps = {', + ' c: "c",', + ' b: "b",', + ' a: "a"', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2 + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.any,', + ' b: PropTypes.any', + ' };', + ' static defaultProps = {', + ' Z: "Z",', + ' a: "a",', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + ignoreCase: true + }], + errors: [{ + message: ERROR_MESSAGE, + line: 8, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.any,', + ' z: PropTypes.any', + ' };', + ' static defaultProps = {', + ' a: "a",', + ' Z: "Z",', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 8, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "a": PropTypes.string,', + ' "b": PropTypes.string', + '};', + 'Hello.defaultProps = {', + ' "b": "b",', + ' "a": "a"', + '};' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 12, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "a": PropTypes.string,', + ' "b": PropTypes.string,', + ' "c": PropTypes.string', + '};', + 'Hello.defaultProps = {', + ' "c": "c",', + ' "b": "b",', + ' "a": "a"', + '};' + ].join('\n'), + parser: 'babel-eslint', + errors: 2 + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "a": PropTypes.string,', + ' "B": PropTypes.string,', + '};', + 'Hello.defaultProps = {', + ' "a": "a",', + ' "B": "B",', + '};' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 12, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.propTypes = {', + ' "a": PropTypes.string,', + ' "B": PropTypes.string,', + '};', + 'Hello.defaultProps = {', + ' "B": "B",', + ' "a": "a",', + '};' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + ignoreCase: true + }], + errors: [{ + message: ERROR_MESSAGE, + line: 12, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'const First = (props) =>
;', + 'const propTypes = {', + ' z: PropTypes.string,', + ' a: PropTypes.any,', + '};', + 'const defaultProps = {', + ' z: "z",', + ' a: "a",', + '};', + 'First.propTypes = propTypes;', + 'First.defaultProps = defaultProps;' + ].join('\n'), + errors: [{ + message: ERROR_MESSAGE, + line: 8, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + ' static propTypes = {', + ' b: PropTypes.string,', + ' ...c.propTypes,', + ' a: PropTypes.string', + ' }', + ' static defaultProps = {', + ' b: "b",', + ' a: "a",', + ' ...c.defaultProps', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 9, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'export default class ClassWithSpreadInPropTypes extends BaseClass {', + ' static propTypes = {', + ' a: PropTypes.string,', + ' b: PropTypes.string,', + ' c: PropTypes.string,', + ' d: PropTypes.string,', + ' e: PropTypes.string,', + ' f: PropTypes.string', + ' }', + ' static defaultProps = {', + ' b: "b",', + ' a: "a",', + ' ...c.defaultProps,', + ' f: "f",', + ' e: "e",', + ' ...d.defaultProps', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2 + }, { + code: [ + 'const defaults = {', + ' b: "b"', + '};', + 'const types = {', + ' a: PropTypes.string,', + ' b: PropTypes.string,', + ' c: PropTypes.string', + '};', + 'function StatelessComponentWithSpreadInPropTypes({ a, b, c }) {', + ' return
{a}{b}{c}
;', + '}', + 'StatelessComponentWithSpreadInPropTypes.propTypes = types;', + 'StatelessComponentWithSpreadInPropTypes.defaultProps = {', + ' c: "c",', + ' a: "a",', + ' ...defaults,', + '};' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: ERROR_MESSAGE, + line: 15, + column: 3, + type: 'Property' + }] + }] +});