2 * @fileoverview Rule that warns about used warning comments
3 * @author Alexander Schmidt <https://github.com/lxanders>
8 const { escapeRegExp } = require("lodash");
9 const astUtils = require("./utils/ast-utils");
11 //------------------------------------------------------------------------------
13 //------------------------------------------------------------------------------
20 description: "disallow specified warning terms in comments",
21 category: "Best Practices",
23 url: "https://eslint.org/docs/rules/no-warning-comments"
37 enum: ["start", "anywhere"]
40 additionalProperties: false
47 const sourceCode = context.getSourceCode(),
48 configuration = context.options[0] || {},
49 warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
50 location = configuration.location || "start",
51 selfConfigRegEx = /\bno-warning-comments\b/u;
54 * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
55 * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
56 * require word boundaries on that side.
57 * @param {string} term A term to convert to a RegExp
58 * @returns {RegExp} The term converted to a RegExp
60 function convertToRegExp(term) {
61 const escaped = escapeRegExp(term);
62 const wordBoundary = "\\b";
63 const eitherOrWordBoundary = `|${wordBoundary}`;
67 * If the term ends in a word character (a-z0-9_), ensure a word
68 * boundary at the end, so that substrings do not get falsely
69 * matched. eg "todo" in a string such as "mastodon".
70 * If the term ends in a non-word character, then \b won't match on
71 * the boundary to the next non-word character, which would likely
72 * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`.
73 * In these cases, use no bounding match. Same applies for the
74 * prefix, handled below.
76 const suffix = /\w$/u.test(term) ? "\\b" : "";
78 if (location === "start") {
81 * When matching at the start, ignore leading whitespace, and
82 * there's no need to worry about word boundaries.
85 } else if (/^\w/u.test(term)) {
86 prefix = wordBoundary;
91 if (location === "start") {
94 * For location "start" the regex should be
95 * ^\s*TERM\b. This checks the word boundary
96 * at the beginning of the comment.
98 return new RegExp(prefix + escaped + suffix, "iu");
102 * For location "anywhere" the regex should be
103 * \bTERM\b|\bTERM\b, this checks the entire comment
106 return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu");
109 const warningRegExps = warningTerms.map(convertToRegExp);
112 * Checks the specified comment for matches of the configured warning terms and returns the matches.
113 * @param {string} comment The comment which is checked.
114 * @returns {Array} All matched warning terms for this comment.
116 function commentContainsWarningTerm(comment) {
119 warningRegExps.forEach((regex, index) => {
120 if (regex.test(comment)) {
121 matches.push(warningTerms[index]);
129 * Checks the specified node for matching warning comments and reports them.
130 * @param {ASTNode} node The AST node being checked.
131 * @returns {void} undefined.
133 function checkComment(node) {
134 if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
138 const matches = commentContainsWarningTerm(node.value);
140 matches.forEach(matchedTerm => {
143 message: "Unexpected '{{matchedTerm}}' comment.",
153 const comments = sourceCode.getAllComments();
155 comments.filter(token => token.type !== "Shebang").forEach(checkComment);