Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(gomod): Simplify dependency extraction #28852

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion lib/modules/manager/gomod/__snapshots__/extract.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -721,12 +721,13 @@ exports[`modules/manager/gomod/extract extractPackageFile() extracts single-line
},
{
"currentValue": "abcdef1",
"datasource": "go",
"depName": "github.com/rarkins/foo",
"depType": "require",
"managerData": {
"lineNumber": 6,
},
"skipReason": "unsupported-version",
"skipReason": "invalid-version",
},
{
"currentValue": "v1.0.0",
Expand All @@ -746,6 +747,15 @@ exports[`modules/manager/gomod/extract extractPackageFile() extracts single-line
"lineNumber": 8,
},
},
{
"datasource": "go",
"depName": "../errors",
"depType": "replace",
"managerData": {
"lineNumber": 10,
},
"skipReason": "local-dependency",
},
{
"currentValue": "v0.0.0",
"datasource": "go",
Expand Down
6 changes: 3 additions & 3 deletions lib/modules/manager/gomod/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ describe('modules/manager/gomod/extract', () => {
it('extracts single-line requires', () => {
const res = extractPackageFile(gomod1)?.deps;
expect(res).toMatchSnapshot();
expect(res).toHaveLength(9);
expect(res).toHaveLength(10);
zharinov marked this conversation as resolved.
Show resolved Hide resolved
expect(res?.filter((e) => e.depType === 'require')).toHaveLength(7);
expect(res?.filter((e) => e.depType === 'indirect')).toHaveLength(1);
expect(res?.filter((e) => e.skipReason)).toHaveLength(1);
expect(res?.filter((e) => e.depType === 'replace')).toHaveLength(1);
expect(res?.filter((e) => e.skipReason)).toHaveLength(2);
expect(res?.filter((e) => e.depType === 'replace')).toHaveLength(2);
});

it('extracts multi-line requires', () => {
Expand Down
172 changes: 17 additions & 155 deletions lib/modules/manager/gomod/extract.ts
Original file line number Diff line number Diff line change
@@ -1,165 +1,27 @@
import semver from 'semver';
import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex';
import { GoDatasource } from '../../datasource/go';
import { GolangVersionDatasource } from '../../datasource/golang-version';
import { isVersion } from '../../versioning/semver';
import { newlineRegex } from '../../../util/regex';
import type { PackageDependency, PackageFileContent } from '../types';
import type { MultiLineParseResult } from './types';
import { parseLine } from './line-parser';

function getDep(
lineNumber: number,
match: RegExpMatchArray,
type: string,
): PackageDependency {
const [, , currentValue] = match;
let [, depName] = match;
depName = depName.replace(regEx(/"/g), '');
const dep: PackageDependency = {
managerData: {
lineNumber,
},
depName,
depType: type,
currentValue,
};
if (isVersion(currentValue)) {
dep.datasource = GoDatasource.id;
} else {
dep.skipReason = 'unsupported-version';
}
const digestMatch = regEx(GoDatasource.pversionRegexp).exec(currentValue);
if (digestMatch?.groups?.digest) {
dep.currentDigest = digestMatch.groups.digest;
dep.digestOneAndOnly = true;
dep.versioning = 'loose';
}
return dep;
}

function getGoDep(
lineNumber: number,
goVer: string,
versioning: string | undefined = undefined,
depType: string = 'golang',
): PackageDependency {
return {
managerData: {
lineNumber,
},
depName: 'go',
depType,
currentValue: goVer,
datasource: GolangVersionDatasource.id,
...(versioning && { versioning }),
};
}

export function extractPackageFile(
content: string,
packageFile?: string,
): PackageFileContent | null {
logger.trace({ content }, 'gomod.extractPackageFile()');
export function extractPackageFile(content: string): PackageFileContent | null {
const deps: PackageDependency[] = [];
try {
const lines = content.split(newlineRegex);
for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
const line = lines[lineNumber];
const goVer = line.startsWith('go ') ? line.replace('go ', '') : null;
if (goVer && semver.validRange(goVer)) {
const dep = getGoDep(lineNumber, goVer, 'go-mod-directive');
deps.push(dep);
continue;
}
const goToolVer = line.startsWith('toolchain go')
? line.replace('toolchain go', '')
: null;
if (goToolVer && semver.valid(goToolVer)) {
const dep = getGoDep(lineNumber, goToolVer, undefined, 'toolchain');
deps.push(dep);
continue;
}
const replaceMatch = regEx(
/^replace\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/,
).exec(line);
if (replaceMatch) {
const dep = getDep(lineNumber, replaceMatch, 'replace');
deps.push(dep);
}
const requireMatch = regEx(/^require\s+([^\s]+)\s+([^\s]+)/).exec(line);
if (requireMatch) {
if (line.endsWith('// indirect')) {
logger.trace({ lineNumber }, `indirect line: "${line}"`);
const dep = getDep(lineNumber, requireMatch, 'indirect');
dep.enabled = false;
deps.push(dep);
} else {
logger.trace({ lineNumber }, `require line: "${line}"`);
const dep = getDep(lineNumber, requireMatch, 'require');
deps.push(dep);
}
}
if (line.trim() === 'require (') {
logger.trace(`Matched multi-line require on line ${lineNumber}`);
const matcher = regEx(/^\s+([^\s]+)\s+([^\s]+)/);
const { reachedLine, detectedDeps } = parseMultiLine(
lineNumber,
lines,
matcher,
'require',
);
lineNumber = reachedLine;
deps.push(...detectedDeps);
} else if (line.trim() === 'replace (') {
logger.trace(`Matched multi-line replace on line ${lineNumber}`);
const matcher = regEx(/^\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/);
const { reachedLine, detectedDeps } = parseMultiLine(
lineNumber,
lines,
matcher,
'replace',
);
lineNumber = reachedLine;
deps.push(...detectedDeps);
}

const lines = content.split(newlineRegex);
for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
const line = lines[lineNumber];
const dep = parseLine(line);
if (!dep) {
continue;
}
} catch (err) /* istanbul ignore next */ {
logger.warn({ err, packageFile }, 'Error extracting go modules');

dep.managerData ??= {};
dep.managerData.lineNumber = lineNumber;

deps.push(dep);
}

if (!deps.length) {
return null;
}
return { deps };
}

function parseMultiLine(
startingLine: number,
lines: string[],
matchRegex: RegExp,
blockType: 'require' | 'replace',
): MultiLineParseResult {
const deps: PackageDependency[] = [];
let lineNumber = startingLine;
let line = '';
do {
lineNumber += 1;
line = lines[lineNumber];
const multiMatch = matchRegex.exec(line);
logger.trace(`${blockType}: "${line}"`);
if (multiMatch && !line.endsWith('// indirect')) {
logger.trace({ lineNumber }, `${blockType} line: "${line}"`);
const dep = getDep(lineNumber, multiMatch, blockType);
dep.managerData!.multiLine = true;
deps.push(dep);
} else if (multiMatch && line.endsWith('// indirect')) {
logger.trace({ lineNumber }, `${blockType} indirect line: "${line}"`);
const dep = getDep(lineNumber, multiMatch, 'indirect');
dep.managerData!.multiLine = true;
dep.enabled = false;
deps.push(dep);
} else if (line.trim() !== ')') {
logger.trace(`No multi-line match: ${line}`);
}
} while (line.trim() !== ')');
return { reachedLine: lineNumber, detectedDeps: deps };
return { deps };
}
Loading