mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-26 23:39:13 +00:00
97327c3d28
* 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()`.
65 lines
1.8 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
}
|
|
});
|