diff --git a/lib/util/string.ts b/lib/util/string.ts index 2cad78d692bc9c473f9e150f497d8200cd103e74..8cb2fe951fcb5d9a78ce7e5f7c2c9ec861c7dd11 100644 --- a/lib/util/string.ts +++ b/lib/util/string.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import { logger } from '../logger'; // Return true if the match string is found at index in content @@ -23,3 +24,8 @@ export function replaceAt( content.substr(index + oldString.length) ); } + +// Return true if the input is non-empty and not whitespace string +export function nonEmptyStringAndNotWhitespace(input: unknown): boolean { + return is.nonEmptyString(input) && !is.emptyStringOrWhitespace(input); +} diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts index 2bf1bc75af2e1f3a0a5afdba4de442e1a6b74a3c..e2903315e972c2257c60ce3ba05f944769b9cbe6 100644 --- a/lib/util/template/index.ts +++ b/lib/util/template/index.ts @@ -161,9 +161,9 @@ function getFilteredObject(input: CompileInput): FilteredObject { for (const field of allAllowed) { const value = obj[field]; if (is.array(value)) { - res[field] = value.map((element) => - getFilteredObject(element as CompileInput) - ); + res[field] = value + .filter(is.plainObject) + .map((element) => getFilteredObject(element as CompileInput)); } else if (is.plainObject(value)) { res[field] = getFilteredObject(value); } else if (!is.undefined(value)) { diff --git a/lib/workers/pr/index.spec.ts b/lib/workers/pr/index.spec.ts index 574ccd28888a0b8883a67bb96b343e95244c9356..25bbf2595d2919cb68546ade97833e076b30660e 100644 --- a/lib/workers/pr/index.spec.ts +++ b/lib/workers/pr/index.spec.ts @@ -807,6 +807,26 @@ describe('workers/pr/index', () => { expect(result).toEqual(['labelA', 'labelB', 'labelC']); }); + it('empty labels ignored', () => { + const result = prWorker.prepareLabels({ + labels: ['labelA', ''], + addLabels: [' ', 'labelB'], + }); + expect(result).toBeArrayOfSize(2); + expect(result).toEqual(['labelA', 'labelB']); + }); + + it('null labels ignored', () => { + const result = prWorker.prepareLabels({ + labels: ['labelA', null], + // an empty space between two commas in an array is categorized as a null value + // eslint-disable-next-line no-sparse-arrays + addLabels: ['labelB', '', undefined, , ,], + }); + expect(result).toBeArrayOfSize(2); + expect(result).toEqual(['labelA', 'labelB']); + }); + it('template labels', () => { const result = prWorker.prepareLabels({ labels: ['datasource-{{{datasource}}}'], @@ -815,5 +835,14 @@ describe('workers/pr/index', () => { expect(result).toBeArrayOfSize(1); expect(result).toEqual(['datasource-npm']); }); + + it('template labels with empty datasource', () => { + const result = prWorker.prepareLabels({ + labels: ['{{{datasource}}}', ' {{{datasource}}} '], + datasource: null, + }); + expect(result).toBeArrayOfSize(0); + expect(result).toEqual([]); + }); }); }); diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index 38a02645abe19fb4cde1a3a67e177ab258dc1127..42c1d3dbd408baf8c6f688ec12ff02c0dffec7e7 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -13,6 +13,7 @@ import { sampleSize } from '../../util'; import { stripEmojis } from '../../util/emoji'; import { deleteBranch, getBranchLastCommitTime } from '../../util/git'; import { regEx } from '../../util/regex'; +import { nonEmptyStringAndNotWhitespace } from '../../util/string'; import * as template from '../../util/template'; import { resolveBranchStatus } from '../branch/status-checks'; import { Limit, incLimitedValue, isLimitReached } from '../global/limits'; @@ -56,9 +57,10 @@ function prepareAssigneesReviewers( export function prepareLabels(config: RenovateConfig): string[] { const labels = config.labels ?? []; const addLabels = config.addLabels ?? []; - return [...new Set([...labels, ...addLabels])].map((label) => - template.compile(label, config) - ); + return [...new Set([...labels, ...addLabels])] + .filter(nonEmptyStringAndNotWhitespace) + .map((label) => template.compile(label, config)) + .filter(nonEmptyStringAndNotWhitespace); } export async function addAssigneesReviewers(