Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7faf1a4

Browse files
mysticateamichalsnik
authored andcommitted
New: html-closing-bracket-spacing rule (fixes #229) (#312)
* New: html-closing-bracket-spacing rule (fixes #229) * fix description * add the check of columns * add autofix check
1 parent cdd4163 commit 7faf1a4

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
183183

184184
| | Rule ID | Description |
185185
|:---|:--------|:------------|
186+
| :wrench: | [html-closing-bracket-spacing](./docs/rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets |
186187
| :wrench: | [html-closing-bracket-newline](./docs/rules/html-closing-bracket-newline.md) | require or disallow a line break before tag's closing brackets |
187188

188189
<!--RULES_TABLE_END-->
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# require or disallow a space before tag's closing brackets (html-closing-bracket-spacing)
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+
This rule enforces consistent spacing style before closing brackets `>` of tags.
6+
7+
```html
8+
<div class="foo"> or <div class="foo" >
9+
<div class="foo"/> or <div class="foo" />
10+
```
11+
12+
## Rule Details
13+
14+
This rule has options.
15+
16+
```json
17+
{
18+
"html-closing-bracket-spacing": ["error", {
19+
"startTag": "always" | "never",
20+
"endTag": "always" | "never",
21+
"selfClosingTag": "always" | "never"
22+
}]
23+
}
24+
```
25+
26+
- `startTag` (`"always" | "never"`) ... Setting for the `>` of start tags (e.g. `<div>`). Default is `"never"`.
27+
- `"always"` ... requires one or more spaces.
28+
- `"never"` ... disallows spaces.
29+
- `endTag` (`"always" | "never"`) ... Setting for the `>` of end tags (e.g. `</div>`). Default is `"never"`.
30+
- `"always"` ... requires one or more spaces.
31+
- `"never"` ... disallows spaces.
32+
- `selfClosingTag` (`"always" | "never"`) ... Setting for the `/>` of self-closing tags (e.g. `<div/>`). Default is `"always"`.
33+
- `"always"` ... requires one or more spaces.
34+
- `"never"` ... disallows spaces.
35+
36+
Examples of **incorrect** code for this rule:
37+
38+
```html
39+
<!--eslint html-closing-bracket-spacing: "error" -->
40+
41+
<div >
42+
<div foo >
43+
<div foo="bar" >
44+
</div >
45+
<div/>
46+
<div foo/>
47+
<div foo="bar"/>
48+
```
49+
50+
Examples of **correct** code for this rule:
51+
52+
```html
53+
<!--eslint html-closing-bracket-spacing: "error" -->
54+
55+
<div>
56+
<div foo>
57+
<div foo="bar">
58+
</div>
59+
<div />
60+
<div foo />
61+
<div foo="bar" />
62+
```
63+
64+
```html
65+
<!--eslint html-closing-bracket-spacing: ["error", {
66+
"startTag": "always",
67+
"endTag": "always",
68+
"selfClosingTag": "always"
69+
}] -->
70+
71+
<div >
72+
<div foo >
73+
<div foo="bar" >
74+
</div >
75+
<div />
76+
<div foo />
77+
<div foo="bar" />
78+
```
79+
80+
# Related rules
81+
82+
- [vue/no-multi-spaces](./no-multi-spaces.md)
83+
- [vue/html-closing-bracket-newline](./html-closing-bracket-newline.md)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
*/
4+
5+
'use strict'
6+
7+
// -----------------------------------------------------------------------------
8+
// Requirements
9+
// -----------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// -----------------------------------------------------------------------------
14+
// Helpers
15+
// -----------------------------------------------------------------------------
16+
17+
/**
18+
* Normalize options.
19+
* @param {{startTag?:"always"|"never",endTag?:"always"|"never",selfClosingTag?:"always"|"never"}} options The options user configured.
20+
* @param {TokenStore} tokens The token store of template body.
21+
* @returns {{startTag:"always"|"never",endTag:"always"|"never",selfClosingTag:"always"|"never"}} The normalized options.
22+
*/
23+
function parseOptions (options, tokens) {
24+
return Object.assign({
25+
startTag: 'never',
26+
endTag: 'never',
27+
selfClosingTag: 'always',
28+
29+
detectType (node) {
30+
const openType = tokens.getFirstToken(node).type
31+
const closeType = tokens.getLastToken(node).type
32+
33+
if (openType === 'HTMLEndTagOpen' && closeType === 'HTMLTagClose') {
34+
return this.endTag
35+
}
36+
if (openType === 'HTMLTagOpen' && closeType === 'HTMLTagClose') {
37+
return this.startTag
38+
}
39+
if (openType === 'HTMLTagOpen' && closeType === 'HTMLSelfClosingTagClose') {
40+
return this.selfClosingTag
41+
}
42+
return null
43+
}
44+
}, options)
45+
}
46+
47+
// -----------------------------------------------------------------------------
48+
// Rule Definition
49+
// -----------------------------------------------------------------------------
50+
51+
module.exports = {
52+
meta: {
53+
docs: {
54+
description: 'require or disallow a space before tag\'s closing brackets',
55+
category: undefined
56+
},
57+
schema: [{
58+
type: 'object',
59+
properties: {
60+
startTag: { enum: ['always', 'never'] },
61+
endTag: { enum: ['always', 'never'] },
62+
selfClosingTag: { enum: ['always', 'never'] }
63+
},
64+
additionalProperties: false
65+
}],
66+
fixable: 'whitespace'
67+
},
68+
69+
create (context) {
70+
const sourceCode = context.getSourceCode()
71+
const tokens =
72+
context.parserServices.getTemplateBodyTokenStore &&
73+
context.parserServices.getTemplateBodyTokenStore()
74+
const options = parseOptions(context.options[0], tokens)
75+
76+
return utils.defineTemplateBodyVisitor(context, {
77+
'VStartTag, VEndTag' (node) {
78+
const type = options.detectType(node)
79+
const lastToken = tokens.getLastToken(node)
80+
const prevToken = tokens.getLastToken(node, 1)
81+
82+
// Skip if EOF exists in the tag or linebreak exists before `>`.
83+
if (type == null || prevToken == null || prevToken.loc.end.line !== lastToken.loc.start.line) {
84+
return
85+
}
86+
87+
// Check and report.
88+
const hasSpace = (prevToken.range[1] !== lastToken.range[0])
89+
if (type === 'always' && !hasSpace) {
90+
context.report({
91+
node,
92+
loc: lastToken.loc,
93+
message: "Expected a space before '{{bracket}}', but not found.",
94+
data: { bracket: sourceCode.getText(lastToken) },
95+
fix: (fixer) => fixer.insertTextBefore(lastToken, ' ')
96+
})
97+
} else if (type === 'never' && hasSpace) {
98+
context.report({
99+
node,
100+
loc: {
101+
start: prevToken.loc.end,
102+
end: lastToken.loc.end
103+
},
104+
message: "Expected no space before '{{bracket}}', but found.",
105+
data: { bracket: sourceCode.getText(lastToken) },
106+
fix: (fixer) => fixer.removeRange([prevToken.range[1], lastToken.range[0]])
107+
})
108+
}
109+
}
110+
})
111+
}
112+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
*/
4+
5+
'use strict'
6+
7+
// -----------------------------------------------------------------------------
8+
// Requirements
9+
// -----------------------------------------------------------------------------
10+
11+
const RuleTester = require('eslint').RuleTester
12+
const rule = require('../../../lib/rules/html-closing-bracket-spacing')
13+
14+
// -----------------------------------------------------------------------------
15+
// Tests
16+
// -----------------------------------------------------------------------------
17+
18+
var ruleTester = new RuleTester({
19+
parser: 'vue-eslint-parser',
20+
parserOptions: {
21+
ecmaVersion: 2015
22+
}
23+
})
24+
25+
ruleTester.run('html-closing-bracket-spacing', rule, {
26+
valid: [
27+
'',
28+
'<template><div></div><div /></template>',
29+
'<template><div foo></div><div foo /></template>',
30+
'<template><div foo=a></div><div foo=a /></template>',
31+
'<template><div foo="a"></div><div foo="a" /></template>',
32+
{
33+
code: '<template ><div ></div><div /></template>',
34+
options: [{ startTag: 'always' }]
35+
},
36+
{
37+
code: '<template><div></div ><div /></template >',
38+
options: [{ endTag: 'always' }]
39+
},
40+
{
41+
code: '<template><div></div><div/></template>',
42+
options: [{ selfClosingTag: 'never' }]
43+
},
44+
'<template><div',
45+
'<template><div></div',
46+
{
47+
code: '<template><div',
48+
options: [{ startTag: 'never', endTag: 'never' }]
49+
},
50+
{
51+
code: '<template><div></div',
52+
options: [{ startTag: 'never', endTag: 'never' }]
53+
}
54+
],
55+
invalid: [
56+
{
57+
code: '<template>\n <div >\n </div >\n <div/>\n</template>',
58+
output: '<template>\n <div>\n </div>\n <div />\n</template>',
59+
errors: [
60+
{ message: "Expected no space before '>', but found.", line: 2, column: 7, endColumn: 9 },
61+
{ message: "Expected no space before '>', but found.", line: 3, column: 8, endColumn: 10 },
62+
{ message: "Expected a space before '/>', but not found.", line: 4, column: 7, endColumn: 9 }
63+
]
64+
},
65+
{
66+
code: '<template>\n <div foo ></div>\n <div foo/>\n</template>',
67+
output: '<template>\n <div foo></div>\n <div foo />\n</template>',
68+
errors: [
69+
{ message: "Expected no space before '>', but found.", line: 2, column: 11, endColumn: 13 },
70+
{ message: "Expected a space before '/>', but not found.", line: 3, column: 11, endColumn: 13 }
71+
]
72+
},
73+
{
74+
code: '<template>\n <div foo="1" ></div>\n <div foo="1"/>\n</template>',
75+
output: '<template>\n <div foo="1"></div>\n <div foo="1" />\n</template>',
76+
errors: [
77+
{ message: "Expected no space before '>', but found.", line: 2, column: 15, endColumn: 17 },
78+
{ message: "Expected a space before '/>', but not found.", line: 3, column: 15, endColumn: 17 }
79+
]
80+
},
81+
{
82+
code: '<template >\n <div>\n </div>\n <div />\n</template >',
83+
output: '<template >\n <div >\n </div >\n <div/>\n</template >',
84+
options: [{
85+
startTag: 'always',
86+
endTag: 'always',
87+
selfClosingTag: 'never'
88+
}],
89+
errors: [
90+
{ message: "Expected a space before '>', but not found.", line: 2, column: 7, endColumn: 8 },
91+
{ message: "Expected a space before '>', but not found.", line: 3, column: 8, endColumn: 9 },
92+
{ message: "Expected no space before '/>', but found.", line: 4, column: 7, endColumn: 10 }
93+
]
94+
}
95+
]
96+
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /