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

118 lines
4.3 KiB
TypeScript

import { isNonEmptyArray } from '@sindresorhus/is';
import { getManagerConfig, mergeChildConfig } from '../../../config/index.ts';
import type { ManagerConfig, RenovateConfig } from '../../../config/types.ts';
import { instrument } from '../../../instrumentation/index.ts';
import { logger } from '../../../logger/index.ts';
import { isCustomManager } from '../../../modules/manager/custom/index.ts';
import {
getEnabledManagersList,
hashMap,
} from '../../../modules/manager/index.ts';
import { scm } from '../../../modules/platform/scm.ts';
import { coerceArray } from '../../../util/array.ts';
import type { ExtractResult, WorkerExtractConfig } from '../../types.ts';
import { getMatchingFiles } from './file-match.ts';
import { getManagerPackageFiles } from './manager-files.ts';
import { processSupersedesManagers } from './supersedes.ts';
export async function extractAllDependencies(
config: RenovateConfig,
): Promise<ExtractResult> {
const managerList = getEnabledManagersList(config.enabledManagers);
const extractList: WorkerExtractConfig[] = [];
const fileList = await scm.getFileList();
function tryConfig(managerConfig: ManagerConfig): void {
const matchingFileList = getMatchingFiles(managerConfig, fileList);
if (matchingFileList.length) {
extractList.push({ ...managerConfig, fileList: matchingFileList });
}
}
instrument('filter packageFiles for managers', () => {
for (const manager of managerList) {
const managerConfig = getManagerConfig(config, manager);
managerConfig.manager = manager;
if (isCustomManager(manager)) {
const filteredCustomManagers = coerceArray(
config.customManagers,
).filter((mgr) => mgr.customType === manager);
for (const customManager of filteredCustomManagers) {
tryConfig(mergeChildConfig(managerConfig, customManager));
}
} else {
tryConfig(managerConfig);
}
}
});
const extractResult: ExtractResult = {
packageFiles: {},
extractionFingerprints: {},
};
// Store the fingerprint of all managers which match any file (even if they do not find any dependencies)
// The cached result needs to be invalidated if the fingerprint of any matching manager changes
for (const { manager } of extractList) {
extractResult.extractionFingerprints[manager] = hashMap.get(manager);
}
const extractDurations: Record<string, number> = {};
const extractResults = await Promise.all(
extractList.map(async (managerConfig) => {
const start = Date.now();
const packageFiles = await instrument(
managerConfig.manager,
async () => await getManagerPackageFiles(managerConfig),
);
const durationMs = Math.round(Date.now() - start);
extractDurations[managerConfig.manager] = durationMs;
return { manager: managerConfig.manager, packageFiles };
}),
);
// De-duplicate results using supersedesManagers
processSupersedesManagers(extractResults);
// Sort alphabetically for stable log output. See #40091
const sortedExtractDurations = Object.keys(extractDurations)
.sort()
.reduce<Record<string, number>>((acc, key) => {
acc[key] = extractDurations[key];
return acc;
}, {});
logger.debug(
{ managers: sortedExtractDurations },
'manager extract durations (ms)',
);
let fileCount = 0;
for (const { manager, packageFiles } of extractResults) {
if (packageFiles?.length) {
fileCount += packageFiles.length;
logger.debug(`Found ${manager} package files`);
extractResult.packageFiles[manager] = (
extractResult.packageFiles[manager] || []
).concat(packageFiles);
}
}
logger.debug(`Found ${fileCount} package file(s)`);
// If enabledManagers is non-empty, check that each of them has at least one extraction.
// If not, log a warning to indicate possible misconfiguration.
if (isNonEmptyArray(config.enabledManagers)) {
for (const enabledManager of config.enabledManagers) {
if (
!(enabledManager.replace('custom.', '') in extractResult.packageFiles)
) {
logger.debug(
{ manager: enabledManager },
`Manager explicitly enabled in "enabledManagers" config, but found no results. Possible config error?`,
);
}
}
}
return extractResult;
}