From 981d5a1cce93b450a7749dc6c7967267f1d485ab Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sun, 22 Jul 2018 07:47:23 +0200
Subject: [PATCH] feat: set prBody formatting per-platform
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Pushes “getPrBody” logic into platform level to allow platforms to individually do things like text replacements and body length limiting.

Closes #2267
---
 lib/platform/github/index.js                  | 13 ++++
 lib/platform/gitlab/index.js                  | 19 ++++++
 lib/platform/vsts/index.js                    | 10 +++
 lib/workers/pr/index.js                       | 67 +++++--------------
 .../platform/__snapshots__/index.spec.js.snap |  3 +
 .../github/__snapshots__/index.spec.js.snap   |  2 +
 test/platform/github/index.spec.js            |  7 ++
 .../gitlab/__snapshots__/index.spec.js.snap   |  2 +
 test/platform/gitlab/index.spec.js            |  7 ++
 .../vsts/__snapshots__/index.spec.js.snap     |  2 +
 test/platform/vsts/index.spec.js              |  8 ++-
 .../pr/__snapshots__/index.spec.js.snap       | 40 -----------
 test/workers/pr/index.spec.js                 | 21 +-----
 13 files changed, 88 insertions(+), 113 deletions(-)

diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js
index 2eb8d3ac22..71824311f8 100644
--- a/lib/platform/github/index.js
+++ b/lib/platform/github/index.js
@@ -45,6 +45,7 @@ module.exports = {
   getPrFiles,
   updatePr,
   mergePr,
+  getPrBody,
   // file
   commitFilesToBranch,
   getFile,
@@ -1108,6 +1109,18 @@ async function mergePr(prNo, branchName) {
   return true;
 }
 
+function getPrBody(input) {
+  let prBody = input.replace(
+    /(https?:\/\/github.com\/[^/]*\/[^/]*\/(issues|pull)\/\w+)/g,
+    '`$1`'
+  );
+  prBody = prBody.replace(
+    /]\(https?:\/\/github.com\/[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\)/gi,
+    ']'
+  );
+  return prBody.substring(0, 60000);
+}
+
 // Generic File operations
 
 async function getFile(filePath, branchName) {
diff --git a/lib/platform/gitlab/index.js b/lib/platform/gitlab/index.js
index d299f395e6..859e3bcfcf 100644
--- a/lib/platform/gitlab/index.js
+++ b/lib/platform/gitlab/index.js
@@ -1,8 +1,12 @@
 const is = require('@sindresorhus/is');
 const addrs = require('email-addresses');
+const showdown = require('showdown');
 const get = require('./gl-got-wrapper');
 const endpoints = require('../../util/endpoints');
 
+const converter = new showdown.Converter();
+converter.setFlavor('github');
+
 let config = {};
 
 module.exports = {
@@ -40,6 +44,7 @@ module.exports = {
   getPrFiles,
   updatePr,
   mergePr,
+  getPrBody,
   // file
   commitFilesToBranch,
   getFile,
@@ -697,6 +702,20 @@ async function mergePr(iid) {
   return true;
 }
 
+function getPrBody(input) {
+  // Convert to HTML using GitHub-flavoured markdown as it is more feature-rich than GitLab's flavour
+  return converter
+    .makeHtml(input)
+    .replace(/<\/?h4[^>]*>/g, '**') // See #954
+    .replace(/Pull Request/g, 'Merge Request')
+    .replace(/PR/g, 'MR')
+    .replace(/<summary>/g, '<h1>')
+    .replace(/<\/summary><\/p>/g, '</h1>')
+    .replace(/<p><details><br \/>\n/g, '')
+    .replace(/<p><\/details><\/p>\n/g, '');
+  // TODO: set maximum length
+}
+
 // Generic File operations
 
 async function getFile(filePath, branchName) {
diff --git a/lib/platform/vsts/index.js b/lib/platform/vsts/index.js
index d0be6f6f7d..b1de220485 100644
--- a/lib/platform/vsts/index.js
+++ b/lib/platform/vsts/index.js
@@ -41,6 +41,7 @@ module.exports = {
   getPrFiles,
   updatePr,
   mergePr,
+  getPrBody,
   // file
   commitFilesToBranch,
   getFile,
@@ -493,6 +494,15 @@ async function mergePr(pr) {
   await null;
 }
 
+function getPrBody(input) {
+  // Remove any HTML we use
+  return input
+    .replace('<summary>', '**')
+    .replace('</summary>', '**')
+    .replace('<details>', '')
+    .replace('</details>', '');
+}
+
 function ensureIssue() {
   // istanbul ignore next
   logger.warn(`ensureIssue() is not implemented`);
diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js
index 15c1af0e7f..2bb6a0afc8 100644
--- a/lib/workers/pr/index.js
+++ b/lib/workers/pr/index.js
@@ -1,10 +1,6 @@
 const handlebars = require('handlebars');
-const showdown = require('showdown');
 const changelogHelper = require('./changelog');
 
-const converter = new showdown.Converter();
-converter.setFlavor('github');
-
 module.exports = {
   ensurePr,
   checkAutoMerge,
@@ -139,12 +135,6 @@ async function ensurePr(prConfig) {
           upgrade.releases.push(release);
         });
       }
-      // istanbul ignore next
-      for (let i = 15; i < upgrade.releases.length; i += 1) {
-        if (upgrade.releases[i].releaseNotes) {
-          upgrade.releases[i].releaseNotes.body = null;
-        }
-      }
     }
     config.upgrades.push(upgrade);
   }
@@ -184,48 +174,21 @@ async function ensurePr(prConfig) {
     }
   }
   prBody = prBody.trim();
-  if (config.isGitHub) {
-    // Put a zero width space after every # followed by a digit
-    prBody = prBody.replace(/#(\d)/gi, '#&#8203;$1');
-    // Put a zero width space after every @ symbol to prevent unintended hyperlinking
-    prBody = prBody.replace(/@/g, '@&#8203;');
-    prBody = prBody.replace(/(`\[?@)&#8203;/g, '$1');
-    // Public GitHub repos need links prevented - see #489
-    prBody = prBody.replace(issueRe, '$1#&#8203;$2$3');
-    prBody = prBody.replace(
-      /(https?:\/\/github.com\/[^/]*\/[^/]*\/(issues|pull)\/\w+)/g,
-      '`$1`'
-    );
-    logger.trace('Escaping links to GitHub users');
-    prBody = prBody.replace(
-      /]\(https?:\/\/github.com\/[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\)/gi,
-      ']'
-    );
-    // convert escaped backticks back to `
-    const backTickRe = /&#x60;([^/]*?)&#x60;/g;
-    prBody = prBody.replace(backTickRe, '`$1`');
-    prBody = prBody.replace(/`#&#8203;(\d+)`/g, '`#$1`');
-  }
-  // It would be nice to abstract this to the platform layer but made difficult due to our create/update check
-  if (config.isGitLab) {
-    // Convert to HTML using GitHub-flavoured markdown as it is more feature-rich than GitLab's flavour
-    prBody = converter
-      .makeHtml(prBody)
-      .replace(/<\/?h4[^>]*>/g, '**') // See #954
-      .replace(/Pull Request/g, 'Merge Request')
-      .replace(/PR/g, 'MR')
-      .replace(/<summary>/g, '<h1>')
-      .replace(/<\/summary><\/p>/g, '</h1>')
-      .replace(/<p><details><br \/>\n/g, '')
-      .replace(/<p><\/details><\/p>\n/g, '');
-  } else if (config.isVsts) {
-    // Remove any HTML we use
-    prBody = prBody
-      .replace('<summary>', '**')
-      .replace('</summary>', '**')
-      .replace('<details>', '')
-      .replace('</details>', '');
-  }
+
+  // Generic replacements/link-breakers
+
+  // Put a zero width space after every # followed by a digit
+  prBody = prBody.replace(/#(\d)/gi, '#&#8203;$1');
+  // Put a zero width space after every @ symbol to prevent unintended hyperlinking
+  prBody = prBody.replace(/@/g, '@&#8203;');
+  prBody = prBody.replace(/(`\[?@)&#8203;/g, '$1');
+  prBody = prBody.replace(issueRe, '$1#&#8203;$2$3');
+  // convert escaped backticks back to `
+  const backTickRe = /&#x60;([^/]*?)&#x60;/g;
+  prBody = prBody.replace(backTickRe, '`$1`');
+  prBody = prBody.replace(/`#&#8203;(\d+)`/g, '`#$1`');
+
+  prBody = platform.getPrBody(prBody);
 
   try {
     if (existingPr) {
diff --git a/test/platform/__snapshots__/index.spec.js.snap b/test/platform/__snapshots__/index.spec.js.snap
index e7232e687d..406bdafa74 100644
--- a/test/platform/__snapshots__/index.spec.js.snap
+++ b/test/platform/__snapshots__/index.spec.js.snap
@@ -31,6 +31,7 @@ Array [
   "getPrFiles",
   "updatePr",
   "mergePr",
+  "getPrBody",
   "commitFilesToBranch",
   "getFile",
   "getCommitMessages",
@@ -68,6 +69,7 @@ Array [
   "getPrFiles",
   "updatePr",
   "mergePr",
+  "getPrBody",
   "commitFilesToBranch",
   "getFile",
   "getCommitMessages",
@@ -105,6 +107,7 @@ Array [
   "getPrFiles",
   "updatePr",
   "mergePr",
+  "getPrBody",
   "commitFilesToBranch",
   "getFile",
   "getCommitMessages",
diff --git a/test/platform/github/__snapshots__/index.spec.js.snap b/test/platform/github/__snapshots__/index.spec.js.snap
index 2352841cfe..161db623cc 100644
--- a/test/platform/github/__snapshots__/index.spec.js.snap
+++ b/test/platform/github/__snapshots__/index.spec.js.snap
@@ -454,6 +454,8 @@ Object {
 }
 `;
 
+exports[`platform/github getPrBody(input) returns updated pr body 1`] = `"\`https://github.com/foo/bar/issues/5\` plus also [a link](\`https://github.com/foo/bar/issues/5\`)"`;
+
 exports[`platform/github getPrFiles() returns files 1`] = `
 Array [
   "renovate.json",
diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js
index 245138fb3c..9a7c16c1f4 100644
--- a/test/platform/github/index.spec.js
+++ b/test/platform/github/index.spec.js
@@ -1481,6 +1481,13 @@ describe('platform/github', () => {
       expect(get.mock.calls).toHaveLength(3);
     });
   });
+  describe('getPrBody(input)', () => {
+    it('returns updated pr body', () => {
+      const input =
+        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
+      expect(github.getPrBody(input)).toMatchSnapshot();
+    });
+  });
   describe('mergePr(prNo) - autodetection', () => {
     beforeEach(async () => {
       function guessInitRepo(...args) {
diff --git a/test/platform/gitlab/__snapshots__/index.spec.js.snap b/test/platform/gitlab/__snapshots__/index.spec.js.snap
index f192d986d0..972298c3ac 100644
--- a/test/platform/gitlab/__snapshots__/index.spec.js.snap
+++ b/test/platform/gitlab/__snapshots__/index.spec.js.snap
@@ -243,6 +243,8 @@ Object {
 }
 `;
 
+exports[`platform/gitlab getPrBody(input) returns updated pr body 1`] = `"<p><a href=\\"https://github.com/foo/bar/issues/5\\">https://github.com/foo/bar/issues/5</a> plus also <a href=\\"https://github.com/foo/bar/issues/5\\">a link</a></p>"`;
+
 exports[`platform/gitlab getPrFiles() returns files 1`] = `
 Array [
   "renovate.json",
diff --git a/test/platform/gitlab/index.spec.js b/test/platform/gitlab/index.spec.js
index 8b255dae51..e6f034b908 100644
--- a/test/platform/gitlab/index.spec.js
+++ b/test/platform/gitlab/index.spec.js
@@ -842,6 +842,13 @@ describe('platform/gitlab', () => {
       expect(get.put.mock.calls.length).toEqual(1);
     });
   });
+  describe('getPrBody(input)', () => {
+    it('returns updated pr body', () => {
+      const input =
+        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
+      expect(gitlab.getPrBody(input)).toMatchSnapshot();
+    });
+  });
   describe('getFile(filePath, branchName)', () => {
     beforeEach(async () => {
       get.mockReturnValueOnce({ body: [{ type: 'blob', path: 'some-path' }] });
diff --git a/test/platform/vsts/__snapshots__/index.spec.js.snap b/test/platform/vsts/__snapshots__/index.spec.js.snap
index 2e8400ff0f..87cefbc924 100644
--- a/test/platform/vsts/__snapshots__/index.spec.js.snap
+++ b/test/platform/vsts/__snapshots__/index.spec.js.snap
@@ -114,6 +114,8 @@ Object {
 }
 `;
 
+exports[`platform/vsts getPrBody(input) returns updated pr body 1`] = `"https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)"`;
+
 exports[`platform/vsts getRepos should return an array of repos 1`] = `
 Array [
   Array [],
diff --git a/test/platform/vsts/index.spec.js b/test/platform/vsts/index.spec.js
index 681c9acddb..193e68d5a2 100644
--- a/test/platform/vsts/index.spec.js
+++ b/test/platform/vsts/index.spec.js
@@ -691,7 +691,13 @@ describe('platform/vsts', () => {
       expect(vstsApi.gitApi.mock.calls.length).toBe(3);
     });
   });
-
+  describe('getPrBody(input)', () => {
+    it('returns updated pr body', () => {
+      const input =
+        '<details>https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
+      expect(vsts.getPrBody(input)).toMatchSnapshot();
+    });
+  });
   describe('Not supported by VSTS (yet!)', () => {
     it('setBranchStatus', () => {
       const res = vsts.setBranchStatus();
diff --git a/test/workers/pr/__snapshots__/index.spec.js.snap b/test/workers/pr/__snapshots__/index.spec.js.snap
index 9580360786..f01ce8fe58 100644
--- a/test/workers/pr/__snapshots__/index.spec.js.snap
+++ b/test/workers/pr/__snapshots__/index.spec.js.snap
@@ -26,22 +26,6 @@ Array [
 ]
 `;
 
-exports[`workers/pr ensurePr should convert to HTML PR for gitlab 1`] = `
-Array [
-  "renovate/dummy-1.x",
-  "Update dependency dummy to v1.1.0",
-  "<p>This Merge Request updates dependency <a href=\\"https://github.com/renovateapp/dummy\\">dummy</a> from <code>v1.0.0</code> to <code>v1.1.0</code></p>
-<h1>Release Notes</h1>
-<h3 id=\\"v110httpsgithubcomrenovateappdummycomparev100v110\\"><a href=\\"https://github.com/renovateapp/dummy/compare/v1.0.0…v1.1.0\\"><code>v1.1.0</code></a></h3>
-<p><a href=\\"https://github.com/renovateapp/dummy/compare/v1.0.0…v1.1.0\\">Compare Source</a></p>
-<hr />
-<p></details></p>",
-  Array [],
-  false,
-  false,
-]
-`;
-
 exports[`workers/pr ensurePr should create PR if success 1`] = `
 Array [
   "renovate/dummy-1.x",
@@ -133,27 +117,3 @@ Object {
 `;
 
 exports[`workers/pr ensurePr should return unmodified existing PR 1`] = `Array []`;
-
-exports[`workers/pr ensurePr should strip HTML PR for vsts 1`] = `
-Array [
-  "renovate/dummy-1.x",
-  "Update dependency dummy to v1.1.0",
-  "This Pull Request updates dependency [dummy](https://github.com/renovateapp/dummy) from \`v1.0.0\` to \`v1.1.0\`
-
-
-
-
-**Release Notes**
-
-### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
-[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
-
-
----
-
-",
-  Array [],
-  false,
-  false,
-]
-`;
diff --git a/test/workers/pr/index.spec.js b/test/workers/pr/index.spec.js
index 12a9459e3c..be24b1a835 100644
--- a/test/workers/pr/index.spec.js
+++ b/test/workers/pr/index.spec.js
@@ -118,6 +118,7 @@ describe('workers/pr', () => {
       config.repositoryUrl = 'https://github.com/renovateapp/dummy';
       platform.createPr.mockReturnValue({ displayNumber: 'New Pull Request' });
       config.upgrades = [config];
+      platform.getPrBody = jest.fn(input => input);
     });
     afterEach(() => {
       jest.clearAllMocks();
@@ -145,26 +146,6 @@ describe('workers/pr', () => {
       expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
       existingPr.body = platform.createPr.mock.calls[0][2];
     });
-    it('should convert to HTML PR for gitlab', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
-      config.prCreation = 'status-success';
-      config.isGitLab = true;
-      const pr = await prWorker.ensurePr(config);
-      expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
-      expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
-      expect(
-        platform.createPr.mock.calls[0][2].indexOf('<p>This Merge Request')
-      ).not.toBe(-1);
-    });
-    it('should strip HTML PR for vsts', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
-      config.prCreation = 'status-success';
-      config.isVsts = true;
-      const pr = await prWorker.ensurePr(config);
-      expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
-      expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
-      expect(platform.createPr.mock.calls[0][2].indexOf('<details>')).toBe(-1);
-    });
     it('should return null if creating PR fails', async () => {
       platform.getBranchStatus.mockReturnValueOnce('success');
       platform.createPr = jest.fn();
-- 
GitLab