Skip to content

Commit

Permalink
Merge e24b53f into 8237551
Browse files Browse the repository at this point in the history
  • Loading branch information
b0gok committed Jan 15, 2018
2 parents 8237551 + e24b53f commit 5f0cf3b
Show file tree
Hide file tree
Showing 5 changed files with 966 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -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)
Expand Down
185 changes: 185 additions & 0 deletions 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 <div />;
}
}

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 <div>{a}{b}{c}</div>;
}
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 <div />;
}
}
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 <div>{a}{b}{c}</div>;
}
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": [<enabled>, {
"ignoreCase": <boolean>,
}]
...
```

### `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.
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -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'),
Expand Down
166 changes: 166 additions & 0 deletions 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);
}
};
}
};

0 comments on commit 5f0cf3b

Please sign in to comment.