no-warning-comments.js 4.7 KB

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