UNPKG

@yusufkandemir/eslint-plugin-lodash-template

Version:

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

177 lines (165 loc) 6.41 kB
"use strict"; const { getSourceCode } = require("eslint-compat-utils"); /** * Get the messageId for before tag open * @param {number} expectedSpaces The number of expected spaces * @param {number} actualSpaces The number of actual spaces * @returns {string} The messageId */ function getMessageIdForTagOpen(expectedSpaces, actualSpaces) { if (expectedSpaces === 0) { if (actualSpaces === 1) { return "unexpectedAfterTagOpen"; } return "expectedNoSpacesAfterTagOpenButNSpaces"; } if (actualSpaces === 0) { return "missingAfterTagOpen"; } return "expectedOneSpaceAfterTagOpenButNSpaces"; } /** * Get the messageId for after tag close * @param {number} expectedSpaces The number of expected spaces * @param {number} actualSpaces The number of actual spaces * @returns {string} The messageId */ function getMessageIdForTagClose(expectedSpaces, actualSpaces) { if (expectedSpaces === 0) { if (actualSpaces === 1) { return "unexpectedBeforeTagClose"; } return "expectedNoSpacesBeforeTagCloseButNSpaces"; } if (actualSpaces === 0) { return "missingBeforeTagClose"; } return "expectedOneSpaceBeforeTagCloseButNSpaces"; } module.exports = { meta: { docs: { description: "enforce unified spacing in micro-template tag. (ex. :ok: `<%= prop %>`, :ng: `<%=prop%>`)", category: "recommended", url: "https://ota-meshi.github.io/eslint-plugin-lodash-template/rules/template-tag-spacing.html", }, fixable: "whitespace", messages: { unexpectedAfterTagOpen: "Expected no spaces after `{{chars}}`, but 1 space found.", expectedNoSpacesAfterTagOpenButNSpaces: "Expected no spaces after `{{chars}}`, but {{n}} spaces found.", missingAfterTagOpen: "Expected 1 space after `{{chars}}`, but no spaces found.", expectedOneSpaceAfterTagOpenButNSpaces: "Expected 1 space after `{{chars}}`, but {{n}} spaces found.", unexpectedBeforeTagClose: "Expected no spaces before `{{chars}}`, but 1 space found.", expectedNoSpacesBeforeTagCloseButNSpaces: "Expected no spaces before `{{chars}}`, but {{n}} spaces found.", missingBeforeTagClose: "Expected 1 space before `{{chars}}`, but no spaces found.", expectedOneSpaceBeforeTagCloseButNSpaces: "Expected 1 space before `{{chars}}`, but {{n}} spaces found.", }, schema: [ { enum: ["always", "never"], }, ], type: "layout", }, create(context) { const sourceCode = getSourceCode(context); if (!sourceCode.parserServices.getMicroTemplateService) { return {}; } const options = context.options[0] || "always"; const microTemplateService = sourceCode.parserServices.getMicroTemplateService(); /** * Check whether the line numbers in the given indices are equal. * @param {number} index1 The index * @param {number} index2 The index * @returns {boolean} `true` if the line numbers in the indices are equal. */ function equalLine(index1, index2) { return ( sourceCode.getLocFromIndex(index1).line === sourceCode.getLocFromIndex(index2).line ); } /** * process micro-template node * @param {ASTNode} node The AST node. * @returns {void} undefined. */ function processNode(node) { const openBrace = node.expressionStart; const closeBrace = node.expressionEnd; const interpolate = node.code; const entity = interpolate.trim(); if (!entity) { return; } const firstCharIndex = openBrace.range[1] + interpolate.indexOf(entity); const lastCharIndex = firstCharIndex + entity.length; const actualOpenSpaces = firstCharIndex - openBrace.range[1]; const actualCloseSpaces = closeBrace.range[0] - lastCharIndex; const expectedSpaces = options === "always" ? 1 : 0; if ( expectedSpaces !== actualOpenSpaces && equalLine(openBrace.range[1], firstCharIndex) ) { context.report({ node: openBrace, messageId: getMessageIdForTagOpen( expectedSpaces, actualOpenSpaces, ), data: { chars: openBrace.chars, n: actualOpenSpaces, }, fix(fixer) { const range = [openBrace.range[1], firstCharIndex]; const text = " ".repeat(expectedSpaces); return fixer.replaceTextRange(range, text); }, }); } if ( expectedSpaces !== actualCloseSpaces && equalLine(lastCharIndex, closeBrace.range[0]) ) { context.report({ node: closeBrace, messageId: getMessageIdForTagClose( expectedSpaces, actualCloseSpaces, ), data: { chars: closeBrace.chars, n: actualCloseSpaces, }, fix(fixer) { const range = [lastCharIndex, closeBrace.range[0]]; const text = " ".repeat(expectedSpaces); return fixer.replaceTextRange(range, text); }, }); } } return { "Program:exit"() { microTemplateService.traverseMicroTemplates({ MicroTemplateEscape: processNode, MicroTemplateInterpolate: processNode, MicroTemplateEvaluate: processNode, }); }, }; }, };