[
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created\n# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages\n\nname: Node.js Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v1\n        with:\n          node-version: 12\n      - run: npm ci\n      - run: npm test\n\n  publish-npm:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v1\n        with:\n          node-version: 12\n          registry-url: https://registry.npmjs.org/\n      - run: npm ci\n      - run: npm publish\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.npm_token}}\n\n  publish-gpr:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v1\n        with:\n          node-version: 12\n          registry-url: https://npm.pkg.github.com/\n      - run: npm ci\n      - run: npm publish\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode\ncoverage\nnode_modules/\npackage-lock.json\ndist/\n\n"
  },
  {
    "path": ".npmignore",
    "content": ".vscode\ncoverage\nsrc/\ndist/test/\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\n\nlanguage: node_js\n\nnode_js:\n  - \"10\"\n  - \"12\"\n\ninstall:\n  - npm install\n\nscript:\n  - npm test\n\ncache:\n  directories:\n    - node_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# v2\n\n## v2.0\n\n## v2.0.0\n\n1. Update README.\n2. Switch AST library from ret to regexp-tree.\n3. Fix incorrect handling of nested quantifiers in disjunctions.\n4. Enhance test suite.\n\nContributors:\n- [davisjam](https://github.com/davisjam)\n\n# v1\n\nThis is the historic release.\n\nContributors:\n- [substack](https://github.com/substack)\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2019-present is held by the authors of the safe-regex module.\n\nThis software is released under the MIT license:\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nOriginal author: James Halliday @substack\nMaintainer: James C. (Jamie) Davis @davisjam\n"
  },
  {
    "path": "README.md",
    "content": "# safe-regex\n\nDetect potentially\n[catastrophic](http://regular-expressions.mobi/catastrophic.html)\n[exponential-time](http://perlgeek.de/blog-en/perl-tips/in-search-of-an-exponetial-regexp.html)\nregular expressions by limiting the\n[star height](https://en.wikipedia.org/wiki/Star_height) to 1.\n\nWARNING: This module has both false positives and false negatives.\nUse [vuln-regex-detector](https://github.com/davisjam/vuln-regex-detector) for improved accuracy.\n\n[![Build Status](https://travis-ci.org/davisjam/safe-regex.svg?branch=master)](https://travis-ci.org/davisjam/safe-regex)\n\n## Example\n\nSuppose you have a script named `safe.js`:\n\n``` js\nvar safe = require('safe-regex');\nvar regex = process.argv.slice(2).join(' ');\nconsole.log(safe(regex));\n```\n\nThis is its behavior:\n\n```\n$ node safe.js '(x+x+)+y'\nfalse\n$ node safe.js '(beep|boop)*'\ntrue\n$ node safe.js '(a+){10}'\nfalse\n$ node safe.js '\\blocation\\s*:[^:\\n]+\\b(Oakland|San Francisco)\\b'\ntrue\n```\n\n## Methods\n\n``` js\nconst safe = require('safe-regex')\n```\n\n### const ok = safe(re, opts={})\n\nReturn a boolean `ok` whether or not the regex `re` is safe and not possibly\ncatastrophic.\n\n`re` can be a `RegExp` object or just a string.\n\nIf the `re` is a string and is an invalid regex, returns `false`.\n\n* `opts.limit` - maximum number of allowed repetitions in the entire regex.\nDefault: `25`.\n\n## Install\n\nWith [npm](https://npmjs.org) do:\n\n```\nnpm install safe-regex\n```\n\n## Resources\n\n### What should I do if my project has a super-linear regex?\n\n1. Confirm that it is *reachable* by untrusted input.\n2. If it is, you can consider whether you can prevent worst-case behavior by trimming the input, revising the regex, or replacing the regex with another algorithm like string functions. For examples, see Table 5 in [this article](http://people.cs.vt.edu/davisjam/downloads/publications/DavisCoghlanServantLee-EcosystemREDOS-ESECFSE18.pdf).\n3. If none of those solutions looks feasible, you might also consider changing regex engines. The [RE2 bindings](https://www.npmjs.com/package/re2) might work, though test carefully to confirm there are no [semantic portability problems](https://medium.com/@davisjam/why-arent-regexes-a-lingua-franca-esecfse19-a36348df3a2?source=friends_link&sk=d21be7f8f723e2080dc993385c6973d1).\n\n### Further reading\n\nThe following documents may be edifying:\n\n- [Research brief on the extent of super-linear regexes in practice](https://medium.com/@davisjam/introduction-987fdc4c7b0?source=friends_link&sk=ceefa4a4ca9617e08ab782c3b1580aea)\n- [Research brief on the variability of super-linear regex behavior across programming languages](https://medium.com/@davisjam/why-arent-regexes-a-lingua-franca-esecfse19-a36348df3a2?source=friends_link&sk=d21be7f8f723e2080dc993385c6973d1)\n- [Comparing regex matching algorithms](https://swtch.com/~rsc/regexp/regexp1.html)\n\n## Project policies\n\n### Versioning\n\nThis project follows [Semantic Versioning 2.0 (semver)](https://semver.org/).\n\nHere are the project-specific meanings of MAJOR, MINOR, and PATCH updates:\n\n- MAJOR: \"Incompatible\" API changes were introduced. There are two types in this module:\n  - Changes that modify the interface\n  - Changes that cause any regexes to be marked as unsafe that were formerly marked as safe\n- MINOR: Functionality was added in a backwards-compatible manner. There are two types in this module:\n  - Refactoring the analyses but not changing their results\n  - Modifying the analyses to reduce false positives, without affecting negatives (false or true)\n- PATCH: I don't anticipate using PATCH for this module\n\n### License\n\n[MIT](https://github.com/davisjam/safe-regex/blob/master/LICENSE)"
  },
  {
    "path": "babel.config.json",
    "content": "{\n    \"presets\": [\n        [\n            \"@babel/preset-env\",\n            {\n                \"targets\": {\n                    \"edge\": \"17\",\n                    \"firefox\": \"60\",\n                    \"chrome\": \"67\",\n                    \"safari\": \"11.1\",\n                    \"ie\": \"11\"\n                }\n            }\n        ]\n    ]\n}"
  },
  {
    "path": "bin/cli.js",
    "content": "#!/usr/bin/env node\n// CLI for querying safe-regex for safety analysis\n// Input: JSON-formatted file with an object with key 'pattern'\n// Output: STDOUT: JSON-formatted object with new key 'isSafe' and value 0 or 1\n//         STDERR: Progress updates\n\nvar safe = require('../'),\n    fs   = require('fs');\n\nif (process.argv.length != 3) {\n  console.error(`Usage: ${process.argv[1]} pattern.json`);\n  process.exit(1);\n}\nconst file = process.argv[2];\n\n// Get pattern\nconst cont = fs.readFileSync(file, 'utf-8');\nlet pattern = JSON.parse(cont);\n\n// Can analyze? Is safe?\nlet canAnalyze = 0;\nlet isSafe = 0;\ntry {\n  isSafe = safe(pattern.pattern) ? 1 : 0;\n  canAnalyze = 1;\n} catch (e) {\n  canAnalyze = 0;\n  isSafe = 'unknown';\n}\n\npattern.canAnalyze = canAnalyze;\npattern.isSafe = isSafe;\n\n// Emit.\nconsole.error(`Pattern /${pattern.pattern}/: canAnalyze ${pattern.canAnalyze} isSafe ${pattern.isSafe}`);\nconsole.log(JSON.stringify(pattern));\nprocess.exit(0);\n"
  },
  {
    "path": "example/safe.js",
    "content": "var safe = require('../');\nvar regex = process.argv.slice(2).join(' ');\nconsole.log(safe(regex));\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"safe-regex\",\n  \"version\": \"2.1.2\",\n  \"description\": \"detect possibly catastrophic, exponential-time regular expressions\",\n  \"main\": \"dist/index.js\",\n  \"dependencies\": {\n    \"babel-jest\": \"^26.3.0\",\n    \"regexp-tree\": \"~0.1.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.11.6\",\n    \"@babel/core\": \"^7.11.6\",\n    \"@babel/preset-env\": \"^7.11.5\",\n    \"jest\": \"^24.9.0\"\n  },\n  \"scripts\": {\n    \"build\": \"babel src -d dist\",\n    \"prepublishOnly\": \"npm run build\",\n    \"test\": \"jest src/*\",\n    \"test-artifact\": \"jest dist/*\"\n  },\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"js\"\n    ],\n    \"testRegex\": \"test.*\\\\.spec\\\\.js$\",\n    \"collectCoverage\": true,\n    \"coverageReporters\": [\n      \"text-summary\",\n      \"html\",\n      \"lcov\"\n    ],\n    \"collectCoverageFrom\": [\n      \".js\"\n    ],\n    \"coverageThreshold\": {\n      \"global\": {\n        \"statements\": 100,\n        \"branches\": 100,\n        \"functions\": 100,\n        \"lines\": 100\n      }\n    }\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/davisjam/safe-regex.git\"\n  },\n  \"homepage\": \"https://github.com/davisjam/safe-regex\",\n  \"keywords\": [\n    \"catastrophic\",\n    \"exponential\",\n    \"regex\",\n    \"safe\",\n    \"sandbox\"\n  ],\n  \"author\": {\n    \"name\": \"James C. (Jamie) Davis\",\n    \"email\": \"davisjam@vt.edu\",\n    \"url\": \"http://people.cs.vt.edu/~davisjam\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/analyzer-family.js",
    "content": "// Load the analyzers\nconst heuristicAnalyzer = require(\"./heuristic-analyzer\");\n\nmodule.exports = [heuristicAnalyzer];\n"
  },
  {
    "path": "src/analyzer.js",
    "content": "// Generic options\nclass AnalyzerOptions {\n  constructor(heuristic_replimit) {\n    this.heuristic_replimit = heuristic_replimit;\n  }\n}\n\nclass AttackString {\n  constructor(prefixAndPumpList, suffix) {\n    this.prefixAndPumpList = prefixAndPumpList;\n    this.suffix = suffix;\n  }\n}\n\n// Abstract class\nclass Analyzer {\n  constructor(analyzerOptions) {\n    this.options = analyzerOptions;\n  }\n\n  // Subclasser must implement\n  // Return boolean\n  isVulnerable(regExp) {\n    return false;\n  }\n\n  // Subclass must implement\n  // Returns an AttackString or null\n  genAttackString(regExp) {\n    return null;\n  }\n}\n\nmodule.exports = function(re, replimit) {\n  // Build an AST\n  let myRegExp = null;\n  let ast = null;\n  try {\n    // Construct a RegExp object\n    if (re instanceof RegExp) {\n      myRegExp = re;\n    } else if (typeof re === \"string\") {\n      myRegExp = new RegExp(re);\n    } else {\n      myRegExp = new RegExp(String(re));\n    }\n\n    // Build an AST\n    ast = regexpTree.parse(myRegExp);\n  } catch (err) {\n    // Invalid or unparseable input\n    return false;\n  }\n\n  let currentStarHeight = 0;\n  let maxObservedStarHeight = 0;\n\n  let repetitionCount = 0;\n\n  regexpTree.traverse(ast, {\n    Repetition: {\n      pre({ node }) {\n        repetitionCount++;\n\n        currentStarHeight++;\n        if (maxObservedStarHeight < currentStarHeight) {\n          maxObservedStarHeight = currentStarHeight;\n        }\n      },\n\n      post({ node }) {\n        currentStarHeight--;\n      }\n    }\n  });\n\n  return maxObservedStarHeight <= 1 && repetitionCount <= replimit;\n};\n\nmodule.exports = {\n  \"AnalyzerOptions\": AnalyzerOptions,\n  \"Analyzer\": Analyzer,\n};\n"
  },
  {
    "path": "src/heuristic-analyzer.js",
    "content": "// Exports an Analyzer subclass\n\nconst regexpTree = require(\"regexp-tree\");\nconst analyzer = require(\"./analyzer\");\n\nclass HeuristicAnalyzer extends analyzer.Analyzer {\n  constructor(analyzerOptions) {\n    super(analyzerOptions);\n  }\n\n  isVulnerable(regExp) {\n    // Heuristic #1: Star height > 1\n    const starHeight = this._measureStarHeight(regExp);\n    if (starHeight > 1) {\n      return true;\n    }\n\n    // Heuristic #2: # repetitions > limit\n    // TODO This is a poor heuristic\n    const nRepetitions = this._measureRepetitions(regExp);\n    if (nRepetitions > this.options.heuristic_replimit) {\n      return true;\n    }\n\n    return false;\n  }\n\n  genAttackString(regExp) {\n    return null;\n  }\n\n  _measureStarHeight(regExp) {\n    let currentStarHeight = 0;\n    let maxObservedStarHeight = 0;\n\n    const ast = regexpTree.parse(regExp);\n\n    regexpTree.traverse(ast, {\n      Repetition: {\n        pre({ node }) {\n          currentStarHeight++;\n          if (maxObservedStarHeight < currentStarHeight) {\n            maxObservedStarHeight = currentStarHeight;\n          }\n        },\n\n        post({ node }) {\n          currentStarHeight--;\n        }\n      }\n    });\n\n    return maxObservedStarHeight;\n  }\n\n  _measureRepetitions(regExp) {\n    let nRepetitions = 0;\n\n    const ast = regexpTree.parse(regExp);\n    regexpTree.traverse(ast, {\n      Repetition: {\n        pre({ node }) {\n          nRepetitions++;\n        }\n      }\n    });\n\n    return nRepetitions;\n  }\n}\n\nmodule.exports = HeuristicAnalyzer;\n"
  },
  {
    "path": "src/index.js",
    "content": "const analyzer = require('./analyzer');\nconst analyzerFamily = require('./analyzer-family');\n\nconst DEFAULT_SAFE_REP_LIMIT = 25;\nconst RET_IS_SAFE = true;\nconst RET_IS_VULNERABLE = false;\n\nclass Args {\n  constructor(regExp, analyzerOptions) {\n    this.regExp = regExp;\n    this.analyzerOptions = analyzerOptions;\n  }\n}\n\nfunction safeRegex(re, opts) {\n  try {\n    const args = buildArgs(re, opts);\n    const analyzerResponses = askAnalyzersIfVulnerable(args);\n\n    // Did any analyzer say true?\n    if (analyzerResponses.find((isVulnerable) => isVulnerable)) {\n      return RET_IS_VULNERABLE;\n    } else {\n      return RET_IS_SAFE;\n    }\n  } catch (err) {\n    // Invalid or unparseable input\n    return false;\n  }\n}\n\nfunction buildArgs(re, opts) {\n  // Build AnalyzerOptions\n  if (!opts) opts = {};\n  const heuristic_replimit = opts.limit === undefined ? DEFAULT_SAFE_REP_LIMIT : opts.limit;\n\n  const analyzerOptions = new analyzer.AnalyzerOptions(heuristic_replimit);\n\n  // Build RegExp\n  let regExp = null;\n  // Construct a RegExp object\n  if (re instanceof RegExp) {\n    regExp = re;\n  } else if (typeof re === 'string') {\n    regExp = new RegExp(re);\n  } else {\n    regExp = new RegExp(String(re));\n  }\n\n  return new Args(regExp, analyzerOptions);\n}\n\nfunction askAnalyzersIfVulnerable(args) {\n  let analyzerSaysVulnerable = [];\n\n  // Query the Analyzers\n  let Analyzer;\n  for (Analyzer of analyzerFamily) {\n    try {\n      const analyzer = new Analyzer(args.analyzerOptions);\n      analyzerSaysVulnerable.push(analyzer.isVulnerable(args.regExp));\n    } catch (err) {\n      /* istanbul ignore next */ // No need to worry about code coverage here.\n      analyzerSaysVulnerable.push(false);\n    }\n  }\n\n  return analyzerSaysVulnerable;\n}\n\n// Export\n\nmodule.exports = safeRegex;"
  },
  {
    "path": "src/test/regex.spec.js",
    "content": "const safeRegex = require(\"../\");\n\nconst REPETITION_LIMIT = 25;\nconst REPETITION_TOO_MUCH = REPETITION_LIMIT + 1;\n\n// TODO Named character classes\n\ntest(\"The full set of JS regex features are supported\", () => {\n  /**\n   * A list of linear-time regexes using a reasonably exhaustive\n   * set of the supported JS regex features.\n   * cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\n   *\n   * The purpose of this list is to check for\n   * full regex syntax support.\n   */\n  const diverseLinTimeRegexes = [\n    /* Truly Regular Expressions */\n\n    // Conjunction\n    /a/,\n    /abc/,\n\n    // Simple zero-width assertions\n    /^a/,\n    /a$/,\n\n    // Quantifiers\n    /^a*/,\n    /^a+/,\n    /a?/,\n    /x{5}/,\n    /x{5,}/,\n    /x{5,10}/,\n\n    // Grouping constructs\n    /(x)/,\n    /(?:x)/,\n\n    // Disjunction\n    /x|y/,\n\n    // Built-in character classes\n    /.\\./,\n    /[\\b]/,\n    /\\b/,\n    /\\B/,\n    /\\cA/,\n    /\\d/,\n    /\\D/,\n    /\\f/,\n    /\\n/,\n    /\\r/,\n    /\\s/,\n    /\\S/,\n    /\\t/,\n    /\\v/,\n    /\\w/,\n    /\\W/,\n    /\\0/,\n    /\\x00/,\n    /\\u0000/,\n    /\\u{0000}/u,\n\n    // Custom character classes\n    /[xyz]/,\n    /[x-z]/,\n    /[^xyz]/,\n    /[^x-z]/,\n\n    /* Extended features */\n\n    // Backreferences\n    /(x) \\1/,\n\n    // Lookaround assertions\n    /x(?=y)/,\n    /x(?!y)/,\n\n    /* Added in ECMAScript 2018 */\n\n    // Lookbehind assertions\n    /(?<=y)x/,\n    /(?!<y)x/,\n\n    // Named capture groups\n    /(?<year>\\d{4})/,\n    /(?<year>a)\\k<year>/,\n    // Tests related to bug #26\n    /(?<year>test?)/,\n    /(?<year>test*)/,\n    /(?<year>test)*/,\n  ];\n\n  diverseLinTimeRegexes.forEach(re => {\n    expect(safeRegex(re)).toBe(true);\n  });\n});\n\ntest(\"linear-time regexes are safe\", () => {\n  const linTime = [\n    /**\n     * No false positives\n     */\n\n    // Standard regex features\n    /a/,\n    /a*/,\n    /^a*/,\n    /^a+$/,\n    /a+$/,\n    /a?/,\n    /a{3,5}/,\n    /a|b/,\n    /(ab)/,\n    /(ab)\\1/,\n    /\\bOakland\\b/,\n    /^((a+)|(b+))/,\n    /(a+)|(b+)/,\n\n    // RE's in a string\n    \"^foo/bar\",\n\n    // non-RE, non-string\n    1,\n  ];\n\n  linTime.forEach(re => {\n    expect(safeRegex(re)).toBe(true);\n  });\n});\n\ntest(\"linear-time regexes are safe, under varying repetition limits\", () => {\n  const re1 = RegExp(\"a?\".repeat(REPETITION_LIMIT) + \"a\".repeat(REPETITION_LIMIT));\n  expect(safeRegex(re1)).toBe(true);\n\n  const LOW_REPETITION_LIMIT = 3;\n  const re2 = RegExp(Array(LOW_REPETITION_LIMIT).join(\"a?\"));\n\n  expect(safeRegex(re2, { limit: LOW_REPETITION_LIMIT })).toBe(true);\n});\n\ntest(\"poly-time regexes are safe (at least according to our heuristics)\", () => {\n  const polyTime = [\n    /^a+a+$/,        // QOA\n    /^a+aa+$/,       // QOA with obvious intermediate run\n    /^a+aaaa+$/,     // QOA with obvious intermediate run\n    /^a+[a-z]a+$/,   // QOA with obvious intermediate run\n    /^a+\\wa+$/,      // QOA with intermediate character class\n    /^a+(\\w|\\d)a+$/, // QOA with valid path through\n    /^a+b?a+$/,      // QOA with valid path through\n    /^a+(cde)*a+$/,  // QOA with valid path through\n    /^.*a*$/,        // QOA by subset\n    /^\\w*\\d*$/,      // QOA by intersection\n    /^\\S+@\\S+\\.\\S+$/, // Example from Django\n    /a+$/,           // QOA under partial-match\n    /abc.*$/,        // QOA under partial-match\n    // TODO It would be nice to have one of the regexes that are poly-time even when they match, due to non-greedy quantifiers (p-NFA)\n  ];\n\n  polyTime.forEach(re => {\n    expect(safeRegex(re)).toBe(true);\n  });\n});\n\ntest(\"exp-time regexes due to star height are unsafe\", () => {\n  const expTime = [\n    // Straightforward star height\n    /(a*)*$/,\n    /(a?)*$/,\n    /(a*)?$/,\n    /(a*)+$/,\n    /(a+)*$/,\n    /(\\wa+)*$/, // Prefix\n    /(\\..*)*$/, // Suffix\n\n    // Branching and nesting.\n    /(a*|b)+$/,\n    /(a|b*)+$/,\n    /(((b*)))+$/,\n    /(((b*))+)$/,\n    /(((b*)+))$/,\n    /(((b)*)+)$/,\n    /(((b)*))+$/,\n\n    // Misc. more complex cases\n    /^(a?){25}(a){25}$/,\n    /(x+x+)+y/,\n    /foo|(x+x+)+y/,\n    /(a+){10}y/,\n    /(a+){2}y/,\n    /(.*){1,32000}[bc]/,\n\n\n    // RE's in a string\n    \"(a+)+\",\n  ];\n\n  expTime.forEach(re => {\n    expect(safeRegex(re)).toBe(false);\n  });\n});\n\ntest(\"linear-time regexes with star height > 1\", () => {\n  // TODO These are false positives, Fix once we improve analysis\n  const linTime = [\n    /(ab*)+$/,\n    /(b*a)+$/,\n  ];\n\n  linTime.forEach(re => {\n    expect(safeRegex(re)).toBe(false);\n  });\n});\n\ntest(\"exp-time regexes due to disjunction are safe (according to current heuristics)\", () => {\n  // TODO These are false negatives. Fix once we improve analysis\n  const expTime = [\n    /(a|a)*$/,       // QOD: obvious \n    /(a|\\w)*$/,      // QOD due to overlap\n    /([abc]|b)*$/,   // QOD due to overlap\n    /(\\w\\w\\w|bab)*$/, // QOD due to overlap, with multi-step internal paths\n  ];\n\n  expTime.forEach(re => {\n    expect(safeRegex(re)).toBe(true);\n  });\n});\n\ntest(\"regex that exceeds repetition limit is unsafe\", () => {\n  const re1 = RegExp(\"a?\".repeat(REPETITION_TOO_MUCH) + \"a\".repeat(REPETITION_TOO_MUCH));\n  expect(safeRegex(re1)).toBe(false);\n\n  const LOW_REPETITION_LIMIT = 3;\n  const re2 = RegExp(\"a?\".repeat(LOW_REPETITION_LIMIT + 1));\n  expect(safeRegex(re2, { limit: LOW_REPETITION_LIMIT })).toBe(false);\n});\n\ntest(\"invalid regexes default to unsafe\", () => {\n  const invalid = [\n    \"(a+\",\n    \"[a-z\",\n    \"*Oakland*\",\n    \"hey(yoo) )\",\n    \"abcde(?>hellow)\",\n    \"[abc\",\n  ];\n\n  invalid.forEach(re => {\n    expect(safeRegex(re)).toBe(false);\n  });\n});"
  }
]