Repository: yannickcr/eslint-plugin-react
Pull request #406 (2016-01-23 00:10:27)

Add jsx-equals-spacing rule
Hi,

This PR is an implementation of proposal #394.
It adds a new rule that enforces or disallows spaces around the `=` in JSX attributes.

Thanks.

yannickcr: Great! Can you squash your commits? Then I think we'll be good to go :)

Patch:
diff --git a/README.md b/README.md
index 2e014f4a..8800c516 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ Finally, enable all of the rules that you would like to use.
     "react/jsx-boolean-value": 1,
     "react/jsx-closing-bracket-location": 1,
     "react/jsx-curly-spacing": 1,
+    "react/jsx-equals-spacing": 1,
     "react/jsx-handler-names": 1,
     "react/jsx-indent-props": 1,
     "react/jsx-indent": 1,
@@ -120,6 +121,7 @@ Finally, enable all of the rules that you would like to use.
 * [jsx-boolean-value](docs/rules/jsx-boolean-value.md): Enforce boolean attributes notation in JSX (fixable)
 * [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md): Validate closing bracket location in JSX
 * [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
+* [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md): Enforce or disallow spaces around equal signs in JSX attributes
 * [jsx-handler-names](docs/rules/jsx-handler-names.md): Enforce event handler naming conventions in JSX
 * [jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX
 * [jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation
diff --git a/docs/rules/jsx-equals-spacing.md b/docs/rules/jsx-equals-spacing.md
new file mode 100644
index 00000000..7c202ecd
--- /dev/null
+++ b/docs/rules/jsx-equals-spacing.md
@@ -0,0 +1,61 @@
+# Enforce or disallow spaces around equal signs in JSX attributes. (jsx-equals-spacing)
+
+Some style guides require or disallow spaces around equal signs.
+
+## Rule Details
+
+This rule will enforce consistency of spacing around equal signs in JSX attributes, by requiring or disallowing one or more spaces before and after `=`.
+
+### Options
+
+There are two options for the rule:
+
+* `"always"` enforces spaces around the equal sign
+* `"never"` disallows spaces around the equal sign (default)
+
+Depending on your coding conventions, you can choose either option by specifying it in your configuration:
+
+```json
+"jsx-equals-spacing": [2, "always"]
+```
+
+#### never
+
+When `"never"` is set, the following patterns are considered warnings:
+
+```js
+<Hello name = {firstname} />;
+<Hello name ={firstname} />;
+<Hello name= {firstname} />;
+```
+
+The following patterns are not warnings:
+
+```js
+<Hello name={firstname} />;
+<Hello name />;
+<Hello {...props} />;
+```
+
+#### always
+
+When `"always"` is used, the following patterns are considered warnings:
+
+```js
+<Hello name={firstname} />;
+<Hello name ={firstname} />;
+<Hello name= {firstname} />;
+```
+
+The following patterns are not warnings:
+
+```js
+<Hello name = {firstname} />;
+<Hello name />;
+<Hello {...props} />;
+```
+
+## When Not To Use It
+
+You can turn this rule off if you are not concerned with the consistency of spacing around equal signs in JSX attributes.
+
diff --git a/index.js b/index.js
index 9ba7f1e5..ad33d96e 100644
--- a/index.js
+++ b/index.js
@@ -23,6 +23,7 @@ module.exports = {
     'jsx-quotes': require('./lib/rules/jsx-quotes'),
     'no-unknown-property': require('./lib/rules/no-unknown-property'),
     'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'),
+    'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'),
     'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
     'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'),
     'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
@@ -62,6 +63,7 @@ module.exports = {
     'jsx-quotes': 0,
     'no-unknown-property': 0,
     'jsx-curly-spacing': 0,
+    'jsx-equals-spacing': 0,
     'jsx-sort-props': 0,
     'jsx-sort-prop-types': 0,
     'jsx-boolean-value': 0,
diff --git a/lib/rules/jsx-equals-spacing.js b/lib/rules/jsx-equals-spacing.js
new file mode 100644
index 00000000..7773c103
--- /dev/null
+++ b/lib/rules/jsx-equals-spacing.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Disallow or enforce spaces around equal signs in JSX attributes.
+ * @author ryym
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+  var config = context.options[0];
+  var sourceCode = context.getSourceCode();
+
+  /**
+   * Determines a given attribute node has an equal sign.
+   * @param {ASTNode} attrNode - The attribute node.
+   * @returns {boolean} Whether or not the attriute node has an equal sign.
+   */
+  function hasEqual(attrNode) {
+    return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null;
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+    JSXOpeningElement: function(node) {
+      node.attributes.forEach(function(attrNode) {
+        if (!hasEqual(attrNode)) {
+          return;
+        }
+
+        var equalToken = sourceCode.getTokenAfter(attrNode.name);
+        var spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken);
+        var spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value);
+
+        switch (config) {
+          default:
+          case 'never':
+            if (spacedBefore) {
+              context.report(attrNode, equalToken.loc.start,
+                'There should be no space before \'=\'');
+            }
+            if (spacedAfter) {
+              context.report(attrNode, equalToken.loc.start,
+                'There should be no space after \'=\'');
+            }
+            break;
+          case 'always':
+            if (!spacedBefore) {
+              context.report(attrNode, equalToken.loc.start,
+                'A space is required before \'=\'');
+            }
+            if (!spacedAfter) {
+              context.report(attrNode, equalToken.loc.start,
+                'A space is required after \'=\'');
+            }
+            break;
+        }
+      });
+    }
+  };
+};
+
+module.exports.schema = [{
+  enum: ['always', 'never']
+}];


Test patch:
diff --git a/tests/lib/rules/jsx-equals-spacing.js b/tests/lib/rules/jsx-equals-spacing.js
new file mode 100644
index 00000000..dce47af1
--- /dev/null
+++ b/tests/lib/rules/jsx-equals-spacing.js
@@ -0,0 +1,154 @@
+/**
+ * @fileoverview Disallow or enforce spaces around equal signs in JSX attributes.
+ * @author ryym
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/jsx-equals-spacing');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('jsx-equals-spacing', rule, {
+  valid: [{
+    code: '<App />',
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo />',
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo="bar" />',
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo={e => bar(e)} />',
+    parserOptions: parserOptions
+  }, {
+    code: '<App {...props} />',
+    parserOptions: parserOptions
+  }, {
+    code: '<App />',
+    options: ['never'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo />',
+    options: ['never'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo="bar" />',
+    options: ['never'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo={e => bar(e)} />',
+    options: ['never'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App {...props} />',
+    options: ['never'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App />',
+    options: ['always'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo />',
+    options: ['always'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo = "bar" />',
+    options: ['always'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App foo = {e => bar(e)} />',
+    options: ['always'],
+    parserOptions: parserOptions
+  }, {
+    code: '<App {...props} />',
+    options: ['always'],
+    parserOptions: parserOptions
+  }],
+
+  invalid: [{
+    code: '<App foo = {bar} />',
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'There should be no space before \'=\''},
+      {message: 'There should be no space after \'=\''}
+    ]
+  }, {
+    code: '<App foo = {bar} />',
+    options: ['never'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'There should be no space before \'=\''},
+      {message: 'There should be no space after \'=\''}
+    ]
+  }, {
+    code: '<App foo ={bar} />',
+    options: ['never'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'There should be no space before \'=\''}
+    ]
+  }, {
+    code: '<App foo= {bar} />',
+    options: ['never'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'There should be no space after \'=\''}
+    ]
+  }, {
+    code: '<App foo= {bar} bar = {baz} />',
+    options: ['never'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'There should be no space after \'=\''},
+      {message: 'There should be no space before \'=\''},
+      {message: 'There should be no space after \'=\''}
+    ]
+  }, {
+    code: '<App foo={bar} />',
+    options: ['always'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'A space is required before \'=\''},
+      {message: 'A space is required after \'=\''}
+    ]
+  }, {
+    code: '<App foo ={bar} />',
+    options: ['always'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'A space is required after \'=\''}
+    ]
+  }, {
+    code: '<App foo= {bar} />',
+    options: ['always'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'A space is required before \'=\''}
+    ]
+  }, {
+    code: '<App foo={bar} bar ={baz} />',
+    options: ['always'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'A space is required before \'=\''},
+      {message: 'A space is required after \'=\''},
+      {message: 'A space is required after \'=\''}
+    ]
+  }]
+});