2 * @fileoverview Rule that warns about used warning comments
3 * @author Alexander Schmidt <https://github.com/lxanders>
8 const escapeRegExp = require("escape-string-regexp");
9 const astUtils = require("./utils/ast-utils");
11 const CHAR_LIMIT = 40;
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
22 description: "disallow specified warning terms in comments",
23 category: "Best Practices",
25 url: "https://eslint.org/docs/rules/no-warning-comments"
39 enum: ["start", "anywhere"]
42 additionalProperties: false
47 unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'."
52 const sourceCode = context.getSourceCode(),
53 configuration = context.options[0] || {},
54 warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
55 location = configuration.location || "start",
56 selfConfigRegEx = /\bno-warning-comments\b/u;
59 * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
60 * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
61 * require word boundaries on that side.
62 * @param {string} term A term to convert to a RegExp
63 * @returns {RegExp} The term converted to a RegExp
65 function convertToRegExp(term) {
66 const escaped = escapeRegExp(term);
67 const wordBoundary = "\\b";
68 const eitherOrWordBoundary = `|${wordBoundary}`;
72 * If the term ends in a word character (a-z0-9_), ensure a word
73 * boundary at the end, so that substrings do not get falsely
74 * matched. eg "todo" in a string such as "mastodon".
75 * If the term ends in a non-word character, then \b won't match on
76 * the boundary to the next non-word character, which would likely
77 * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`.
78 * In these cases, use no bounding match. Same applies for the
79 * prefix, handled below.
81 const suffix = /\w$/u.test(term) ? "\\b" : "";
83 if (location === "start") {
86 * When matching at the start, ignore leading whitespace, and
87 * there's no need to worry about word boundaries.
90 } else if (/^\w/u.test(term)) {
91 prefix = wordBoundary;
96 if (location === "start") {
99 * For location "start" the regex should be
100 * ^\s*TERM\b. This checks the word boundary
101 * at the beginning of the comment.
103 return new RegExp(prefix + escaped + suffix, "iu");
107 * For location "anywhere" the regex should be
108 * \bTERM\b|\bTERM\b, this checks the entire comment
115 eitherOrWordBoundary +
122 const warningRegExps = warningTerms.map(convertToRegExp);
125 * Checks the specified comment for matches of the configured warning terms and returns the matches.
126 * @param {string} comment The comment which is checked.
127 * @returns {Array} All matched warning terms for this comment.
129 function commentContainsWarningTerm(comment) {
132 warningRegExps.forEach((regex, index) => {
133 if (regex.test(comment)) {
134 matches.push(warningTerms[index]);
142 * Checks the specified node for matching warning comments and reports them.
143 * @param {ASTNode} node The AST node being checked.
144 * @returns {void} undefined.
146 function checkComment(node) {
147 const comment = node.value;
150 astUtils.isDirectiveComment(node) &&
151 selfConfigRegEx.test(comment)
156 const matches = commentContainsWarningTerm(comment);
158 matches.forEach(matchedTerm => {
159 let commentToDisplay = "";
160 let truncated = false;
162 for (const c of comment.trim().split(/\s+/u)) {
163 const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c;
165 if (tmp.length <= CHAR_LIMIT) {
166 commentToDisplay = tmp;
175 messageId: "unexpectedComment",
178 comment: `${commentToDisplay}${
179 truncated ? "..." : ""
188 const comments = sourceCode.getAllComments();
191 .filter(token => token.type !== "Shebang")
192 .forEach(checkComment);