diff --git a/README.md b/README.md
index 883b4106da..0b8101a0a9 100644
--- a/README.md
+++ b/README.md
@@ -156,6 +156,7 @@ Enable the rules that you would like to use.
* [react/jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md): Limit to one expression per line in JSX
* [react/jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md): Enforce curly braces or disallow unnecessary curly braces in JSX
* [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
+* [react/jsx-sort-default-props](docs/rules/jsx-sort-default-props.md): Enforce default props alphabetical sorting
* [react/jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting (fixable)
* [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
* [react/jsx-tag-spacing](docs/rules/jsx-tag-spacing.md): Validate whitespace in and around the JSX opening and closing brackets (fixable)
diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md
new file mode 100644
index 0000000000..d4b48f8181
--- /dev/null
+++ b/docs/rules/jsx-sort-default-props.md
@@ -0,0 +1,185 @@
+# Enforce defaultProps declarations alphabetical sorting (react/jsx-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/jsx-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 1b3a1238da..7b539e2664 100644
--- a/index.js
+++ b/index.js
@@ -35,6 +35,7 @@ const allRules = {
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'),
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
+ 'jsx-sort-default-props': require('./lib/rules/jsx-sort-default-props'),
'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing'),
diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js
new file mode 100644
index 0000000000..967e0e419f
--- /dev/null
+++ b/lib/rules/jsx-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/jsx-sort-default-props.js b/tests/lib/rules/jsx-sort-default-props.js
new file mode 100644
index 0000000000..c4090487ed
--- /dev/null
+++ b/tests/lib/rules/jsx-sort-default-props.js
@@ -0,0 +1,613 @@
+/**
+ * @fileoverview Tests for jsx-sort-default-props
+ * @author Vladimir Kattsov
+ */
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/jsx-sort-default-props');
+const RuleTester = require('eslint').RuleTester;
+
+const parserOptions = {
+ ecmaVersion: 8,
+ sourceType: 'module',
+ ecmaFeatures: {
+ experimentalObjectRestSpread: true,
+ jsx: true
+ }
+};
+
+require('babel-eslint');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+const ERROR_MESSAGE = 'Default prop types declarations should be sorted alphabetically';
+
+const ruleTester = new RuleTester({parserOptions});
+ruleTester.run('jsx-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'
+ }]
+ }]
+});