UNPKG

eslint-plugin-san

Version:

Official ESLint plugin for San

131 lines (117 loc) 4.83 kB
/** * @author Toru Nagashima <https://github.com/mysticatea> */ 'use strict'; /* eslint-disable */ // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- const utils = require('../utils'); // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- /** * @typedef { {startTag?:"always"|"never",endTag?:"always"|"never",selfClosingTag?:"always"|"never"} } Options */ /** * Normalize options. * @param {Options} options The options user configured. * @param {ParserServices.TokenStore} tokens The token store of template body. * @returns {Options & { detectType: (node: VStartTag | VEndTag) => 'never' | 'always' | null }} The normalized options. */ function parseOptions(options, tokens) { const opts = Object.assign( { startTag: 'never', endTag: 'never', selfClosingTag: 'always' }, options ); return Object.assign(opts, { /** * @param {VStartTag | VEndTag} node * @returns {'never' | 'always' | null} */ detectType(node) { const openType = tokens.getFirstToken(node).type; const closeType = tokens.getLastToken(node).type; if (openType === 'HTMLEndTagOpen' && closeType === 'HTMLTagClose') { return opts.endTag; } if (openType === 'HTMLTagOpen' && closeType === 'HTMLTagClose') { return opts.startTag; } if (openType === 'HTMLTagOpen' && closeType === 'HTMLSelfClosingTagClose') { return opts.selfClosingTag; } return null; } }); } // ----------------------------------------------------------------------------- // Rule Definition // ----------------------------------------------------------------------------- module.exports = { meta: { type: 'layout', docs: { description: "require or disallow a space before tag's closing brackets", categories: ['strongly-recommended'], url: 'https://ecomfe.github.io/eslint-plugin-san/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' }, /** @param {RuleContext} context */ 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, { /** @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; } // 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]]) }); } } }); } };