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

338 lines
8.8 KiB
TypeScript

import type { RangeStrategy } from '../../../types/versioning.ts';
import { parseVersion } from '../nuget/parser.ts';
import type { NugetVersion } from '../nuget/types.ts';
import type { NewValueConfig, VersioningApi } from '../types.ts';
import {
adaptToPrecision,
bumpAtPrecision,
ensurePrereleaseMatches,
intervalOf,
isLessThanLowerBound,
matches as matchesRange,
parseRange,
rangeToString,
releaseParts,
twiddle,
} from './range.ts';
import type { PaketConstraint, PaketRange } from './types.ts';
import { compare, sameReleaseParts } from './version.ts';
export const id = 'paket';
export const displayName = 'Paket';
export const urls = [
'[Paket version constraints](https://fsprojects.github.io/Paket/nuget-dependencies.html#Version-constraints)',
'[NuGet package versioning](https://learn.microsoft.com/nuget/concepts/package-versioning)',
];
export const supportsRanges = true;
export const supportedRangeStrategies: RangeStrategy[] = [
'bump',
'widen',
'replace',
'update-lockfile',
];
function isVersion(input: string | undefined | null): boolean {
return !!input && parseVersion(input) !== null;
}
function isValid(input: string): boolean {
return parseRange(input) !== null;
}
function isSingleVersionRange(range: PaketRange): boolean {
if (range.constraints.length !== 1 || range.prereleaseTags.length > 0) {
return false;
}
const { operator } = range.constraints[0];
return operator === '' || operator === '=' || operator === '==';
}
function isSingleVersion(input: string): boolean {
const range = parseRange(input);
return !!range && isSingleVersionRange(range);
}
function isStable(input: string): boolean {
const version = parseVersion(input);
if (version) {
return !version.prerelease;
}
const range = parseRange(input);
return (
!!range &&
isSingleVersionRange(range) &&
!range.constraints[0].version.prerelease
);
}
function isCompatible(version: string): boolean {
return isVersion(version);
}
function getMajor(version: string): number | null {
return parseVersion(version)?.major ?? null;
}
function getMinor(version: string): number | null {
return parseVersion(version)?.minor ?? null;
}
function getPatch(version: string): number | null {
return parseVersion(version)?.patch ?? null;
}
function compareVersions(version: string, other: string): number | null {
const x = parseVersion(version);
const y = parseVersion(other);
return x && y ? compare(x, y) : null;
}
function equals(version: string, other: string): boolean {
return compareVersions(version, other) === 0;
}
function isGreaterThan(version: string, other: string): boolean {
const cmp = compareVersions(version, other);
return cmp !== null && cmp > 0;
}
function sortVersions(version: string, other: string): number {
return compareVersions(version, other) ?? 0;
}
function matches(version: string, range: string): boolean {
const v = parseVersion(version);
const r = parseRange(range);
return !!v && !!r && matchesRange(v, r);
}
function isLessThanRange(version: string, range: string): boolean {
const v = parseVersion(version);
const r = parseRange(range);
return !!v && !!r && isLessThanLowerBound(v, r);
}
function satisfyingVersions(versions: string[], range: string): string[] {
return versions.filter((v) => matches(v, range)).sort(sortVersions);
}
function getSatisfyingVersion(
versions: string[],
range: string,
): string | null {
return satisfyingVersions(versions, range).pop() ?? null;
}
function minSatisfyingVersion(
versions: string[],
range: string,
): string | null {
return satisfyingVersions(versions, range).shift() ?? null;
}
function getPinnedValue(newVersion: string): string {
return newVersion;
}
function replaceConstraint(
constraint: PaketConstraint,
v: NugetVersion,
): PaketConstraint {
const { operator, version } = constraint;
switch (operator) {
case '':
case '=':
case '==':
return { operator, version: v };
case '>':
case '>=':
return constraint;
case '<=':
return compare(v, version) > 0 ? { operator, version: v } : constraint;
case '<':
return compare(v, version) >= 0 || sameReleaseParts(v, version)
? {
operator,
version: bumpAtPrecision(v, releaseParts(version).length),
}
: constraint;
case '~>': {
const cap = twiddle(version);
return compare(v, cap) >= 0 || sameReleaseParts(v, cap)
? {
operator,
version: adaptToPrecision(v, releaseParts(version).length),
}
: constraint;
}
}
}
/**
* Rewrite `~> band` optionally followed by a `>=`/`>` floor: the band follows
* the new version at its old precision, and the floor sticks to the exact new
* version unless it becomes redundant.
*/
function pessimisticWithFloor(range: PaketRange, v: NugetVersion): PaketRange {
const [pessimistic] = range.constraints;
const band = adaptToPrecision(v, releaseParts(pessimistic.version).length);
const constraints: PaketConstraint[] =
compare(band, v) === 0
? [{ operator: '~>', version: band }]
: [
{ operator: '~>', version: band },
{ operator: '>=', version: v },
];
return { ...range, constraints };
}
function replaceRange(range: PaketRange, v: NugetVersion): PaketRange {
const [first, second] = range.constraints;
if (
first.operator === '~>' &&
(second?.operator === '>' || second?.operator === '>=')
) {
return pessimisticWithFloor(range, v);
}
return {
...range,
constraints: range.constraints.map((c) => replaceConstraint(c, v)),
};
}
function bumpConstraint(
constraint: PaketConstraint,
v: NugetVersion,
): PaketConstraint {
const { operator, version } = constraint;
switch (operator) {
case '>=':
return compare(v, version) > 0 ? { operator, version: v } : constraint;
case '>':
// the lower bound guard in `getNewValue` ensures `v` is above `version`
return { operator: '>=', version: v };
case '~>': {
const adapted = adaptToPrecision(v, releaseParts(version).length);
return compare(adapted, version) > 0
? { operator, version: adapted }
: constraint;
}
default:
return replaceConstraint(constraint, v);
}
}
function bumpRange(range: PaketRange, v: NugetVersion): PaketRange {
const [first, second] = range.constraints;
if (
first.operator === '~>' &&
second?.operator !== '<' &&
second?.operator !== '<='
) {
return pessimisticWithFloor(range, v);
}
return {
...range,
constraints: range.constraints.map((c) => bumpConstraint(c, v)),
};
}
function widenRange(range: PaketRange, v: NugetVersion): PaketRange {
const interval = intervalOf(range.constraints);
const [first] = range.constraints;
if (interval.kind !== 'range' || first.operator !== '~>') {
return {
...range,
constraints: range.constraints.map((c) => replaceConstraint(c, v)),
};
}
const { from, fromInclusive, to, toInclusive } = interval;
const upperViolated = toInclusive
? compare(v, to) > 0
: compare(v, to) >= 0 || sameReleaseParts(v, to);
if (!upperViolated) {
return range;
}
// The pessimistic operator acts as both bounds, so decompose it into
// explicit bounds to widen upwards while keeping the lower bound.
const lower: PaketConstraint = {
operator: fromInclusive ? '>=' : '>',
version: from,
};
const upper: PaketConstraint = toInclusive
? { operator: '<=', version: v }
: { operator: '<', version: bumpAtPrecision(v, releaseParts(to).length) };
return { ...range, constraints: [lower, upper] };
}
function getNewValue({
currentValue,
rangeStrategy,
newVersion,
}: NewValueConfig): string | null {
const v = parseVersion(newVersion);
if (!v) {
return null;
}
if (isVersion(currentValue)) {
return newVersion;
}
const range = parseRange(currentValue);
if (!range) {
return null;
}
if (rangeStrategy === 'pin') {
return `${range.strategy ?? ''}${newVersion}`;
}
if (isLessThanLowerBound(v, range)) {
return currentValue;
}
let result: PaketRange;
if (rangeStrategy === 'bump') {
result = bumpRange(range, v);
} else if (matchesRange(v, range)) {
return currentValue;
} else if (rangeStrategy === 'widen') {
result = widenRange(range, v);
} else {
result = replaceRange(range, v);
}
const newValue = rangeToString(ensurePrereleaseMatches(result, v));
return newValue === rangeToString(range) ? currentValue : newValue;
}
export const api: VersioningApi = {
equals,
getMajor,
getMinor,
getPatch,
getNewValue,
getPinnedValue,
getSatisfyingVersion,
minSatisfyingVersion,
isCompatible,
isGreaterThan,
isLessThanRange,
isSingleVersion,
isStable,
isValid,
isVersion,
matches,
sortVersions,
};
export default api;