123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- /**
- * @author Toru Nagashima
- * @copyright 2016 Toru Nagashima. All rights reserved.
- * See LICENSE file in root directory for full license.
- */
- 'use strict'
- const utils = require('../utils')
- /**
- * @param {number} lineBreaks
- */
- function getPhrase(lineBreaks) {
- switch (lineBreaks) {
- case 0: {
- return 'no line breaks'
- }
- case 1: {
- return '1 line break'
- }
- default: {
- return `${lineBreaks} line breaks`
- }
- }
- }
- /**
- * @typedef LineBreakBehavior
- * @type {('always'|'never')}
- */
- /**
- * @typedef LineType
- * @type {('singleline'|'multiline')}
- */
- /**
- * @typedef RuleOptions
- * @type {object}
- * @property {LineBreakBehavior} singleline - The behavior for single line tags.
- * @property {LineBreakBehavior} multiline - The behavior for multiline tags.
- * @property {object} selfClosingTag
- * @property {LineBreakBehavior} selfClosingTag.singleline - The behavior for single line self closing tags.
- * @property {LineBreakBehavior} selfClosingTag.multiline - The behavior for multiline self closing tags.
- */
- /**
- * @param {VStartTag | VEndTag} node - The node representing a start or end tag.
- * @param {RuleOptions} options - The options for line breaks.
- * @param {LineType} type - The type of line break.
- * @returns {number} - The expected line breaks.
- */
- function getExpectedLineBreaks(node, options, type) {
- const isSelfClosingTag = node.type === 'VStartTag' && node.selfClosing
- if (
- isSelfClosingTag &&
- options.selfClosingTag &&
- options.selfClosingTag[type]
- ) {
- return options.selfClosingTag[type] === 'always' ? 1 : 0
- }
- return options[type] === 'always' ? 1 : 0
- }
- module.exports = {
- meta: {
- type: 'layout',
- docs: {
- description:
- "require or disallow a line break before tag's closing brackets",
- categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
- url: 'https://eslint.vuejs.org/rules/html-closing-bracket-newline.html'
- },
- fixable: 'whitespace',
- schema: [
- {
- type: 'object',
- properties: {
- singleline: { enum: ['always', 'never'] },
- multiline: { enum: ['always', 'never'] },
- selfClosingTag: {
- type: 'object',
- properties: {
- singleline: { enum: ['always', 'never'] },
- multiline: { enum: ['always', 'never'] }
- },
- additionalProperties: false,
- minProperties: 1
- }
- },
- additionalProperties: false
- }
- ],
- messages: {
- expectedBeforeClosingBracket:
- 'Expected {{expected}} before closing bracket, but {{actual}} found.'
- }
- },
- /** @param {RuleContext} context */
- create(context) {
- const options = Object.assign(
- {},
- {
- singleline: 'never',
- multiline: 'always'
- },
- context.options[0] || {}
- )
- const sourceCode = context.getSourceCode()
- const template =
- sourceCode.parserServices.getTemplateBodyTokenStore &&
- sourceCode.parserServices.getTemplateBodyTokenStore()
- return utils.defineDocumentVisitor(context, {
- /** @param {VStartTag | VEndTag} node */
- 'VStartTag, VEndTag'(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 = getExpectedLineBreaks(node, options, type)
- 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
- },
- messageId: 'expectedBeforeClosingBracket',
- 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)
- }
- })
- }
- }
- })
- }
- }
|