From 6fdefcfcc95b2b511e910b9d9cfe313b2dcec8f0 Mon Sep 17 00:00:00 2001 From: Vladimir Kattsov Date: Mon, 16 Oct 2017 22:39:00 +0300 Subject: [PATCH 1/6] Add jsx-sort-default-props rule --- README.md | 1 + docs/rules/jsx-sort-default-props.md | 97 +++++ index.js | 1 + lib/rules/jsx-sort-default-props.js | 166 ++++++++ tests/lib/rules/jsx-sort-default-props.js | 478 ++++++++++++++++++++++ 5 files changed, 743 insertions(+) create mode 100644 docs/rules/jsx-sort-default-props.md create mode 100644 lib/rules/jsx-sort-default-props.js create mode 100644 tests/lib/rules/jsx-sort-default-props.js 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..a55a38b786 --- /dev/null +++ b/docs/rules/jsx-sort-default-props.md @@ -0,0 +1,97 @@ +# Enforce propTypes declarations alphabetical sorting (react/jsx-sort-default-props) + +Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes 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
; + } +} +``` + +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
; + } +} +``` + +## 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..935b0e0694 --- /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..5e81aebca6 --- /dev/null +++ b/tests/lib/rules/jsx-sort-default-props.js @@ -0,0 +1,478 @@ +/** + * @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: [ + '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' + }] + }] +}); From 5770c72094f30c4a2dd7a09abbe85f4f3a309425 Mon Sep 17 00:00:00 2001 From: Fernando Maia Date: Tue, 21 Nov 2017 11:29:42 -0200 Subject: [PATCH 2/6] Surround sort-default-props keywords with backticks and fix typo --- docs/rules/jsx-sort-default-props.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index a55a38b786..b211eaf019 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -1,10 +1,10 @@ -# Enforce propTypes declarations alphabetical sorting (react/jsx-sort-default-props) +# Enforce defaultProps declarations alphabetical sorting (react/jsx-sort-default-props) -Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain. +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. +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: @@ -94,4 +94,4 @@ 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. +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. From 4f004557ab980ae54b03e9dcdc3742835c48ecae Mon Sep 17 00:00:00 2001 From: Fernando Maia Date: Tue, 21 Nov 2017 12:01:32 -0200 Subject: [PATCH 3/6] Add SFC examples in the documentation and tests --- docs/rules/jsx-sort-default-props.md | 14 ++++++ lib/rules/jsx-sort-default-props.js | 54 +++++++++++------------ tests/lib/rules/jsx-sort-default-props.js | 20 +++++++++ 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index b211eaf019..f7a3c498b7 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -40,6 +40,13 @@ class Component extends React.Component { return
; } } + +const Component = (props) => (...); +Component.defaultProps = { + z: "z", + y: "y", + a: "a" +}; ``` The following patterns are considered okay and do not cause warnings: @@ -76,6 +83,13 @@ class Component extends React.Component { return
; } } + +const Component = (props) => (...); +Component.defaultProps = { + a: "a", + y: "y", + z: "z" +}; ``` ## Rule Options diff --git a/lib/rules/jsx-sort-default-props.js b/lib/rules/jsx-sort-default-props.js index 935b0e0694..967e0e419f 100644 --- a/lib/rules/jsx-sort-default-props.js +++ b/lib/rules/jsx-sort-default-props.js @@ -4,7 +4,7 @@ */ 'use strict'; -// const variableUtil = require('../util/variable'); +const variableUtil = require('../util/variable'); // ------------------------------------------------------------------------------ // Rule Definition @@ -33,7 +33,7 @@ module.exports = { const sourceCode = context.getSourceCode(); const configuration = context.options[0] || {}; const ignoreCase = configuration.ignoreCase || false; - // const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []); + const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []); /** * Get properties name @@ -73,19 +73,19 @@ module.exports = { * @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; - // } + 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 @@ -124,18 +124,18 @@ module.exports = { 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; + 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; } diff --git a/tests/lib/rules/jsx-sort-default-props.js b/tests/lib/rules/jsx-sort-default-props.js index 5e81aebca6..93098bc6f4 100644 --- a/tests/lib/rules/jsx-sort-default-props.js +++ b/tests/lib/rules/jsx-sort-default-props.js @@ -474,5 +474,25 @@ ruleTester.run('jsx-sort-default-props', rule, { 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' + }] }] }); From ba394d292c21f9edd91c896a7cc3716164c55eb0 Mon Sep 17 00:00:00 2001 From: Vladimir Kattsov Date: Thu, 11 Jan 2018 23:30:50 +0300 Subject: [PATCH 4/6] Fix wording in docs --- docs/rules/jsx-sort-default-props.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index f7a3c498b7..6c0e1725cc 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -49,7 +49,7 @@ Component.defaultProps = { }; ``` -The following patterns are considered okay and do not cause warnings: +The following patterns are considered okay and do **not** cause warnings: ```jsx var Component = createReactClass({ From 558576c7920c62df030c7d0819f544b4cabf9cdd Mon Sep 17 00:00:00 2001 From: Vladimir Kattsov Date: Mon, 15 Jan 2018 22:17:55 +0300 Subject: [PATCH 5/6] Add more tests with spread, add examples --- docs/rules/jsx-sort-default-props.md | 36 ++++++++++++ tests/lib/rules/jsx-sort-default-props.js | 70 +++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index 6c0e1725cc..f5a242eac2 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -47,6 +47,24 @@ Component.defaultProps = { 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, +}; ``` The following patterns are considered okay and do **not** cause warnings: @@ -90,6 +108,24 @@ Component.defaultProps = { 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, +}; ``` ## Rule Options diff --git a/tests/lib/rules/jsx-sort-default-props.js b/tests/lib/rules/jsx-sort-default-props.js index 93098bc6f4..18d544c023 100644 --- a/tests/lib/rules/jsx-sort-default-props.js +++ b/tests/lib/rules/jsx-sort-default-props.js @@ -262,6 +262,27 @@ ruleTester.run('jsx-sort-default-props', rule, { '}' ].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\')', @@ -494,5 +515,54 @@ ruleTester.run('jsx-sort-default-props', rule, { 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: [ + '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' + }] }] }); From e24b53f9f217218a4546596f485ba1a40fef637f Mon Sep 17 00:00:00 2001 From: Vladimir Kattsov Date: Mon, 15 Jan 2018 22:43:13 +0300 Subject: [PATCH 6/6] Add tests and examples with multiple spreads --- docs/rules/jsx-sort-default-props.md | 38 +++++++++++++++++++ tests/lib/rules/jsx-sort-default-props.js | 45 +++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/docs/rules/jsx-sort-default-props.md b/docs/rules/jsx-sort-default-props.md index f5a242eac2..d4b48f8181 100644 --- a/docs/rules/jsx-sort-default-props.md +++ b/docs/rules/jsx-sort-default-props.md @@ -65,6 +65,25 @@ StatelessComponentWithSpreadInPropTypes.defaultProps = { 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: @@ -126,6 +145,25 @@ StatelessComponentWithSpreadInPropTypes.defaultProps = { 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 diff --git a/tests/lib/rules/jsx-sort-default-props.js b/tests/lib/rules/jsx-sort-default-props.js index 18d544c023..c4090487ed 100644 --- a/tests/lib/rules/jsx-sort-default-props.js +++ b/tests/lib/rules/jsx-sort-default-props.js @@ -262,6 +262,28 @@ ruleTester.run('jsx-sort-default-props', rule, { '}' ].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 = {', @@ -537,6 +559,29 @@ ruleTester.run('jsx-sort-default-props', rule, { 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 = {',