UNPKG

@yusufkandemir/eslint-plugin-lodash-template

Version:

ESLint plugin for John Resig-style micro template, Lodash's template, Underscore's template and EJS.

179 lines (162 loc) 5.95 kB
"use strict"; const utils = require("../utils"); const { getSourceCode } = require("eslint-compat-utils"); module.exports = { meta: { docs: { description: 'disallow multiple spaces in HTML tags. (ex. :ng: `<input···type="text">`)', category: "recommended-with-html", url: "https://ota-meshi.github.io/eslint-plugin-lodash-template/rules/no-multi-spaces-in-html-tag.html", }, fixable: "whitespace", messages: { unexpected: "Multiple spaces found before `{{displayValue}}`.", }, schema: [], type: "layout", }, create(context) { const sourceCode = getSourceCode(context); if (!sourceCode.parserServices.getMicroTemplateService) { return {}; } if (!utils.isHtmlFile(context.getFilename())) { return {}; } const microTemplateService = sourceCode.parserServices.getMicroTemplateService(); /** * Convert start tag to Tokens array * @param {HTMLStartTag} startTag The start tag * @returns {Array} Then tokens */ function startTagToTokens(startTag) { const tokens = [startTag.tagOpen]; tokens.push(...startTag.attributes); tokens.push(...startTag.ignoredAttributes); tokens.push(startTag.tagClose); return tokens .filter((t) => Boolean(t)) .sort((a, b) => a.range[0] - b.range[0]); } /** * Convert end tag to Tokens array * @param {HTMLEndTag} endTag The end tag * @returns {Array} Then tokens */ function endTagToTokens(endTag) { const tokens = [endTag.tagOpen, endTag.tagClose]; return tokens .filter((t) => Boolean(t)) .sort((a, b) => a.range[0] - b.range[0]); } /** * Define the function which fixes the problem. * @param {number} start The spaces location start. * @param {number} end The spaces location end. * @returns {function} The defined function. */ function defineFix(start, end) { return (fixer) => fixer.replaceTextRange([start, end], " "); } /** * Get the location intersection in template tags. * @param {number} start The location start. * @param {number} end The location end. * @returns {Array} the location intersection in template tags. */ function getIntersectionTemplateTags(start, end) { return microTemplateService .getMicroTemplateTokens() .filter( (token) => Math.max(start, token.range[0]) <= Math.min(end, token.range[1]), ) .sort((a, b) => a.range[0] - b.range[0]); } /** * Generate target tokens iterator * @param {Array} tokens The html tokens * @returns {object} The tokens data */ function* genTargetTokens(tokens) { let prevToken = tokens.shift(); for (const token of tokens) { const start = prevToken.range[1]; const end = token.range[0]; const tags = getIntersectionTemplateTags(start, end); for (const tag of tags) { yield { prevToken, token: tag, }; prevToken = tag; } yield { prevToken, token, }; prevToken = token; } } /** * Process tokens. * @param {Array} tokens The tokens. * @returns {void} */ function processTokens(tokens) { for (const tokenInfo of genTargetTokens(tokens)) { const prevToken = tokenInfo.prevToken; const token = tokenInfo.token; const start = prevToken.range[1]; const end = token.range[0]; const spaces = end - start; if ( spaces > 1 && token.loc.start.line === prevToken.loc.start.line && !sourceCode.text.slice(start, end).trim() ) { context.report({ node: token, loc: { start: prevToken.loc.end, end: token.loc.start, }, messageId: "unexpected", fix: defineFix(start, end), data: { displayValue: formatReportedHTMLToken(token), }, }); } } } /** * Formats value of given token for error message by first line. * @param {Token} token The token * @returns {string} formatted value * @private */ function formatReportedHTMLToken(token) { const valueLines = sourceCode.getText(token).split("\n"); const value = valueLines[0]; return value; } return { "Program:exit"() { microTemplateService.traverseDocumentNodes({ HTMLStartTag(node) { const tokens = startTagToTokens(node); processTokens(tokens); }, HTMLEndTag(node) { const tokens = endTagToTokens(node); processTokens(tokens); }, }); }, }; }, };