prefer-regex-literals.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * @fileoverview Rule to disallow use of the `RegExp` constructor in favor of regular expression literals
  3. * @author Milos Djermanovic
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. /**
  15. * Determines whether the given node is a string literal.
  16. * @param {ASTNode} node Node to check.
  17. * @returns {boolean} True if the node is a string literal.
  18. */
  19. function isStringLiteral(node) {
  20. return node.type === "Literal" && typeof node.value === "string";
  21. }
  22. /**
  23. * Determines whether the given node is a template literal without expressions.
  24. * @param {ASTNode} node Node to check.
  25. * @returns {boolean} True if the node is a template literal without expressions.
  26. */
  27. function isStaticTemplateLiteral(node) {
  28. return node.type === "TemplateLiteral" && node.expressions.length === 0;
  29. }
  30. //------------------------------------------------------------------------------
  31. // Rule Definition
  32. //------------------------------------------------------------------------------
  33. module.exports = {
  34. meta: {
  35. type: "suggestion",
  36. docs: {
  37. description: "disallow use of the `RegExp` constructor in favor of regular expression literals",
  38. category: "Best Practices",
  39. recommended: false,
  40. url: "https://eslint.org/docs/rules/prefer-regex-literals"
  41. },
  42. schema: [],
  43. messages: {
  44. unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
  45. }
  46. },
  47. create(context) {
  48. /**
  49. * Determines whether the given identifier node is a reference to a global variable.
  50. * @param {ASTNode} node `Identifier` node to check.
  51. * @returns {boolean} True if the identifier is a reference to a global variable.
  52. */
  53. function isGlobalReference(node) {
  54. const scope = context.getScope();
  55. const variable = findVariable(scope, node);
  56. return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
  57. }
  58. /**
  59. * Determines whether the given node is a String.raw`` tagged template expression
  60. * with a static template literal.
  61. * @param {ASTNode} node Node to check.
  62. * @returns {boolean} True if the node is String.raw`` with a static template.
  63. */
  64. function isStringRawTaggedStaticTemplateLiteral(node) {
  65. return node.type === "TaggedTemplateExpression" &&
  66. node.tag.type === "MemberExpression" &&
  67. node.tag.object.type === "Identifier" &&
  68. node.tag.object.name === "String" &&
  69. isGlobalReference(node.tag.object) &&
  70. astUtils.getStaticPropertyName(node.tag) === "raw" &&
  71. isStaticTemplateLiteral(node.quasi);
  72. }
  73. /**
  74. * Determines whether the given node is considered to be a static string by the logic of this rule.
  75. * @param {ASTNode} node Node to check.
  76. * @returns {boolean} True if the node is a static string.
  77. */
  78. function isStaticString(node) {
  79. return isStringLiteral(node) ||
  80. isStaticTemplateLiteral(node) ||
  81. isStringRawTaggedStaticTemplateLiteral(node);
  82. }
  83. return {
  84. Program() {
  85. const scope = context.getScope();
  86. const tracker = new ReferenceTracker(scope);
  87. const traceMap = {
  88. RegExp: {
  89. [CALL]: true,
  90. [CONSTRUCT]: true
  91. }
  92. };
  93. for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
  94. const args = node.arguments;
  95. if (
  96. (args.length === 1 || args.length === 2) &&
  97. args.every(isStaticString)
  98. ) {
  99. context.report({ node, messageId: "unexpectedRegExp" });
  100. }
  101. }
  102. }
  103. };
  104. }
  105. };