Files
renovate/lib/data/index.spec.ts
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

65 lines
1.8 KiB
TypeScript

import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { isPlainObject } from '@sindresorhus/is';
import { coerceArray } from '../util/array.ts';
interface FileConfig {
/** Top-level keys that must appear first, in the given order. */
firstKeys?: string[];
}
const fileConfigs: Record<string, FileConfig> = {
'abandonments.json': {},
'changelog-urls.json': {},
'monorepo.json': {},
'replacements.json': { firstKeys: ['all'] },
'source-urls.json': {},
};
const files = Object.keys(fileConfigs);
function checkKeysSorted(
obj: Record<string, unknown>,
path: string,
depth: number,
firstKeys?: string[],
): void {
let keys = Object.keys(obj);
const expectedFirst = depth === 0 ? coerceArray(firstKeys) : [];
expect(
keys.slice(0, expectedFirst.length),
`${path} should start with [${expectedFirst.join(', ')}]`,
).toStrictEqual(expectedFirst);
keys = keys.slice(expectedFirst.length);
expect(keys, `${path} keys should be sorted alphabetically`).toStrictEqual(
[...keys].sort(),
);
for (const [key, value] of Object.entries(obj)) {
if (isPlainObject(value)) {
checkKeysSorted(value, `${path}.${key}`, depth + 1);
}
}
}
describe('data/index', () => {
for (const file of files) {
describe(`${file}`, () => {
let data: Record<string, unknown>;
beforeAll(async () => {
data = JSON.parse(
await readFile(join(import.meta.dirname, file), 'utf8'),
) as Record<string, unknown>;
});
it('keys are sorted alphabetically', () => {
const { firstKeys } = fileConfigs[file];
const root = Object.fromEntries(
Object.entries(data).filter(([k]) => k !== '$schema'),
);
checkKeysSorted(root, file, 0, firstKeys);
});
});
}
});