Files
Michael Kriese 1ea8602aaa test(manager): reach 100% branch coverage by closing the else-if chains (#46222)
* test(manager): cover twelve else-if branches

The trailing `else if` of a chain leaves v8 counting an implicit else
that no coverage-ignore hint can suppress. Fourteen of those were marked
as structurally unreachable, but only two of them actually are -- each of
these twelve has an input that takes it:

- `bazel`: an archive url whose file name is only a suffix, which strips
  to an empty value, so neither value nor digest is set
- `cargo`: a `[package]` section with no `version` at all
- `composer`: an inline `package` repository, which the discriminated
  union does carry, contrary to the comment that claimed otherwise
- `deps-edn`: an `:mvn/repos` entry whose spec is a plain string
- `gomod`: an update where only the `go` directive changed, leaving no
  additional dependencies to count
- `helmfile`: a release with an empty chart name, which is neither a
  local path nor an oci ref and yields an empty repo name
- `nuget`: a lock file that is gone after the restore
- `swift`: a comma where `from` expects a colon, and where `from:`
  expects a version -- both are labels the state machine searches for and
  then ignores
- `terraform`: a module source too short to be a registry module
- `tflint-plugin`: a quoted attribute that is neither version nor source
- `travis`: a matrix `node_js` that is a number

The stale comments go with them. Three more in `swift` were already
wrong before this change: the branches they described were covered.

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* refactor(manager): collapse two exhaustive else-if chains

`glasskube` and `gradle` were the only two of the fourteen trailing
`else if` branches that really are unreachable, and both are provable
from a type rather than from a reading of the surrounding code:

- `glasskube` parses resources with a schema that admits exactly
  `Package`, `ClusterPackage` and `PackageRepository`, and filters
  anything else out
- `gradle`'s `ContentDescriptorSpec.mode` is `'include' | 'exclude'`

Turning the final `else if` into a plain `else` drops the phantom branch
v8 was counting. With these, the manager tree reaches 100% branch
coverage. The glasskube check is inverted so that the single-literal
discriminant narrows for the compiler.

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager/travis): inline the numeric node_js fixture

The three-line fixture went into `__fixtures__` only because every other
fixture in this spec is loaded through `Fixtures.get`. A reader of the
test should be able to see the input without opening a second file, so
write it inline with `codeBlock`.

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
2026-09-16 06:43:03 +00:00

167 lines
4.4 KiB
TypeScript

import { isString, isTruthy } from '@sindresorhus/is';
import { z } from 'zod/v4';
import { regEx } from '../../../../util/regex.ts';
import { parseUrl } from '../../../../util/url.ts';
import { GithubReleasesDatasource } from '../../../datasource/github-releases/index.ts';
import { GithubTagsDatasource } from '../../../datasource/github-tags/index.ts';
import { GitlabReleasesDatasource } from '../../../datasource/gitlab-releases/index.ts';
import { GitlabTagsDatasource } from '../../../datasource/gitlab-tags/index.ts';
import type { PackageDependency } from '../../types.ts';
// Source: https://bazel.build/rules/lib/repo/http
const archives = [
'.zip',
'.tar',
'.jar',
'.war',
'.aar',
'.ar',
'.deb',
'.gz',
'.tar.gz',
'.tgz',
'.bz2',
'.tar.bz2',
'.tbz2',
'.xz',
'.tar.xz',
'.txz',
'.zst',
'.tar.zst',
'.tzst',
];
const archiveSuffixRegex = regEx(
`(?:${archives.map((archive) => RegExp.escape(archive)).join('|')})$`,
);
function stripArchiveSuffix(value: string): string {
return value.replace(archiveSuffixRegex, '');
}
function isHash(value: unknown): value is string {
return isString(value) && regEx(/[0-9a-z]{40}/i).test(value);
}
export function parseGithubPath(
pathname: string,
): Partial<PackageDependency> | null {
const [p0, p1, p2, p3, p4, p5] = pathname.split('/').slice(1);
const packageName = `${p0}/${p1}`;
let datasource = '';
let value: string | null = null;
if (p2 === 'releases' && p3 === 'download') {
datasource = GithubReleasesDatasource.id;
value = p4;
} else if (p2 === 'archive' && p3 === 'refs' && p4 === 'tags') {
// https://github.com/foo/bar/archive/refs/tags/v1.2.3.tar.gz
datasource = GithubTagsDatasource.id;
value = p5;
} else if (p2 === 'archive') {
// https://github.com/foo/bar/archive/1.2.3.tar.gz
datasource = GithubTagsDatasource.id;
value = p3;
}
if (!value) {
return null;
}
value = stripArchiveSuffix(value);
return isHash(value)
? { datasource, packageName, currentDigest: value }
: { datasource, packageName, currentValue: value };
}
function parseGitlabPath(pathname: string): Partial<PackageDependency> | null {
// https://gitlab.com/libeigen/eigen/-/archive/3.3.5/eigen-3.3.5.zip
// https://gitlab.com/libeigen/eigen/-/archive/90ee821c563fa20db4d64d6991ddca256d5c52f2/eigen-90ee821c563fa20db4d64d6991ddca256d5c52f2.tar.gz
const [p0, p1, p2, p3, p4] = pathname.split('/').slice(1);
const packageName = `${p0}/${p1}`;
if (p2 === '-' && p3 === 'archive' && p4) {
return isHash(p4)
? {
datasource: GitlabTagsDatasource.id,
packageName,
currentDigest: p4,
}
: {
datasource: GitlabReleasesDatasource.id,
packageName,
currentValue: p4,
};
}
return null;
}
export function parseArchiveUrl(
urlString: string | undefined | null,
): Partial<PackageDependency> | null {
if (!urlString) {
return null;
}
const url = parseUrl(urlString);
if (url?.host === 'github.com') {
return parseGithubPath(url.pathname);
}
if (url?.host === 'gitlab.com') {
return parseGitlabPath(url.pathname);
}
return null;
}
export const httpRules = [
'http_archive',
'_http_archive',
'http_file',
'_http_file',
] as const;
export const HttpTarget = z
.object({
rule: z.enum(httpRules),
name: z.string(),
url: z.string().optional(),
urls: z.array(z.string()).optional(),
sha256: z.string(),
})
.refine(({ url, urls }) => !!url || !!urls)
.transform(({ rule, name, url, urls = [] }): PackageDependency[] => {
const parsedUrl = [url, ...urls].map(parseArchiveUrl).find(isTruthy);
if (!parsedUrl) {
return [];
}
const dep: PackageDependency = {
datasource: parsedUrl.datasource,
depType: rule,
depName: name,
packageName: parsedUrl.packageName,
};
// We don't want to set both `currentValue` and `currentDigest`.
//
// What we want is to provide the first occurrence of `currentValue`,
// or, if it's not available, `currentDigest`.
//
// Auto-replace mechanism will replace this first occurrence,
// and artifact update function will do the rest.
//
// Hence, `if-else-if` is being used here.
if (parsedUrl.currentValue) {
dep.currentValue = parsedUrl.currentValue;
} else if (parsedUrl.currentDigest) {
dep.currentDigest = parsedUrl.currentDigest;
}
return [dep];
});