From 16d88aee4c3c0ff48ddc18f6e066255399f3cd9e Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Thu, 5 Jul 2018 15:10:50 +0200
Subject: [PATCH] feat: ignoreDeprecated

Renovate now ignores any upgrades that are marked as deprecated, unless the current version is itself also deprecated. The new config option `ignoreDeprecated` can be set to false to disable this if necessary.

Closes #1988
---
 lib/config/definitions.js                     |  8 ++++
 lib/datasource/npm.js                         |  3 ++
 .../repository/process/lookup/filter.js       | 26 ++++++++++++-
 .../repository/process/lookup/index.js        |  3 +-
 .../datasource/__snapshots__/npm.spec.js.snap |  1 +
 .../lookup/__snapshots__/index.spec.js.snap   | 37 +++++++++++++++++++
 .../repository/process/lookup/index.spec.js   | 15 ++++++++
 website/docs/configuration-options.md         |  4 ++
 8 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 0a4dfd2c08..b15de71a5a 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -476,6 +476,14 @@ const options = [
     stage: 'package',
     type: 'boolean',
   },
+  {
+    name: 'ignoreDeprecated',
+    description:
+      'Ignore deprecated versions unless the current version is deprecated',
+    stage: 'package',
+    type: 'boolean',
+    default: true,
+  },
   {
     name: 'unstablePattern',
     description: 'Regex for identifying unstable versions (docker only)',
diff --git a/lib/datasource/npm.js b/lib/datasource/npm.js
index bb20f7d472..14c66fdb70 100644
--- a/lib/datasource/npm.js
+++ b/lib/datasource/npm.js
@@ -227,6 +227,9 @@ async function getDependencyInner(name, retries = 5) {
         release.canBeUnpublished =
           moment().diff(moment(release.releaseTimestamp), 'days') === 0;
       }
+      if (res.versions[version].deprecated) {
+        release.isDeprecated = true;
+      }
       return release;
     });
     logger.trace({ dep }, 'dep');
diff --git a/lib/workers/repository/process/lookup/filter.js b/lib/workers/repository/process/lookup/filter.js
index 0ead9b86a7..93052ccf8d 100644
--- a/lib/workers/repository/process/lookup/filter.js
+++ b/lib/workers/repository/process/lookup/filter.js
@@ -4,10 +4,17 @@ module.exports = {
   filterVersions,
 };
 
-function filterVersions(config, fromVersion, latestVersion, versions) {
+function filterVersions(
+  config,
+  fromVersion,
+  latestVersion,
+  versions,
+  releases
+) {
   const {
     versionScheme,
     ignoreUnstable,
+    ignoreDeprecated,
     respectLatest,
     allowedVersions,
   } = config;
@@ -23,6 +30,23 @@ function filterVersions(config, fromVersion, latestVersion, versions) {
     isGreaterThan(version, fromVersion)
   );
 
+  // Don't upgrade from non-deprecated to deprecated
+  const fromRelease = releases.find(release => release.version === fromVersion);
+  if (ignoreDeprecated && fromRelease && !fromRelease.isDeprecated) {
+    filteredVersions = filteredVersions.filter(version => {
+      const versionRelease = releases.find(
+        release => release.version === version
+      );
+      if (versionRelease.isDeprecated) {
+        logger.debug(
+          `Skipping ${config.depName}@${version} because it is deprecated`
+        );
+        return false;
+      }
+      return true;
+    });
+  }
+
   if (allowedVersions) {
     if (isValid(allowedVersions)) {
       filteredVersions = filteredVersions.filter(version =>
diff --git a/lib/workers/repository/process/lookup/index.js b/lib/workers/repository/process/lookup/index.js
index 920ffa16ff..1f23f69372 100644
--- a/lib/workers/repository/process/lookup/index.js
+++ b/lib/workers/repository/process/lookup/index.js
@@ -102,7 +102,8 @@ async function lookupUpdates(config) {
     config,
     fromVersion,
     dependency.latestVersion,
-    allVersions
+    allVersions,
+    releases
   );
   if (!filteredVersions.length) {
     return res;
diff --git a/test/datasource/__snapshots__/npm.spec.js.snap b/test/datasource/__snapshots__/npm.spec.js.snap
index 68ed4c27fd..dbed38b2aa 100644
--- a/test/datasource/__snapshots__/npm.spec.js.snap
+++ b/test/datasource/__snapshots__/npm.spec.js.snap
@@ -146,6 +146,7 @@ Marking the latest version of an npm package as deprecated results in the entire
     Object {
       "canBeUnpublished": false,
       "gitRef": undefined,
+      "isDeprecated": true,
       "releaseTimestamp": "2018-05-07T07:21:53+02:00",
       "version": "0.0.2",
     },
diff --git a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
index 248972d544..63d8a42d37 100644
--- a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
+++ b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
@@ -121,6 +121,43 @@ Array [
 ]
 `;
 
+exports[`manager/npm/lookup .lookupUpdates() ignores deprecated 1`] = `
+Object {
+  "deprecationMessage": "On registry \`https://registry.npmjs.org/\`, the \\"latest\\" version (v1.4.1) of dependency \`q2\` has the following deprecation notice:
+
+\`true\`
+
+Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.",
+  "releases": Array [
+    Object {
+      "canBeUnpublished": false,
+      "gitRef": "b26cace16f6070e756b6a546cf2693bece03f8f8",
+      "releaseTimestamp": "2015-04-26T16:42:11.311Z",
+      "version": "1.3.0",
+    },
+    Object {
+      "canBeUnpublished": false,
+      "gitRef": "05e20dc704421ca820553721c7178168a8461506",
+      "releaseTimestamp": "2015-05-09T16:52:40.699Z",
+      "version": "1.4.0",
+    },
+  ],
+  "repositoryUrl": "https://github.com/kriskowal/q",
+  "updates": Array [
+    Object {
+      "canBeUnpublished": false,
+      "fromVersion": "1.3.0",
+      "newMajor": 1,
+      "newMinor": 4,
+      "newValue": "1.4.0",
+      "releaseTimestamp": "2015-05-09T16:52:40.699Z",
+      "toVersion": "1.4.0",
+      "updateType": "minor",
+    },
+  ],
+}
+`;
+
 exports[`manager/npm/lookup .lookupUpdates() ignores pinning for ranges when other upgrade exists 1`] = `
 Array [
   Object {
diff --git a/test/workers/repository/process/lookup/index.spec.js b/test/workers/repository/process/lookup/index.spec.js
index a0f1bad64f..8fd4807ae6 100644
--- a/test/workers/repository/process/lookup/index.spec.js
+++ b/test/workers/repository/process/lookup/index.spec.js
@@ -858,5 +858,20 @@ describe('manager/npm/lookup', () => {
       expect(res.releases).toHaveLength(3);
       expect(res.repositoryUrl).toBeDefined();
     });
+    it('ignores deprecated', async () => {
+      config.currentValue = '1.3.0';
+      config.depName = 'q2';
+      config.purl = 'pkg:npm/q2';
+      const returnJson = JSON.parse(JSON.stringify(qJson));
+      returnJson.name = 'q2';
+      returnJson.versions['1.4.1'].deprecated = 'true';
+      nock('https://registry.npmjs.org')
+        .get('/q2')
+        .reply(200, returnJson);
+      const res = await lookup.lookupUpdates(config);
+      expect(res).toMatchSnapshot();
+      expect(res.releases).toHaveLength(2);
+      expect(res.updates[0].toVersion).toEqual('1.4.0');
+    });
   });
 });
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index 30bd58f7c4..48be7c0962 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -216,6 +216,10 @@ By default, Renovate will "slugify" the groupName to determine the branch name.
 
 And then the branchName would be `renovate/eslint` instead.
 
+## ignoreDeprecated
+
+By default, Renovate won't update any packages to deprecated versions unless the package version was _already_ deprecated. The goal of this is to make sure you don't upgrade from a non-deprecated version to a deprecated one just because it's higher than the current version. If for some reason you wish to _force_ deprecated updates on Renovate, you can set `ignoreDeprecated` to `false`, but this is not recommended for most situations.
+
 ## ignoreDeps
 
 The `ignoreDeps` configuration field allows you to define a list of dependency names to be ignored by Renovate. Currently it supports only "exact match" dependency names and not any patterns. e.g. to ignore both `eslint` and `eslint-config-base` you would add this to your config:
-- 
GitLab