Skip to content

Commit

Permalink
Merge pull request #1215 from rsolomon/jsx-closing-tag-location
Browse files Browse the repository at this point in the history
[New] Add indentation rule for closing tag of multi-line jsx
  • Loading branch information
ljharb committed Jun 9, 2017
2 parents 52f54be + ba0b25b commit 81c8e35
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -122,6 +122,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#

* [react/jsx-boolean-value](docs/rules/jsx-boolean-value.md): Enforce boolean attributes notation in JSX (fixable)
* [react/jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md): Validate closing bracket location in JSX (fixable)
* [react/jsx-closing-tag-location](docs/rules/jsx-closing-tag-location.md): Validate closing tag location in JSX (fixable)
* [react/jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes (fixable)
* [react/jsx-equals-spacing](docs/rules/jsx-equals-spacing.md): Enforce or disallow spaces around equal signs in JSX attributes (fixable)
* [react/jsx-filename-extension](docs/rules/jsx-filename-extension.md): Restrict file extensions that may contain JSX
Expand Down
38 changes: 38 additions & 0 deletions docs/rules/jsx-closing-tag-location.md
@@ -0,0 +1,38 @@
# Validate closing tag location in JSX (react/jsx-closing-tag-location)

Enforce the closing tag location for multiline JSX elements.

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

## Rule Details

This rule checks all JSX multiline elements with children (non-self-closing) and verifies the location of the closing tag. The expectation is that the closing tag is aligned with the opening tag on its own line.

The following patterns are considered warnings:

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

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

The following are not considered warnings:

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

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

## When not to use

If you do not care about closing tag JSX alignment then you can disable this rule.
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -41,6 +41,7 @@ var allRules = {
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
'jsx-indent': require('./lib/rules/jsx-indent'),
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
'jsx-closing-tag-location': require('./lib/rules/jsx-closing-tag-location'),
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
'forbid-component-props': require('./lib/rules/forbid-component-props'),
Expand Down
87 changes: 87 additions & 0 deletions lib/rules/jsx-closing-tag-location.js
@@ -0,0 +1,87 @@
/**
* @fileoverview Validate closing tag location in JSX
* @author Ross Solomon
*/
'use strict';

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Validate closing tag location for multiline JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'whitespace'
},

create: function(context) {
var sourceCode = context.getSourceCode();

/**
* Checks if the node is the first in its line, excluding whitespace.
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the first node in its line
*/
function isNodeFirstInLine(node) {
let token = node;
let lines;
do {
token = sourceCode.getTokenBefore(token);
lines = token.type === 'JSXText'
? token.value.split('\n')
: null;
} while (
token.type === 'JSXText' &&
/^\s*$/.test(lines[lines.length - 1])
);

var startLine = node.loc.start.line;
var endLine = token ? token.loc.end.line : -1;
return startLine !== endLine;
}

return {
JSXClosingElement: function(node) {
if (!node.parent) {
return;
}

const opening = node.parent.openingElement;
if (opening.loc.start.line === node.loc.start.line) {
return;
}

if (opening.loc.start.column === node.loc.start.column) {
return;
}

let message;
if (!isNodeFirstInLine(node)) {
message = 'Closing tag of a multiline JSX expression must be on its own line.';
} else {
message = 'Expected closing tag to match indentation of opening.';
}

context.report({
node: node,
loc: node.loc,
message,
fix: function(fixer) {
const indent = Array(opening.loc.start.column + 1).join(' ');
if (isNodeFirstInLine(node)) {
return fixer.replaceTextRange(
[node.start - node.loc.start.column, node.start],
indent
);
}

return fixer.insertTextBefore(node, `\n${indent}`);
}
});
}
};
}
};
65 changes: 65 additions & 0 deletions tests/lib/rules/jsx-closing-tag-location.js
@@ -0,0 +1,65 @@
/**
* @fileoverview Validate closing tag location in JSX
* @author Ross Solomon
*/
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/jsx-closing-tag-location');
const RuleTester = require('eslint').RuleTester;
const parserOptions = {
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
};

const MESSAGE_MATCH_INDENTATION = [{message: 'Expected closing tag to match indentation of opening.'}];
const MESSAGE_OWN_LINE = [{message: 'Closing tag of a multiline JSX expression must be on its own line.'}];

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-closing-tag-location', rule, {
valid: [{
code: [
'<App>',
' foo',
'</App>'
].join('\n')
}, {
code: [
'<App>foo</App>'
].join('\n')
}],

invalid: [{
code: [
'<App>',
' foo',
' </App>'
].join('\n'),
output: [
'<App>',
' foo',
'</App>'
].join('\n'),
errors: MESSAGE_MATCH_INDENTATION
}, {
code: [
'<App>',
' foo</App>'
].join('\n'),
output: [
'<App>',
' foo',
'</App>'
].join('\n'),
errors: MESSAGE_OWN_LINE
}]
});

0 comments on commit 81c8e35

Please sign in to comment.