no-control-regex.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /**
  2. * @fileoverview Rule to forbid control characters from regular expressions.
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. const RegExpValidator = require("regexpp").RegExpValidator;
  7. const collector = new (class {
  8. constructor() {
  9. this.ecmaVersion = 2018;
  10. this._source = "";
  11. this._controlChars = [];
  12. this._validator = new RegExpValidator(this);
  13. }
  14. onPatternEnter() {
  15. this._controlChars = [];
  16. }
  17. onCharacter(start, end, cp) {
  18. if (cp >= 0x00 &&
  19. cp <= 0x1F &&
  20. (
  21. this._source.codePointAt(start) === cp ||
  22. this._source.slice(start, end).startsWith("\\x") ||
  23. this._source.slice(start, end).startsWith("\\u")
  24. )
  25. ) {
  26. this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`);
  27. }
  28. }
  29. collectControlChars(regexpStr) {
  30. try {
  31. this._source = regexpStr;
  32. this._validator.validatePattern(regexpStr); // Call onCharacter hook
  33. } catch {
  34. // Ignore syntax errors in RegExp.
  35. }
  36. return this._controlChars;
  37. }
  38. })();
  39. //------------------------------------------------------------------------------
  40. // Rule Definition
  41. //------------------------------------------------------------------------------
  42. module.exports = {
  43. meta: {
  44. type: "problem",
  45. docs: {
  46. description: "disallow control characters in regular expressions",
  47. category: "Possible Errors",
  48. recommended: true,
  49. url: "https://eslint.org/docs/rules/no-control-regex"
  50. },
  51. schema: [],
  52. messages: {
  53. unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}."
  54. }
  55. },
  56. create(context) {
  57. /**
  58. * Get the regex expression
  59. * @param {ASTNode} node node to evaluate
  60. * @returns {RegExp|null} Regex if found else null
  61. * @private
  62. */
  63. function getRegExpPattern(node) {
  64. if (node.regex) {
  65. return node.regex.pattern;
  66. }
  67. if (typeof node.value === "string" &&
  68. (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") &&
  69. node.parent.callee.type === "Identifier" &&
  70. node.parent.callee.name === "RegExp" &&
  71. node.parent.arguments[0] === node
  72. ) {
  73. return node.value;
  74. }
  75. return null;
  76. }
  77. return {
  78. Literal(node) {
  79. const pattern = getRegExpPattern(node);
  80. if (pattern) {
  81. const controlCharacters = collector.collectControlChars(pattern);
  82. if (controlCharacters.length > 0) {
  83. context.report({
  84. node,
  85. messageId: "unexpected",
  86. data: {
  87. controlChars: controlCharacters.join(", ")
  88. }
  89. });
  90. }
  91. }
  92. }
  93. };
  94. }
  95. };