Skip to content

Commit 8061f8a

Browse files
committed
New: html-closing-bracket-newline (fixes #169)
1 parent deedd7a commit 8061f8a

File tree

3 files changed

+505
-0
lines changed

3 files changed

+505
-0
lines changed

Diff for: docs/rules/html-closing-bracket-newline.md

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# require or disallow a line break before tag's closing brackets (html-closing-bracket-newline)
2+
3+
- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
4+
5+
People have own preference about the location of closing brackets.
6+
This rule enforces a line break (or no line break) before tag's closing brackets.
7+
8+
```html
9+
<div
10+
id="foo"
11+
class="bar"> <!-- On the same line with the last attribute. -->
12+
</div>
13+
14+
<div
15+
id="foo"
16+
class="bar"
17+
> <!-- On the next line. -->
18+
</div>
19+
```
20+
21+
## Rule Details
22+
23+
```json
24+
{
25+
"html-closing-bracket-newline": ["error", {
26+
"singleline": "never",
27+
"multiline": "never"
28+
}]
29+
}
30+
```
31+
32+
- `singleline` ... the configuration for single-line elements. It's a single-line element if the element does not have attributes or the last attribute is on the same line of the opening bracket.
33+
- `"never"` ... disallow line breaks before the closing bracket of elements. This is the default.
34+
- `"always"` ... require one line break before the closing bracket of elements.
35+
- `multiline` ... the configuration for multiline elements. It's a multiline element if the last attribute is not on the same line of the opening bracket.
36+
- `"never"` ... disallow line breaks before the closing bracket of elements. This is the default.
37+
- `"always"` ... require one line break before the closing bracket of elements.
38+
39+
Plus, you can use [`vue/html-indent`](./html-indent.md) rule to enforce indent-level of the closing brackets.
40+
41+
:-1: Examples of **incorrect** code for this rule:
42+
43+
```html
44+
/*eslint html-closing-bracket-newline: "error"*/
45+
46+
<div id="foo" class="bar"
47+
>
48+
<div
49+
id="foo"
50+
class="bar"
51+
>
52+
<div
53+
id="foo"
54+
class="bar"
55+
>
56+
```
57+
58+
:+1: Examples of **correct** code for this rule:
59+
60+
```html
61+
/*eslint html-closing-bracket-newline: "error"*/
62+
63+
<div id="foo" class="bar">
64+
<div
65+
id="foo"
66+
class="bar">
67+
```
68+
69+
:-1: Examples of **incorrect** code for `{ "multiline": "always" }`:
70+
71+
```html
72+
/*eslint html-closing-bracket-newline: ["error", { multiline: always }]*/
73+
74+
<div id="foo" class="bar"
75+
>
76+
<div
77+
id="foo"
78+
class="bar">
79+
```
80+
81+
:+1: Examples of **correct** code for `{ "multiline": "always" }`:
82+
83+
```html
84+
/*eslint html-closing-bracket-newline: ["error", { multiline: always }]*/
85+
86+
<div id="foo" class="bar">
87+
<div
88+
id="foo"
89+
class="bar"
90+
>
91+
<div
92+
id="foo"
93+
class="bar"
94+
>
95+
```

Diff for: lib/rules/html-closing-bracket-newline.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @author Toru Nagashima
3+
* @copyright 2016 Toru Nagashima. All rights reserved.
4+
* See LICENSE file in root directory for full license.
5+
*/
6+
'use strict'
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const utils = require('../utils')
13+
14+
// ------------------------------------------------------------------------------
15+
// Helpers
16+
// ------------------------------------------------------------------------------
17+
18+
function getPhrase (lineBreaks) {
19+
switch (lineBreaks) {
20+
case 0: return 'no line breaks'
21+
case 1: return '1 line break'
22+
default: return `${lineBreaks} line breaks`
23+
}
24+
}
25+
26+
/**
27+
* Creates AST event handlers for html-closing-bracket-newline.
28+
*
29+
* @param {RuleContext} context - The rule context.
30+
* @returns {object} AST event handlers.
31+
*/
32+
function create (context) {
33+
const options = context.options[0] || {}
34+
const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
35+
36+
return utils.defineTemplateBodyVisitor(context, {
37+
'VStartTag, VEndTag' (node) {
38+
const closingBracketToken = template.getLastToken(node)
39+
if (closingBracketToken.type !== 'HTMLSelfClosingTagClose' && closingBracketToken.type !== 'HTMLTagClose') {
40+
return
41+
}
42+
43+
const prevToken = template.getTokenBefore(closingBracketToken)
44+
const type = (node.loc.start.line === prevToken.loc.end.line) ? 'singleline' : 'multiline'
45+
const expectedLineBreaks = (options[type] === 'always') ? 1 : 0
46+
const actualLineBreaks = (closingBracketToken.loc.start.line - prevToken.loc.end.line)
47+
48+
if (actualLineBreaks !== expectedLineBreaks) {
49+
context.report({
50+
node,
51+
loc: {
52+
start: prevToken.loc.end,
53+
end: closingBracketToken.loc.start
54+
},
55+
message: 'Expected {{expected}} before closing bracket, but {{actual}} found.',
56+
data: {
57+
expected: getPhrase(expectedLineBreaks),
58+
actual: getPhrase(actualLineBreaks)
59+
},
60+
fix (fixer) {
61+
const range = [prevToken.range[1], closingBracketToken.range[0]]
62+
const text = '\n'.repeat(expectedLineBreaks)
63+
return fixer.replaceTextRange(range, text)
64+
}
65+
})
66+
}
67+
}
68+
})
69+
}
70+
71+
// ------------------------------------------------------------------------------
72+
// Rule Definition
73+
// ------------------------------------------------------------------------------
74+
75+
module.exports = {
76+
create,
77+
meta: {
78+
docs: {
79+
description: "require or disallow a line break before tag's closing brackets",
80+
category: 'Stylistic Issues',
81+
recommended: false
82+
},
83+
fixable: 'whitespace',
84+
schema: [{
85+
type: 'object',
86+
properties: {
87+
'singleline': { enum: ['always', 'never'] },
88+
'multiline': { enum: ['always', 'never'] }
89+
},
90+
additionalProperties: false
91+
}]
92+
}
93+
}

0 commit comments

Comments
 (0)