Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…eact into issues/2083
  • Loading branch information
himynameisdave committed Dec 20, 2018
2 parents 9253c10 + 77e3fd0 commit f7e3121
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 8 deletions.
19 changes: 18 additions & 1 deletion docs/rules/jsx-indent.md
Expand Up @@ -31,10 +31,11 @@ The following patterns are considered warnings:
## Rule Options

It takes an option as the second parameter which can be `"tab"` for tab-based indentation or a positive number for space indentations.
To enable checking the indentation of attributes, use the third parameter to turn on the `checkAttributes` option (default is false).

```js
...
"react/jsx-indent": [<enabled>, 'tab'|<number>]
"react/jsx-indent": [<enabled>, 'tab'|<number>, {checkAttributes: <boolean>}]
...
```

Expand All @@ -52,6 +53,14 @@ The following patterns are considered warnings:
<App>
<Hello />
</App>

// [2, 2, {checkAttributes: true}]
<App render={
<Hello render={
(bar) => <div>hi</div>
}
/>
</App>
```
The following patterns are **not** warnings:
Expand All @@ -75,6 +84,14 @@ The following patterns are **not** warnings:
<App>
<Hello />
</App>

// [2, 2, {checkAttributes: false}]
<App render={
<Hello render={
(bar) => <div>hi</div>
}
/>
</App>
```
## When not to use
Expand Down
22 changes: 22 additions & 0 deletions lib/rules/jsx-indent.js
Expand Up @@ -50,6 +50,14 @@ module.exports = {
}, {
type: 'integer'
}]
}, {
type: 'object',
properties: {
checkAttributes: {
type: 'boolean'
}
},
additionalProperties: false
}]
},

Expand All @@ -73,6 +81,8 @@ module.exports = {
}

const indentChar = indentType === 'space' ? ' ' : '\t';
const options = context.options[1] || {};
const checkAttributes = options.checkAttributes || false;

/**
* Responsible for fixing the indentation issue fix
Expand Down Expand Up @@ -242,11 +252,23 @@ module.exports = {
checkNodesIndent(node, peerElementIndent);
}

function handleAttribute(node) {
if (!checkAttributes || node.value.type !== 'JSXExpressionContainer') {
return;
}
const nameIndent = getNodeIndent(node.name);
const lastToken = sourceCode.getLastToken(node.value);
const firstInLine = astUtil.getFirstNodeInLine(context, lastToken);
const indent = node.name.loc.start.line === firstInLine.loc.start.line ? 0 : nameIndent;
checkNodesIndent(firstInLine, indent);
}

return {
JSXOpeningElement: handleOpeningElement,
JSXOpeningFragment: handleOpeningElement,
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement,
JSXAttribute: handleAttribute,
JSXExpressionContainer: function(node) {
if (!node.parent) {
return;
Expand Down
22 changes: 19 additions & 3 deletions lib/rules/no-unused-state.js
Expand Up @@ -59,6 +59,14 @@ function getInitialClassInfo() {
};
}

function isSetStateCall(node) {
return (
node.callee.type === 'MemberExpression' &&
isThisExpression(node.callee.object) &&
getName(node.callee.property) === 'setState'
);
}

module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -246,13 +254,21 @@ module.exports = {
// If we're looking at a `this.setState({})` invocation, record all the
// properties as state fields.
if (
node.callee.type === 'MemberExpression' &&
isThisExpression(node.callee.object) &&
getName(node.callee.property) === 'setState' &&
isSetStateCall(node) &&
node.arguments.length > 0 &&
node.arguments[0].type === 'ObjectExpression'
) {
addStateFields(node.arguments[0]);
} else if (
isSetStateCall(node) &&
node.arguments.length > 0 &&
node.arguments[0].type === 'ArrowFunctionExpression' &&
node.arguments[0].body.type === 'ObjectExpression'
) {
if (node.arguments[0].params.length > 0) {
classInfo.aliases.add(getName(node.arguments[0].params[0]));
}
addStateFields(node.arguments[0].body);
}
},

Expand Down
18 changes: 15 additions & 3 deletions lib/util/ast.js
Expand Up @@ -68,13 +68,14 @@ function getComponentProperties(node) {
}
}


/**
* Checks if the node is the first in its line, excluding whitespace.
* Gets the first node in a line from the initial node, excluding whitespace.
* @param {Object} context The node to check
* @param {ASTNode} node The node to check
* @return {Boolean} true if it's the first node in its line
* @return {ASTNode} the first node in the line
*/
function isNodeFirstInLine(context, node) {
function getFirstNodeInLine(context, node) {
const sourceCode = context.getSourceCode();
let token = node;
let lines;
Expand All @@ -87,7 +88,17 @@ function isNodeFirstInLine(context, node) {
token.type === 'JSXText' &&
/^\s*$/.test(lines[lines.length - 1])
);
return token;
}

/**
* Checks if the node is the first in its line, excluding whitespace.
* @param {Object} context The node to check
* @param {ASTNode} node The node to check
* @return {Boolean} true if it's the first node in its line
*/
function isNodeFirstInLine(context, node) {
const token = getFirstNodeInLine(context, node);
const startLine = node.loc.start.line;
const endLine = token ? token.loc.end.line : -1;
return startLine !== endLine;
Expand Down Expand Up @@ -131,6 +142,7 @@ function isClass(node) {

module.exports = {
findReturnStatement: findReturnStatement,
getFirstNodeInLine: getFirstNodeInLine,
getPropertyName: getPropertyName,
getPropertyNameNode: getPropertyNameNode,
getComponentProperties: getComponentProperties,
Expand Down
15 changes: 14 additions & 1 deletion lib/util/makeNoMethodSetStateRule.js
Expand Up @@ -10,14 +10,27 @@ const docsUrl = require('./docsUrl');
// Rule Definition
// ------------------------------------------------------------------------------

function mapTitle(methodName) {
const map = {
componentDidMount: 'did-mount',
componentDidUpdate: 'did-update',
componentWillUpdate: 'will-update'
};
const title = map[methodName];
if (!title) {
throw Error(`No docsUrl for '${methodName}'`);
}
return `no-${title}-set-state`;
}

function makeNoMethodSetStateRule(methodName, shouldCheckUnsafeCb) {
return {
meta: {
docs: {
description: `Prevent usage of setState in ${methodName}`,
category: 'Best Practices',
recommended: false,
url: docsUrl(methodName)
url: docsUrl(mapTitle(methodName))
},

schema: [{
Expand Down
162 changes: 162 additions & 0 deletions tests/lib/rules/jsx-indent.js
Expand Up @@ -716,6 +716,110 @@ ruleTester.run('jsx-indent', rule, {
`,
parser: 'babel-eslint',
options: [2]
}, {
code: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
output: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
options: [2]
}, {
code: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
)}
\t/>
);
`,
output: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
\t\t)}
\t/>
);
`,
options: ['tab']
}, {
code: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
output: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
options: [2, {checkAttributes: false}]
}, {
code: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
)}
\t/>
);
`,
output: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
\t\t)}
\t/>
);
`,
options: ['tab', {checkAttributes: false}]
}],

invalid: [{
Expand Down Expand Up @@ -1478,5 +1582,63 @@ ruleTester.run('jsx-indent', rule, {
errors: [
{message: 'Expected indentation of 4 space characters but found 2.'}
]
}, {
code: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
output: `
const Component = () => (
<View
ListFooterComponent={(
<View
rowSpan={3}
placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
/>
)}
/>
);
`,
options: [2, {checkAttributes: true}],
errors: [
{message: 'Expected indentation of 8 space characters but found 4.'}
]
}, {
code: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
)}
\t/>
);
`,
output: `
const Component = () => (
\t<View
\t\tListFooterComponent={(
\t\t\t<View
\t\t\t\trowSpan={3}
\t\t\t\tplaceholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do"
\t\t\t/>
\t\t)}
\t/>
);
`,
options: ['tab', {checkAttributes: true}],
errors: [
{message: 'Expected indentation of 2 tab characters but found 0.'}
]
}]
});

0 comments on commit f7e3121

Please sign in to comment.