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

92 lines
3.2 KiB
TypeScript

import { isUndefined } from '@sindresorhus/is';
import { CONFIG_VALIDATION } from '../../../constants/error-messages.ts';
import { regEx } from '../../../util/regex.ts';
import { GenericVersioningApi } from '../generic.ts';
import type { VersioningApiConstructor } from '../types.ts';
import type { RegExpVersion } from './types.ts';
export const id = 'regex';
export const displayName = 'Regular Expression';
export const urls = [];
export const supportsRanges = false;
export class RegExpVersioningApi extends GenericVersioningApi<RegExpVersion> {
// config is expected to be overridden by a user-specified RegExp value
// sample values:
//
// * emulates the "semver" configuration:
// RegExp('^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(-(?<prerelease>.*))?$')
// * emulates the "docker" configuration:
// RegExp('^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(-(?<compatibility>.*))?$')
// * matches the versioning approach used by the Python images on DockerHub:
// RegExp('^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(?<prerelease>[^.-]+)?(-(?<compatibility>.*))?$');
// * matches the versioning approach used by the Bitnami images on DockerHub:
// RegExp('^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(:?-(?<compatibility>.*-r)(?<build>\\d+))?$');
protected readonly _config: RegExp;
constructor(_new_config: string | undefined) {
super();
const new_config = _new_config ?? '^(?<major>\\d+)?$';
// without at least one of {major, minor, patch} specified in the regex,
// this versioner will not work properly
if (
!new_config.includes('<major>') &&
!new_config.includes('<minor>') &&
!new_config.includes('<patch>')
) {
const error = new Error(CONFIG_VALIDATION);
error.validationSource = new_config;
error.validationError =
'regex versioning needs at least one major, minor or patch group defined';
throw error;
}
// TODO: should we validate the user has not added extra unsupported
// capture groups? (#9717)
this._config = regEx(new_config);
}
// convenience method for passing a string into a Version given current config.
protected _parse(version: string): RegExpVersion | null {
const groups = this._config?.exec(version)?.groups;
if (!groups) {
return null;
}
const { major, minor, patch, build, revision, prerelease, compatibility } =
groups;
const release = [
isUndefined(major) ? 0 : Number.parseInt(major, 10),
isUndefined(minor) ? 0 : Number.parseInt(minor, 10),
isUndefined(patch) ? 0 : Number.parseInt(patch, 10),
];
if (build) {
release.push(Number.parseInt(build, 10));
if (revision) {
release.push(Number.parseInt(revision, 10));
}
}
return {
release,
prerelease,
compatibility,
};
}
override isCompatible(version: string, current: string): boolean {
const parsedVersion = this._parse(version);
const parsedCurrent = this._parse(current);
return !!(
parsedVersion &&
parsedVersion.compatibility === parsedCurrent?.compatibility
);
}
}
export const api: VersioningApiConstructor = RegExpVersioningApi;
export default api;