From 719354793f970de9860a8b78ef34c793657fe3b4 Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Sat, 17 Dec 2022 08:12:57 +0100 Subject: [PATCH] feat(gomod): directive versioning (#19453) --- lib/modules/manager/gomod/extract.spec.ts | 2 +- lib/modules/manager/gomod/extract.ts | 2 +- lib/modules/versioning/api.ts | 2 + .../versioning/go-mod-directive/index.spec.ts | 88 +++++++++++++++++++ .../versioning/go-mod-directive/index.ts | 67 ++++++++++++++ .../versioning/go-mod-directive/readme.md | 17 ++++ 6 files changed, 176 insertions(+), 2 deletions(-) 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.spec.ts b/lib/modules/manager/gomod/extract.spec.ts index 6bd4bc24a3b7fc..4d9b35b36ff95f 100644 --- a/lib/modules/manager/gomod/extract.spec.ts +++ b/lib/modules/manager/gomod/extract.spec.ts @@ -66,7 +66,7 @@ replace ( depType: 'golang', currentValue: '1.18', datasource: 'golang-version', - versioning: 'npm', + versioning: 'go-mod-directive', rangeStrategy: 'replace', }, { 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/api.ts b/lib/modules/versioning/api.ts index f9899d112ac099..089d31360de60c 100644 --- a/lib/modules/versioning/api.ts +++ b/lib/modules/versioning/api.ts @@ -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'; @@ -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); 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" + } + ] +} +```