Skip to content

Commit

Permalink
Merge pull request #1641 from cjskillingstad/feat/jsx-tag-spacing_bef…
Browse files Browse the repository at this point in the history
…ore_closing

Add beforeClosing option to jsx-tag-spacing rule
  • Loading branch information
ljharb committed Jan 12, 2018
2 parents 07345b4 + 2043520 commit 0e983de
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 10 deletions.
49 changes: 46 additions & 3 deletions docs/rules/jsx-tag-spacing.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# Validate whitespace in and around the JSX opening and closing brackets (react/jsx-tag-spacing)

Enforce or forbid spaces after the opening bracket, before the closing bracket of self-closing elements, and between the angle bracket and slash of JSX closing or self-closing elements.
Enforce or forbid spaces after the opening bracket, before the closing bracket, before the closing bracket of self-closing elements, and between the angle bracket and slash of JSX closing or self-closing elements.

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.

## Rule Details

This rule checks the whitespace inside and surrounding the JSX syntactic elements.

This rule takes one argument, an object with 3 possible keys: `closingSlash`, `beforeSelfClosing` and `afterOpening`. Each key can receive the value `"allow"` to disable that specific check.
This rule takes one argument, an object with 4 possible keys: `closingSlash`, `beforeSelfClosing`, `afterOpening`, and `beforeClosing`. Each key can receive the value `"allow"` to disable that specific check.

The default values are:

```json
{
"closingSlash": "never",
"beforeSelfClosing": "always",
"afterOpening": "never"
"afterOpening": "never",
"beforeClosing": "allow"
}
```

Expand Down Expand Up @@ -176,6 +177,48 @@ The following patterns are **not** considered warnings when configured `"allow-m
/>
```

### `beforeClosing`

This check can be set to `"always"`, `"never"`, or `"allow"` (to disable it).

If it is `"always"` the check warns whenever whitespace is missing before the closing bracket of a JSX opening element or whenever a space is missing before the closing bracket closing element. If `"never"`, then it warns if a space is present before the closing bracket of either a JSX opening element or closing element. This rule will never warn for self closing JSX elements. The default value of this check is `"allow"`.

The following patterns are considered warnings when configured `"always"`:

```jsx
<Hello></Hello>
<Hello></Hello >
<Hello ></Hello>
```

The following patterns are **not** considered warnings when configured `"always"`:

```jsx
<Hello ></Hello >
<Hello
firstName="John"
>
</Hello >
```

The following patterns are considered warnings when configured `"never"`:

```jsx
<Hello ></Hello>
<Hello></Hello >
<Hello ></Hello >
```

The following patterns are **not** considered warnings when configured `"never"`:

```jsx
<Hello></Hello>
<Hello
firstName="John"
>
</Hello>
```

## When Not To Use It

You can turn this rule off if you are not concerned with the consistency of spacing in or around JSX brackets.
61 changes: 59 additions & 2 deletions lib/rules/jsx-tag-spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,52 @@ function validateAfterOpening(context, node, option) {
}
}

function validateBeforeClosing(context, node, option) {
// Don't enforce this rule for self closing tags
if (!node.selfClosing) {
const sourceCode = context.getSourceCode();

const NEVER_MESSAGE = 'A space is forbidden before closing bracket';
const ALWAYS_MESSAGE = 'Whitespace is required before closing bracket';

const lastTokens = sourceCode.getLastTokens(node, 2);
const closingToken = lastTokens[1];
const leftToken = lastTokens[0];

if (leftToken.loc.start.line !== closingToken.loc.start.line) {
return;
}

const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingToken);

if (option === 'never' && !adjacent) {
context.report({
node: node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start
},
message: NEVER_MESSAGE,
fix: function(fixer) {
return fixer.removeRange([leftToken.range[1], closingToken.range[0]]);
}
});
} else if (option === 'always' && adjacent) {
context.report({
node: node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start
},
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(closingToken, ' ');
}
});
}
}
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand All @@ -191,12 +237,16 @@ module.exports = {
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow']
},
beforeClosing: {
enum: ['always', 'never', 'allow']
}
},
default: {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never'
afterOpening: 'never',
beforeClosing: 'allow'
},
additionalProperties: false
}
Expand All @@ -206,7 +256,8 @@ module.exports = {
const options = {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never'
afterOpening: 'never',
beforeClosing: 'allow'
};
for (const key in options) {
if (has(options, key) && has(context.options[0] || {}, key)) {
Expand All @@ -225,6 +276,9 @@ module.exports = {
if (options.beforeSelfClosing !== 'allow' && node.selfClosing) {
validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
}
if (options.beforeClosing !== 'allow') {
validateBeforeClosing(context, node, options.beforeClosing);
}
},
JSXClosingElement: function (node) {
if (options.afterOpening !== 'allow') {
Expand All @@ -233,6 +287,9 @@ module.exports = {
if (options.closingSlash !== 'allow') {
validateClosingSlash(context, node, options.closingSlash);
}
if (options.beforeClosing !== 'allow') {
validateBeforeClosing(context, node, options.beforeClosing);
}
}
};
}
Expand Down
115 changes: 110 additions & 5 deletions tests/lib/rules/jsx-tag-spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,35 @@ function closingSlashOptions(option) {
return [{
closingSlash: option,
beforeSelfClosing: 'allow',
afterOpening: 'allow'
afterOpening: 'allow',
beforeClosing: 'allow'
}];
}

function beforeSelfClosingOptions(option) {
return [{
closingSlash: 'allow',
beforeSelfClosing: option,
afterOpening: 'allow'
afterOpening: 'allow',
beforeClosing: 'allow'
}];
}

function afterOpeningOptions(option) {
return [{
closingSlash: 'allow',
beforeSelfClosing: 'allow',
afterOpening: option
afterOpening: option,
beforeClosing: 'allow'
}];
}

function beforeClosingOptions(option) {
return [{
closingSlash: 'allow',
beforeSelfClosing: 'allow',
afterOpening: 'allow',
beforeClosing: option
}];
}

Expand Down Expand Up @@ -139,19 +151,62 @@ ruleTester.run('jsx-tag-spacing', rule, {
'App/>'
].join('\n'),
options: afterOpeningOptions('allow-multiline')
}, {
code: '<App />',
options: beforeClosingOptions('never')
}, {
code: '<App></App>',
options: beforeClosingOptions('never')
}, {
code: [
'<App',
'foo="bar"',
'>',
'</App>'
].join('\n'),
options: beforeClosingOptions('never')
}, {
code: [
'<App',
' foo="bar"',
'>',
'</App>'
].join('\n'),
options: beforeClosingOptions('never')
}, {
code: '<App ></App >',
options: beforeClosingOptions('always')
}, {
code: [
'<App',
'foo="bar"',
'>',
'</App >'
].join('\n'),
options: beforeClosingOptions('always')
}, {
code: [
'<App',
' foo="bar"',
'>',
'</App >'
].join('\n'),
options: beforeClosingOptions('always')
}, {
code: '<App/>',
options: [{
closingSlash: 'never',
beforeSelfClosing: 'never',
afterOpening: 'never'
afterOpening: 'never',
beforeClosing: 'never'
}]
}, {
code: '< App / >',
options: [{
closingSlash: 'always',
beforeSelfClosing: 'always',
afterOpening: 'always'
afterOpening: 'always',
beforeClosing: 'always'
}]
}],

Expand Down Expand Up @@ -306,5 +361,55 @@ ruleTester.run('jsx-tag-spacing', rule, {
output: '<App/>',
errors: [{message: 'A space is forbidden after opening bracket'}],
options: afterOpeningOptions('allow-multiline')
}, {
code: '<App ></App>',
output: '<App></App>',
errors: [{message: 'A space is forbidden before closing bracket'}],
options: beforeClosingOptions('never')
}, {
code: '<App></App >',
output: '<App></App>',
errors: [{message: 'A space is forbidden before closing bracket'}],
options: beforeClosingOptions('never')
}, {
code: [
'<App',
'foo="bar"',
'>',
'</App >'
].join('\n'),
output: [
'<App',
'foo="bar"',
'>',
'</App>'
].join('\n'),
errors: [{message: 'A space is forbidden before closing bracket'}],
options: beforeClosingOptions('never')
}, {
code: '<App></App >',
output: '<App ></App >',
errors: [{message: 'Whitespace is required before closing bracket'}],
options: beforeClosingOptions('always')
}, {
code: '<App ></App>',
output: '<App ></App >',
errors: [{message: 'Whitespace is required before closing bracket'}],
options: beforeClosingOptions('always')
}, {
code: [
'<App',
'foo="bar"',
'>',
'</App>'
].join('\n'),
output: [
'<App',
'foo="bar"',
'>',
'</App >'
].join('\n'),
errors: [{message: 'Whitespace is required before closing bracket'}],
options: beforeClosingOptions('always')
}]
});

0 comments on commit 0e983de

Please sign in to comment.