From 893df627e4fbec4f832885a25dcb7ed3a11449b3 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 12 Apr 2021 06:11:25 +0200
Subject: [PATCH] feat(validation): language/manager top-level check (#9498)

---
 .../__snapshots__/validation.spec.ts.snap     | 13 ++++++++++
 lib/config/validation.spec.ts                 | 26 +++++++++++++++++++
 lib/config/validation.ts                      | 10 ++++++-
 3 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/lib/config/__snapshots__/validation.spec.ts.snap b/lib/config/__snapshots__/validation.spec.ts.snap
index 21366d0738..e969c62fcb 100644
--- a/lib/config/__snapshots__/validation.spec.ts.snap
+++ b/lib/config/__snapshots__/validation.spec.ts.snap
@@ -137,6 +137,19 @@ Array [
 ]
 `;
 
+exports[`config/validation validateConfig(config) errors if language or manager objects are nested 1`] = `
+Array [
+  Object {
+    "message": "The \\"docker\\" object can only be configured at the top level of a config but was found inside \\"major.minor\\"",
+    "topic": "Configuration Error",
+  },
+  Object {
+    "message": "The \\"gradle\\" object can only be configured at the top level of a config but was found inside \\"java\\"",
+    "topic": "Configuration Error",
+  },
+]
+`;
+
 exports[`config/validation validateConfig(config) errors if regexManager fields are missing 1`] = `
 Array [
   Object {
diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts
index 452283349e..b7a3aa8ae7 100644
--- a/lib/config/validation.spec.ts
+++ b/lib/config/validation.spec.ts
@@ -484,6 +484,32 @@ describe('config/validation', () => {
       expect(warnings).toMatchSnapshot();
     });
 
+    it('errors if language or manager objects are nested', async () => {
+      const config = {
+        python: {
+          enabled: false,
+        },
+        java: {
+          gradle: {
+            enabled: false,
+          },
+        },
+        major: {
+          minor: {
+            docker: {
+              automerge: true,
+            },
+          },
+        },
+      } as never;
+      const { warnings, errors } = await configValidation.validateConfig(
+        config
+      );
+      expect(errors).toHaveLength(2);
+      expect(warnings).toHaveLength(0);
+      expect(errors).toMatchSnapshot();
+    });
+
     it('warns if hostType has the wrong parent', async () => {
       const config = {
         hostType: 'npm',
diff --git a/lib/config/validation.ts b/lib/config/validation.ts
index 100003ec52..633ce2c231 100644
--- a/lib/config/validation.ts
+++ b/lib/config/validation.ts
@@ -1,5 +1,5 @@
 import is from '@sindresorhus/is';
-import { getManagerList } from '../manager';
+import { getLanguageList, getManagerList } from '../manager';
 import { configRegexPredicate, isConfigRegex, regEx } from '../util/regex';
 import * as template from '../util/template';
 import { hasValidSchedule, hasValidTimezone } from '../workers/branch/schedule';
@@ -41,6 +41,8 @@ export function getParentName(parentPath: string): string {
     : '.';
 }
 
+const topLevelObjects = getLanguageList().concat(getManagerList());
+
 export async function validateConfig(
   config: RenovateConfig,
   isPreset?: boolean,
@@ -115,6 +117,12 @@ export async function validateConfig(
       });
       continue; // eslint-disable-line
     }
+    if (parentPath && topLevelObjects.includes(key)) {
+      errors.push({
+        topic: 'Configuration Error',
+        message: `The "${key}" object can only be configured at the top level of a config but was found inside "${parentPath}"`,
+      });
+    }
     if (key === 'fileMatch') {
       if (parentPath === undefined) {
         errors.push({
-- 
GitLab