diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 37b19ebe5ca5b9c6b617200bf6055b51c5801476..785fd217b2e67fcd6006ba9e6f3b9aac740466c7 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -444,13 +444,51 @@ describe('config/validation', () => { `); }); + it('errors if invalid regexManager customType', async () => { + const config = { + regexManagers: [ + { + customType: 'unknown', + fileMatch: ['some-file'], + matchStrings: ['^(?<depName>foo)(?<currentValue>bar)$'], + datasourceTemplate: 'maven', + versioningTemplate: 'gradle', + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + config as any, + true + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(1); + expect(errors).toMatchInlineSnapshot(` + [ + { + "message": "Invalid customType: unknown. Key is not a custom manager", + "topic": "Configuration Error", + }, + ] + `); + }); + it('errors if empty regexManager matchStrings', async () => { const config = { regexManagers: [ - { customType: 'regex', fileMatch: ['foo'], matchStrings: [] }, { customType: 'regex', fileMatch: ['foo'], + matchStrings: [], + depNameTemplate: 'foo', + datasourceTemplate: 'bar', + currentValueTemplate: 'baz', + }, + { + customType: 'regex', + fileMatch: ['foo'], + depNameTemplate: 'foo', + datasourceTemplate: 'bar', + currentValueTemplate: 'baz', }, ], }; @@ -499,6 +537,9 @@ describe('config/validation', () => { customType: 'regex', fileMatch: ['Dockerfile'], matchStrings: ['***$}{]]['], + depNameTemplate: 'foo', + datasourceTemplate: 'bar', + currentValueTemplate: 'baz', }, ], }; @@ -510,6 +551,26 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); }); + // testing if we get all errors at once or not (possible), this does not include customType or fileMatch + // since they are common to all custom managers + it('validates all possible regex manager options', async () => { + const config: RenovateConfig = { + regexManagers: [ + { + customType: 'regex', + fileMatch: ['Dockerfile'], + matchStrings: ['***$}{]]['], // invalid matchStrings regex, no depName, datasource and currentValue + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + config, + true + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(4); + }); + it('passes if regexManager fields are present', async () => { const config: RenovateConfig = { regexManagers: [ diff --git a/lib/config/validation.ts b/lib/config/validation.ts index e1cf284e3b304ce65e360ae64a351ce3d5ac9b9d..7679a7379a3dd7c56f290f9d2182f5e3f66f4842 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -1,5 +1,7 @@ import is from '@sindresorhus/is'; import { allManagersList, getManagerList } from '../modules/manager'; +import { isCustomManager } from '../modules/manager/custom'; +import type { RegexManagerConfig } from '../modules/manager/custom/regex/types'; import { configRegexPredicate, isConfigRegex, regEx } from '../util/regex'; import * as template from '../util/template'; import { @@ -10,6 +12,8 @@ import { migrateConfig } from './migration'; import { getOptions } from './options'; import { resolveConfigPresets } from './presets'; import type { + CustomManager, + RegexManagerTemplates, RenovateConfig, RenovateOptions, ValidationMessage, @@ -416,8 +420,7 @@ export async function validateConfig( 'autoReplaceStringTemplate', 'depTypeTemplate', ]; - // TODO: fix types #22198 - for (const regexManager of val as any[]) { + for (const regexManager of val as CustomManager[]) { if ( Object.keys(regexManager).some( (k) => !allowedKeys.includes(k) @@ -432,49 +435,19 @@ export async function validateConfig( ', ' )}`, }); - } else if (is.nonEmptyString(regexManager.customType)) { + } else if ( + is.nonEmptyString(regexManager.customType) && + isCustomManager(regexManager.customType) + ) { if (is.nonEmptyArray(regexManager.fileMatch)) { - if (is.nonEmptyArray(regexManager.matchStrings)) { - let validRegex = false; - for (const matchString of regexManager.matchStrings) { - try { - regEx(matchString); - validRegex = true; - } catch (e) { - errors.push({ - topic: 'Configuration Error', - message: `Invalid regExp for ${currentPath}: \`${String( - matchString - )}\``, - }); - } - } - if (validRegex) { - const mandatoryFields = [ - 'depName', - 'currentValue', - 'datasource', - ]; - for (const field of mandatoryFields) { - if ( - !regexManager[`${field}Template`] && - !regexManager.matchStrings.some( - (matchString: string) => - matchString.includes(`(?<${field}>`) - ) - ) { - errors.push({ - topic: 'Configuration Error', - message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`, - }); - } - } - } - } else { - errors.push({ - topic: 'Configuration Error', - message: `Each Regex Manager must contain a non-empty matchStrings array`, - }); + switch (regexManager.customType) { + case 'regex': + validateRegexManagerFields( + regexManager, + currentPath, + errors + ); + break; } } else { errors.push({ @@ -483,10 +456,20 @@ export async function validateConfig( }); } } else { - errors.push({ - topic: 'Configuration Error', - message: `Each Regex Manager must contain a non-empty customType string`, - }); + if ( + is.emptyString(regexManager.customType) || + is.undefined(regexManager.customType) + ) { + errors.push({ + topic: 'Configuration Error', + message: `Each Regex Manager must contain a non-empty customType string`, + }); + } else { + errors.push({ + topic: 'Configuration Error', + message: `Invalid customType: ${regexManager.customType}. Key is not a custom manager`, + }); + } } } } @@ -672,3 +655,43 @@ export async function validateConfig( warnings.sort(sortAll); return { errors, warnings }; } + +function validateRegexManagerFields( + regexManager: RegexManagerConfig, + currentPath: string, + errors: ValidationMessage[] +): void { + if (is.nonEmptyArray(regexManager.matchStrings)) { + for (const matchString of regexManager.matchStrings) { + try { + regEx(matchString); + } catch (e) { + errors.push({ + topic: 'Configuration Error', + message: `Invalid regExp for ${currentPath}: \`${matchString}\``, + }); + } + } + } else { + errors.push({ + topic: 'Configuration Error', + message: `Each Regex Manager must contain a non-empty matchStrings array`, + }); + } + + const mandatoryFields = ['depName', 'currentValue', 'datasource']; + for (const field of mandatoryFields) { + const templateField = `${field}Template` as keyof RegexManagerTemplates; + if ( + !regexManager[templateField] && + !regexManager.matchStrings.some((matchString) => + matchString.includes(`(?<${field}>`) + ) + ) { + errors.push({ + topic: 'Configuration Error', + message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`, + }); + } + } +}