diff --git a/README.md b/README.md
index 4fe0991837..db2b7029f4 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,7 @@ Finally, enable all of the rules that you would like to use.
* [display-name](docs/rules/display-name.md): Prevent missing displayName in a React component definition
* [jsx-boolean-value](docs/rules/jsx-boolean-value.md): Enforce boolean attributes notation in JSX
+* [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
* [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
* [jsx-quotes](docs/rules/jsx-quotes.md): Enforce quote style for JSX attributes
* [jsx-sort-prop-types](docs/rules/jsx-sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
diff --git a/docs/rules/jsx-curly-spacing.md b/docs/rules/jsx-curly-spacing.md
new file mode 100644
index 0000000000..8eebc238e5
--- /dev/null
+++ b/docs/rules/jsx-curly-spacing.md
@@ -0,0 +1,67 @@
+# Enforce or disallow spaces inside of curly braces in JSX attributes. (jsx-curly-spacing)
+
+While formatting preferences are very personal, a number of style guides require or disallow spaces between curly braces.
+
+## Rule Details
+
+This rule aims to maintain consistency around the spacing inside of JSX attributes.
+
+It either requires or disallows spaces between those braces and the values inside of them.
+
+### Options
+
+There are two main options for the rule:
+
+* `"always"` enforces a space inside of curly braces
+* `"never"` disallows spaces inside of curly braces (default)
+
+Depending on your coding conventions, you can choose either option by specifying it in your configuration:
+
+```json
+"jsx-curly-spacing": [2, "always"]
+```
+
+#### never
+
+When `"never"` is set, the following patterns are considered warnings:
+
+```js
+;
+;
+;
+;
+```
+
+The following patterns are not warnings:
+
+```js
+;
+;
+```
+
+#### always
+
+When `"always"` is used, the following patterns are considered warnings:
+
+```js
+;
+;
+;
+```
+
+The following patterns are not warnings:
+
+```js
+;
+;
+;
+```
+
+## When Not To Use It
+
+You can turn this rule off if you are not concerned with the consistency around the spacing inside of JSX attributes.
+
diff --git a/index.js b/index.js
index f642eca6bf..57446f641c 100644
--- a/index.js
+++ b/index.js
@@ -16,6 +16,7 @@ module.exports = {
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'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-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'),
@@ -37,6 +38,7 @@ module.exports = {
'jsx-no-undef': 0,
'jsx-quotes': 0,
'no-unknown-property': 0,
+ 'jsx-curly-spacing': 0,
'jsx-sort-props': 0,
'jsx-sort-prop-types': 0,
'jsx-boolean-value': 0,
diff --git a/lib/rules/jsx-curly-spacing.js b/lib/rules/jsx-curly-spacing.js
new file mode 100644
index 0000000000..acad1459f6
--- /dev/null
+++ b/lib/rules/jsx-curly-spacing.js
@@ -0,0 +1,114 @@
+/**
+ * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
+ * @author Jamund Ferguson, Brandyn Bennett, Michael Ficarra, Vignesh Anand, Jamund Ferguson, Yannick Croissant
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var spaced = context.options[0] === 'always';
+
+ // --------------------------------------------------------------------------
+ // Helpers
+ // --------------------------------------------------------------------------
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Reports that there shouldn't be a space after the first token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportNoBeginningSpace(node, token) {
+ context.report(node, token.loc.start,
+ 'There should be no space after \'' + token.value + '\'');
+ }
+
+ /**
+ * Reports that there shouldn't be a space before the last token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportNoEndingSpace(node, token) {
+ context.report(node, token.loc.start,
+ 'There should be no space before \'' + token.value + '\'');
+ }
+
+ /**
+ * Reports that there should be a space after the first token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportRequiredBeginningSpace(node, token) {
+ context.report(node, token.loc.start,
+ 'A space is required after \'' + token.value + '\'');
+ }
+
+ /**
+ * Reports that there should be a space before the last token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportRequiredEndingSpace(node, token) {
+ context.report(node, token.loc.start,
+ 'A space is required before \'' + token.value + '\'');
+ }
+
+ /**
+ * Determines if spacing in curly braces is valid.
+ * @param {ASTNode} node The AST node to check.
+ * @param {Token} first The first token to check (should be the opening brace)
+ * @param {Token} second The second token to check (should be first after the opening brace)
+ * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
+ * @param {Token} last The last token to check (should be closing brace)
+ * @returns {void}
+ */
+ function validateBraceSpacing(node, first, second, penultimate, last) {
+ if (spaced && !isSpaced(first, second)) {
+ reportRequiredBeginningSpace(node, first);
+ }
+ if (!spaced && isSpaced(first, second)) {
+ reportNoBeginningSpace(node, first);
+ }
+ if (spaced && !isSpaced(penultimate, last)) {
+ reportRequiredEndingSpace(node, last);
+ }
+ if (!spaced && isSpaced(penultimate, last)) {
+ reportNoEndingSpace(node, last);
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Public
+ // --------------------------------------------------------------------------
+
+ return {
+ JSXExpressionContainer: function(node) {
+ var first = context.getFirstToken(node);
+ var second = context.getFirstToken(node, 1);
+ var penultimate = context.getLastToken(node, 1);
+ var last = context.getLastToken(node);
+
+ validateBraceSpacing(node, first, second, penultimate, last);
+ }
+ };
+};
+
+module.exports.schema = [{
+ enum: ['always', 'never']
+}];
diff --git a/tests/lib/rules/jsx-curly-spacing.js b/tests/lib/rules/jsx-curly-spacing.js
new file mode 100644
index 0000000000..39132fada2
--- /dev/null
+++ b/tests/lib/rules/jsx-curly-spacing.js
@@ -0,0 +1,110 @@
+/**
+ * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
+ * @author Yannick Croissant
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var eslint = require('eslint').linter;
+var ESLintTester = require('eslint-tester');
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var eslintTester = new ESLintTester(eslint);
+eslintTester.addRuleTest('lib/rules/jsx-curly-spacing', {
+ valid: [{
+ code: ';',
+ args: 1,
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'never'],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'always'],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'never'],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'always'],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: [
+ ';'
+ ].join('\n'),
+ args: [1, 'always'],
+ ecmaFeatures: {jsx: true}
+ }],
+
+ invalid: [{
+ code: ';',
+ args: [1, 'never'],
+ errors: [{
+ message: 'There should be no space after \'{\''
+ }, {
+ message: 'There should be no space before \'}\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'always'],
+ errors: [{
+ message: 'A space is required after \'{\''
+ }, {
+ message: 'A space is required before \'}\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'always'],
+ errors: [{
+ message: 'A space is required before \'}\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'always'],
+ errors: [{
+ message: 'A space is required after \'{\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'never'],
+ errors: [{
+ message: 'There should be no space after \'{\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: ';',
+ args: [1, 'never'],
+ errors: [{
+ message: 'There should be no space before \'}\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }, {
+ code: [
+ ';'
+ ].join('\n'),
+ args: [1, 'never'],
+ errors: [{
+ message: 'There should be no space after \'{\''
+ }, {
+ message: 'There should be no space before \'}\''
+ }],
+ ecmaFeatures: {jsx: true}
+ }]
+});