Files
renovate/lib/modules/manager/gomod/extract.ts
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

133 lines
3.1 KiB
TypeScript

import { newlineRegex } from '../../../util/regex.ts';
import type { PackageDependency, PackageFileContent } from '../types.ts';
import {
endBlockRegex,
excludeBlockStartRegex,
parseLine,
} from './line-parser.ts';
function findMatchingModule(
tool: PackageDependency,
deps: PackageDependency[],
): PackageDependency | undefined {
let bestMatch: PackageDependency | undefined;
const normalizedTool = `${tool.depName!}/`;
// Find the longest matching prefix for the tool within the dependencies
for (const dep of deps) {
if (
normalizedTool.startsWith(`${dep.depName!}/`) &&
dep.depName!.length > (bestMatch?.depName!.length ?? 0)
) {
bestMatch = dep;
}
}
return bestMatch;
}
export function extractPackageFile(content: string): PackageFileContent | null {
const deps: PackageDependency[] = [];
const tools: PackageDependency[] = [];
let inExcludeBlock = false;
const lines = content.split(newlineRegex);
for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
const line = lines[lineNumber];
const dep = parseLine(line);
if (inExcludeBlock) {
if (endBlockRegex.test(line)) {
inExcludeBlock = false;
}
continue;
}
if (!dep) {
if (excludeBlockStartRegex.test(line)) {
inExcludeBlock = true;
}
continue;
}
if (dep.depType === 'tool') {
tools.push(dep);
continue;
}
dep.managerData ??= {};
dep.managerData.lineNumber = lineNumber;
deps.push(dep);
}
for (const tool of tools) {
const match = findMatchingModule(tool, deps);
if (match?.depType === 'indirect') {
delete match.enabled;
}
}
if (!deps.length) {
return null;
}
const packageFile: PackageFileContent = {
deps,
};
const goDirective = packageFile.deps.find(
(dep) =>
dep.depName === 'go' &&
dep.depType === 'golang' &&
dep.datasource === 'golang-version',
);
if (goDirective?.currentValue) {
packageFile.extractedConstraints ??= {};
const range = convertGoDirectiveToSemVerRange(goDirective.currentValue);
// v8 ignore else -- a parsed go directive always yields a version
if (range.version) {
packageFile.extractedConstraints['%goMod'] = range.version;
packageFile.constraintsVersioning ??= {};
packageFile.constraintsVersioning['%goMod'] = range.versioning;
}
}
const toolchainDirective = packageFile.deps.find(
(dep) =>
dep.depName === 'go' &&
dep.depType === 'toolchain' &&
dep.datasource === 'golang-version',
);
if (toolchainDirective?.currentValue) {
packageFile.extractedConstraints ??= {};
packageFile.extractedConstraints.golang = toolchainDirective.currentValue;
}
return packageFile;
}
export function convertGoDirectiveToSemVerRange(
goDirective: string | undefined,
):
| {
version: string;
versioning: string;
}
| {
version: undefined;
} {
if (!goDirective) {
return {
version: undefined,
};
}
const parts = goDirective.split('.');
return {
version: `~${parts[0]}.${parts[1]}.x`,
versioning: 'semver-coerced',
};
}