UNPKG

eslint-plugin-san

Version:

Official ESLint plugin for San

118 lines (107 loc) 4.35 kB
/** * @author Toru Nagashima * @copyright 2016 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ 'use strict'; /* eslint-disable */ // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ const utils = require('../utils'); // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ /** * @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`; } } // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { meta: { type: 'layout', docs: { description: "require or disallow a line break before tag's closing brackets", categories: ['strongly-recommended'], url: 'https://ecomfe.github.io/eslint-plugin-san/rules/html-closing-bracket-newline.html' }, fixable: 'whitespace', schema: [ { type: 'object', properties: { singleline: {enum: ['always', 'never']}, multiline: {enum: ['always', 'never']} }, additionalProperties: false } ] }, /** @param {RuleContext} context */ create(context) { const options = Object.assign( {}, { singleline: 'never', multiline: 'always' }, context.options[0] || {} ); const template = 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; } 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]]; let text = ''; let startColumn = node.loc.start.column; if (typeof startColumn !== 'number' || isNaN(startColumn) || startColumn < 0) { startColumn = 0; } if (expectedLineBreaks === 1) { text = `\n${' '.repeat(startColumn)}` } return fixer.replaceTextRange(range, text); } }); } } }); } };