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} + }] +});