mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 15:01:49 +00:00
f711fd46e2
* test(workers/repository): mark seven unreachable guards for v8 These are defensive paths that no input reaches, and the guards stay as they are. What changes is that v8 now knows to skip them: the existing `istanbul ignore` comments are inert under the v8 provider, so the branches behind them were still being counted. - `versioning/index`: a string input can only fail the schema with the custom issue its own transform raises - `versioning/conda`: pep440 always answers for a well-formed `==X.Y.*` - `versioning/ruby`: callers only pass versions that have already parsed - `repository/index`: only called when the repository logged a problem - `config-migration/branch/rebase`: JSON5 output is always serializable back to JSON - `onboarding/pr/pr-list`: base branches are unique Record keys, so the sort never compares one with itself - `changelog/release-notes`: a matched release always carries its own url - `changelog/source`: `getInRangeReleases` only returns null on paths it already ignores Two notes on the hints. They have to be a single-line comment directly above the statement -- a two-line reason, or anything in between such as an oxlint directive, breaks the association silently. And `v8 ignore else` covers an `if` with no else, while `v8 ignore next` covers an expression-level branch such as `??` or a ternary. `versioning/index` also swaps `issue && issue.code` for `issue?.code`, which is what the oxlint directive sitting in the way was suppressing. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * test(workers/repository): cover the last five pr worker guards None of these needed an ignore comment; each has a real input: - `updatePrDebugData` gets a describe of its own. It is exported but was only reached through a pr whose debug data already carried labels, so the case where an existing pr has none went untaken. - a `not-pending` branch whose status is no longer yellow - a changelog error that is not a missing GitHub token - an existing pr updated because a rebase was requested - a platform that answers `createPr` with null, so assignees and reviewers are skipped With these, `update/pr/index.ts` is fully covered. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * test(workers/repository): cover the last four branch update guards - `bump-versions` reads the current version off disk when the pending change for that file carries no contents. All three guards involved are covered by one case where the file is pending in both the package file and the artifact list with no contents on either entry. An earlier attempt at this looked like it failed: the bump does happen, but the result is appended to `updatedPackageFiles` rather than `updatedArtifacts`, because the file is already pending there. - `get-updated` keeps a package file another upgrade already wrote, for a manager that is asked for a lockfile-only update but has no `updateLockedDependency`. With these, `lib/workers/repository` is fully covered. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * ci: pin the branch coverage threshold at 100 A full run on this branch reports 100% on all four metrics: statements (49435/49435), branches (25727/25727), functions (7414/7414) and lines (48924/48924). Branches joins the other three at 100 rather than taking another rounding-down step. There is no longer a margin to leave: any new uncovered branch is a regression, and the threshold should say so. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> --------- Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
167 lines
4.3 KiB
TypeScript
167 lines
4.3 KiB
TypeScript
import { eq, major, minor, patch, prerelease } from '@renovatebot/ruby-semver';
|
|
import type { SegmentElement } from '@renovatebot/ruby-semver/dist/ruby/version.js';
|
|
import { create } from '@renovatebot/ruby-semver/dist/ruby/version.js';
|
|
import { coerceArray } from '../../../util/array.ts';
|
|
import { regEx } from '../../../util/regex.ts';
|
|
|
|
interface RubyVersion {
|
|
major: number;
|
|
minor: number;
|
|
patch: number;
|
|
prerelease: string[] | null;
|
|
}
|
|
|
|
function releaseSegments(version: string): SegmentElement[] {
|
|
const v = create(version);
|
|
// v8 ignore else -- callers only pass versions that have already parsed
|
|
if (v) {
|
|
return v.release().getSegments();
|
|
}
|
|
/* istanbul ignore next */
|
|
return [];
|
|
}
|
|
|
|
function parse(version: string): RubyVersion {
|
|
return {
|
|
major: major(version),
|
|
minor: minor(version),
|
|
patch: patch(version),
|
|
prerelease: prerelease(version),
|
|
};
|
|
}
|
|
|
|
function floor(version: string): string {
|
|
const segments = releaseSegments(version);
|
|
if (segments.length <= 1) {
|
|
// '~> 2' is equivalent to '~> 2.0', thus no need to floor
|
|
return segments.join('.');
|
|
}
|
|
return [...segments.slice(0, -1), 0].join('.');
|
|
}
|
|
|
|
function adapt(left: string, right: string): string {
|
|
return left.split('.').slice(0, right.split('.').length).join('.');
|
|
}
|
|
|
|
function trimZeroes(version: string): string {
|
|
const segments = version.split('.');
|
|
while (segments.length > 0 && segments.at(-1) === '0') {
|
|
segments.pop();
|
|
}
|
|
return segments.join('.');
|
|
}
|
|
|
|
// Returns the upper bound of `~>` operator.
|
|
function pgteUpperBound(version: string): string {
|
|
const segments = releaseSegments(version);
|
|
if (segments.length > 1) {
|
|
segments.pop();
|
|
}
|
|
return incrementLastSegment(segments.join('.'));
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function incrementLastSegment(version: string): string {
|
|
const segments = releaseSegments(version);
|
|
const nextLast = parseInt(segments.pop() as string, 10) + 1;
|
|
|
|
return [...segments, nextLast].join('.');
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function incrementMajor(
|
|
maj: number,
|
|
min: number,
|
|
ptch: number,
|
|
pre: string[],
|
|
): number {
|
|
return min === 0 || ptch === 0 || pre.length === 0 ? maj + 1 : maj;
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function incrementMinor(min: number, ptch: number, pre: string[]): number {
|
|
return ptch === 0 || pre.length === 0 ? min + 1 : min;
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function incrementPatch(ptch: number, pre: string[]): number {
|
|
return pre.length === 0 ? ptch + 1 : ptch;
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function increment(from: string, to: string): string {
|
|
const parsed = parse(from);
|
|
const { major: maj, prerelease: pre } = parsed;
|
|
let { minor: min, patch: ptch } = parsed;
|
|
min = min || 0;
|
|
ptch = ptch || 0;
|
|
|
|
let nextVersion: string;
|
|
const adapted = adapt(to, from);
|
|
if (eq(from, adapted)) {
|
|
return incrementLastSegment(from);
|
|
}
|
|
|
|
function isStable(x: string): boolean {
|
|
return regEx(/^[0-9.-/]+$/).test(x);
|
|
}
|
|
if (major(from) !== major(adapted)) {
|
|
nextVersion = [incrementMajor(maj, min, ptch, coerceArray(pre)), 0, 0].join(
|
|
'.',
|
|
);
|
|
} else if (minor(from) !== minor(adapted)) {
|
|
nextVersion = [maj, incrementMinor(min, ptch, coerceArray(pre)), 0].join(
|
|
'.',
|
|
);
|
|
} else if (patch(from) !== patch(adapted)) {
|
|
nextVersion = [maj, min, incrementPatch(ptch, coerceArray(pre))].join('.');
|
|
} else if (isStable(from) && isStable(adapted)) {
|
|
nextVersion = [maj, min, incrementPatch(ptch, coerceArray(pre))].join('.');
|
|
} else {
|
|
nextVersion = [maj, min, ptch].join('.');
|
|
}
|
|
|
|
return increment(nextVersion, to);
|
|
}
|
|
|
|
// istanbul ignore next
|
|
function decrement(version: string): string {
|
|
const segments = releaseSegments(version);
|
|
const nextSegments = segments
|
|
.reverse()
|
|
.reduce(
|
|
(
|
|
accumulator: number[],
|
|
segment: SegmentElement,
|
|
index: number,
|
|
): number[] => {
|
|
if (index === 0) {
|
|
return [(segment as number) - 1];
|
|
}
|
|
|
|
if (accumulator[index - 1] === -1) {
|
|
return [
|
|
...accumulator.slice(0, index - 1),
|
|
0,
|
|
(segment as number) - 1,
|
|
];
|
|
}
|
|
|
|
return [...accumulator, segment as number];
|
|
},
|
|
[],
|
|
);
|
|
|
|
return nextSegments.reverse().join('.');
|
|
}
|
|
|
|
export {
|
|
adapt,
|
|
decrement,
|
|
floor,
|
|
increment,
|
|
parse,
|
|
pgteUpperBound,
|
|
trimZeroes,
|
|
};
|