From ab1c5b20ee7a51a47b6067ebfaa71cdb01d1e6a7 Mon Sep 17 00:00:00 2001
From: Sumit Nihalani <nihalanisumit@gmail.com>
Date: Mon, 14 Oct 2019 08:40:53 -0700
Subject: [PATCH] refactor: use re2 instead of RegExp (#4441)

---
 lib/config/presets.ts                         |  3 +-
 lib/config/validation.ts                      | 32 ++++++-------------
 lib/datasource/go/index.ts                    |  3 +-
 lib/manager/ansible/update.ts                 |  3 +-
 lib/manager/bazel/extract.ts                  |  3 +-
 lib/manager/bazel/update.ts                   |  5 +--
 lib/manager/buildkite/update.ts               |  3 +-
 lib/manager/bundler/extract.ts                | 15 ++++-----
 .../__snapshots__/validation.spec.ts.snap     |  6 +---
 test/config/validation.spec.ts                | 15 ++++++++-
 10 files changed, 45 insertions(+), 43 deletions(-)

diff --git a/lib/config/presets.ts b/lib/config/presets.ts
index 95933fa9d6..9af6b7b778 100644
--- a/lib/config/presets.ts
+++ b/lib/config/presets.ts
@@ -7,6 +7,7 @@ import * as npm from '../datasource/npm';
 import * as gitlab from '../datasource/gitlab';
 import { RenovateConfig } from './common';
 import { mergeChildConfig } from './utils';
+import { regEx } from '../util/regex';
 
 const datasources = {
   github,
@@ -126,7 +127,7 @@ export function replaceArgs(
   if (is.string(obj)) {
     let returnStr = obj;
     for (const [arg, argVal] of Object.entries(argMapping)) {
-      const re = new RegExp(`{{${arg}}}`, 'g');
+      const re = regEx(`{{${arg}}}`, 'g');
       returnStr = returnStr.replace(re, argVal);
     }
     return returnStr;
diff --git a/lib/config/validation.ts b/lib/config/validation.ts
index 91984b7cfe..8c30c3aea5 100644
--- a/lib/config/validation.ts
+++ b/lib/config/validation.ts
@@ -1,10 +1,10 @@
 import is from '@sindresorhus/is';
-import safe from 'safe-regex';
 import { getOptions, RenovateOptions } from './definitions';
 import { resolveConfigPresets } from './presets';
 import { hasValidSchedule, hasValidTimezone } from '../workers/branch/schedule';
 import * as managerValidator from './validation-helpers/managers';
 import { RenovateConfig, ValidationMessage } from './common';
+import { regEx } from '../util/regex';
 
 const options = getOptions();
 
@@ -183,13 +183,7 @@ export async function validateConfig(
               !(val && val.length === 1 && val[0] === '*')
             ) {
               try {
-                RegExp(val as any);
-                if (!safe(val as any)) {
-                  errors.push({
-                    depName: 'Configuration Error',
-                    message: `Unsafe regExp for ${currentPath}: \`${val}\``,
-                  });
-                }
+                regEx(val as any);
               } catch (e) {
                 errors.push({
                   depName: 'Configuration Error',
@@ -198,21 +192,15 @@ export async function validateConfig(
               }
             }
             if (key === 'fileMatch') {
-              try {
-                for (const fileMatch of val) {
-                  RegExp(fileMatch);
-                  if (!safe(fileMatch)) {
-                    errors.push({
-                      depName: 'Configuration Error',
-                      message: `Unsafe regExp for ${currentPath}: \`${fileMatch}\``,
-                    });
-                  }
+              for (const fileMatch of val) {
+                try {
+                  regEx(fileMatch);
+                } catch (e) {
+                  errors.push({
+                    depName: 'Configuration Error',
+                    message: `Invalid regExp for ${currentPath}: \`${fileMatch}\``,
+                  });
                 }
-              } catch (e) {
-                errors.push({
-                  depName: 'Configuration Error',
-                  message: `Invalid regExp for ${currentPath}: \`${val}\``,
-                });
               }
             }
             if (
diff --git a/lib/datasource/go/index.ts b/lib/datasource/go/index.ts
index 9bbfde96b5..b555235fce 100644
--- a/lib/datasource/go/index.ts
+++ b/lib/datasource/go/index.ts
@@ -2,6 +2,7 @@ import { logger } from '../../logger';
 import got from '../../util/got';
 import * as github from '../github';
 import { DigestConfig, PkgReleaseConfig, ReleaseResult } from '../common';
+import { regEx } from '../../util/regex';
 
 interface DataSource {
   datasource: string;
@@ -30,7 +31,7 @@ async function getDatasource(name: string): Promise<DataSource | null> {
       hostType: 'go',
     })).body;
     const sourceMatch = res.match(
-      new RegExp(`<meta\\s+name="go-source"\\s+content="${name}\\s+([^\\s]+)`)
+      regEx(`<meta\\s+name="go-source"\\s+content="${name}\\s+([^\\s]+)`)
     );
     if (sourceMatch) {
       const [, goSourceUrl] = sourceMatch;
diff --git a/lib/manager/ansible/update.ts b/lib/manager/ansible/update.ts
index a24b532877..8b7b2282f6 100644
--- a/lib/manager/ansible/update.ts
+++ b/lib/manager/ansible/update.ts
@@ -1,6 +1,7 @@
 import { logger } from '../../logger';
 import { getNewFrom } from '../dockerfile/update';
 import { Upgrade } from '../common';
+import { regEx } from '../../util/regex';
 
 export default function updateDependency(
   fileContent: string,
@@ -11,7 +12,7 @@ export default function updateDependency(
     logger.debug(`ansible.updateDependency(): ${newFrom}`);
     const lines = fileContent.split('\n');
     const lineToChange = lines[upgrade.managerData.lineNumber];
-    const imageLine = new RegExp(/^(\s*image:\s*'?"?)[^\s'"]+('?"?\s*)$/);
+    const imageLine = regEx(`^(\\s*image:\\s*'?"?)[^\\s'"]+('?"?\\s*)$`);
     if (!lineToChange.match(imageLine)) {
       logger.debug('No image line found');
       return null;
diff --git a/lib/manager/bazel/extract.ts b/lib/manager/bazel/extract.ts
index f97ba65b08..ae420a6d53 100644
--- a/lib/manager/bazel/extract.ts
+++ b/lib/manager/bazel/extract.ts
@@ -3,6 +3,7 @@ import parse from 'github-url-from-git';
 import { parse as _parse } from 'url';
 import { logger } from '../../logger';
 import { PackageDependency, PackageFile } from '../common';
+import { regEx } from '../../util/regex';
 
 interface UrlParsedResult {
   repo: string;
@@ -78,7 +79,7 @@ function parseContent(content: string): string[] {
     (acc, prefix) => [
       ...acc,
       ...content
-        .split(new RegExp(prefix + '\\s*\\(', 'g'))
+        .split(regEx(prefix + '\\s*\\(', 'g'))
         .slice(1)
         .map(base => {
           const ind = findBalancedParenIndex(base);
diff --git a/lib/manager/bazel/update.ts b/lib/manager/bazel/update.ts
index 3f55a4a21a..7f9f4e55d0 100644
--- a/lib/manager/bazel/update.ts
+++ b/lib/manager/bazel/update.ts
@@ -2,6 +2,7 @@ import { fromStream } from 'hasha';
 import got from '../../util/got';
 import { logger } from '../../logger';
 import { Upgrade } from '../common';
+import { regEx } from '../../util/regex';
 
 function updateWithNewVersion(
   content: string,
@@ -137,7 +138,7 @@ export async function updateDependency(
       const hash = await getHashFromUrl(url);
       newDef = setNewHash(upgrade.managerData.def, hash);
       newDef = newDef.replace(
-        new RegExp(`(strip_prefix\\s*=\\s*)"[^"]*"`),
+        regEx(`(strip_prefix\\s*=\\s*)"[^"]*"`),
         `$1"${shortRepo}-${upgrade.newDigest}"`
       );
       const match =
@@ -151,7 +152,7 @@ export async function updateDependency(
     if (newDef.endsWith('\n')) {
       existingRegExStr += '\n';
     }
-    const existingDef = new RegExp(existingRegExStr);
+    const existingDef = regEx(existingRegExStr);
     // istanbul ignore if
     if (!fileContent.match(existingDef)) {
       logger.info('Cannot match existing string');
diff --git a/lib/manager/buildkite/update.ts b/lib/manager/buildkite/update.ts
index 14e7da9691..943cd4e09d 100644
--- a/lib/manager/buildkite/update.ts
+++ b/lib/manager/buildkite/update.ts
@@ -1,5 +1,6 @@
 import { logger } from '../../logger';
 import { Upgrade } from '../common';
+import { regEx } from '../../util/regex';
 
 export function updateDependency(
   currentFileContent: string,
@@ -10,7 +11,7 @@ export function updateDependency(
     logger.debug(`buildkite.updateDependency: ${upgrade.newValue}`);
     const lines = currentFileContent.split('\n');
     const lineToChange = lines[lineIdx];
-    const depLine = new RegExp(/^(\s+[^#]+#)[^:]+(:.*)$/);
+    const depLine = regEx(`^(\\s+[^#]+#)[^:]+(:.*)$`);
     if (!lineToChange.match(depLine)) {
       logger.debug('No image line found');
       return null;
diff --git a/lib/manager/bundler/extract.ts b/lib/manager/bundler/extract.ts
index 7d7da5032c..3deb6481f9 100644
--- a/lib/manager/bundler/extract.ts
+++ b/lib/manager/bundler/extract.ts
@@ -2,6 +2,7 @@ import { logger } from '../../logger';
 import { isValid } from '../../versioning/ruby';
 import { PackageFile, PackageDependency } from '../common';
 import { platform } from '../../platform';
+import { regEx } from '../../util/regex';
 
 export { extractPackageFile };
 
@@ -22,7 +23,7 @@ async function extractPackageFile(
       sourceMatch =
         sourceMatch ||
         line.match(
-          new RegExp(`^source ${delimiter}([^${delimiter}]+)${delimiter}\\s*$`)
+          regEx(`^source ${delimiter}([^${delimiter}]+)${delimiter}\\s*$`)
         );
     }
     if (sourceMatch) {
@@ -32,9 +33,7 @@ async function extractPackageFile(
     for (const delimiter of delimiters) {
       rubyMatch =
         rubyMatch ||
-        line.match(
-          new RegExp(`^ruby ${delimiter}([^${delimiter}]+)${delimiter}`)
-        );
+        line.match(regEx(`^ruby ${delimiter}([^${delimiter}]+)${delimiter}`));
     }
     if (rubyMatch) {
       res.compatibility = { ruby: rubyMatch[1] };
@@ -43,9 +42,9 @@ async function extractPackageFile(
     let gemDelimiter: string;
     for (const delimiter of delimiters) {
       const gemMatchRegex = `^gem ${delimiter}([^${delimiter}]+)${delimiter}(,\\s+${delimiter}([^${delimiter}]+)${delimiter}){0,2}`;
-      if (line.match(new RegExp(gemMatchRegex))) {
+      if (line.match(regEx(gemMatchRegex))) {
         gemDelimiter = delimiter;
-        gemMatch = gemMatch || line.match(new RegExp(gemMatchRegex));
+        gemMatch = gemMatch || line.match(regEx(gemMatchRegex));
       }
     }
     if (gemMatch) {
@@ -56,7 +55,7 @@ async function extractPackageFile(
       if (gemMatch[3]) {
         dep.currentValue = gemMatch[0]
           .substring(`gem ${gemDelimiter}${dep.depName}${gemDelimiter},`.length)
-          .replace(new RegExp(gemDelimiter, 'g'), '')
+          .replace(regEx(gemDelimiter, 'g'), '')
           .trim();
         if (!isValid(dep.currentValue)) {
           dep.skipReason = 'invalid-value';
@@ -100,7 +99,7 @@ async function extractPackageFile(
     }
     for (const delimiter of delimiters) {
       const sourceBlockMatch = line.match(
-        new RegExp(`^source\\s+${delimiter}(.*?)${delimiter}\\s+do`)
+        regEx(`^source\\s+${delimiter}(.*?)${delimiter}\\s+do`)
       );
       if (sourceBlockMatch) {
         const repositoryUrl = sourceBlockMatch[1];
diff --git a/test/config/__snapshots__/validation.spec.ts.snap b/test/config/__snapshots__/validation.spec.ts.snap
index efe8cdf624..72bc3fe2ad 100644
--- a/test/config/__snapshots__/validation.spec.ts.snap
+++ b/test/config/__snapshots__/validation.spec.ts.snap
@@ -59,10 +59,6 @@ Array [
     "depName": "Configuration Error",
     "message": "Invalid regExp for npm.fileMatch: \`abc ([a-z]+) ([a-z]+))\`",
   },
-  Object {
-    "depName": "Configuration Error",
-    "message": "Unsafe regExp for docker.fileMatch: \`(x+x+)+y\`",
-  },
 ]
 `;
 
@@ -113,7 +109,7 @@ Array [
   },
   Object {
     "depName": "Configuration Error",
-    "message": "Unsafe regExp for packageRules[0].excludePackagePatterns: \`(x+x+)+y\`",
+    "message": "Invalid regExp for packageRules[0].excludePackagePatterns: \`(x+x+)+y\`",
   },
 ]
 `;
diff --git a/test/config/validation.spec.ts b/test/config/validation.spec.ts
index 38f4ad7be9..a0d3f3b944 100644
--- a/test/config/validation.spec.ts
+++ b/test/config/validation.spec.ts
@@ -138,6 +138,7 @@ describe('config/validation', () => {
       expect(errors).toMatchSnapshot();
       expect(errors).toHaveLength(0);
     });
+
     it('errors for unsafe fileMatches', async () => {
       const config = {
         npm: {
@@ -151,8 +152,20 @@ describe('config/validation', () => {
         config
       );
       expect(warnings).toHaveLength(0);
-      expect(errors).toHaveLength(2);
+      expect(errors).toHaveLength(1);
       expect(errors).toMatchSnapshot();
     });
+
+    it('validates regEx for each fileMatch', async () => {
+      const config = {
+        fileMatch: ['js', '***$}{]]['],
+      };
+      const { warnings, errors } = await configValidation.validateConfig(
+        config,
+        true
+      );
+      expect(warnings).toHaveLength(0);
+      expect(errors).toHaveLength(1);
+    });
   });
 });
-- 
GitLab