Repository: yannickcr/eslint-plugin-react
Pull request #949 (2016-11-07 12:41:58)

jsx-max-props-per-line: autofix
Hi this is my attempt at implementing #929. This aims to implement the fix such that a line with more than max props can be broken into more than 2 lines:
```jsx
// Test case
<App a b c d e />

// [1]
<App a
b
c
d
e />

// [2]
<App a b
c d
e />
```
Would like an opinion on this if it is overkill. An alternate way of fixing would be under the `simple fix` commit (a simple addition of `\n` at a only one point).

Also, aware of #882 and hope to see some progress!


snowypowers: Rebased. Looks like the new options doesn't really affect how the autofix will work so should be fine. 
Added fix outputs to all the new invalid tests cases.

A second opinion is always welcomed :)
kylpo: I'd love for this to land soon! It will enable me to start using `prettier` (with `prettier-eslint`) and prevent it from messing with my `max-props-per-line` preference.
snowypowers: Rebased as a single atomic commit.
@lencioni @yannickcr 
timhwang21: Also would be happy for this to be released!
kylpo: I am just using @snowypowers's repo/branch until this lands. Working perfectly for my `prettier-eslint` setup, thanks @snowypowers!

Patch:
diff --git a/README.md b/README.md
index 1f1441a5..bd7b856b 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/docs/rules/jsx-max-props-per-line.md b/docs/rules/jsx-max-props-per-line.md
index 68571625..d0a167c8 100644
--- a/docs/rules/jsx-max-props-per-line.md
+++ b/docs/rules/jsx-max-props-per-line.md
@@ -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`.
diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js
index c4344fd8..88bd8370 100644
--- a/lib/rules/jsx-max-props-per-line.js
+++ b/lib/rules/jsx-max-props-per-line.js
@@ -16,7 +16,7 @@ module.exports = {
       category: 'Stylistic Issues',
       recommended: false
     },
-
+    fixable: 'code',
     schema: [{
       type: 'object',
       properties: {
@@ -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) {
@@ -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 {
@@ -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)
             });
           }
         });


Test patch:
diff --git a/tests/lib/rules/jsx-max-props-per-line.js b/tests/lib/rules/jsx-max-props-per-line.js
index 00ea0d42..1966e5e6 100644
--- a/tests/lib/rules/jsx-max-props-per-line.js
+++ b/tests/lib/rules/jsx-max-props-per-line.js
@@ -64,17 +64,37 @@ 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',
@@ -82,7 +102,15 @@ ruleTester.run('jsx-max-props-per-line', rule, {
       '  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',
@@ -90,7 +118,15 @@ ruleTester.run('jsx-max-props-per-line', rule, {
       '  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',
@@ -98,18 +134,37 @@ ruleTester.run('jsx-max-props-per-line', rule, {
       '  }} 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'}]
   }, {
@@ -117,14 +172,27 @@ ruleTester.run('jsx-max-props-per-line', rule, {
       '<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 {',
@@ -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}],