Files
Michael Kriese 55a7341be2 test(manager): close long-tail branch coverage gaps (#45954)
* test(manager): close azure-pipelines, bundler and cocoapods gaps

Adds a pipeline whose step task is not versioned, so nothing is
extracted, and a gem lock line carrying no version, which is skipped.

Marks four guards that cannot be false: `extractContainer()` always
returns a dep, `getLockFilePath()` always returns a path, and the
cocoapods dep name, spec name and git account all derive from values
their enclosing check already proved present.

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

* test(manager): close cargo, composer and cpanfile gaps

Adds a composer repository map that disables something other than
packagist, which is dropped rather than recorded.

Marks five guards: the cargo warning only fires for a crate dep and its
retry only runs when a dep was already current, a gitlab rule without a
token never passes the auth check, and both cpanfile handlers run only
once their match produced a value.

Two implicit elses stay counted, in the cargo version resolver and the
composer repository mapper, since a coverage-ignore hint does not attach
to an `else if`.

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

* test(manager): close deps-edn, docker-compose and dockerfile gaps

Adds three extractions: a deps.edn alias whose value is not a map, a
compose extension carrying no image, and a dockerfile escape directive
naming a character other than a backtick, which leaves the default in
place.

Marks the deps-edn repository name check, since object keys are always
strings, and notes the repository spec chain, where a coverage-ignore
hint does not attach to an `else if`.

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

* test(manager/flux): close the flux coverage gaps

Adds two GitRepository resources whose url is ssh rather than http, one
pinned to a tag and one to a commit. Neither yields a source url, since
only http urls can be turned into one.

Marks the Kustomization image guard: the schema rejects an image without
a name, so `extractImage()` cannot return null there.

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

* test(manager): mark github-actions, gitlabci and glasskube guards

None of these six needs a test. A parsed action ref is either a docker
or a repository ref, `getDep()` always returns a dep for a job container
and a service, and the remaining three need a listed file that cannot be
read, a listed file that yields no deps, or a manifest whose
repositories match no name.

The glasskube resource kind chain stays counted, since a coverage-ignore
hint does not attach to an `else if`.

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

* test(manager): mark gomod and gradle-wrapper guards

Four guards that need setups no test provides: a removed dep that is not
added back, a go.mod the replace massaging leaves unchanged, and a
daemon-jvm file carrying no toolchain version. A parsed go directive
always yields a version.

The additional-dependency count chain stays counted, since a
coverage-ignore hint does not attach to an `else if`.

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-15 07:10:10 +00:00

160 lines
4.5 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import { getSiblingFileName, localPathExists } from '../../../util/fs/index.ts';
import { newlineRegex, regEx } from '../../../util/regex.ts';
import { coerceString } from '../../../util/string.ts';
import { GitTagsDatasource } from '../../datasource/git-tags/index.ts';
import { GithubTagsDatasource } from '../../datasource/github-tags/index.ts';
import { GitlabTagsDatasource } from '../../datasource/gitlab-tags/index.ts';
import { PodDatasource } from '../../datasource/pod/index.ts';
import type { PackageDependency, PackageFileContent } from '../types.ts';
import type { ParsedLine } from './types.ts';
const regexMappings = [
regEx(`^\\s*pod\\s+(['"])(?<spec>[^'"/]+)(/(?<subspec>[^'"]+))?(['"])`),
regEx(
`^\\s*pod\\s+(['"])[^'"]+(['"])\\s*,\\s*(['"])(?<currentValue>[^'"]+)(['"])\\s*$`,
),
regEx(`,\\s*:git\\s*=>\\s*(['"])(?<git>[^'"]+)(['"])`),
regEx(`,\\s*:tag\\s*=>\\s*(['"])(?<tag>[^'"]+)(['"])`),
regEx(`,\\s*:path\\s*=>\\s*(['"])(?<path>[^'"]+)(['"])`),
regEx(`^\\s*source\\s*(['"])(?<source>[^'"]+)(['"])`),
];
export function parseLine(line: string): ParsedLine {
let result: ParsedLine = {};
if (!line) {
return result;
}
for (const regex of Object.values(regexMappings)) {
const match = regex.exec(line.replace(regEx(/#.*$/), ''));
if (match?.groups) {
result = { ...result, ...match.groups };
}
}
if (result.spec) {
const depName = result.subspec
? `${result.spec}/${result.subspec}`
: result.spec;
const specName = result.spec;
// v8 ignore else -- both derive from `result.spec`, already truthy above
if (depName) {
result.depName = depName;
}
// v8 ignore else -- see above
if (specName) {
result.specName = specName;
}
delete result.spec;
delete result.subspec;
}
return result;
}
export function gitDep(parsedLine: ParsedLine): PackageDependency | null {
const { depName, git, tag } = parsedLine;
const platformMatch = regEx(
/[@/](?<platform>github|gitlab)\.com[:/](?<account>[^/]+)\/(?<repo>[^/]+)/,
).exec(coerceString(git));
if (platformMatch?.groups) {
const { account, repo, platform } = platformMatch.groups;
// v8 ignore else -- the regex only matches when both groups are present
if (account && repo) {
const datasource =
platform === 'github'
? GithubTagsDatasource.id
: GitlabTagsDatasource.id;
return {
datasource,
depName,
packageName: `${account}/${repo.replace(regEx(/\.git$/), '')}`,
currentValue: tag,
};
}
}
return {
datasource: GitTagsDatasource.id,
depName,
packageName: git,
currentValue: tag,
};
}
export async function extractPackageFile(
content: string,
packageFile: string,
): Promise<PackageFileContent | null> {
logger.trace(`cocoapods.extractPackageFile(${packageFile})`);
const deps: PackageDependency[] = [];
const lines: string[] = content.split(newlineRegex);
const registryUrls: string[] = [];
for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
const line = lines[lineNumber];
const parsedLine = parseLine(line);
const {
depName,
specName,
currentValue,
git,
tag,
path,
source,
}: ParsedLine = parsedLine;
if (source) {
registryUrls.push(source.replace(regEx(/\/*$/), ''));
}
if (depName) {
const managerData = { lineNumber };
let dep: PackageDependency = {
depName,
sharedVariableName: specName,
skipReason: 'unspecified-version',
};
if (currentValue) {
dep = {
depName,
sharedVariableName: specName,
datasource: PodDatasource.id,
currentValue,
managerData,
registryUrls,
};
} else if (git) {
if (tag) {
dep = { ...gitDep(parsedLine), managerData };
} else {
dep = {
depName,
sharedVariableName: specName,
skipReason: 'git-dependency',
};
}
} else if (path) {
dep = {
depName,
sharedVariableName: specName,
skipReason: 'path-dependency',
};
}
deps.push(dep);
}
}
const res: PackageFileContent = { deps };
const lockFile = getSiblingFileName(packageFile, 'Podfile.lock');
// istanbul ignore if
if (await localPathExists(lockFile)) {
res.lockFiles = [lockFile];
}
return res;
}