From c660368ca310ada099ed3d060693c1241d213ed6 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Sat, 7 May 2022 16:42:31 +0200 Subject: [PATCH 1/3] Enable `vue/html-closing-bracket-*` for top-level tags --- lib/rules/block-tag-newline.js | 13 +- lib/rules/html-closing-bracket-newline.js | 111 ++++++++----- lib/rules/html-closing-bracket-spacing.js | 110 ++++++++----- .../lib/rules/html-closing-bracket-newline.js | 150 ++++++++++++++++++ .../lib/rules/html-closing-bracket-spacing.js | 107 +++++++++++++ 5 files changed, 406 insertions(+), 85 deletions(-) diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js index 1fe427afd..8459f5760 100644 --- a/lib/rules/block-tag-newline.js +++ b/lib/rules/block-tag-newline.js @@ -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, {}, @@ -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) + } } } } diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js index 6963de8f1..7cf88028d 100644 --- a/lib/rules/html-closing-bracket-newline.js +++ b/lib/rules/html-closing-bracket-newline.js @@ -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( {}, { @@ -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( + 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) + } + } } } - }) + ) } } diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js index 95de6d01f..12e0df31e 100644 --- a/lib/rules/html-closing-bracket-spacing.js +++ b/lib/rules/html-closing-bracket-spacing.js @@ -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) + } + } } } - }) + ) } } diff --git a/tests/lib/rules/html-closing-bracket-newline.js b/tests/lib/rules/html-closing-bracket-newline.js index 4c07c80db..b72c46b97 100644 --- a/tests/lib/rules/html-closing-bracket-newline.js +++ b/tests/lib/rules/html-closing-bracket-newline.js @@ -337,6 +337,156 @@ tester.run('html-closing-bracket-newline', rule, { 'Expected 1 line break before closing bracket, but no line breaks found.', 'Expected 1 line break before closing bracket, but no line breaks found.' ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'never', + multiline: 'never' + } + ], + errors: [ + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 2, + column: 18, + endLine: 3, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 4, + column: 19, + endLine: 5, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 6, + column: 16, + endLine: 7, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 8, + column: 17, + endLine: 9, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 10, + column: 15, + endLine: 11, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 11, + column: 17, + endLine: 12, + endColumn: 9 + } + ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'always', + multiline: 'always' + } + ], + errors: [ + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 2, + column: 18, + endColumn: 18 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 3, + column: 19, + endColumn: 19 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 4, + column: 16, + endColumn: 16 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 5, + column: 17, + endColumn: 17 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 15, + endColumn: 15 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 23, + endColumn: 23 + } + ] } ] }) diff --git a/tests/lib/rules/html-closing-bracket-spacing.js b/tests/lib/rules/html-closing-bracket-spacing.js index 154ba87b2..af03d0bfd 100644 --- a/tests/lib/rules/html-closing-bracket-spacing.js +++ b/tests/lib/rules/html-closing-bracket-spacing.js @@ -114,6 +114,56 @@ ruleTester.run('html-closing-bracket-spacing', rule, { } ] }, + { + code: ` + + + + `, + output: ` + + + + `, + errors: [ + { + message: "Expected no space before '>', but found.", + line: 2, + column: 18, + endColumn: 20 + }, + { + message: "Expected no space before '>', but found.", + line: 2, + column: 30, + endColumn: 32 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 16, + endColumn: 18 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 26, + endColumn: 28 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 15, + endColumn: 17 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 24, + endColumn: 26 + } + ] + }, { code: '', output: '', @@ -144,6 +194,63 @@ ruleTester.run('html-closing-bracket-spacing', rule, { endColumn: 10 } ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + startTag: 'always', + endTag: 'always', + selfClosingTag: 'never' + } + ], + errors: [ + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 18, + endColumn: 19 + }, + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 29, + endColumn: 30 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 16, + endColumn: 17 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 25, + endColumn: 26 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 15, + endColumn: 16 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 23, + endColumn: 24 + } + ] } ] }) From a38798d129cdfb646078c89889d767fc5c9cbb20 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Mon, 9 May 2022 13:25:59 +0200 Subject: [PATCH 2/3] Switch to much simpler `defineDocumentVisitor` --- lib/rules/html-closing-bracket-newline.js | 111 ++++++++-------------- lib/rules/html-closing-bracket-spacing.js | 109 ++++++++------------- 2 files changed, 75 insertions(+), 145 deletions(-) diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js index 7cf88028d..9ac1d0f52 100644 --- a/lib/rules/html-closing-bracket-newline.js +++ b/lib/rules/html-closing-bracket-newline.js @@ -56,13 +56,6 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { - const df = - context.parserServices.getDocumentFragment && - context.parserServices.getDocumentFragment() - if (!df) { - return {} - } - const options = Object.assign( {}, { @@ -75,76 +68,48 @@ module.exports = { context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() - /** @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 - - return utils.defineTemplateBodyVisitor( - context, - { - /** @param {VStartTag | VEndTag} node */ - '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 - } + return utils.defineDocumentVisitor(context, { + /** @param {VStartTag | VEndTag} node */ + 'VStartTag, VEndTag'(node) { + const closingBracketToken = template.getLastToken(node) + if ( + closingBracketToken.type !== 'HTMLSelfClosingTagClose' && + closingBracketToken.type !== 'HTMLTagClose' + ) { + return + } - verify(element.startTag) + 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 (element.endTag !== null) { - verify(element.endTag) + 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) } - } + }) } } - ) + }) } } diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js index 12e0df31e..a5900e1d9 100644 --- a/lib/rules/html-closing-bracket-spacing.js +++ b/lib/rules/html-closing-bracket-spacing.js @@ -86,86 +86,51 @@ 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) - /** @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) + return utils.defineDocumentVisitor(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 + } - if (element.endTag !== null) { - verify(element.endTag) - } - } + // 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]]) + }) } } - ) + }) } } From 7a8e982bccbc3f508315a908c7f1a358177ecb2c Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Mon, 9 May 2022 13:26:53 +0200 Subject: [PATCH 3/3] Add type annotation again --- lib/rules/html-closing-bracket-spacing.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js index a5900e1d9..e60fba491 100644 --- a/lib/rules/html-closing-bracket-spacing.js +++ b/lib/rules/html-closing-bracket-spacing.js @@ -93,6 +93,7 @@ module.exports = { const options = parseOptions(context.options[0], tokens) return utils.defineDocumentVisitor(context, { + /** @param {VStartTag | VEndTag} node */ 'VStartTag, VEndTag'(node) { const type = options.detectType(node) const lastToken = tokens.getLastToken(node)