Files
renovate/lib/modules/manager/gomod/line-parser.ts
Alex Sweet 87111d8141 fix(manager/gomod): match gofmt-style unindented closing paren in block end regex (#45781)
* fix(manager/gomod): match gofmt-style unindented closing paren in block end regex

gofmt places the closing ) of require/replace/exclude blocks at column 0,
but endBlockRegex required at least one leading whitespace character. This
caused inExcludeBlock to never reset after an exclude (...) block formatted
by gofmt, silently discarding every subsequent line in the file, including
all later require/replace blocks.

* fix(manager/gomod): correct test expectation for valid version

v0.17.3 is a valid version so no skipReason is set; the test incorrectly
asserted skipReason: 'invalid-version', copied from other fixtures using
the deliberately invalid v1.2 placeholder.
2026-09-21 08:54:34 +00:00

195 lines
5.0 KiB
TypeScript

import semver from 'semver';
import { regEx } from '../../../util/regex.ts';
import { GoDatasource } from '../../datasource/go/index.ts';
import { GolangVersionDatasource } from '../../datasource/golang-version/index.ts';
import { isVersion } from '../../versioning/semver/index.ts';
import type { PackageDependency } from '../types.ts';
function trimQuotes(str: string): string {
return str.replace(regEx(/^"(?<value>.*)"$/), '$<value>');
}
const requireRegex = regEx(
/^(?<keyword>require)?\s+(?<module>[^\s]+\/?[^\s]+)\s+(?<version>[^\s]+)(?:\s*\/\/\s*(?<comment>[^\s]+)\s*)?$/,
);
const replaceRegex = regEx(
/^(?<keyword>replace)?\s+(?<module>[^\s]+\/?[^\s]+)\s*=>\s*(?<replacement>[^\s]+)(?:\s+(?<version>[^\s]+))?(?:\s*\/\/\s*(?<comment>[^\s]+)\s*)?$/,
);
export const excludeBlockStartRegex = regEx(/^(?<keyword>exclude)\s+\(\s*$/);
export const endBlockRegex = regEx(/^\s*\)\s*$/);
const toolRegex = regEx(/^(?<keyword>tool)?\s+(?<module>[^\s]+\/?[^\s]+)\s*$/);
const goVersionRegex = regEx(/^\s*go\s+(?<version>[^\s]+)\s*$/);
const toolchainVersionRegex = regEx(/^\s*toolchain\s+go(?<version>[^\s]+)\s*$/);
const pseudoVersionRegex = regEx(GoDatasource.pversionRegexp);
const placeholderPseudoVersion = 'v0.0.0-00010101000000-000000000000';
function extractDigest(input: string): string | undefined {
const match = pseudoVersionRegex.exec(input);
return match?.groups?.digest;
}
function isPlaceholderPseudoVersion(version: string): boolean {
return version === placeholderPseudoVersion;
}
export function parseLine(input: string): PackageDependency | null {
const goVersionMatches = goVersionRegex.exec(input)?.groups;
if (goVersionMatches) {
const { version: currentValue } = goVersionMatches;
const dep: PackageDependency = {
datasource: GolangVersionDatasource.id,
versioning: 'go-mod-directive',
depType: 'golang',
depName: 'go',
currentValue,
commitMessageTopic: 'go module directive',
};
if (!semver.validRange(currentValue)) {
dep.skipReason = 'invalid-version';
}
return dep;
}
const toolchainMatches = toolchainVersionRegex.exec(input)?.groups;
if (toolchainMatches) {
const { version: currentValue } = toolchainMatches;
const dep: PackageDependency = {
datasource: GolangVersionDatasource.id,
depType: 'toolchain',
depName: 'go',
currentValue,
commitMessageTopic: 'go toolchain directive',
};
if (!semver.valid(currentValue)) {
dep.skipReason = 'invalid-version';
}
return dep;
}
const requireMatches = requireRegex.exec(input)?.groups;
if (requireMatches) {
const { keyword, module, version: currentValue, comment } = requireMatches;
const depName = trimQuotes(module);
const dep: PackageDependency = {
datasource: GoDatasource.id,
depType: 'require',
depName,
currentValue,
};
if (isVersion(currentValue)) {
const digest = extractDigest(currentValue);
if (digest) {
dep.currentDigest = digest;
dep.digestOneAndOnly = true;
dep.versioning = 'loose';
if (isPlaceholderPseudoVersion(currentValue)) {
dep.skipReason = 'invalid-version';
}
}
} else {
dep.skipReason = 'invalid-version';
}
if (comment === 'indirect') {
dep.depType = 'indirect';
dep.enabled = false;
}
if (!keyword) {
dep.managerData = { multiLine: true };
}
return dep;
}
const replaceMatches = replaceRegex.exec(input)?.groups;
if (replaceMatches) {
const {
keyword,
replacement,
version: currentValue,
comment,
} = replaceMatches;
const depName = trimQuotes(replacement);
const dep: PackageDependency = {
datasource: GoDatasource.id,
depType: 'replace',
depName,
currentValue,
};
if (isVersion(currentValue)) {
const digest = extractDigest(currentValue);
if (digest) {
dep.currentDigest = digest;
dep.digestOneAndOnly = true;
dep.versioning = 'loose';
if (isPlaceholderPseudoVersion(currentValue)) {
dep.skipReason = 'invalid-version';
}
}
} else if (currentValue) {
dep.skipReason = 'invalid-version';
} else {
dep.skipReason = 'unspecified-version';
delete dep.currentValue;
}
if (comment === 'indirect') {
dep.depType = 'indirect';
dep.enabled = false;
}
if (!keyword) {
dep.managerData = { multiLine: true };
}
if (depName.startsWith('/') || depName.startsWith('.')) {
dep.skipReason = 'local-dependency';
}
return dep;
}
const toolMatches = toolRegex.exec(input)?.groups;
if (toolMatches) {
const { keyword, module } = toolMatches;
const depName = trimQuotes(module);
const dep: PackageDependency = {
datasource: GoDatasource.id,
depType: 'tool',
depName,
skipReason: 'unversioned-reference',
};
if (!keyword) {
dep.managerData = { multiLine: true };
}
return dep;
}
return null;
}