Skip to content

Commit

Permalink
Add jsx-curly-spacing rule (fixes #142)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yannick Croissant committed Jul 10, 2015
1 parent 81bde27 commit 250b9eb
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions docs/rules/jsx-curly-spacing.md
Original file line number Diff line number Diff line change
@@ -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
<Hello name={ firstname } />;
<Hello name={ firstname} />;
<Hello name={firstname } />;
<Hello name={
firstname
} />;
```

The following patterns are not warnings:

```js
<Hello name={firstname} />;
<Hello name={{ firstname: 'John', lastname: 'Doe' }} />;
```

#### 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={ {firstname: 'John', lastname: 'Doe'} } />;
<Hello name={
firstname
} />;
```

## 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.

2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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,
Expand Down
114 changes: 114 additions & 0 deletions lib/rules/jsx-curly-spacing.js
Original file line number Diff line number Diff line change
@@ -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']
}];
110 changes: 110 additions & 0 deletions tests/lib/rules/jsx-curly-spacing.js
Original file line number Diff line number Diff line change
@@ -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: '<App foo={bar} />;',
args: 1,
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={bar} />;',
args: [1, 'never'],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={ bar } />;',
args: [1, 'always'],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={{ bar:baz }} />;',
args: [1, 'never'],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={ {bar:baz} } />;',
args: [1, 'always'],
ecmaFeatures: {jsx: true}
}, {
code: [
'<App foo={',
'bar',
'} />;'
].join('\n'),
args: [1, 'always'],
ecmaFeatures: {jsx: true}
}],

invalid: [{
code: '<App foo={ bar } />;',
args: [1, 'never'],
errors: [{
message: 'There should be no space after \'{\''
}, {
message: 'There should be no space before \'}\''
}],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={bar} />;',
args: [1, 'always'],
errors: [{
message: 'A space is required after \'{\''
}, {
message: 'A space is required before \'}\''
}],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={ bar} />;',
args: [1, 'always'],
errors: [{
message: 'A space is required before \'}\''
}],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={bar } />;',
args: [1, 'always'],
errors: [{
message: 'A space is required after \'{\''
}],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={ bar} />;',
args: [1, 'never'],
errors: [{
message: 'There should be no space after \'{\''
}],
ecmaFeatures: {jsx: true}
}, {
code: '<App foo={bar } />;',
args: [1, 'never'],
errors: [{
message: 'There should be no space before \'}\''
}],
ecmaFeatures: {jsx: true}
}, {
code: [
'<App foo={',
'bar',
'} />;'
].join('\n'),
args: [1, 'never'],
errors: [{
message: 'There should be no space after \'{\''
}, {
message: 'There should be no space before \'}\''
}],
ecmaFeatures: {jsx: true}
}]
});

0 comments on commit 250b9eb

Please sign in to comment.