@yusufkandemir/eslint-plugin-lodash-template
Version:
ESLint plugin for John Resig-style micro template, Lodash's template, Underscore's template and EJS.
152 lines (133 loc) • 5.36 kB
JavaScript
const utils = require("../utils");
const { getSourceCode } = require("eslint-compat-utils");
/**
* Returns whether the provided node is an ESLint directive comment or not
* @param {HTMLComment} node The comment token to be checked
* @returns {boolean} `true` if the node is an ESLint directive comment
*/
function isDirectiveComment(node) {
const comment = node.value.trim();
return comment.indexOf("eslint-") === 0;
}
module.exports = {
meta: {
docs: {
description:
"disallow specified warning terms in HTML comments. (ex. :ng: `<!-- TODO:task -->`)",
category: "recommended-with-html",
url: "https://ota-meshi.github.io/eslint-plugin-lodash-template/rules/no-warning-html-comments.html",
},
messages: {
unexpected: "Unexpected '{{matchedTerm}}' comment.",
},
schema: [
{
type: "object",
properties: {
terms: {
type: "array",
items: {
type: "string",
},
},
location: {
enum: ["start", "anywhere"],
},
},
additionalProperties: false,
},
],
type: "suggestion",
},
create(context) {
const sourceCode = getSourceCode(context);
if (!sourceCode.parserServices.getMicroTemplateService) {
return {};
}
if (!utils.isHtmlFile(context.getFilename())) {
return {};
}
const configuration = context.options[0] || {};
const warningTerms = configuration.terms || ["todo", "fixme", "xxx"];
const location = configuration.location || "anywhere";
const selfConfigRegEx = /\b[-a-z]+\/no-warning-html-comments\b/u;
/**
* Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
* location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
* require word boundaries on that side.
*
* @param {string} term A term to convert to a RegExp
* @returns {RegExp} The term converted to a RegExp
*/
function convertToRegExp(term) {
const escaped = term.replace(/[$\-/?[-^{-}]/gu, "\\$&");
let prefix = undefined;
// If the term ends in a word character (a-z0-9_), ensure a word
// boundary at the end, so that substrings do not get falsely
// matched. eg "todo" in a string such as "mastodon".
// If the term ends in a non-word character, then \b won't match on
// the boundary to the next non-word character, which would likely
// be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`.
// In these cases, use no bounding match. Same applies for the
// prefix, handled below.
const suffix = /\w$/u.test(term) ? "\\b" : "";
if (location === "start") {
// When matching at the start, ignore leading whitespace, and
// there's no need to worry about word boundaries.
prefix = "^\\s*";
} else if (/^\w/u.test(term)) {
prefix = "\\b";
} else {
prefix = "";
}
return new RegExp(prefix + escaped + suffix, "iu");
}
const warningRegExps = warningTerms.map(convertToRegExp);
/**
* Checks the specified comment for matches of the configured warning terms and returns the matches.
* @param {string} comment The comment which is checked.
* @returns {Array} All matched warning terms for this comment.
*/
function commentContainsWarningTerm(comment) {
const matches = [];
warningRegExps.forEach((regex, index) => {
if (regex.test(comment)) {
matches.push(warningTerms[index]);
}
});
return matches;
}
/**
* Checks the specified node for matching warning comments and reports them.
* @param {ASTNode} node The AST node being checked.
* @returns {void} undefined.
*/
function checkComment(node) {
if (isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
return;
}
const matches = commentContainsWarningTerm(node.value);
for (const matchedTerm of matches) {
context.report({
node,
messageId: "unexpected",
data: {
matchedTerm,
},
});
}
}
const microTemplateService =
sourceCode.parserServices.getMicroTemplateService();
return {
"Program:exit"() {
microTemplateService.traverseDocumentNodes({
HTMLComment(comment) {
checkComment(comment);
},
});
},
};
},
};
;