1 | /**
|
2 | * @fileoverview Rule that warns about used warning comments
|
3 | * @author Alexander Schmidt <https://github.com/lxanders>
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | const astUtils = require("../ast-utils");
|
9 |
|
10 | //------------------------------------------------------------------------------
|
11 | // Rule Definition
|
12 | //------------------------------------------------------------------------------
|
13 |
|
14 | module.exports = {
|
15 | meta: {
|
16 | docs: {
|
17 | description: "disallow specified warning terms in comments",
|
18 | category: "Best Practices",
|
19 | recommended: false,
|
20 | url: "https://eslint.org/docs/rules/no-warning-comments"
|
21 | },
|
22 |
|
23 | schema: [
|
24 | {
|
25 | type: "object",
|
26 | properties: {
|
27 | terms: {
|
28 | type: "array",
|
29 | items: {
|
30 | type: "string"
|
31 | }
|
32 | },
|
33 | location: {
|
34 | enum: ["start", "anywhere"]
|
35 | }
|
36 | },
|
37 | additionalProperties: false
|
38 | }
|
39 | ]
|
40 | },
|
41 |
|
42 | create(context) {
|
43 |
|
44 | const sourceCode = context.getSourceCode(),
|
45 | configuration = context.options[0] || {},
|
46 | warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
|
47 | location = configuration.location || "start",
|
48 | selfConfigRegEx = /\bno-warning-comments\b/;
|
49 |
|
50 | /**
|
51 | * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
|
52 | * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
|
53 | * require word boundaries on that side.
|
54 | *
|
55 | * @param {string} term A term to convert to a RegExp
|
56 | * @returns {RegExp} The term converted to a RegExp
|
57 | */
|
58 | function convertToRegExp(term) {
|
59 | const escaped = term.replace(/[-/\\$^*+?.()|[\]{}]/g, "\\$&");
|
60 | let prefix;
|
61 |
|
62 | /*
|
63 | * If the term ends in a word character (a-z0-9_), ensure a word
|
64 | * boundary at the end, so that substrings do not get falsely
|
65 | * matched. eg "todo" in a string such as "mastodon".
|
66 | * If the term ends in a non-word character, then \b won't match on
|
67 | * the boundary to the next non-word character, which would likely
|
68 | * be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`.
|
69 | * In these cases, use no bounding match. Same applies for the
|
70 | * prefix, handled below.
|
71 | */
|
72 | const suffix = /\w$/.test(term) ? "\\b" : "";
|
73 |
|
74 | if (location === "start") {
|
75 |
|
76 | /*
|
77 | * When matching at the start, ignore leading whitespace, and
|
78 | * there's no need to worry about word boundaries.
|
79 | */
|
80 | prefix = "^\\s*";
|
81 | } else if (/^\w/.test(term)) {
|
82 | prefix = "\\b";
|
83 | } else {
|
84 | prefix = "";
|
85 | }
|
86 |
|
87 | return new RegExp(prefix + escaped + suffix, "i");
|
88 | }
|
89 |
|
90 | const warningRegExps = warningTerms.map(convertToRegExp);
|
91 |
|
92 | /**
|
93 | * Checks the specified comment for matches of the configured warning terms and returns the matches.
|
94 | * @param {string} comment The comment which is checked.
|
95 | * @returns {Array} All matched warning terms for this comment.
|
96 | */
|
97 | function commentContainsWarningTerm(comment) {
|
98 | const matches = [];
|
99 |
|
100 | warningRegExps.forEach((regex, index) => {
|
101 | if (regex.test(comment)) {
|
102 | matches.push(warningTerms[index]);
|
103 | }
|
104 | });
|
105 |
|
106 | return matches;
|
107 | }
|
108 |
|
109 | /**
|
110 | * Checks the specified node for matching warning comments and reports them.
|
111 | * @param {ASTNode} node The AST node being checked.
|
112 | * @returns {void} undefined.
|
113 | */
|
114 | function checkComment(node) {
|
115 | if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
|
116 | return;
|
117 | }
|
118 |
|
119 | const matches = commentContainsWarningTerm(node.value);
|
120 |
|
121 | matches.forEach(matchedTerm => {
|
122 | context.report({
|
123 | node,
|
124 | message: "Unexpected '{{matchedTerm}}' comment.",
|
125 | data: {
|
126 | matchedTerm
|
127 | }
|
128 | });
|
129 | });
|
130 | }
|
131 |
|
132 | return {
|
133 | Program() {
|
134 | const comments = sourceCode.getAllComments();
|
135 |
|
136 | comments.filter(token => token.type !== "Shebang").forEach(checkComment);
|
137 | }
|
138 | };
|
139 | }
|
140 | };
|