-
-
Notifications
You must be signed in to change notification settings - Fork 681
/
Copy pathhtml-closing-bracket-spacing.js
113 lines (100 loc) · 3.78 KB
/
html-closing-bracket-spacing.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* @author Toru Nagashima <https://github.com/mysticatea>
*/
'use strict'
// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------
const utils = require('../utils')
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
/**
* Normalize options.
* @param {{startTag?:"always"|"never",endTag?:"always"|"never",selfClosingTag?:"always"|"never"}} options The options user configured.
* @param {TokenStore} tokens The token store of template body.
* @returns {{startTag:"always"|"never",endTag:"always"|"never",selfClosingTag:"always"|"never"}} The normalized options.
*/
function parseOptions (options, tokens) {
return Object.assign({
startTag: 'never',
endTag: 'never',
selfClosingTag: 'always',
detectType (node) {
const openType = tokens.getFirstToken(node).type
const closeType = tokens.getLastToken(node).type
if (openType === 'HTMLEndTagOpen' && closeType === 'HTMLTagClose') {
return this.endTag
}
if (openType === 'HTMLTagOpen' && closeType === 'HTMLTagClose') {
return this.startTag
}
if (openType === 'HTMLTagOpen' && closeType === 'HTMLSelfClosingTagClose') {
return this.selfClosingTag
}
return null
}
}, options)
}
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'require or disallow a space before tag\'s closing brackets',
category: 'strongly-recommended',
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.0/docs/rules/html-closing-bracket-spacing.md'
},
schema: [{
type: 'object',
properties: {
startTag: { enum: ['always', 'never'] },
endTag: { enum: ['always', 'never'] },
selfClosingTag: { enum: ['always', 'never'] }
},
additionalProperties: false
}],
fixable: 'whitespace'
},
create (context) {
const sourceCode = context.getSourceCode()
const tokens =
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()
const options = parseOptions(context.options[0], tokens)
return utils.defineTemplateBodyVisitor(context, {
'VStartTag, VEndTag' (node) {
const type = options.detectType(node)
const lastToken = tokens.getLastToken(node)
const prevToken = tokens.getLastToken(node, 1)
// Skip if EOF exists in the tag or linebreak exists before `>`.
if (type == null || prevToken == null || prevToken.loc.end.line !== lastToken.loc.start.line) {
return
}
// Check and report.
const hasSpace = (prevToken.range[1] !== lastToken.range[0])
if (type === 'always' && !hasSpace) {
context.report({
node,
loc: lastToken.loc,
message: "Expected a space before '{{bracket}}', but not found.",
data: { bracket: sourceCode.getText(lastToken) },
fix: (fixer) => fixer.insertTextBefore(lastToken, ' ')
})
} else if (type === 'never' && hasSpace) {
context.report({
node,
loc: {
start: prevToken.loc.end,
end: lastToken.loc.end
},
message: "Expected no space before '{{bracket}}', but found.",
data: { bracket: sourceCode.getText(lastToken) },
fix: (fixer) => fixer.removeRange([prevToken.range[1], lastToken.range[0]])
})
}
}
})
}
}