'use strict'; var safe = require('safe-regex'); var define = require('define-property'); var extend = require('extend-shallow'); var not = require('regex-not'); var MAX_LENGTH = 1024 * 64; /** * Session cache */ var cache = {}; /** * Create a regular expression from the given `pattern` string. * * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. * @param {Object} `options` * @return {RegExp} * @api public */ module.exports = function(patterns, options) { if (!Array.isArray(patterns)) { return makeRe(patterns, options); } return makeRe(patterns.join('|'), options); }; /** * Create a regular expression from the given `pattern` string. * * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. * @param {Object} `options` * @return {RegExp} * @api public */ function makeRe(pattern, options) { if (pattern instanceof RegExp) { return pattern; } if (typeof pattern !== 'string') { throw new TypeError('expected a string'); } if (pattern.length > MAX_LENGTH) { throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); } var key = pattern; // do this before shallow cloning options, it's a lot faster if (!options || (options && options.cache !== false)) { key = createKey(pattern, options); if (cache.hasOwnProperty(key)) { return cache[key]; } } var opts = extend({}, options); if (opts.contains === true) { if (opts.negate === true) { opts.strictNegate = false; } else { opts.strict = false; } } if (opts.strict === false) { opts.strictOpen = false; opts.strictClose = false; } var open = opts.strictOpen !== false ? '^' : ''; var close = opts.strictClose !== false ? '$' : ''; var flags = opts.flags || ''; var regex; if (opts.nocase === true && !/i/.test(flags)) { flags += 'i'; } try { if (opts.negate || typeof opts.strictNegate === 'boolean') { pattern = not.create(pattern, opts); } var str = open + '(?:' + pattern + ')' + close; regex = new RegExp(str, flags); if (opts.safe === true && safe(regex) === false) { throw new Error('potentially unsafe regular expression: ' + regex.source); } } catch (err) { if (opts.strictErrors === true || opts.safe === true) { err.key = key; err.pattern = pattern; err.originalOptions = options; err.createdOptions = opts; throw err; } try { regex = new RegExp('^' + pattern.replace(/(\W)/g, '\\$1') + '$'); } catch (err) { regex = /.^/; //<= match nothing } } if (opts.cache !== false) { memoize(regex, key, pattern, opts); } return regex; } /** * Memoize generated regex. This can result in dramatic speed improvements * and simplify debugging by adding options and pattern to the regex. It can be * disabled by passing setting `options.cache` to false. */ function memoize(regex, key, pattern, options) { define(regex, 'cached', true); define(regex, 'pattern', pattern); define(regex, 'options', options); define(regex, 'key', key); cache[key] = regex; } /** * Create the key to use for memoization. The key is generated * by iterating over the options and concatenating key-value pairs * to the pattern string. */ function createKey(pattern, options) { if (!options) return pattern; var key = pattern; for (var prop in options) { if (options.hasOwnProperty(prop)) { key += ';' + prop + '=' + String(options[prop]); } } return key; } /** * Expose `makeRe` */ module.exports.makeRe = makeRe;