Files
Sebastian Poxhofer 97327c3d28 refactor(linters): add prefer-coerce-array rule (#45492)
* refactor(linters): add prefer-coerce-array rule

Refs #45217

`x ?? []` and `coerceArray(x)` say the same thing, but only the second
one is searchable and consistent. The rule flags a `??` whose default is
a bare `[]` literal; a non-empty default or an asserted `([] as Foo[])`
still passes.

Applying it across `lib/` surfaced three spots the helper cannot cover,
which keep `?? []` with a disable comment or a narrower fix:

- `package-files.ts` destructures a tuple, which `coerceArray()` widens
- `generate.spec.ts` defaults `matchAll()`, an iterator rather than an array
- `mise/upgradeable-tooling.ts` declares a `Record` return, so its
  `?? []` fallback was wrong to begin with and becomes `coerceObject()`

* refactor(platform/github): hoist the GHE version header lookup

`Object.entries(...).find()` returns a tuple, and defaulting it with
`?? []` only to destructure the second element reads as an array
default when it is not one. Name the found entry instead, which drops
the `?? []` rather than routing a tuple through `coerceArray()`.
2026-08-28 09:27:58 +00:00

100 lines
3.4 KiB
TypeScript

import { isArray, isNonEmptyStringAndNotWhitespace } from '@sindresorhus/is';
import { dequal } from 'dequal';
import type { RenovateConfig } from '../../../../config/types.ts';
import { logger } from '../../../../logger/index.ts';
import { platform } from '../../../../modules/platform/index.ts';
import { coerceArray } from '../../../../util/array.ts';
import * as template from '../../../../util/template/index.ts';
/**
* Filter labels that go over the maximum char limit, based on platform limits.
*/
function trimLabel(label: string, limit: number): string {
const trimmed = label.trim();
if (trimmed.length <= limit) {
return trimmed;
}
return trimmed.slice(0, limit).trim();
}
export function prepareLabels(config: RenovateConfig): string[] {
const labelCharLimit = platform.labelCharLimit?.() ?? 50;
const labels = coerceArray(config.labels);
const addLabels = coerceArray(config.addLabels);
return [...new Set([...labels, ...addLabels])]
.filter(isNonEmptyStringAndNotWhitespace)
.map((label) => template.compile(label, config))
.filter(isNonEmptyStringAndNotWhitespace)
.map((label) => trimLabel(label, labelCharLimit))
.sort();
}
/**
* Determine changed labels between old and new label arrays.
*
* This function takes two arrays of labels, 'oldLabels' and 'newLabels', and calculates the labels
* that need to be added and removed to transition from 'oldLabels' to 'newLabels'.
*/
export function getChangedLabels(
oldLabels: string[] | undefined,
newLabels: string[] | undefined,
): [string[] | undefined, string[] | undefined] {
const labelsToAdd = newLabels?.filter((l) => !oldLabels?.includes(l));
const labelsToRemove = oldLabels?.filter((l) => !newLabels?.includes(l));
return [labelsToAdd, labelsToRemove];
}
/**
* Check if labels in the PR have been modified.
*
* This function compares two arrays of labels, 'prInitialLabels' and 'prCurrentLabels',
* to determine if they are different, indicating that labels in the PR have been modified.
*/
export function areLabelsModified(
prInitialLabels: string[],
prCurrentLabels: string[],
): boolean {
const modified = !dequal(prInitialLabels.sort(), prCurrentLabels.sort());
if (modified) {
logger.debug(
{ prInitialLabels, prCurrentLabels },
'PR labels have been modified by user, skipping labels update',
);
}
return modified;
}
/**
* Determine if labels should be updated in the Pull Request.
*/
export function shouldUpdateLabels(
prInitialLabels: string[] | undefined,
prCurrentLabels: string[] | undefined,
configuredLabels: string[] | undefined,
): boolean {
// If the 'labelsInDebugData' field is undefined
// it means the PR was created before the update-labels logic was merged, and labels should not be updated.
// Reference: https://github.com/renovatebot/renovate/pull/25340
if (!isArray(prInitialLabels)) {
return false;
}
// If the labels are unchanged, they should not be updated
if (dequal(coerceArray(configuredLabels).sort(), prInitialLabels.sort())) {
return false;
}
// If the labels in the PR have been modified by the user, they should not be updated
if (areLabelsModified(prInitialLabels, coerceArray(prCurrentLabels))) {
logger.debug('Labels have been modified by user - skipping labels update.');
return false;
}
logger.debug('Labels have been changed in repo config- updating labels.');
return true;
}