diff --git a/lib/config/__snapshots__/validation.spec.ts.snap b/lib/config/__snapshots__/validation.spec.ts.snap index 21366d0738a3155577dd3e7b586344aafd313ad0..e969c62fcbf63b8f9a77163360015684a53ffb57 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 452283349e2cc588fa24243a4117f1a5c6ebdd5d..b7a3aa8ae7deb6382652dd7c2c5762a9e68f3d7e 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 100003ec5201e5144dee99bc1b470e75fc4b91c3..633ce2c231a49bce441e895a8674e47d14646659 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({