@yusufkandemir/eslint-plugin-lodash-template
Version:
ESLint plugin for John Resig-style micro template, Lodash's template, Underscore's template and EJS.
211 lines (191 loc) • 7.68 kB
JavaScript
const { getSourceCode } = require("eslint-compat-utils");
module.exports = {
meta: {
docs: {
description:
"disallow multiple spaces in scriptlet. (ex. :ng: `<% if···(test)···{ %>`)",
category: "recommended",
url: "https://ota-meshi.github.io/eslint-plugin-lodash-template/rules/no-multi-spaces-in-scriptlet.html",
},
fixable: "whitespace",
messages: {
unexpected: "Multiple spaces found before `{{displayValue}}`.",
},
schema: [
{
type: "object",
properties: {
exceptions: {
type: "object",
patternProperties: {
"^([A-Z][a-z]*)+$": {
type: "boolean",
},
},
additionalProperties: false,
},
ignoreEOLComments: {
type: "boolean",
},
},
additionalProperties: false,
},
],
type: "layout",
},
create(context) {
const sourceCode = getSourceCode(context);
if (!sourceCode.parserServices.getMicroTemplateService) {
return {};
}
const options = context.options[0] || {};
const ignoreEOLComments = options.ignoreEOLComments;
const exceptions = Object.assign(
{ Property: true },
options.exceptions,
);
const hasExceptions =
Object.keys(exceptions).filter((key) => exceptions[key]).length > 0;
const microTemplateService =
sourceCode.parserServices.getMicroTemplateService();
/**
* Formats value of given comment token for error message by truncating its length.
* @param {Token} token comment token
* @returns {string} formatted value
* @private
*/
function formatReportedCommentValue(token) {
const valueLines = token.value.split("\n");
const value = valueLines[0];
const formattedValue = `${value.slice(0, 12)}...`;
return valueLines.length === 1 && value.length <= 12
? value
: formattedValue;
}
/**
* Get the template tag token containing a script.
* @param {Token} token The script token.
* @returns {Token} The token if found or null if not found.
*/
function getTemplateTagByToken(token) {
return microTemplateService
.getMicroTemplateTokens()
.find(
(t) =>
t.expressionStart.range[1] <= token.range[0] &&
token.range[0] < t.expressionEnd.range[0],
);
}
/**
* Checks if the given token is a comment token or not.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a comment token.
*/
function isCommentToken(token) {
return (
token.type === "Line" ||
token.type === "Block" ||
token.type === "Shebang"
);
}
/**
* Checks if the given token is ignore or not.
*
* @param {Token} leftToken - The token to check.
* @param {number} leftIndex - The index of left token.
* @param {Token} rightToken - The token to check.
* @returns {boolean} `true` if the token is ignore.
*/
function isIgnores(leftToken, leftIndex, rightToken) {
const tokensAndComments = sourceCode.tokensAndComments;
const leftTemplateTag = getTemplateTagByToken(leftToken);
const rightTemplateTag = getTemplateTagByToken(rightToken);
if (!leftTemplateTag || !rightTemplateTag) {
return true;
}
// Ignore if token is the first token of the template tag script
if (
!sourceCode.text
.slice(
rightTemplateTag.expressionStart.range[1],
rightToken.range[0],
)
.trim()
) {
return true;
}
// Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
if (
ignoreEOLComments &&
isCommentToken(rightToken) &&
(leftIndex === tokensAndComments.length - 2 ||
rightToken.loc.end.line <
tokensAndComments[leftIndex + 2].loc.start.line)
) {
return true;
}
// Ignore tokens that are in a node in the "exceptions" object
if (hasExceptions) {
const parentNode = sourceCode.getNodeByRangeIndex(
rightToken.range[0] - 1,
);
if (parentNode && exceptions[parentNode.type]) {
return true;
}
}
return false;
}
return {
"Program:exit"() {
sourceCode.tokensAndComments.forEach(
(leftToken, leftIndex, tokensAndComments) => {
if (leftIndex === tokensAndComments.length - 1) {
return;
}
const rightToken = tokensAndComments[leftIndex + 1];
// Ignore tokens that don't have 2 spaces between them or are on different lines
if (
!sourceCode.text
.slice(leftToken.range[1], rightToken.range[0])
.includes(" ") ||
leftToken.loc.end.line < rightToken.loc.start.line
) {
return;
}
if (isIgnores(leftToken, leftIndex, rightToken)) {
return;
}
let displayValue = undefined;
if (rightToken.type === "Block") {
displayValue = `/*${formatReportedCommentValue(
rightToken,
)}*/`;
} else if (rightToken.type === "Line") {
displayValue = `//${formatReportedCommentValue(
rightToken,
)}`;
} else {
displayValue = rightToken.value;
}
context.report({
node: rightToken,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start,
},
messageId: "unexpected",
data: { displayValue },
fix: (fixer) =>
fixer.replaceTextRange(
[leftToken.range[1], rightToken.range[0]],
" ",
),
});
},
);
},
};
},
};
;