Files
Sebastian Poxhofer e0cec3dc95 refactor(lint): add types-location rule and enable it for lib/modules (#45497)
feat(lint): add `types-location` rule and enable it for `lib/modules`

Reviewers consistently ask for exported types to be moved into a
colocated `types.ts`. Add a `renovate/types-location` oxlint rule that
reports `export interface` / `export type` declarations outside such a
file, and turn it on across `lib/modules`.

Re-exports (`export type { X }`, `export type *`), file-local types, and
values (enums, classes, functions) are left alone. `schema.ts` files and
`schema/` directories are excluded via config, matching how
`zod-schema-location` is configured — inferred types belong next to the
schema they are derived from.

Relocating the 87 existing violations means:

- new `types.ts` files for the modules that had none
- `terragrunt/common.ts` folded into its `types.ts`, as it held nothing
  else
- `RangeBound` in `versioning/gradle/compare.ts` un-exported, since only
  a file-local interface referenced it
- `bazel-module/parser/fragments.ts` split from its inferred types, which
  now live in `parser/types.ts` and infer through a namespace import so
  each schema keeps its merged name
2026-08-27 19:52:09 +00:00

120 lines
3.6 KiB
TypeScript

import { isEmptyArray, isTruthy } from '@sindresorhus/is';
import jsonata from 'jsonata';
import { migrateDatasource } from '../../../../config/migrations/custom/datasource-migration.ts';
import { logger } from '../../../../logger/index.ts';
import * as template from '../../../../util/template/index.ts';
import { parseUrl } from '../../../../util/url.ts';
import type { PackageDependency } from '../../types.ts';
import type { ValidMatchFields } from '../types.ts';
import { checkIsValidDependency, validMatchFields } from '../utils.ts';
import { QueryResultZod } from './schema.ts';
import type { JSONataManagerTemplates, JsonataExtractConfig } from './types.ts';
export async function handleMatching(
json: unknown,
packageFile: string,
config: JsonataExtractConfig,
): Promise<PackageDependency[]> {
let results: Record<string, string>[] = [];
const { matchStrings: jsonataQueries } = config;
for (const query of jsonataQueries) {
// won't fail as this is verified during config validation
const jsonataExpression = jsonata(query);
let queryResult: unknown;
try {
// this now throws for unknown reasons
queryResult = await jsonataExpression.evaluate(json);
} catch (err) {
logger.warn(
{ err, jsonataQuery: query, packageFile },
'Error executing jsonata query. Please check your query.',
);
continue;
}
// allows empty dep object cause templates can be used to configure the required fields
// if some issues arise then the isValidDependency call will catch them later on
if (!queryResult || isEmptyArray(queryResult)) {
logger.debug(
{
jsonataQuery: query,
packageFile,
},
'The jsonata query returned no matches. Possible error, please check your query. Skipping',
);
continue;
}
const parsed = QueryResultZod.safeParse(queryResult);
if (parsed.success) {
results = results.concat(parsed.data);
} else {
logger.warn(
{ err: parsed.error, jsonataQuery: query, packageFile, queryResult },
'Query results failed schema validation',
);
continue;
}
}
return results
.map((dep) => createDependency(dep, config))
.filter(isTruthy)
.filter((dep) =>
checkIsValidDependency(dep, packageFile, 'custom.jsonata'),
);
}
export function createDependency(
queryResult: Record<string, string>,
config: JsonataExtractConfig,
): PackageDependency | null {
const dependency: PackageDependency = {};
for (const field of validMatchFields) {
const fieldTemplate = `${field}Template` as keyof JSONataManagerTemplates;
const tmpl = config[fieldTemplate];
if (tmpl) {
try {
const compiled = template.compile(tmpl, queryResult, false);
updateDependency(field, compiled, dependency);
} catch {
logger.debug(
{ template: tmpl },
'Error compiling template for JSONata manager',
);
return null;
}
} else if (queryResult[field]) {
updateDependency(field, queryResult[field], dependency);
}
}
return dependency;
}
function updateDependency(
field: ValidMatchFields,
value: string,
dependency: PackageDependency,
): PackageDependency {
switch (field) {
case 'registryUrl': {
const url = parseUrl(value)?.toString();
if (!url) {
logger.debug({ url: value }, 'Invalid JSONata manager registryUrl');
break;
}
dependency.registryUrls = [url];
break;
}
case 'datasource':
dependency.datasource = migrateDatasource(value);
break;
default:
dependency[field] = value;
break;
}
return dependency;
}