diff --git a/CHANGELOG.md b/CHANGELOG.md index bc5c6bc55a..0d8a066c34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added * [`hook-use-state`]: add `allowDestructuredState` option ([#3449][] @ljharb) +* add [`sort-default-props`] and deprecate [`jsx-sort-default-props`] ([#1861][] @alexzherdev) [#3449]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3449 +[#1861]: https://github.com/jsx-eslint/eslint-plugin-react/pull/1861 ## [7.31.9] - 2022.10.09 @@ -4023,6 +4025,7 @@ If you're still not using React 15 you can keep the old behavior by setting the [`require-render-return`]: docs/rules/require-render-return.md [`self-closing-comp`]: docs/rules/self-closing-comp.md [`sort-comp`]: docs/rules/sort-comp.md +[`sort-default-props`]: docs/rules/sort-default-props.md [`sort-prop-types`]: docs/rules/sort-prop-types.md [`state-in-constructor`]: docs/rules/state-in-constructor.md [`static-property-placement`]: docs/rules/static-property-placement.md diff --git a/README.md b/README.md index a7c98e5739..2862f522b4 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,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) | Disallow extra closing tags for components without children | | | | | [react/sort-comp](docs/rules/sort-comp.md) | Enforce component methods order | +| | | | [react/sort-default-props](docs/rules/sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | [react/sort-prop-types](docs/rules/sort-prop-types.md) | Enforce propTypes declarations alphabetical sorting | | | | | [react/state-in-constructor](docs/rules/state-in-constructor.md) | Enforce class component state initialization style | | | | | [react/static-property-placement](docs/rules/static-property-placement.md) | Enforces where React component static properties should be positioned. | @@ -216,7 +217,7 @@ Enable the rules that you would like to use. | | | | [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md) | Enforce PascalCase for user-defined JSX components | | | 🔧 | | [react/jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | | | | [react/jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | -| | | | [react/jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | +| | | | [react/jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting. ❌ This rule is deprecated. | | | 🔧 | | [react/jsx-sort-props](docs/rules/jsx-sort-props.md) | Enforce props alphabetical sorting | | | 🔧 | | [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md) | Enforce spacing before closing bracket in JSX. ❌ This rule is deprecated. | | | 🔧 | | [react/jsx-tag-spacing](docs/rules/jsx-tag-spacing.md) | Enforce whitespace in and around the JSX opening and closing brackets | diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index 6188e8d10c..db6331abde 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -1,6 +1,6 @@ # Enforce defaultProps declarations alphabetical sorting (react/jsx-sort-default-props) -💼 This rule is enabled in the following [configs](https://github.com/jsx-eslint/eslint-plugin-react#shareable-configurations): `all`. +❌ This rule is deprecated. Please use the [`sort-default-props`](./sort-default-props.md) rule instead. 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. diff --git a/docs/rules/sort-default-props.md b/docs/rules/sort-default-props.md new file mode 100644 index 0000000000..fd4bb07fe5 --- /dev/null +++ b/docs/rules/sort-default-props.md @@ -0,0 +1,187 @@ +# Enforce defaultProps declarations alphabetical sorting (react/sort-default-props) + +💼 This rule is enabled in the following [configs](https://github.com/jsx-eslint/eslint-plugin-react#shareable-configurations): `all`. + +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 be3c94a992..12c60874b3 100644 --- a/index.js +++ b/index.js @@ -98,6 +98,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'), 'state-in-constructor': require('./lib/rules/state-in-constructor'), 'static-property-placement': require('./lib/rules/static-property-placement'), diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index f7062b81c5..a951976336 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -1,6 +1,7 @@ /** * @fileoverview Enforce default props alphabetical sorting * @author Vladimir Kattsov + * @deprecated */ 'use strict'; @@ -8,6 +9,9 @@ const variableUtil = require('../util/variable'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const log = require('../util/log'); + +let isWarnedForDeprecation = false; // ------------------------------------------------------------------------------ // Rule Definition @@ -19,6 +23,7 @@ const messages = { module.exports = { meta: { + deprecated: true, docs: { description: 'Enforce defaultProps declarations alphabetical sorting', category: 'Stylistic Issues', @@ -168,6 +173,15 @@ module.exports = { checkNode(node.parent.right); }, + + Program() { + 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..aae419ba18 --- /dev/null +++ b/lib/rules/sort-default-props.js @@ -0,0 +1,174 @@ +/** + * @fileoverview Enforce default props alphabetical sorting + * @author Vladimir Kattsov + * @deprecated + */ + +'use strict'; + +const variableUtil = require('../util/variable'); +const docsUrl = require('../util/docsUrl'); +const report = require('../util/report'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const messages = { + propsNotSorted: 'Default prop types declarations should be sorted alphabetically', +}; + +module.exports = { + meta: { + docs: { + description: 'Enforce defaultProps declarations alphabetical sorting', + category: 'Stylistic Issues', + recommended: false, + url: docsUrl('sort-default-props'), + }, + // fixable: 'code', + + messages, + + schema: [{ + type: 'object', + properties: { + ignoreCase: { + type: 'boolean', + }, + }, + additionalProperties: false, + }], + }, + + create(context) { + const configuration = context.options[0] || {}; + const ignoreCase = configuration.ignoreCase || 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; + } + 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) + } + if (node.type === 'ClassProperty') { + const tokens = context.getSourceCode().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 context.getSourceCode().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) { + // function fix(fixer) { + // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase); + // } + + declarations.reduce((prev, curr, idx, decls) => { + if (/Spread(?:Property|Element)$/.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) { + report(context, messages.propsNotSorted, 'propsNotSorted', { + node: curr, + // fix + }); + + return prev; + } + + return curr; + }, declarations[0]); + } + + function checkNode(node) { + if (!node) { + return; + } + if (node.type === 'ObjectExpression') { + checkSorted(node.properties); + } else if (node.type === 'Identifier') { + const propTypesObject = findVariableByName(node.name); + if (propTypesObject && propTypesObject.properties) { + checkSorted(propTypesObject.properties); + } + } + } + + // -------------------------------------------------------------------------- + // Public API + // -------------------------------------------------------------------------- + + return { + 'ClassProperty, PropertyDefinition'(node) { + if (!isDefaultPropsDeclaration(node)) { + return; + } + + checkNode(node.value); + }, + + MemberExpression(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..8e3a35974d --- /dev/null +++ b/tests/lib/rules/sort-default-props.js @@ -0,0 +1,938 @@ +/** + * @fileoverview Tests for sort-default-props + * @author Vladimir Kattsov + */ + +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const babelEslintVersion = require('babel-eslint/package.json').version; +const semver = require('semver'); +const RuleTester = require('eslint').RuleTester; + +const rule = require('../../../lib/rules/sort-default-props'); + +const parsers = require('../../helpers/parsers'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, +}; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester({ parserOptions }); +ruleTester.run('sort-default-props', rule, { + valid: parsers.all([].concat( + { + code: ` + var First = createReactClass({ + render: function() { + return
; + } + }); + `, + }, + { + 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
; + } + }); + `, + }, + { + 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
; + } + }); + `, + 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
; + } + }); + `, + }, + { + 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"; + `, + }, + { + 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" + }; + `, + 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
; + } + } + `, + features: ['class fields'], + }, + { + code: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "aria-controls": PropTypes.string + }; + Hello.defaultProps = { + "aria-controls": "aria-controls" + }; + `, + options: [{ ignoreCase: true }], + }, + semver.satisfies(babelEslintVersion, '< 9') ? { + // 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
; + } + } + `, + parser: parsers.BABEL_ESLINT, + } : [], + { + code: ` + var Hello = createReactClass({ + render: function() { + let { a, ...b } = obj; + let c = { ...d }; + return
; + } + }); + `, + }, + { + 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
; + } + }); + `, + }, + { + 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" + } + } + `, + features: ['class fields'], + }, + { + 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 + } + } + `, + features: ['class fields'], + }, + { + 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" + }; + `, + }, + { + code: ` + const propTypes = require('./externalPropTypes') + const defaultProps = require('./externalDefaultProps') + const TextFieldLabel = (props) => { + return
; + }; + TextFieldLabel.propTypes = propTypes; + TextFieldLabel.defaultProps = defaultProps; + `, + }, + { + 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; + `, + }, + { + code: ` + const defaults = { + b: "b" + }; + const First = (props) =>
; + export const propTypes = { + a: PropTypes.string, + b: PropTypes.string, + z: PropTypes.string, + }; + export const defaultProps = { + ...defaults, + a: "a", + z: "z", + }; + First.propTypes = propTypes; + First.defaultProps = defaultProps; + `, + }, + { + code: ` + class First extends React.Component { + render() { + return
; + } + } + + First.defaultProps = { + a: PropTypes.any, + onBar: PropTypes.func, + onFoo: PropTypes.func, + z: PropTypes.string, + }; + `, + } + )), + + invalid: parsers.all([ + { + 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
; + } + } + `, + features: ['class fields'], + errors: [ + { + messageId: 'propsNotSorted', + line: 11, + column: 13, + type: 'Property', + }, + ], /* , + output: ` + 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
; + } + } + ` */ + }, + { + 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
; + } + } + `, + /* output: ` + 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
; + } + } + `, */ + features: ['class fields'], + errors: 2, + }, + { + code: ` + class Component extends React.Component { + static propTypes = { + a: PropTypes.any, + b: PropTypes.any + }; + static defaultProps = { + Z: "Z", + a: "a", + }; + render() { + return
; + } + } + `, + /* output: ` + class Component extends React.Component { + static propTypes = { + a: PropTypes.any, + b: PropTypes.any + }; + static defaultProps = { + a: "a", + Z: "Z", + }; + render() { + return
; + } + } + `, */ + features: ['class fields'], + options: [{ ignoreCase: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 9, + column: 13, + type: 'Property', + }, + ], + }, + { + code: ` + class Component extends React.Component { + static propTypes = { + a: PropTypes.any, + z: PropTypes.any + }; + static defaultProps = { + a: "a", + Z: "Z", + }; + render() { + return
; + } + } + `, + /* output: ` + class Component extends React.Component { + static propTypes = { + a: PropTypes.any, + z: PropTypes.any + }; + static defaultProps = { + Z: "Z", + a: "a", + }; + render() { + return
; + } + } + `, */ + features: ['class fields'], + errors: [ + { + messageId: 'propsNotSorted', + line: 9, + column: 13, + 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" + }; + `, + /* output: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "a": PropTypes.string, + "b": PropTypes.string + }; + Hello.defaultProps = { + "a": "a", + "b": "b" + }; + `, */ + errors: [ + { + messageId: 'propsNotSorted', + line: 13, + column: 11, + 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" + }; + `, + /* output: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "a": PropTypes.string, + "b": PropTypes.string, + "c": PropTypes.string + }; + Hello.defaultProps = { + "a": "a", + "b": "b", + "c": "c" + }; + `, */ + 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", + }; + `, + /* output: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "a": PropTypes.string, + "B": PropTypes.string, + }; + Hello.defaultProps = { + "B": "B", + "a": "a", + }; + `, */ + errors: [ + { + messageId: 'propsNotSorted', + line: 13, + column: 11, + type: 'Property', + }, + ], + }, + // { + // Disabled test for comments -- fails + // code: ` + // class Hello extends React.Component { + // render() { + // return
Hello
; + // } + // } + // Hello.propTypes = { + // "a": PropTypes.string, + // "B": PropTypes.string, + // }; + // Hello.defaultProps = { + // /* a */ + // "a": "a", + // /* B */ + // "B": "B", + // }; + // `, + // errors: [ + // { + // messageId: 'propsNotSorted', + // line: 14, + // column: 3, + // type: 'Property' + // } + // ], + // output: ` + // class Hello extends React.Component { + // render() { + // return
Hello
; + // } + // } + // Hello.propTypes = { + // "a": PropTypes.string, + // "B": PropTypes.string, + // }; + // Hello.defaultProps = { + // /* B */ + // "B": "B", + // /* a */ + // "a": "a", + // }; + // ` + // }, + { + code: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "a": PropTypes.string, + "B": PropTypes.string, + }; + Hello.defaultProps = { + "B": "B", + "a": "a", + }; + `, + /* output: ` + class Hello extends React.Component { + render() { + return
Hello
; + } + } + Hello.propTypes = { + "a": PropTypes.string, + "B": PropTypes.string, + }; + Hello.defaultProps = { + "a": "a", + "B": "B", + }; + `, */ + options: [{ ignoreCase: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 13, + column: 11, + 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; + `, + /* output: ` + const First = (props) =>
; + const propTypes = { + z: PropTypes.string, + a: PropTypes.any, + }; + const defaultProps = { + a: "a", + z: "z", + }; + First.propTypes = propTypes; + First.defaultProps = defaultProps; + `, */ + errors: [ + { + messageId: 'propsNotSorted', + line: 9, + column: 11, + 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 + } + } + `, + /* output: ` + export default class ClassWithSpreadInPropTypes extends BaseClass { + static propTypes = { + b: PropTypes.string, + ...c.propTypes, + a: PropTypes.string + } + static defaultProps = { + a: "a", + b: "b", + ...c.defaultProps + } + } + `, */ + features: ['class fields'], + errors: [ + { + messageId: 'propsNotSorted', + line: 10, + column: 13, + 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 + } + } + `, + /* output: ` + 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 + } + } + `, */ + features: ['class fields'], + 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, + }; + `, + /* output: ` + 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, + }; + `, */ + errors: [ + { + messageId: 'propsNotSorted', + line: 16, + column: 11, + type: 'Property', + }, + ], + }, + { + code: ` + class First extends React.Component { + render() { + return
; + } + } + + First.defaultProps = { + a: PropTypes.any, + z: PropTypes.string, + onFoo: PropTypes.func, + onBar: PropTypes.func, + }; + `, + errors: [ + { messageId: 'propsNotSorted', line: 11 }, + { messageId: 'propsNotSorted', line: 12 }, + ], + }, + ]), +});