123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. 'use strict';
  2. var safe = require('safe-regex');
  3. var define = require('define-property');
  4. var extend = require('extend-shallow');
  5. var not = require('regex-not');
  6. var MAX_LENGTH = 1024 * 64;
  7. /**
  8. * Session cache
  9. */
  10. var cache = {};
  11. /**
  12. * Create a regular expression from the given `pattern` string.
  13. *
  14. * @param {String|RegExp} `pattern` Pattern can be a string or regular expression.
  15. * @param {Object} `options`
  16. * @return {RegExp}
  17. * @api public
  18. */
  19. module.exports = function(patterns, options) {
  20. if (!Array.isArray(patterns)) {
  21. return makeRe(patterns, options);
  22. }
  23. return makeRe(patterns.join('|'), options);
  24. };
  25. /**
  26. * Create a regular expression from the given `pattern` string.
  27. *
  28. * @param {String|RegExp} `pattern` Pattern can be a string or regular expression.
  29. * @param {Object} `options`
  30. * @return {RegExp}
  31. * @api public
  32. */
  33. function makeRe(pattern, options) {
  34. if (pattern instanceof RegExp) {
  35. return pattern;
  36. }
  37. if (typeof pattern !== 'string') {
  38. throw new TypeError('expected a string');
  39. }
  40. if (pattern.length > MAX_LENGTH) {
  41. throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters');
  42. }
  43. var key = pattern;
  44. // do this before shallow cloning options, it's a lot faster
  45. if (!options || (options && options.cache !== false)) {
  46. key = createKey(pattern, options);
  47. if (cache.hasOwnProperty(key)) {
  48. return cache[key];
  49. }
  50. }
  51. var opts = extend({}, options);
  52. if (opts.contains === true) {
  53. if (opts.negate === true) {
  54. opts.strictNegate = false;
  55. } else {
  56. opts.strict = false;
  57. }
  58. }
  59. if (opts.strict === false) {
  60. opts.strictOpen = false;
  61. opts.strictClose = false;
  62. }
  63. var open = opts.strictOpen !== false ? '^' : '';
  64. var close = opts.strictClose !== false ? '$' : '';
  65. var flags = opts.flags || '';
  66. var regex;
  67. if (opts.nocase === true && !/i/.test(flags)) {
  68. flags += 'i';
  69. }
  70. try {
  71. if (opts.negate || typeof opts.strictNegate === 'boolean') {
  72. pattern = not.create(pattern, opts);
  73. }
  74. var str = open + '(?:' + pattern + ')' + close;
  75. regex = new RegExp(str, flags);
  76. if (opts.safe === true && safe(regex) === false) {
  77. throw new Error('potentially unsafe regular expression: ' + regex.source);
  78. }
  79. } catch (err) {
  80. if (opts.strictErrors === true || opts.safe === true) {
  81. err.key = key;
  82. err.pattern = pattern;
  83. err.originalOptions = options;
  84. err.createdOptions = opts;
  85. throw err;
  86. }
  87. try {
  88. regex = new RegExp('^' + pattern.replace(/(\W)/g, '\\$1') + '$');
  89. } catch (err) {
  90. regex = /.^/; //<= match nothing
  91. }
  92. }
  93. if (opts.cache !== false) {
  94. memoize(regex, key, pattern, opts);
  95. }
  96. return regex;
  97. }
  98. /**
  99. * Memoize generated regex. This can result in dramatic speed improvements
  100. * and simplify debugging by adding options and pattern to the regex. It can be
  101. * disabled by passing setting `options.cache` to false.
  102. */
  103. function memoize(regex, key, pattern, options) {
  104. define(regex, 'cached', true);
  105. define(regex, 'pattern', pattern);
  106. define(regex, 'options', options);
  107. define(regex, 'key', key);
  108. cache[key] = regex;
  109. }
  110. /**
  111. * Create the key to use for memoization. The key is generated
  112. * by iterating over the options and concatenating key-value pairs
  113. * to the pattern string.
  114. */
  115. function createKey(pattern, options) {
  116. if (!options) return pattern;
  117. var key = pattern;
  118. for (var prop in options) {
  119. if (options.hasOwnProperty(prop)) {
  120. key += ';' + prop + '=' + String(options[prop]);
  121. }
  122. }
  123. return key;
  124. }
  125. /**
  126. * Expose `makeRe`
  127. */
  128. module.exports.makeRe = makeRe;