123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- /**
- * @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: {
- type: 'layout',
- docs: {
- description: 'require or disallow a space before tag\'s closing brackets',
- category: 'strongly-recommended',
- url: 'https://eslint.vuejs.org/rules/html-closing-bracket-spacing.html'
- },
- 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]])
- })
- }
- }
- })
- }
- }
|