From c49c884baed063b48b954f920474f255459fcd34 Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Wed, 4 Dec 2019 04:29:27 +0100
Subject: [PATCH] fix(typescript): convert workers/pr/changelog to ts (#4888)

---
 lib/workers/pr/changelog/common.ts            | 49 ++++++++++
 .../{hbs-template.js => hbs-template.ts}      |  2 +-
 .../pr/changelog/{index.js => index.ts}       | 17 ++--
 .../{release-notes.js => release-notes.ts}    | 85 ++++++++++--------
 .../pr/changelog/{releases.js => releases.ts} | 31 ++++---
 .../{source-github.js => source-github.ts}    | 39 ++++----
 lib/workers/pr/pr-body.js                     |  2 +-
 package.json                                  |  1 +
 test/util.ts                                  |  7 ++
 ...{index.spec.js.snap => index.spec.ts.snap} |  0
 ...{index.spec.js.snap => index.spec.ts.snap} | 22 ++---
 ...pec.js.snap => release-notes.spec.ts.snap} |  0
 ...ses.spec.js.snap => releases.spec.ts.snap} |  0
 .../{index.spec.js => index.spec.ts}          | 40 ++++-----
 ...se-notes.spec.js => release-notes.spec.ts} | 35 ++++----
 .../{releases.spec.js => releases.spec.ts}    | 11 ++-
 .../pr/{index.spec.js => index.spec.ts}       | 90 ++++++++++---------
 yarn.lock                                     | 12 +++
 18 files changed, 269 insertions(+), 174 deletions(-)
 create mode 100644 lib/workers/pr/changelog/common.ts
 rename lib/workers/pr/changelog/{hbs-template.js => hbs-template.ts} (92%)
 rename lib/workers/pr/changelog/{index.js => index.ts} (58%)
 rename lib/workers/pr/changelog/{release-notes.js => release-notes.ts} (82%)
 rename lib/workers/pr/changelog/{releases.js => releases.ts} (68%)
 rename lib/workers/pr/changelog/{source-github.js => source-github.ts} (82%)
 create mode 100644 test/util.ts
 rename test/workers/pr/__snapshots__/{index.spec.js.snap => index.spec.ts.snap} (100%)
 rename test/workers/pr/changelog/__snapshots__/{index.spec.js.snap => index.spec.ts.snap} (95%)
 rename test/workers/pr/changelog/__snapshots__/{release-notes.spec.js.snap => release-notes.spec.ts.snap} (100%)
 rename test/workers/pr/changelog/__snapshots__/{releases.spec.js.snap => releases.spec.ts.snap} (100%)
 rename test/workers/pr/changelog/{index.spec.js => index.spec.ts} (87%)
 rename test/workers/pr/changelog/{release-notes.spec.js => release-notes.spec.ts} (84%)
 rename test/workers/pr/changelog/{releases.spec.js => releases.spec.ts} (89%)
 rename test/workers/pr/{index.spec.js => index.spec.ts} (85%)

diff --git a/lib/workers/pr/changelog/common.ts b/lib/workers/pr/changelog/common.ts
new file mode 100644
index 0000000000..36a7a76c18
--- /dev/null
+++ b/lib/workers/pr/changelog/common.ts
@@ -0,0 +1,49 @@
+import { Release } from '../../../datasource';
+
+export interface ChangeLogNotes {
+  body?: string;
+  id?: number;
+  name?: string;
+  tag?: string;
+  url: string;
+}
+
+export interface ChangeLogChange {
+  date: Date;
+  message: string;
+  sha: string;
+}
+
+export interface ChangeLogRelease {
+  changes: ChangeLogChange[];
+  compare: { url?: string };
+  date: string | Date;
+  releaseNotes?: ChangeLogNotes;
+  version: string;
+}
+
+export interface ChangeLogProject {
+  depName?: string;
+  github: string;
+  githubApiBaseURL?: string;
+  githubBaseURL: string;
+  repository: string;
+}
+
+export interface ChangeLogResult {
+  hasReleaseNotes?: boolean;
+  project: ChangeLogProject;
+  versions: ChangeLogRelease[];
+}
+
+export interface ChangeLogConfig {
+  depName: string;
+  depType?: string;
+  endpoint: string;
+  fromVersion: string;
+  manager?: string;
+  releases: Release[];
+  sourceUrl?: string;
+  toVersion: string;
+  versionScheme: string;
+}
diff --git a/lib/workers/pr/changelog/hbs-template.js b/lib/workers/pr/changelog/hbs-template.ts
similarity index 92%
rename from lib/workers/pr/changelog/hbs-template.js
rename to lib/workers/pr/changelog/hbs-template.ts
index 6f2aba58a3..f646ea2f9e 100644
--- a/lib/workers/pr/changelog/hbs-template.js
+++ b/lib/workers/pr/changelog/hbs-template.ts
@@ -1,4 +1,4 @@
-module.exports = `### Release Notes
+export default `### Release Notes
 
 {{#each upgrades as |upgrade|}}
 
diff --git a/lib/workers/pr/changelog/index.js b/lib/workers/pr/changelog/index.ts
similarity index 58%
rename from lib/workers/pr/changelog/index.js
rename to lib/workers/pr/changelog/index.ts
index 70ddcea4d5..40a69f31cc 100644
--- a/lib/workers/pr/changelog/index.js
+++ b/lib/workers/pr/changelog/index.ts
@@ -1,13 +1,14 @@
-const { logger } = require('../../../logger');
-const versioning = require('../../../versioning');
-const sourceGithub = require('./source-github');
-const { getReleases } = require('./releases');
+import { logger } from '../../../logger';
+import * as versioning from '../../../versioning';
+import * as sourceGithub from './source-github';
+import { getReleases } from './releases';
+import { ChangeLogConfig, ChangeLogResult } from './common';
 
-module.exports = {
-  getChangeLogJSON,
-};
+export * from './common';
 
-async function getChangeLogJSON(args) {
+export async function getChangeLogJSON(
+  args: ChangeLogConfig
+): Promise<ChangeLogResult | null> {
   const { sourceUrl, versionScheme, fromVersion, toVersion } = args;
   if (!sourceUrl) {
     return null;
diff --git a/lib/workers/pr/changelog/release-notes.js b/lib/workers/pr/changelog/release-notes.ts
similarity index 82%
rename from lib/workers/pr/changelog/release-notes.js
rename to lib/workers/pr/changelog/release-notes.ts
index f7f3f8bf4a..17b599085a 100644
--- a/lib/workers/pr/changelog/release-notes.js
+++ b/lib/workers/pr/changelog/release-notes.ts
@@ -4,21 +4,17 @@ import MarkdownIt from 'markdown-it';
 
 import { api } from '../../../platform/github/gh-got-wrapper';
 import { logger } from '../../../logger';
+import { ChangeLogResult, ChangeLogNotes } from './common';
 
 const ghGot = api.get;
 
 const markdown = new MarkdownIt('zero');
 markdown.enable(['heading', 'lheading']);
 
-export {
-  getReleaseList,
-  massageBody,
-  getReleaseNotesMd,
-  getReleaseNotes,
-  addReleaseNotes,
-};
-
-async function getReleaseList(githubApiBaseURL, repository) {
+export async function getReleaseList(
+  githubApiBaseURL: string,
+  repository: string
+): Promise<ChangeLogNotes[]> {
   logger.trace('getReleaseList()');
   // istanbul ignore if
   if (!githubApiBaseURL) {
@@ -27,7 +23,15 @@ async function getReleaseList(githubApiBaseURL, repository) {
   try {
     let url = githubApiBaseURL.replace(/\/?$/, '/');
     url += `repos/${repository}/releases?per_page=100`;
-    const res = await ghGot(url);
+    const res = await ghGot<
+      {
+        html_url: string;
+        id: number;
+        tag_name: string;
+        name: string;
+        body: string;
+      }[]
+    >(url);
     return res.body.map(release => ({
       url: release.html_url,
       id: release.id,
@@ -41,7 +45,10 @@ async function getReleaseList(githubApiBaseURL, repository) {
   }
 }
 
-function massageBody(input, githubBaseURL) {
+export function massageBody(
+  input: string | undefined | null,
+  githubBaseURL: string
+): string {
   let body = input || '';
   // Convert line returns
   body = body.replace(/\r\n/g, '\n');
@@ -67,16 +74,16 @@ function massageBody(input, githubBaseURL) {
   return body.trim();
 }
 
-async function getReleaseNotes(
-  repository,
-  version,
-  depName,
-  githubBaseURL,
-  githubApiBaseURL
-) {
+export async function getReleaseNotes(
+  repository: string,
+  version: string,
+  depName: string,
+  githubBaseURL: string,
+  githubApiBaseURL: string
+): Promise<ChangeLogNotes | null> {
   logger.trace(`getReleaseNotes(${repository}, ${version}, ${depName})`);
   const releaseList = await getReleaseList(githubApiBaseURL, repository);
-  let releaseNotes;
+  let releaseNotes: ChangeLogNotes | null = null;
   releaseList.forEach(release => {
     if (
       release.tag === version ||
@@ -87,7 +94,7 @@ async function getReleaseNotes(
       releaseNotes.url = `${githubBaseURL}${repository}/releases/${release.tag}`;
       releaseNotes.body = massageBody(releaseNotes.body, githubBaseURL);
       if (!releaseNotes.body.length) {
-        releaseNotes = undefined;
+        releaseNotes = null;
       } else {
         releaseNotes.body = linkify(releaseNotes.body, {
           repository: `https://github.com/${repository}`,
@@ -99,10 +106,10 @@ async function getReleaseNotes(
   return releaseNotes;
 }
 
-function sectionize(text, level) {
-  const sections = [];
+function sectionize(text: string, level: number): string[] {
+  const sections: [number, number][] = [];
   const lines = text.split('\n');
-  const tokens = markdown.parse(text);
+  const tokens = markdown.parse(text, undefined);
   tokens.forEach(token => {
     if (token.type === 'heading_open') {
       const lev = +token.tag.substr(1);
@@ -112,7 +119,7 @@ function sectionize(text, level) {
     }
   });
   sections.push([-1, lines.length]);
-  const result = [];
+  const result: string[] = [];
   for (let i = 1; i < sections.length; i += 1) {
     const [lev, start] = sections[i - 1];
     const [, end] = sections[i];
@@ -123,25 +130,25 @@ function sectionize(text, level) {
   return result;
 }
 
-async function getReleaseNotesMd(
-  repository,
-  version,
-  githubBaseURL,
-  githubApiBaseUrl
-) {
+export async function getReleaseNotesMd(
+  repository: string,
+  version: string,
+  githubBaseURL: string,
+  githubApiBaseUrl: string
+): Promise<ChangeLogNotes | null> {
   logger.trace(`getReleaseNotesMd(${repository}, ${version})`);
   const skippedRepos = ['facebook/react-native'];
   // istanbul ignore if
   if (skippedRepos.includes(repository)) {
     return null;
   }
-  let changelogFile;
+  let changelogFile: string;
   let changelogMd = '';
   try {
     let apiPrefix = githubApiBaseUrl.replace(/\/?$/, '/');
 
     apiPrefix += `repos/${repository}/contents/`;
-    const filesRes = await ghGot(apiPrefix);
+    const filesRes = await ghGot<{ name: string }[]>(apiPrefix);
     const files = filesRes.body
       .map(f => f.name)
       .filter(f => changelogFilenameRegex.test(f));
@@ -156,7 +163,9 @@ async function getReleaseNotesMd(
         `Multiple candidates for changelog file, using ${changelogFile}`
       );
     }
-    const fileRes = await ghGot(`${apiPrefix}/${changelogFile}`);
+    const fileRes = await ghGot<{ content: string }>(
+      `${apiPrefix}/${changelogFile}`
+    );
     changelogMd =
       Buffer.from(fileRes.body.content, 'base64').toString() + '\n#\n##';
   } catch (err) {
@@ -208,19 +217,21 @@ async function getReleaseNotesMd(
   return null;
 }
 
-async function addReleaseNotes(input) {
+export async function addReleaseNotes(
+  input: ChangeLogResult
+): Promise<ChangeLogResult> {
   if (!(input && input.project && input.project.github && input.versions)) {
     logger.debug('Missing project or versions');
     return input;
   }
-  const output = { ...input, versions: [] };
+  const output: ChangeLogResult = { ...input, versions: [] };
   const repository = input.project.github.replace(/\.git$/, '');
   const cacheNamespace = 'changelog-github-notes';
-  function getCacheKey(version) {
+  function getCacheKey(version: string): string {
     return `${repository}:${version}`;
   }
   for (const v of input.versions) {
-    let releaseNotes;
+    let releaseNotes: ChangeLogNotes;
     const cacheKey = getCacheKey(v.version);
     releaseNotes = await renovateCache.get(cacheNamespace, cacheKey);
     if (!releaseNotes) {
diff --git a/lib/workers/pr/changelog/releases.js b/lib/workers/pr/changelog/releases.ts
similarity index 68%
rename from lib/workers/pr/changelog/releases.js
rename to lib/workers/pr/changelog/releases.ts
index 5357922b4f..941b6573c0 100644
--- a/lib/workers/pr/changelog/releases.js
+++ b/lib/workers/pr/changelog/releases.ts
@@ -1,12 +1,8 @@
-const { getPkgReleases } = require('../../../datasource');
-const { logger } = require('../../../logger');
-const versioning = require('../../../versioning');
+import { getPkgReleases, Release, PkgReleaseConfig } from '../../../datasource';
+import { logger } from '../../../logger';
+import { get, VersioningApi } from '../../../versioning';
 
-module.exports = {
-  getReleases,
-};
-
-function matchesMMP(version, v1, v2) {
+function matchesMMP(version: VersioningApi, v1: string, v2: string): boolean {
   return (
     version.getMajor(v1) === version.getMajor(v2) &&
     version.getMinor(v1) === version.getMinor(v2) &&
@@ -14,15 +10,28 @@ function matchesMMP(version, v1, v2) {
   );
 }
 
-function matchesUnstable(version, v1, v2) {
+function matchesUnstable(
+  version: VersioningApi,
+  v1: string,
+  v2: string
+): boolean {
   return !version.isStable(v1) && matchesMMP(version, v1, v2);
 }
 
-async function getReleases(config) {
+export type ReleaseConfig = PkgReleaseConfig & {
+  fromVersion: string;
+  releases?: Release[];
+  sourceUrl?: string;
+  toVersion: string;
+};
+
+export async function getReleases(
+  config: ReleaseConfig
+): Promise<Release[] | null> {
   const { versionScheme, fromVersion, toVersion, depName, datasource } = config;
   try {
     const pkgReleases = (await getPkgReleases(config)).releases;
-    const version = versioning.get(versionScheme);
+    const version = get(versionScheme);
 
     const releases = pkgReleases
       .filter(release => version.isCompatible(release.version, fromVersion))
diff --git a/lib/workers/pr/changelog/source-github.js b/lib/workers/pr/changelog/source-github.ts
similarity index 82%
rename from lib/workers/pr/changelog/source-github.js
rename to lib/workers/pr/changelog/source-github.ts
index e1b49523f1..b805085ccb 100644
--- a/lib/workers/pr/changelog/source-github.js
+++ b/lib/workers/pr/changelog/source-github.ts
@@ -1,22 +1,25 @@
+import URL from 'url';
 import { api } from '../../../platform/github/gh-got-wrapper';
-
-const URL = require('url');
-const { logger } = require('../../../logger');
-const hostRules = require('../../../util/host-rules');
-const versioning = require('../../../versioning');
-const { addReleaseNotes } = require('./release-notes');
+import { logger } from '../../../logger';
+import * as hostRules from '../../../util/host-rules';
+import * as versioning from '../../../versioning';
+import { addReleaseNotes } from './release-notes';
+import { ChangeLogResult, ChangeLogRelease, ChangeLogConfig } from './common';
+import { Release } from '../../../datasource';
 
 const ghGot = api.get;
 
-export { getChangeLogJSON };
-
-async function getTags(endpoint, versionScheme, repository) {
+async function getTags(
+  endpoint: string,
+  versionScheme: string,
+  repository: string
+): Promise<string[]> {
   let url = endpoint
     ? endpoint.replace(/\/?$/, '/')
     : /* istanbul ignore next: not possible to test, maybe never possible? */ 'https://api.github.com/';
   url += `repos/${repository}/tags?per_page=100`;
   try {
-    const res = await ghGot(url, {
+    const res = await ghGot<{ name: string }[]>(url, {
       paginate: true,
     });
 
@@ -39,7 +42,7 @@ async function getTags(endpoint, versionScheme, repository) {
   }
 }
 
-async function getChangeLogJSON({
+export async function getChangeLogJSON({
   endpoint,
   versionScheme,
   fromVersion,
@@ -48,7 +51,7 @@ async function getChangeLogJSON({
   releases,
   depName,
   manager,
-}) {
+}: ChangeLogConfig): Promise<ChangeLogResult | null> {
   if (sourceUrl === 'https://github.com/DefinitelyTyped/DefinitelyTyped') {
     logger.debug('No release notes for @types');
     return null;
@@ -89,9 +92,9 @@ async function getChangeLogJSON({
     return null;
   }
 
-  let tags;
+  let tags: string[];
 
-  async function getRef(release) {
+  async function getRef(release: Release): Promise<string | null> {
     if (!tags) {
       tags = await getTags(endpoint, versionScheme, repository);
     }
@@ -109,13 +112,13 @@ async function getChangeLogJSON({
   }
 
   const cacheNamespace = 'changelog-github-release';
-  function getCacheKey(prev, next) {
+  function getCacheKey(prev: string, next: string): string {
     return `${manager}:${depName}:${prev}:${next}`;
   }
 
-  const changelogReleases = [];
+  const changelogReleases: ChangeLogRelease[] = [];
   // compare versions
-  const include = v =>
+  const include = (v: string): boolean =>
     version.isGreaterThan(v, fromVersion) &&
     !version.isGreaterThan(v, toVersion);
   for (let i = 1; i < validReleases.length; i += 1) {
@@ -151,7 +154,7 @@ async function getChangeLogJSON({
     }
   }
 
-  let res = {
+  let res: ChangeLogResult = {
     project: {
       githubApiBaseURL,
       githubBaseURL,
diff --git a/lib/workers/pr/pr-body.js b/lib/workers/pr/pr-body.js
index f922010a14..bf11567937 100644
--- a/lib/workers/pr/pr-body.js
+++ b/lib/workers/pr/pr-body.js
@@ -1,9 +1,9 @@
 import is from '@sindresorhus/is';
 import { platform } from '../../platform';
+import releaseNotesHbs from './changelog/hbs-template';
 
 const handlebars = require('handlebars');
 const { logger } = require('../../logger');
-const releaseNotesHbs = require('./changelog/hbs-template');
 const { getPrConfigDescription } = require('./pr-body-config');
 
 const versioning = require('../../versioning');
diff --git a/package.json b/package.json
index e889b687bd..3385bd8029 100644
--- a/package.json
+++ b/package.json
@@ -182,6 +182,7 @@
     "@types/later": "1.2.5",
     "@types/lodash": "4.14.149",
     "@types/luxon": "1.21.0",
+    "@types/markdown-it": "0.0.9",
     "@types/moment-timezone": "0.5.12",
     "@types/nock": "10.0.3",
     "@types/node": "11.15.3",
diff --git a/test/util.ts b/test/util.ts
new file mode 100644
index 0000000000..1c6d9624e8
--- /dev/null
+++ b/test/util.ts
@@ -0,0 +1,7 @@
+/**
+ * Simple wrapper for getting mocked version of a module
+ * @param module module which is mocked by `jest.mock`
+ */
+export function mocked<T>(module: T): jest.Mocked<T> {
+  return module as never;
+}
diff --git a/test/workers/pr/__snapshots__/index.spec.js.snap b/test/workers/pr/__snapshots__/index.spec.ts.snap
similarity index 100%
rename from test/workers/pr/__snapshots__/index.spec.js.snap
rename to test/workers/pr/__snapshots__/index.spec.ts.snap
diff --git a/test/workers/pr/changelog/__snapshots__/index.spec.js.snap b/test/workers/pr/changelog/__snapshots__/index.spec.ts.snap
similarity index 95%
rename from test/workers/pr/changelog/__snapshots__/index.spec.js.snap
rename to test/workers/pr/changelog/__snapshots__/index.spec.ts.snap
index 23396fe33b..54e0ad951a 100644
--- a/test/workers/pr/changelog/__snapshots__/index.spec.js.snap
+++ b/test/workers/pr/changelog/__snapshots__/index.spec.ts.snap
@@ -15,14 +15,14 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
       "changes": Array [],
       "compare": Object {},
       "date": "2017-12-24T03:20:46.238Z",
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.4.2",
     },
     Object {
@@ -66,14 +66,14 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
       "changes": Array [],
       "compare": Object {},
       "date": "2017-12-24T03:20:46.238Z",
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.4.2",
     },
     Object {
@@ -117,14 +117,14 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
       "changes": Array [],
       "compare": Object {},
       "date": "2017-12-24T03:20:46.238Z",
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.4.2",
     },
     Object {
@@ -168,14 +168,14 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
       "changes": Array [],
       "compare": Object {},
       "date": "2017-12-24T03:20:46.238Z",
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.4.2",
     },
     Object {
@@ -219,7 +219,7 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
@@ -274,14 +274,14 @@ Object {
       "changes": Array [],
       "compare": Object {},
       "date": undefined,
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.5.2",
     },
     Object {
       "changes": Array [],
       "compare": Object {},
       "date": "2017-12-24T03:20:46.238Z",
-      "releaseNotes": undefined,
+      "releaseNotes": null,
       "version": "2.4.2",
     },
     Object {
diff --git a/test/workers/pr/changelog/__snapshots__/release-notes.spec.js.snap b/test/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap
similarity index 100%
rename from test/workers/pr/changelog/__snapshots__/release-notes.spec.js.snap
rename to test/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap
diff --git a/test/workers/pr/changelog/__snapshots__/releases.spec.js.snap b/test/workers/pr/changelog/__snapshots__/releases.spec.ts.snap
similarity index 100%
rename from test/workers/pr/changelog/__snapshots__/releases.spec.js.snap
rename to test/workers/pr/changelog/__snapshots__/releases.spec.ts.snap
diff --git a/test/workers/pr/changelog/index.spec.js b/test/workers/pr/changelog/index.spec.ts
similarity index 87%
rename from test/workers/pr/changelog/index.spec.js
rename to test/workers/pr/changelog/index.spec.ts
index 1bc3c6d435..8a44a6fc39 100644
--- a/test/workers/pr/changelog/index.spec.js
+++ b/test/workers/pr/changelog/index.spec.ts
@@ -1,19 +1,17 @@
 import { api } from '../../../../lib/platform/github/gh-got-wrapper';
+import * as hostRules from '../../../../lib/util/host-rules';
+import {
+  getChangeLogJSON,
+  ChangeLogConfig,
+} from '../../../../lib/workers/pr/changelog';
+import { mocked } from '../../../util';
 
 jest.mock('../../../../lib/platform/github/gh-got-wrapper');
 jest.mock('../../../../lib/datasource/npm');
 
-/** @type any */
-const ghGot = api.get;
+const ghGot = mocked(api).get;
 
-const hostRules = require('../../../../lib/util/host-rules');
-
-const { getChangeLogJSON } = require('../../../../lib/workers/pr/changelog');
-const releaseNotes = require('../../../../lib/workers/pr/changelog/release-notes');
-
-releaseNotes.addReleaseNotes = jest.fn(input => input);
-
-const upgrade = {
+const upgrade: ChangeLogConfig = {
   endpoint: 'https://api.github.com/',
   depName: 'renovate',
   versionScheme: 'semver',
@@ -90,18 +88,16 @@ describe('workers/pr/changelog', () => {
       ).toMatchSnapshot();
     });
     it('uses GitHub tags', async () => {
-      ghGot.mockReturnValueOnce(
-        Promise.resolve({
-          body: [
-            { name: '0.9.0' },
-            { name: '1.0.0' },
-            { name: '1.4.0' },
-            { name: 'v2.3.0' },
-            { name: '2.2.2' },
-            { name: 'v2.4.2' },
-          ],
-        })
-      );
+      ghGot.mockResolvedValueOnce({
+        body: [
+          { name: '0.9.0' },
+          { name: '1.0.0' },
+          { name: '1.4.0' },
+          { name: 'v2.3.0' },
+          { name: '2.2.2' },
+          { name: 'v2.4.2' },
+        ],
+      } as never);
       expect(
         await getChangeLogJSON({
           ...upgrade,
diff --git a/test/workers/pr/changelog/release-notes.spec.js b/test/workers/pr/changelog/release-notes.spec.ts
similarity index 84%
rename from test/workers/pr/changelog/release-notes.spec.js
rename to test/workers/pr/changelog/release-notes.spec.ts
index b8cce3c2ff..6a0771551f 100644
--- a/test/workers/pr/changelog/release-notes.spec.js
+++ b/test/workers/pr/changelog/release-notes.spec.ts
@@ -6,8 +6,7 @@ import {
   getReleaseNotesMd,
 } from '../../../../lib/workers/pr/changelog/release-notes';
 
-/** @type any */
-const ghGot = got;
+const ghGot: jest.Mock<Promise<{ body: unknown }>> = got as never;
 
 const angularJsChangelogMd = fs.readFileSync(
   'test/workers/pr/_fixtures/angular.js.md',
@@ -35,12 +34,12 @@ describe('workers/pr/release-notes', () => {
   describe('addReleaseNotes()', () => {
     it('returns input if invalid', async () => {
       const input = { a: 1 };
-      expect(await addReleaseNotes(input)).toEqual(input);
+      expect(await addReleaseNotes(input as never)).toEqual(input);
     });
   });
   describe('getReleaseNotes()', () => {
-    it('should return undefined for release notes without body', async () => {
-      ghGot.mockReturnValueOnce({
+    it('should return null for release notes without body', async () => {
+      ghGot.mockResolvedValueOnce({
         body: [{ tag_name: 'v1.0.0' }, { tag_name: 'v1.0.1' }],
       });
       const res = await getReleaseNotes(
@@ -50,12 +49,12 @@ describe('workers/pr/release-notes', () => {
         'https://github.com/',
         'https://api.github.com/'
       );
-      expect(res).toBeUndefined();
+      expect(res).toBeNull();
     });
     it.each([[''], ['v'], ['other-']])(
       'gets release notes with body',
       async prefix => {
-        ghGot.mockReturnValueOnce({
+        ghGot.mockResolvedValueOnce({
           body: [
             { tag_name: `${prefix}1.0.0` },
             {
@@ -87,7 +86,7 @@ describe('workers/pr/release-notes', () => {
       expect(res).toBeNull();
     });
     it('handles files mismatch', async () => {
-      ghGot.mockReturnValueOnce({
+      ghGot.mockResolvedValueOnce({
         body: [{ name: 'lib' }, { name: 'README.md' }],
       });
       const res = await getReleaseNotesMd(
@@ -100,8 +99,8 @@ describe('workers/pr/release-notes', () => {
     });
     it('handles wrong format', async () => {
       ghGot
-        .mockReturnValueOnce({ body: contentsResponse })
-        .mockReturnValueOnce({
+        .mockResolvedValueOnce({ body: contentsResponse })
+        .mockResolvedValueOnce({
           body: {
             content: Buffer.from('not really markdown').toString('base64'),
           },
@@ -116,8 +115,8 @@ describe('workers/pr/release-notes', () => {
     });
     it('handles bad markdown', async () => {
       ghGot
-        .mockReturnValueOnce({ body: contentsResponse })
-        .mockReturnValueOnce({
+        .mockResolvedValueOnce({ body: contentsResponse })
+        .mockResolvedValueOnce({
           body: {
             content: Buffer.from(`#\nha\nha\n#\nha\nha`).toString('base64'),
           },
@@ -132,8 +131,8 @@ describe('workers/pr/release-notes', () => {
     });
     it('parses angular.js', async () => {
       ghGot
-        .mockReturnValueOnce({ body: contentsResponse })
-        .mockReturnValueOnce({
+        .mockResolvedValueOnce({ body: contentsResponse })
+        .mockResolvedValueOnce({
           body: {
             content: Buffer.from(angularJsChangelogMd).toString('base64'),
           },
@@ -149,8 +148,8 @@ describe('workers/pr/release-notes', () => {
     });
     it('parses jest', async () => {
       ghGot
-        .mockReturnValueOnce({ body: contentsResponse })
-        .mockReturnValueOnce({
+        .mockResolvedValueOnce({ body: contentsResponse })
+        .mockResolvedValueOnce({
           body: {
             content: Buffer.from(jestChangelogMd).toString('base64'),
           },
@@ -166,8 +165,8 @@ describe('workers/pr/release-notes', () => {
     });
     it('parses js-yaml', async () => {
       ghGot
-        .mockReturnValueOnce({ body: contentsResponse })
-        .mockReturnValueOnce({
+        .mockResolvedValueOnce({ body: contentsResponse })
+        .mockResolvedValueOnce({
           body: {
             content: Buffer.from(jsYamlChangelogMd).toString('base64'),
           },
diff --git a/test/workers/pr/changelog/releases.spec.js b/test/workers/pr/changelog/releases.spec.ts
similarity index 89%
rename from test/workers/pr/changelog/releases.spec.js
rename to test/workers/pr/changelog/releases.spec.ts
index f79aa195e0..bd0bafb498 100644
--- a/test/workers/pr/changelog/releases.spec.js
+++ b/test/workers/pr/changelog/releases.spec.ts
@@ -1,16 +1,15 @@
-import releases from '../../../../lib/workers/pr/changelog/releases';
-
-const datasource = require('../../../../lib/datasource');
+import * as releases from '../../../../lib/workers/pr/changelog/releases';
+import * as datasource from '../../../../lib/datasource';
+import { mocked } from '../../../util';
 
 jest.mock('../../../../lib/datasource');
 
-/** @type any */
-const ds = datasource;
+const ds = mocked(datasource);
 
 describe('workers/pr/changelog/releases', () => {
   describe('getReleaseNotes()', () => {
     beforeEach(() => {
-      ds.getPkgReleases.mockReturnValueOnce({
+      ds.getPkgReleases.mockResolvedValueOnce({
         releases: [
           {
             version: '1.0.0',
diff --git a/test/workers/pr/index.spec.js b/test/workers/pr/index.spec.ts
similarity index 85%
rename from test/workers/pr/index.spec.js
rename to test/workers/pr/index.spec.ts
index 61208bc832..912ee3f025 100644
--- a/test/workers/pr/index.spec.js
+++ b/test/workers/pr/index.spec.ts
@@ -1,14 +1,17 @@
-const prWorker = require('../../../lib/workers/pr');
-/** @type any */
-const changelogHelper = require('../../../lib/workers/pr/changelog');
-const defaultConfig = require('../../../lib/config/defaults').getConfig();
-/** @type any */
-const { platform } = require('../../../lib/platform');
+import * as prWorker from '../../../lib/workers/pr';
+import * as _changelogHelper from '../../../lib/workers/pr/changelog';
+import { getConfig } from '../../../lib/config/defaults';
+import { platform as _platform, Pr } from '../../../lib/platform';
+import { mocked } from '../../util';
+
+const changelogHelper = mocked(_changelogHelper);
+const platform = mocked(_platform);
+const defaultConfig = getConfig();
 
 jest.mock('../../../lib/workers/pr/changelog');
 
 changelogHelper.getChangeLogJSON = jest.fn();
-changelogHelper.getChangeLogJSON.mockReturnValue({
+changelogHelper.getChangeLogJSON.mockResolvedValue({
   project: {
     githubBaseURL: 'https://github.com/',
     github: 'renovateapp/dummy',
@@ -61,8 +64,8 @@ describe('workers/pr', () => {
     it('should automerge if enabled and pr is mergeable', async () => {
       config.automerge = true;
       pr.isModified = false;
-      platform.getBranchStatus.mockReturnValueOnce('success');
-      platform.mergePr.mockReturnValueOnce(true);
+      platform.getBranchStatus.mockResolvedValueOnce('success');
+      platform.mergePr.mockResolvedValueOnce(true);
       await prWorker.checkAutoMerge(pr, config);
       expect(platform.mergePr).toHaveBeenCalledTimes(1);
     });
@@ -71,20 +74,20 @@ describe('workers/pr', () => {
       config.automergeType = 'pr-comment';
       config.automergeComment = '!merge';
       pr.isModified = false;
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       await prWorker.checkAutoMerge(pr, config);
       expect(platform.ensureComment).toHaveBeenCalledTimes(1);
     });
     it('should not automerge if enabled and pr is mergeable but cannot rebase', async () => {
       config.automerge = true;
       pr.isModified = true;
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       await prWorker.checkAutoMerge(pr, config);
       expect(platform.mergePr).toHaveBeenCalledTimes(0);
     });
     it('should not automerge if enabled and pr is mergeable but branch status is not success', async () => {
       config.automerge = true;
-      platform.getBranchStatus.mockReturnValueOnce('pending');
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
       await prWorker.checkAutoMerge(pr, config);
       expect(platform.mergePr).toHaveBeenCalledTimes(0);
     });
@@ -102,15 +105,15 @@ describe('workers/pr', () => {
     });
   });
   describe('ensurePr', () => {
-    /** @type any */
     let config;
-    const existingPr = {
+    // TODO fix type
+    const existingPr: Pr = {
       displayNumber: 'Existing PR',
       title: 'Update dependency dummy to v1.1.0',
       body:
         'Some body<!-- Reviewable:start -->something<!-- Reviewable:end -->\n\n',
       isModified: false,
-    };
+    } as never;
     beforeEach(() => {
       config = {
         ...defaultConfig,
@@ -127,7 +130,10 @@ describe('workers/pr', () => {
       config.sourceUrl = 'https://github.com/renovateapp/dummy';
       config.sourceDirectory = 'packages/a';
       config.changelogUrl = 'https://github.com/renovateapp/dummy/changelog.md';
-      platform.createPr.mockReturnValue({ displayNumber: 'New Pull Request' });
+      // TODO fix type
+      platform.createPr.mockResolvedValue({
+        displayNumber: 'New Pull Request',
+      } as never);
       config.upgrades = [config];
       platform.getPrBody = jest.fn(input => input);
       platform.getBranchPr = jest.fn();
@@ -141,24 +147,24 @@ describe('workers/pr', () => {
         throw new Error('oops');
       });
       config.newValue = '1.2.0';
-      platform.getBranchPr.mockReturnValueOnce(existingPr);
+      platform.getBranchPr.mockResolvedValueOnce(existingPr);
       const pr = await prWorker.ensurePr(config);
       expect(pr).toBeNull();
     });
     it('should return null if waiting for success', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('failed');
+      platform.getBranchStatus.mockResolvedValueOnce('failed');
       config.prCreation = 'status-success';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toBeNull();
     });
     it('should return needs-approval if prCreation set to approval', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       config.prCreation = 'approval';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toBe('needs-pr-approval');
     });
     it('should create PR if success', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       config.prCreation = 'status-success';
       config.automerge = true;
       config.schedule = 'before 5am';
@@ -199,7 +205,7 @@ describe('workers/pr', () => {
       expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
     });
     it('should add note about Pin', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       config.prCreation = 'status-success';
       config.isPin = true;
       config.updateType = 'pin';
@@ -214,7 +220,7 @@ describe('workers/pr', () => {
       );
     });
     it('should return null if creating PR fails', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       platform.createPr = jest.fn();
       platform.createPr.mockImplementationOnce(() => {
         throw new Error('Validation Failed (422)');
@@ -224,23 +230,25 @@ describe('workers/pr', () => {
       expect(pr).toBeNull();
     });
     it('should return null if waiting for not pending', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('pending');
-      platform.getBranchLastCommitTime.mockImplementationOnce(() => new Date());
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
+      platform.getBranchLastCommitTime.mockImplementationOnce(() =>
+        Promise.resolve(new Date())
+      );
       config.prCreation = 'not-pending';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toBeNull();
     });
     it('should create PR if pending timeout hit', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('pending');
-      platform.getBranchLastCommitTime.mockImplementationOnce(
-        () => new Date('2017-01-01')
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
+      platform.getBranchLastCommitTime.mockImplementationOnce(() =>
+        Promise.resolve(new Date('2017-01-01'))
       );
       config.prCreation = 'not-pending';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
     });
     it('should create PR if no longer pending', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('failed');
+      platform.getBranchStatus.mockResolvedValueOnce('failed');
       config.prCreation = 'not-pending';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
@@ -318,7 +326,7 @@ describe('workers/pr', () => {
       expect(config.reviewers).toEqual(expect.arrayContaining(reviewers));
     });
     it('should return unmodified existing PR', async () => {
-      platform.getBranchPr.mockReturnValueOnce(existingPr);
+      platform.getBranchPr.mockResolvedValueOnce(existingPr);
       config.semanticCommitScope = null;
       config.automerge = true;
       config.schedule = 'before 5am';
@@ -333,7 +341,7 @@ describe('workers/pr', () => {
           .replace(' ', '  ')
           .replace('\n', '\r\n')
       );
-      platform.getBranchPr.mockReturnValueOnce(modifiedPr);
+      platform.getBranchPr.mockResolvedValueOnce(modifiedPr);
       config.semanticCommitScope = null;
       config.automerge = true;
       config.schedule = 'before 5am';
@@ -345,13 +353,13 @@ describe('workers/pr', () => {
       config.newValue = '1.2.0';
       config.automerge = true;
       config.schedule = 'before 5am';
-      platform.getBranchPr.mockReturnValueOnce(existingPr);
+      platform.getBranchPr.mockResolvedValueOnce(existingPr);
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchSnapshot();
     });
     it('should return modified existing PR title', async () => {
       config.newValue = '1.2.0';
-      platform.getBranchPr.mockReturnValueOnce({
+      platform.getBranchPr.mockResolvedValueOnce({
         ...existingPr,
         title: 'wrong',
       });
@@ -362,14 +370,14 @@ describe('workers/pr', () => {
       config.automerge = true;
       config.automergeType = 'branch';
       config.branchAutomergeFailureMessage = 'branch status error';
-      platform.getBranchStatus.mockReturnValueOnce('failure');
+      platform.getBranchStatus.mockResolvedValueOnce('failed');
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
     });
     it('should create PR if branch automerging failed', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       config.forcePr = true;
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
@@ -377,16 +385,16 @@ describe('workers/pr', () => {
     it('should return null if branch automerging not failed', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      platform.getBranchStatus.mockReturnValueOnce('pending');
-      platform.getBranchLastCommitTime.mockReturnValueOnce(new Date());
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
+      platform.getBranchLastCommitTime.mockResolvedValueOnce(new Date());
       const pr = await prWorker.ensurePr(config);
       expect(pr).toBeNull();
     });
     it('should not return null if branch automerging taking too long', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      platform.getBranchStatus.mockReturnValueOnce('pending');
-      platform.getBranchLastCommitTime.mockReturnValueOnce(
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
+      platform.getBranchLastCommitTime.mockResolvedValueOnce(
         new Date('2018-01-01')
       );
       const pr = await prWorker.ensurePr(config);
@@ -398,7 +406,7 @@ describe('workers/pr', () => {
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
     });
     it('should create privateRepo PR if success', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('success');
+      platform.getBranchStatus.mockResolvedValueOnce('success');
       config.prCreation = 'status-success';
       config.privateRepo = false;
       const pr = await prWorker.ensurePr(config);
@@ -407,8 +415,8 @@ describe('workers/pr', () => {
       existingPr.body = platform.createPr.mock.calls[0][2];
     });
     it('should create PR if waiting for not pending but artifactErrors', async () => {
-      platform.getBranchStatus.mockReturnValueOnce('pending');
-      platform.getBranchLastCommitTime.mockImplementationOnce(() => new Date());
+      platform.getBranchStatus.mockResolvedValueOnce('pending');
+      platform.getBranchLastCommitTime.mockResolvedValueOnce(new Date());
       config.prCreation = 'not-pending';
       config.artifactErrors = [{}];
       config.platform = 'gitlab';
diff --git a/yarn.lock b/yarn.lock
index 9a410ef2f2..f0a1cc4f95 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1252,6 +1252,11 @@
   resolved "https://registry.yarnpkg.com/@types/later/-/later-1.2.5.tgz#56297f82ea0002549159219d75844a9380ad18bd"
   integrity sha512-oROpXZfXR+6uQyz4wBs2gZYxvZJ5Zp+iSEJWrIWgvipSnzLcHCpR9br4XUqRBPFS1EP971JlnWfXhr5so5E1sg==
 
+"@types/linkify-it@*":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806"
+  integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
+
 "@types/lodash@4.14.149":
   version "4.14.149"
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
@@ -1262,6 +1267,13 @@
   resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.21.0.tgz#db792d29f535d49522cb6d94dd9da053efc950a1"
   integrity sha512-Zhrf65tpjOlVIYrUhX9eu1VzRo8iixQDLFPbfqFxPpG4pBTNNPZ2BFhYE0IAsDfW9GWg+RcrUqiLwrGJH4rq4w==
 
+"@types/markdown-it@0.0.9":
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
+  integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
+  dependencies:
+    "@types/linkify-it" "*"
+
 "@types/minimatch@*":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
-- 
GitLab