Skip to content

Commit

Permalink
Merge pull request #949 from snowypowers/max-props-per-line-fix
Browse files Browse the repository at this point in the history
jsx-max-props-per-line: autofix
  • Loading branch information
ljharb committed May 19, 2017
2 parents 45e3b9f + 0fb170b commit f0dcaca
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -128,7 +128,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation (fixable)
* [react/jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX (fixable)
* [react/jsx-key](docs/rules/jsx-key.md): Validate JSX has key prop when in array or iterator
* [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
* [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX (fixable)
* [react/jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
* [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes
* [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/jsx-max-props-per-line.md
Expand Up @@ -2,6 +2,8 @@

Limiting the maximum of props on a single line can improve readability.

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. However, fix does not include indentation. Please rerun lint to correct those errors.

## Rule Details

This rule checks all JSX elements and verifies that the number of props per line do not exceed the maximum allowed. Props are considered to be in a new line if there is a line break between the start of the prop and the end of the previous prop. A spread attribute counts as one prop. This rule is off by default and when on the default maximum of props on one line is `1`.
Expand Down
28 changes: 24 additions & 4 deletions lib/rules/jsx-max-props-per-line.js
Expand Up @@ -16,7 +16,7 @@ module.exports = {
category: 'Stylistic Issues',
recommended: false
},

fixable: 'code',
schema: [{
type: 'object',
properties: {
Expand Down Expand Up @@ -46,6 +46,25 @@ module.exports = {
return propNode.name.name;
}

function generateFixFunction(line, max) {
var output = [];
var front = line[0].start;
var back = line[line.length - 1].end;
for (var i = 0; i < line.length; i += max) {
var nodes = line.slice(i, i + max);
output.push(nodes.reduce(function(prev, curr) {
if (prev === '') {
return sourceCode.getText(curr);
}
return `${prev} ${sourceCode.getText(curr)}`;
}, ''));
}
var code = output.join('\n');
return function(fixer) {
return fixer.replaceTextRange([front, back], code);
};
}

return {
JSXOpeningElement: function (node) {
if (!node.attributes.length) {
Expand All @@ -59,7 +78,7 @@ module.exports = {
var firstProp = node.attributes[0];
var linePartitionedProps = [[firstProp]];

node.attributes.reduce(function(last, decl) {
node.attributes.reduce(function (last, decl) {
if (last.loc.end.line === decl.loc.start.line) {
linePartitionedProps[linePartitionedProps.length - 1].push(decl);
} else {
Expand All @@ -68,12 +87,13 @@ module.exports = {
return decl;
});

linePartitionedProps.forEach(function(propsInLine) {
linePartitionedProps.forEach(function (propsInLine) {
if (propsInLine.length > maximum) {
var name = getPropName(propsInLine[maximum]);
context.report({
node: propsInLine[maximum],
message: `Prop \`${name}\` must be placed on a new line`
message: `Prop \`${name}\` must be placed on a new line`,
fix: generateFixFunction(propsInLine, maximum)
});
}
});
Expand Down
106 changes: 95 additions & 11 deletions tests/lib/rules/jsx-max-props-per-line.js
Expand Up @@ -64,67 +64,135 @@ ruleTester.run('jsx-max-props-per-line', rule, {

invalid: [{
code: '<App foo bar baz />;',
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App foo',
'bar',
'baz />;'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: '<App foo bar baz />;',
output: [
'<App foo bar',
'baz />;'
].join('\n'),
options: [{maximum: 2}],
errors: [{message: 'Prop `baz` must be placed on a new line'}]
}, {
code: '<App {...this.props} bar />;',
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App {...this.props}',
'bar />;'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: '<App bar {...this.props} />;',
errors: [{message: 'Prop `this.props` must be placed on a new line'}]
output: [
'<App bar',
'{...this.props} />;'
].join('\n'),
errors: [{message: 'Prop `this.props` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App',
' foo bar',
' baz',
'/>'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App',
' foo',
'bar',
' baz',
'/>'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App',
' foo {...this.props}',
' baz',
'/>'
].join('\n'),
errors: [{message: 'Prop `this.props` must be placed on a new line'}]
output: [
'<App',
' foo',
'{...this.props}',
' baz',
'/>'
].join('\n'),
errors: [{message: 'Prop `this.props` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App',
' foo={{',
' }} bar',
'/>'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App',
' foo={{',
' }}',
'bar',
'/>'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App foo={{',
'}} bar />'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App foo={{',
'}}',
'bar />'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App foo bar={{',
'}} baz />'
].join('\n'),
output: [
'<App foo bar={{',
'}}',
'baz />'
].join('\n'),
options: [{maximum: 2}],
errors: [{message: 'Prop `baz` must be placed on a new line'}]
}, {
code: [
'<App foo={{',
'}} {...rest} />'
].join('\n'),
errors: [{message: 'Prop `rest` must be placed on a new line'}]
output: [
'<App foo={{',
'}}',
'{...rest} />'
].join('\n'),
errors: [{message: 'Prop `rest` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App {',
' ...this.props',
'} bar />'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}]
output: [
'<App {',
' ...this.props',
'}',
'bar />'
].join('\n'),
errors: [{message: 'Prop `bar` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App {',
Expand All @@ -133,12 +201,28 @@ ruleTester.run('jsx-max-props-per-line', rule, {
' ...rest',
'} />'
].join('\n'),
errors: [{message: 'Prop `rest` must be placed on a new line'}]
output: [
'<App {',
' ...this.props',
'}',
'{',
' ...rest',
'} />'
].join('\n'),
errors: [{message: 'Prop `rest` must be placed on a new line'}],
parserOptions: parserOptions
}, {
code: [
'<App',
' foo={{',
' }} bar baz',
' }} bar baz bor',
'/>'
].join('\n'),
output: [
'<App',
' foo={{',
' }} bar',
'baz bor',
'/>'
].join('\n'),
options: [{maximum: 2}],
Expand Down

0 comments on commit f0dcaca

Please sign in to comment.