no-control-regex.js 3.1 KB

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