Skip to content

Commit

Permalink
feat(gomod): directive versioning (#19453)
Browse files Browse the repository at this point in the history
  • Loading branch information
rarkins committed Dec 17, 2022
1 parent ef7f520 commit 7193547
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/modules/manager/gomod/extract.spec.ts
Expand Up @@ -66,7 +66,7 @@ replace (
depType: 'golang',
currentValue: '1.18',
datasource: 'golang-version',
versioning: 'npm',
versioning: 'go-mod-directive',
rangeStrategy: 'replace',
},
{
Expand Down
2 changes: 1 addition & 1 deletion lib/modules/manager/gomod/extract.ts
Expand Up @@ -45,7 +45,7 @@ function getGoDep(lineNumber: number, goVer: string): PackageDependency {
depType: 'golang',
currentValue: goVer,
datasource: GolangVersionDatasource.id,
versioning: 'npm',
versioning: 'go-mod-directive',
rangeStrategy: 'replace',
};
}
Expand Down
2 changes: 2 additions & 0 deletions lib/modules/versioning/api.ts
Expand Up @@ -5,6 +5,7 @@ import * as conan from './conan';
import * as debian from './debian';
import * as docker from './docker';
import * as git from './git';
import * as goModDirective from './go-mod-directive';
import * as gradle from './gradle';
import * as hashicorp from './hashicorp';
import * as helm from './helm';
Expand Down Expand Up @@ -42,6 +43,7 @@ api.set(conan.id, conan.api);
api.set(debian.id, debian.api);
api.set(docker.id, docker.api);
api.set(git.id, git.api);
api.set(goModDirective.id, goModDirective.api);
api.set(gradle.id, gradle.api);
api.set(hashicorp.id, hashicorp.api);
api.set(helm.id, helm.api);
Expand Down
88 changes: 88 additions & 0 deletions lib/modules/versioning/go-mod-directive/index.spec.ts
@@ -0,0 +1,88 @@
import { api as semver } from '.';

describe('modules/versioning/go-mod-directive/index', () => {
test.each`
version | range | expected
${'1.16.0'} | ${'1.16'} | ${true}
${'1.16.1'} | ${'1.16'} | ${true}
${'1.15.0'} | ${'1.16'} | ${false}
${'1.19.1'} | ${'1.16'} | ${true}
${'2.0.0'} | ${'1.16'} | ${false}
`(
'matches("$version", "$range") === "$expected"',
({ version, range, expected }) => {
expect(semver.matches(version, range)).toBe(expected);
}
);

test.each`
versions | range | expected
${['1.16.0', '1.16.1', '1.17.0']} | ${'1.16'} | ${'1.17.0'}
`(
'getSatisfyingVersion($versions, "$range") === "$expected"',
({ versions, range, expected }) => {
expect(semver.getSatisfyingVersion(versions, range)).toBe(expected);
}
);

test.each`
version | expected
${'1'} | ${false}
${'1.2'} | ${true}
${'1.2.3'} | ${false}
`('isValid("$version") === $expected', ({ version, expected }) => {
expect(!!semver.isValid(version)).toBe(expected);
});

test.each`
version | expected
${'1'} | ${false}
${'1.2'} | ${false}
${'1.2.3'} | ${true}
`('isVersion("$version") === $expected', ({ version, expected }) => {
expect(!!semver.isVersion(version)).toBe(expected);
});

test.each`
version | range | expected
${'1.15.0'} | ${'1.16'} | ${true}
${'1.19.0'} | ${'1.16'} | ${false}
`(
'isLessThanRange("$version", "$range") === "$expected"',
({ version, range, expected }) => {
expect(semver.isLessThanRange?.(version, range)).toBe(expected);
}
);

test.each`
versions | range | expected
${['1.15.0', '1.16.0', '1.16.1']} | ${'1.16'} | ${'1.16.0'}
${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'1.16'} | ${null}
`(
'minSatisfyingVersion($versions, "$range") === "$expected"',
({ versions, range, expected }) => {
expect(semver.minSatisfyingVersion(versions, range)).toBe(expected);
}
);

test.each`
currentValue | rangeStrategy | currentVersion | newVersion | expected
${'1.16'} | ${'bump'} | ${'1.16.4'} | ${'1.17.0'} | ${'1.17'}
${'1.16'} | ${'bump'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'}
${'1.16'} | ${'replace'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'}
${'1.16'} | ${'replace'} | ${'1.16.4'} | ${'2.0.0'} | ${'2.0'}
${'1.16'} | ${'widen'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'}
`(
'getNewValue("$currentValue", "$rangeStrategy", "$currentVersion", "$newVersion") === "$expected"',
({ currentValue, rangeStrategy, currentVersion, newVersion, expected }) => {
expect(
semver.getNewValue({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
})
).toBe(expected);
}
);
});
67 changes: 67 additions & 0 deletions lib/modules/versioning/go-mod-directive/index.ts
@@ -0,0 +1,67 @@
import type { RangeStrategy } from '../../../types/versioning';
import { regEx } from '../../../util/regex';
import { api as npm } from '../npm';
import type { NewValueConfig, VersioningApi } from '../types';

export const id = 'go-mod-directive';
export const displayName = 'Go Modules Directive';
export const urls = ['https://go.dev/ref/mod'];
export const supportsRanges = true;
export const supportedRangeStrategies: RangeStrategy[] = ['bump', 'replace'];

const validRegex = regEx(/^\d+\.\d+$/);

function toNpmRange(range: string): string {
return `^${range}`;
}

function shorten(version: string): string {
return version.split('.').slice(0, 2).join('.');
}

function getNewValue({
currentValue,
rangeStrategy,
newVersion,
}: NewValueConfig): string {
if (rangeStrategy === 'bump') {
return shorten(newVersion);
}
if (rangeStrategy === 'replace' && !matches(currentValue, newVersion)) {
return shorten(newVersion);
}
return currentValue;
}

function getSatisfyingVersion(
versions: string[],
range: string
): string | null {
return npm.getSatisfyingVersion(versions, toNpmRange(range));
}

const isLessThanRange = (version: string, range: string): boolean =>
npm.isLessThanRange!(version, toNpmRange(range));

export const isValid = (input: string): boolean => !!input.match(validRegex);

const matches = (version: string, range: string): boolean =>
npm.matches(version, toNpmRange(range));

function minSatisfyingVersion(
versions: string[],
range: string
): string | null {
return npm.minSatisfyingVersion(versions, toNpmRange(range));
}

export const api: VersioningApi = {
...npm,
getNewValue,
getSatisfyingVersion,
isLessThanRange,
isValid,
matches,
minSatisfyingVersion,
};
export default api;
17 changes: 17 additions & 0 deletions lib/modules/versioning/go-mod-directive/readme.md
@@ -0,0 +1,17 @@
This versioning is used exclusively for the `go` directive in `go.mod` files.

It ensures that a value like `1.16` is treated like `^1.16` and not `~1.16`.

By default this will mean that the `go` directive in `go.mod` files won't get upgraded to any new Go version, such as `1.19`.
If you wish to upgrade this value every time there's a new minor Go release, configure `rangeStrategy` to be `"bump"` like so:

```json
{
"packageRules": [
{
"matchDatasources": ["golang-version"],
"rangeStrategy": "bump"
}
]
}
```

0 comments on commit 7193547

Please sign in to comment.