From 757154ded4ce0fb8ac1d2f72dc19dc9115fa455a Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sat, 28 Apr 2018 08:56:15 +0200
Subject: [PATCH] feat: add paths selector to packageRules (#1879)

---
 lib/config/definitions.js                     |  2 +-
 lib/config/validation.js                      |  3 ++-
 lib/workers/package-file/dep-type.js          | 13 +++++++++-
 .../__snapshots__/validation.spec.js.snap     |  2 +-
 test/config/validation.spec.js                |  6 +++++
 test/workers/package-file/dep-type.spec.js    | 25 +++++++++++++++++++
 .../2017-10-05-configuration-options.md       |  2 +-
 7 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 5e3331619c..90eda768f5 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -293,7 +293,7 @@ const options = [
   {
     name: 'paths',
     description:
-      'List of strings or glob patterns to match against package files. Applicable inside pathRules only',
+      'List of strings or glob patterns to match against package files. Applicable inside pathRules or packageRules only',
     type: 'list',
     stage: 'repository',
     cli: false,
diff --git a/lib/config/validation.js b/lib/config/validation.js
index 81886c41be..3e71df5ae7 100644
--- a/lib/config/validation.js
+++ b/lib/config/validation.js
@@ -144,6 +144,7 @@ async function validateConfig(config, isPreset, parentPath) {
             }
 
             const selectors = [
+              'paths',
               'depTypeList',
               'packageNames',
               'packagePatterns',
@@ -209,7 +210,7 @@ async function validateConfig(config, isPreset, parentPath) {
             }
             if (
               (selectors.includes(key) || key === 'matchCurrentVersion') &&
-              !(parentPath && parentPath.match(/packageRules\[\d+\]$/)) && // Inside a packageRule
+              !(parentPath && parentPath.match(/p.*Rules\[\d+\]$/)) && // Inside a packageRule
               (parentPath || !isPreset) // top level in a preset
             ) {
               errors.push({
diff --git a/lib/workers/package-file/dep-type.js b/lib/workers/package-file/dep-type.js
index 555e79fb03..66791f3fb3 100644
--- a/lib/workers/package-file/dep-type.js
+++ b/lib/workers/package-file/dep-type.js
@@ -1,3 +1,4 @@
+const minimatch = require('minimatch');
 const configParser = require('../../config');
 const pkgWorker = require('./package');
 const { extractDependencies } = require('../../manager');
@@ -48,7 +49,7 @@ async function renovateDepType(packageContent, config) {
 }
 
 function getDepConfig(depTypeConfig, dep) {
-  const { depName: dependency } = dep;
+  const { depName: dependency, packageFile } = dep;
   let depConfig = configParser.mergeChildConfig(depTypeConfig, dep);
   // Apply any matching package rules
   if (depConfig.packageRules) {
@@ -58,6 +59,7 @@ function getDepConfig(depTypeConfig, dep) {
     );
     depConfig.packageRules.forEach(packageRule => {
       let {
+        paths,
         depTypeList,
         packageNames,
         packagePatterns,
@@ -66,6 +68,7 @@ function getDepConfig(depTypeConfig, dep) {
         matchCurrentVersion,
       } = packageRule;
       // Setting empty arrays simplifies our logic later
+      paths = paths || [];
       depTypeList = depTypeList || [];
       packageNames = packageNames || [];
       packagePatterns = packagePatterns || [];
@@ -81,6 +84,14 @@ function getDepConfig(depTypeConfig, dep) {
       ) {
         packagePatterns = ['.*'];
       }
+      if (paths.length) {
+        const isMatch = paths.some(
+          rulePath =>
+            packageFile.includes(rulePath) || minimatch(packageFile, rulePath)
+        );
+        positiveMatch = positiveMatch || isMatch;
+        negativeMatch = negativeMatch || !isMatch;
+      }
       if (depTypeList.length) {
         const isMatch = depTypeList.includes(dep.depType);
         positiveMatch = positiveMatch || isMatch;
diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap
index ed8699402a..232ac12e3e 100644
--- a/test/config/__snapshots__/validation.spec.js.snap
+++ b/test/config/__snapshots__/validation.spec.js.snap
@@ -48,7 +48,7 @@ Array [
   },
   Object {
     "depName": "Configuration Error",
-    "message": "packageRules: Each packageRule must contain at least one selector (depTypeList, packageNames, packagePatterns, excludePackageNames, excludePackagePatterns). If you wish for configuration to apply to all packages, it is not necessary to place it inside a packageRule at all.",
+    "message": "packageRules: Each packageRule must contain at least one selector (paths, depTypeList, packageNames, packagePatterns, excludePackageNames, excludePackagePatterns). If you wish for configuration to apply to all packages, it is not necessary to place it inside a packageRule at all.",
   },
   Object {
     "depName": "Configuration Error",
diff --git a/test/config/validation.spec.js b/test/config/validation.spec.js
index d010aaf597..88a64ef4c9 100644
--- a/test/config/validation.spec.js
+++ b/test/config/validation.spec.js
@@ -43,6 +43,12 @@ describe('config/validation', () => {
         semanticCommitType: 7,
         lockFileMaintenance: false,
         extends: [':timezone(Europe/Brussel)'],
+        pathRules: [
+          {
+            paths: ['examples/**'],
+            labels: ['examples'],
+          },
+        ],
         packageRules: [
           {
             excludePackageNames: ['foo'],
diff --git a/test/workers/package-file/dep-type.spec.js b/test/workers/package-file/dep-type.spec.js
index 9bcfb3be43..13d8511ea3 100644
--- a/test/workers/package-file/dep-type.spec.js
+++ b/test/workers/package-file/dep-type.spec.js
@@ -308,5 +308,30 @@ describe('lib/workers/package-file/dep-type', () => {
       });
       expect(res1.x).toBeDefined();
     });
+    it('matches paths', () => {
+      const config = {
+        packageRules: [
+          {
+            paths: ['examples/**', 'lib/'],
+            x: 1,
+          },
+        ],
+      };
+      const res1 = depTypeWorker.getDepConfig(config, {
+        depName: 'test',
+        packageFile: 'examples/foo/package.json',
+      });
+      expect(res1.x).toBeDefined();
+      const res2 = depTypeWorker.getDepConfig(config, {
+        depName: 'test',
+        packageFile: 'package.json',
+      });
+      expect(res2.x).toBeUndefined();
+      const res3 = depTypeWorker.getDepConfig(config, {
+        depName: 'test',
+        packageFile: 'lib/a/package.json',
+      });
+      expect(res3.x).toBeDefined();
+    });
   });
 });
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index 7d455bedd3..d5cba5254d 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -868,7 +868,7 @@ Path rules are convenient to use if you wish to apply configuration rules to cer
 
 ## paths
 
-List of strings or glob patterns to match against package files. Applicable inside pathRules only.
+List of strings or glob patterns to match against package files. Applicable inside pathRules or packageRules only.
 
 | name    | value |
 | ------- | ----- |
-- 
GitLab