| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 | /** * @author Toru Nagashima * @copyright 2016 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */'use strict'// ------------------------------------------------------------------------------// Requirements// ------------------------------------------------------------------------------const utils = require('../utils')// ------------------------------------------------------------------------------// Helpers// ------------------------------------------------------------------------------/** * These strings wil be displayed in error messages. */const ELEMENT_TYPE = Object.freeze({  NORMAL: 'HTML elements',  VOID: 'HTML void elements',  COMPONENT: 'Vue.js custom components',  SVG: 'SVG elements',  MATH: 'MathML elements'})/** * Normalize the given options. * @param {Object|undefined} options The raw options object. * @returns {Object} Normalized options. */function parseOptions (options) {  return {    [ELEMENT_TYPE.NORMAL]: (options && options.html && options.html.normal) || 'always',    [ELEMENT_TYPE.VOID]: (options && options.html && options.html.void) || 'never',    [ELEMENT_TYPE.COMPONENT]: (options && options.html && options.html.component) || 'always',    [ELEMENT_TYPE.SVG]: (options && options.svg) || 'always',    [ELEMENT_TYPE.MATH]: (options && options.math) || 'always'  }}/** * Get the elementType of the given element. * @param {VElement} node The element node to get. * @returns {string} The elementType of the element. */function getElementType (node) {  if (utils.isCustomComponent(node)) {    return ELEMENT_TYPE.COMPONENT  }  if (utils.isHtmlElementNode(node)) {    if (utils.isHtmlVoidElementName(node.name)) {      return ELEMENT_TYPE.VOID    }    return ELEMENT_TYPE.NORMAL  }  if (utils.isSvgElementNode(node)) {    return ELEMENT_TYPE.SVG  }  if (utils.isMathMLElementNode(node)) {    return ELEMENT_TYPE.MATH  }  return 'unknown elements'}/** * Check whether the given element is empty or not. * This ignores whitespaces, doesn't ignore comments. * @param {VElement} node The element node to check. * @param {SourceCode} sourceCode The source code object of the current context. * @returns {boolean} `true` if the element is empty. */function isEmpty (node, sourceCode) {  const start = node.startTag.range[1]  const end = (node.endTag != null) ? node.endTag.range[0] : node.range[1]  return sourceCode.text.slice(start, end).trim() === ''}// ------------------------------------------------------------------------------// Rule Definition// ------------------------------------------------------------------------------module.exports = {  meta: {    docs: {      description: 'enforce self-closing style',      category: 'strongly-recommended',      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/html-self-closing.md'    },    fixable: 'code',    schema: {      definitions: {        optionValue: {          enum: ['always', 'never', 'any']        }      },      type: 'array',      items: [{        type: 'object',        properties: {          html: {            type: 'object',            properties: {              normal: { $ref: '#/definitions/optionValue' },              void: { $ref: '#/definitions/optionValue' },              component: { $ref: '#/definitions/optionValue' }            },            additionalProperties: false          },          svg: { $ref: '#/definitions/optionValue' },          math: { $ref: '#/definitions/optionValue' }        },        additionalProperties: false      }],      maxItems: 1    }  },  create (context) {    const sourceCode = context.getSourceCode()    const options = parseOptions(context.options[0])    let hasInvalidEOF = false    return utils.defineTemplateBodyVisitor(context, {      'VElement' (node) {        if (hasInvalidEOF) {          return        }        const elementType = getElementType(node)        const mode = options[elementType]        if (mode === 'always' && !node.startTag.selfClosing && isEmpty(node, sourceCode)) {          context.report({            node,            loc: node.loc,            message: 'Require self-closing on {{elementType}} (<{{name}}>).',            data: { elementType, name: node.rawName },            fix: (fixer) => {              const tokens = context.parserServices.getTemplateBodyTokenStore()              const close = tokens.getLastToken(node.startTag)              if (close.type !== 'HTMLTagClose') {                return null              }              return fixer.replaceTextRange([close.range[0], node.range[1]], '/>')            }          })        }        if (mode === 'never' && node.startTag.selfClosing) {          context.report({            node,            loc: node.loc,            message: 'Disallow self-closing on {{elementType}} (<{{name}}/>).',            data: { elementType, name: node.rawName },            fix: (fixer) => {              const tokens = context.parserServices.getTemplateBodyTokenStore()              const close = tokens.getLastToken(node.startTag)              if (close.type !== 'HTMLSelfClosingTagClose') {                return null              }              if (elementType === ELEMENT_TYPE.VOID) {                return fixer.replaceText(close, '>')              }              return fixer.replaceText(close, `></${node.rawName}>`)            }          })        }      }    }, {      Program (node) {        hasInvalidEOF = utils.hasInvalidEOF(node)      }    })  }}
 |