require-unicode-regexp.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * @fileoverview Rule to enforce the use of `u` flag on RegExp.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const {
  10. CALL,
  11. CONSTRUCT,
  12. ReferenceTracker,
  13. getStringIfConstant
  14. } = require("@eslint-community/eslint-utils");
  15. const astUtils = require("./utils/ast-utils.js");
  16. const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
  17. //------------------------------------------------------------------------------
  18. // Rule Definition
  19. //------------------------------------------------------------------------------
  20. /** @type {import('../shared/types').Rule} */
  21. module.exports = {
  22. meta: {
  23. type: "suggestion",
  24. docs: {
  25. description: "Enforce the use of `u` flag on RegExp",
  26. recommended: false,
  27. url: "https://eslint.org/docs/rules/require-unicode-regexp"
  28. },
  29. hasSuggestions: true,
  30. messages: {
  31. addUFlag: "Add the 'u' flag.",
  32. requireUFlag: "Use the 'u' flag."
  33. },
  34. schema: []
  35. },
  36. create(context) {
  37. const sourceCode = context.getSourceCode();
  38. return {
  39. "Literal[regex]"(node) {
  40. const flags = node.regex.flags || "";
  41. if (!flags.includes("u")) {
  42. context.report({
  43. messageId: "requireUFlag",
  44. node,
  45. suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)
  46. ? [
  47. {
  48. fix(fixer) {
  49. return fixer.insertTextAfter(node, "u");
  50. },
  51. messageId: "addUFlag"
  52. }
  53. ]
  54. : null
  55. });
  56. }
  57. },
  58. Program(node) {
  59. const scope = sourceCode.getScope(node);
  60. const tracker = new ReferenceTracker(scope);
  61. const trackMap = {
  62. RegExp: { [CALL]: true, [CONSTRUCT]: true }
  63. };
  64. for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) {
  65. const [patternNode, flagsNode] = refNode.arguments;
  66. const pattern = getStringIfConstant(patternNode, scope);
  67. const flags = getStringIfConstant(flagsNode, scope);
  68. if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) {
  69. context.report({
  70. messageId: "requireUFlag",
  71. node: refNode,
  72. suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)
  73. ? [
  74. {
  75. fix(fixer) {
  76. if (flagsNode) {
  77. if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
  78. const flagsNodeText = sourceCode.getText(flagsNode);
  79. return fixer.replaceText(flagsNode, [
  80. flagsNodeText.slice(0, flagsNodeText.length - 1),
  81. flagsNodeText.slice(flagsNodeText.length - 1)
  82. ].join("u"));
  83. }
  84. // We intentionally don't suggest concatenating + "u" to non-literals
  85. return null;
  86. }
  87. const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis
  88. return fixer.insertTextAfter(
  89. penultimateToken,
  90. astUtils.isCommaToken(penultimateToken)
  91. ? ' "u",'
  92. : ', "u"'
  93. );
  94. },
  95. messageId: "addUFlag"
  96. }
  97. ]
  98. : null
  99. });
  100. }
  101. }
  102. }
  103. };
  104. }
  105. };