From 847766ccb2435e38316d61fe3f04bb316dbbda13 Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Fri, 16 Dec 2022 18:09:10 +0100 Subject: [PATCH] feat(gomod): directive versioning Closes #16715 --- lib/modules/manager/gomod/extract.ts | 2 +- .../versioning/go-mod-directive/index.spec.ts | 88 +++++++++++++++++++ .../versioning/go-mod-directive/index.ts | 67 ++++++++++++++ .../versioning/go-mod-directive/readme.md | 17 ++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 lib/modules/versioning/go-mod-directive/index.spec.ts create mode 100644 lib/modules/versioning/go-mod-directive/index.ts create mode 100644 lib/modules/versioning/go-mod-directive/readme.md diff --git a/lib/modules/manager/gomod/extract.ts b/lib/modules/manager/gomod/extract.ts index 91f7c86eebc88e..47ea1f1c5c46fe 100644 --- a/lib/modules/manager/gomod/extract.ts +++ b/lib/modules/manager/gomod/extract.ts @@ -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', }; } diff --git a/lib/modules/versioning/go-mod-directive/index.spec.ts b/lib/modules/versioning/go-mod-directive/index.spec.ts new file mode 100644 index 00000000000000..1a0fa03780b274 --- /dev/null +++ b/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); + } + ); +}); diff --git a/lib/modules/versioning/go-mod-directive/index.ts b/lib/modules/versioning/go-mod-directive/index.ts new file mode 100644 index 00000000000000..d62b842e430b16 --- /dev/null +++ b/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; diff --git a/lib/modules/versioning/go-mod-directive/readme.md b/lib/modules/versioning/go-mod-directive/readme.md new file mode 100644 index 00000000000000..b78971a20b8463 --- /dev/null +++ b/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" + } + ] +} +```