Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable vue/html-closing-bracket-* for top-level tags #1883

Merged
merged 3 commits into from May 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 4 additions & 9 deletions lib/rules/block-tag-newline.js
Expand Up @@ -347,13 +347,6 @@ module.exports = {

const verify = normalizeOptionValue(context.options[0])

/**
* @returns {VElement[]}
*/
function getTopLevelHTMLElements() {
return documentFragment.children.filter(utils.isVElement)
}

return utils.defineTemplateBodyVisitor(
context,
{},
Expand All @@ -364,8 +357,10 @@ module.exports = {
return
}

for (const element of getTopLevelHTMLElements()) {
verify(element)
for (const element of documentFragment.children) {
if (utils.isVElement(element)) {
verify(element)
}
}
}
}
Expand Down
111 changes: 73 additions & 38 deletions lib/rules/html-closing-bracket-newline.js
Expand Up @@ -56,6 +56,13 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
const df =
context.parserServices.getDocumentFragment &&
context.parserServices.getDocumentFragment()
if (!df) {
return {}
}

const options = Object.assign(
{},
{
Expand All @@ -68,48 +75,76 @@ module.exports = {
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()

return utils.defineTemplateBodyVisitor(context, {
/** @param {VStartTag | VEndTag} node */
'VStartTag, VEndTag'(node) {
const closingBracketToken = template.getLastToken(node)
if (
closingBracketToken.type !== 'HTMLSelfClosingTagClose' &&
closingBracketToken.type !== 'HTMLTagClose'
) {
return
}
/** @param {VStartTag | VEndTag} node */
function verify(node) {
const closingBracketToken = template.getLastToken(node)
if (
closingBracketToken.type !== 'HTMLSelfClosingTagClose' &&
closingBracketToken.type !== 'HTMLTagClose'
) {
return
}

const prevToken = template.getTokenBefore(closingBracketToken)
const type =
node.loc.start.line === prevToken.loc.end.line
? 'singleline'
: 'multiline'
const expectedLineBreaks = options[type] === 'always' ? 1 : 0
const actualLineBreaks =
closingBracketToken.loc.start.line - prevToken.loc.end.line

if (actualLineBreaks !== expectedLineBreaks) {
context.report({
node,
loc: {
start: prevToken.loc.end,
end: closingBracketToken.loc.start
},
message:
'Expected {{expected}} before closing bracket, but {{actual}} found.',
data: {
expected: getPhrase(expectedLineBreaks),
actual: getPhrase(actualLineBreaks)
},
fix(fixer) {
/** @type {Range} */
const range = [prevToken.range[1], closingBracketToken.range[0]]
const text = '\n'.repeat(expectedLineBreaks)
return fixer.replaceTextRange(range, text)
}
})
}
}

const documentFragment = df

const prevToken = template.getTokenBefore(closingBracketToken)
const type =
node.loc.start.line === prevToken.loc.end.line
? 'singleline'
: 'multiline'
const expectedLineBreaks = options[type] === 'always' ? 1 : 0
const actualLineBreaks =
closingBracketToken.loc.start.line - prevToken.loc.end.line
return utils.defineTemplateBodyVisitor(
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
context,
{
/** @param {VStartTag | VEndTag} node */
'VStartTag, VEndTag': verify
},
{
/** @param {Program} node */
Program(node) {
if (utils.hasInvalidEOF(node)) {
return
}

if (actualLineBreaks !== expectedLineBreaks) {
context.report({
node,
loc: {
start: prevToken.loc.end,
end: closingBracketToken.loc.start
},
message:
'Expected {{expected}} before closing bracket, but {{actual}} found.',
data: {
expected: getPhrase(expectedLineBreaks),
actual: getPhrase(actualLineBreaks)
},
fix(fixer) {
/** @type {Range} */
const range = [prevToken.range[1], closingBracketToken.range[0]]
const text = '\n'.repeat(expectedLineBreaks)
return fixer.replaceTextRange(range, text)
for (const element of documentFragment.children) {
if (!utils.isVElement(element) || element.name === 'template') {
continue
}
})

verify(element.startTag)

if (element.endTag !== null) {
verify(element.endTag)
}
}
}
}
})
)
}
}
110 changes: 72 additions & 38 deletions lib/rules/html-closing-bracket-spacing.js
Expand Up @@ -86,52 +86,86 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
const df =
context.parserServices.getDocumentFragment &&
context.parserServices.getDocumentFragment()
if (!df) {
return {}
}

const sourceCode = context.getSourceCode()
const tokens =
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()
const options = parseOptions(context.options[0], tokens)

return utils.defineTemplateBodyVisitor(context, {
/** @param {VStartTag | VEndTag} node */
'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
}
/** @param {VStartTag | VEndTag} node */
function verify(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]])
})
}
}

const documentFragment = df

return utils.defineTemplateBodyVisitor(
context,
{
'VStartTag, VEndTag': verify
},
{
/** @param {Program} node */
Program(node) {
if (utils.hasInvalidEOF(node)) {
return
}

for (const element of documentFragment.children) {
if (!utils.isVElement(element) || element.name === 'template') {
continue
}

verify(element.startTag)

// 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]])
})
if (element.endTag !== null) {
verify(element.endTag)
}
}
}
}
})
)
}
}