import { codeBlock } from 'common-tags'; import { describe } from 'vitest'; import { Fixtures } from '~test/fixtures.ts'; import { getDefaultVersioning } from '../../datasource/common.ts'; import * as allVersioning from '../../versioning/index.ts'; import { convertGoDirectiveToSemVerRange } from './extract.ts'; import { extractPackageFile } from './index.ts'; const gomod1 = Fixtures.get('1/go-mod'); const gomod2 = Fixtures.get('2/go-mod'); describe('modules/manager/gomod/extract', () => { describe('extractPackageFile()', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); it('extracts single-line requires', () => { const res = extractPackageFile(gomod1); expect(res).toEqual({ deps: [ { datasource: 'go', depType: 'require', depName: 'github.com/pkg/errors', currentValue: 'v0.7.0', managerData: { lineNumber: 2 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/aws/aws-sdk-go', currentValue: 'v1.15.21', managerData: { lineNumber: 3 }, }, { datasource: 'go', depType: 'indirect', depName: 'github.com/davecgh/go-spew', currentValue: 'v1.0.0', enabled: false, managerData: { lineNumber: 4 }, }, { datasource: 'go', depType: 'require', depName: 'golang.org/x/foo', currentValue: 'v1.0.0', managerData: { lineNumber: 5 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/rarkins/foo', currentValue: 'abcdef1', skipReason: 'invalid-version', managerData: { lineNumber: 6 }, }, { datasource: 'go', depType: 'require', depName: 'gopkg.in/russross/blackfriday.v1', currentValue: 'v1.0.0', managerData: { lineNumber: 7 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/Azure/azure-sdk-for-go', currentValue: 'v25.1.0+incompatible', managerData: { lineNumber: 8 }, }, { datasource: 'go', depType: 'replace', depName: '../errors', skipReason: 'local-dependency', managerData: { lineNumber: 10 }, }, { datasource: 'go', depType: 'replace', depName: 'github.com/pravesht/gocql', currentValue: 'v0.0.0', managerData: { lineNumber: 11 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/caarlos0/env', currentValue: 'v3.5.0+incompatible', managerData: { lineNumber: 13 }, }, { datasource: 'go', depType: 'require', depName: 'sigs.k8s.io/structured-merge-diff/v4', currentValue: 'v4.7.0', managerData: { lineNumber: 15 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/cucumber/common/messages/go/v18', currentValue: 'v18.0.0', managerData: { lineNumber: 16 }, }, ], }); }); it('extracts multi-line requires', () => { const res = extractPackageFile(gomod2); expect(res).toEqual({ deps: [ { datasource: 'go', depType: 'require', depName: 'github.com/davecgh/go-spew', currentValue: 'v1.1.0', managerData: { multiLine: true, lineNumber: 3 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/emirpasic/gods', currentValue: 'v1.9.0', managerData: { multiLine: true, lineNumber: 4 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/fatih/color', currentValue: 'v1.7.0', managerData: { multiLine: true, lineNumber: 5 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/spf13/jwalterweatherman', currentValue: 'v0.0.0-20180814060501-14d3d4c51834', currentDigest: '14d3d4c51834', digestOneAndOnly: true, versioning: 'loose', managerData: { multiLine: true, lineNumber: 6 }, }, { datasource: 'go', depType: 'require', depName: 'github.com/src-d/gcfg/v2', currentValue: 'v2.3.0', managerData: { multiLine: true, lineNumber: 7 }, }, { datasource: 'go', depType: 'require', depName: 'golang.org/x/text', currentValue: 'v0.3.0', managerData: { multiLine: true, lineNumber: 8 }, }, { datasource: 'go', depType: 'require', depName: 'gopkg.in/src-d/go-billy.v4', currentValue: 'v4.2.0', managerData: { multiLine: true, lineNumber: 9 }, }, { datasource: 'go', depType: 'indirect', depName: 'golang.org/x/net', currentValue: 'v0.0.0-20191003171128-d98b1b443823', currentDigest: 'd98b1b443823', digestOneAndOnly: true, versioning: 'loose', enabled: false, managerData: { multiLine: true, lineNumber: 10 }, }, ], }); }); it('ignores empty spaces in multi-line requires', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod go 1.19 require ( cloud.google.com/go v0.45.1 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect ) `; const res = extractPackageFile(goMod)?.deps; expect(res).toHaveLength(3); }); it('extracts replace directives from multi-line and single line', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod go 1.23 replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0 replace ( k8s.io/client-go => k8s.io/client-go v0.21.9 ) replace ( k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3 k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 // indirect k8s.io/code-generator => k8s.io/code-generator v0.17.3 ) `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { lineNumber: 1, }, depName: 'go', depType: 'golang', currentValue: '1.23', datasource: 'golang-version', versioning: 'go-mod-directive', commitMessageTopic: 'go module directive', }, { managerData: { lineNumber: 2, }, depName: 'github.com/pravesht/gocql', depType: 'replace', currentValue: 'v0.0.0', datasource: 'go', }, { managerData: { lineNumber: 4, multiLine: true, }, depName: 'k8s.io/client-go', depType: 'replace', currentValue: 'v0.21.9', datasource: 'go', }, { managerData: { lineNumber: 7, multiLine: true, }, depName: 'k8s.io/cloud-provider', depType: 'replace', currentValue: 'v0.17.3', datasource: 'go', }, { managerData: { lineNumber: 8, multiLine: true, }, depName: 'k8s.io/cluster-bootstrap', depType: 'indirect', enabled: false, currentValue: 'v0.17.3', datasource: 'go', }, { managerData: { lineNumber: 9, multiLine: true, }, depName: 'k8s.io/code-generator', depType: 'replace', currentValue: 'v0.17.3', datasource: 'go', }, ], extractedConstraints: { '%goMod': '~1.23.x', }, constraintsVersioning: { '%goMod': 'semver-coerced', }, }); }); it('extracts replace directives from non-public module path', () => { const goMod = codeBlock` module github.com/JamieTanna-Mend-testing/tka-9783-golang-pro-main go 1.25.5 require pro-lib v0.0.0-00010101000000-000000000000 replace pro-lib => github.com/ns-rpro-dev-tests/golang-pro-lib/libs/src/ns v0.0.0-20260219031232-e6910bd8fb97 `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { lineNumber: 1, }, depName: 'go', depType: 'golang', currentValue: '1.25.5', datasource: 'golang-version', versioning: 'go-mod-directive', commitMessageTopic: 'go module directive', }, { managerData: { lineNumber: 2, }, depName: 'pro-lib', depType: 'require', currentValue: 'v0.0.0-00010101000000-000000000000', currentDigest: '000000000000', datasource: 'go', digestOneAndOnly: true, versioning: 'loose', skipReason: 'invalid-version', }, { managerData: { lineNumber: 3, }, depName: 'github.com/ns-rpro-dev-tests/golang-pro-lib/libs/src/ns', depType: 'replace', currentValue: 'v0.0.0-20260219031232-e6910bd8fb97', currentDigest: 'e6910bd8fb97', datasource: 'go', digestOneAndOnly: true, versioning: 'loose', }, ], extractedConstraints: { '%goMod': '~1.25.x', }, constraintsVersioning: { '%goMod': 'semver-coerced', }, }); }); // https://go.dev/doc/modules/gomod-ref#exclude it('ignores exclude directives from multi-line and single line', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod exclude github.com/pravesht/gocql v0.0.0 exclude ( k8s.io/client-go v0.21.9 ) exclude ( k8s.io/cloud-provider v0.17.3 k8s.io/cluster-bootstrap v0.17.3 // indirect k8s.io/code-generator v0.17.3 ) `; const res = extractPackageFile(goMod); expect(res).toBeNull(); }); it('extracts the toolchain directive', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod go 1.23 toolchain go1.23.3 replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0 `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { lineNumber: 1, }, depName: 'go', depType: 'golang', currentValue: '1.23', datasource: 'golang-version', versioning: 'go-mod-directive', commitMessageTopic: 'go module directive', }, { managerData: { lineNumber: 2, }, depName: 'go', depType: 'toolchain', currentValue: '1.23.3', datasource: 'golang-version', commitMessageTopic: 'go toolchain directive', }, { managerData: { lineNumber: 3, }, depName: 'github.com/pravesht/gocql', depType: 'replace', currentValue: 'v0.0.0', datasource: 'go', }, ], extractedConstraints: { '%goMod': '~1.23.x', golang: '1.23.3', }, constraintsVersioning: { '%goMod': 'semver-coerced', }, }); }); it('extracts single-line tool directives', () => { const goMod = codeBlock` require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { datasource: 'go', depName: 'github.com/oapi-codegen/oapi-codegen/v2', depType: 'indirect', currentValue: 'v2.4.1', managerData: { lineNumber: 0 }, }, ], }); }); it('extracts multi-line tool directives', () => { const goMod = codeBlock` require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect tool ( github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen ) `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { datasource: 'go', depName: 'github.com/oapi-codegen/oapi-codegen/v2', depType: 'indirect', currentValue: 'v2.4.1', managerData: { lineNumber: 0 }, }, ], }); }); }); it('extracts tool directives with required modules', () => { const goMod = codeBlock` require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { datasource: 'go', depName: 'github.com/oapi-codegen/oapi-codegen/v2', depType: 'require', currentValue: 'v2.4.1', managerData: { lineNumber: 0 }, }, ], }); }); it('extracts tool directives of sub-modules', () => { const goMod = codeBlock` require ( github.com/foo/bar v1.2.3 github.com/foo/bar/sub1/sub2 v4.5.6 // indirect github.com/foo/bar/sub1 v7.8.9 // indirect github.com/foo/bar/sub1/sub2/cmd/hell v10.11.12 // indirect ) tool github.com/foo/bar/sub1/sub2/cmd/hello `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { datasource: 'go', depName: 'github.com/foo/bar', depType: 'require', currentValue: 'v1.2.3', managerData: { lineNumber: 1, multiLine: true }, }, { datasource: 'go', depName: 'github.com/foo/bar/sub1/sub2', depType: 'indirect', currentValue: 'v4.5.6', managerData: { lineNumber: 2, multiLine: true }, }, { datasource: 'go', depName: 'github.com/foo/bar/sub1', depType: 'indirect', currentValue: 'v7.8.9', enabled: false, managerData: { lineNumber: 3, multiLine: true }, }, { datasource: 'go', depName: 'github.com/foo/bar/sub1/sub2/cmd/hell', depType: 'indirect', currentValue: 'v10.11.12', enabled: false, managerData: { lineNumber: 4, multiLine: true }, }, ], }); }); it('extracts tool directives with exact match', () => { const goMod = codeBlock` require github.com/foo/bar v1.2.3 // indirect tool github.com/foo/bar `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { datasource: 'go', depName: 'github.com/foo/bar', depType: 'indirect', currentValue: 'v1.2.3', managerData: { lineNumber: 0 }, }, ], }); }); it('extracts tool directives with no matching dependencies', () => { const goMod = codeBlock` tool github.com/foo/bar/sub/cmd/hello `; const res = extractPackageFile(goMod); expect(res).toBeNull(); }); /** * * https://go.dev/doc/modules/gomod-ref#retract * https://go.dev/doc/modules/gomod-ref#godebug */ it('ignores directives unrelated to dependencies', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod godebug asynctimerchan=0 godebug ( default=go1.21 panicnil=1 ) retract v3.0.0 retract [v2.0.0,v2.0.5] // Build broken on some platforms. retract ( v1.0.0 // Published accidentally. v1.0.1 // Contains retractions only. ) `; const res = extractPackageFile(goMod); expect(res).toBeNull(); }); it('marks placeholder pseudo versions with skipReason invalid-version', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod go 1.19 require ( github.com/foo/bar v1.2.3 github.com/baz/qux v0.0.0-00010101000000-000000000000 github.com/example/local v0.0.0-00010101000000-000000000000 // indirect github.com/non/placeholder v1.2.4-0.20230101120000-abcdef123456 monorepo v0.0.0-00010101000000-000000000000 ) `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { lineNumber: 1, }, depName: 'go', depType: 'golang', currentValue: '1.19', datasource: 'golang-version', versioning: 'go-mod-directive', commitMessageTopic: 'go module directive', }, { managerData: { lineNumber: 3, multiLine: true, }, depName: 'github.com/foo/bar', depType: 'require', currentValue: 'v1.2.3', datasource: 'go', }, { managerData: { lineNumber: 4, multiLine: true, }, depName: 'github.com/baz/qux', depType: 'require', currentValue: 'v0.0.0-00010101000000-000000000000', datasource: 'go', skipReason: 'invalid-version', currentDigest: '000000000000', digestOneAndOnly: true, versioning: 'loose', }, { managerData: { lineNumber: 5, multiLine: true, }, depName: 'github.com/example/local', depType: 'indirect', currentValue: 'v0.0.0-00010101000000-000000000000', datasource: 'go', skipReason: 'invalid-version', enabled: false, currentDigest: '000000000000', digestOneAndOnly: true, versioning: 'loose', }, { managerData: { lineNumber: 6, multiLine: true, }, depName: 'github.com/non/placeholder', depType: 'require', currentValue: 'v1.2.4-0.20230101120000-abcdef123456', datasource: 'go', currentDigest: 'abcdef123456', digestOneAndOnly: true, versioning: 'loose', }, { managerData: { lineNumber: 7, multiLine: true, }, depName: 'monorepo', depType: 'require', currentValue: 'v0.0.0-00010101000000-000000000000', datasource: 'go', currentDigest: '000000000000', digestOneAndOnly: true, versioning: 'loose', skipReason: 'invalid-version', }, ], extractedConstraints: { '%goMod': '~1.19.x', }, constraintsVersioning: { '%goMod': 'semver-coerced', }, }); }); it.each(['1.19', '1.19.0', '1.19.5'])( 'extracts `go` directive %s as a `%goMod` extracted constraint as a SemVer-minor compatible range', (goDirective) => { const goMod = codeBlock` module github.com/renovate-tests/gomod go ${goDirective} `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { lineNumber: 1, }, depName: 'go', depType: 'golang', currentValue: goDirective, datasource: 'golang-version', versioning: 'go-mod-directive', commitMessageTopic: 'go module directive', }, ], extractedConstraints: { // NOTE that this is extracted as a range for the whole SemVer minor version '%goMod': '~1.19.x', }, constraintsVersioning: { '%goMod': 'semver-coerced', }, }); }, ); describe('the extracted version can be used as a SemVer constraint', () => { const goMod = codeBlock` module github.com/renovate-tests/gomod go 1.19 `; const res = extractPackageFile(goMod); const datasourceVersioningName = getDefaultVersioning( res!.deps[0].datasource, ); const versioningName = res!.constraintsVersioning!['%goMod']; const versioning = allVersioning.get(versioningName); const constraint = res!.extractedConstraints!['%goMod']!; it('extracts the expected versioning and constraints', () => { // NOTE that this is not the `go-mod-directive` versioning, as that comes from `constraintsVersioning` expect(datasourceVersioningName).toEqual('semver'); expect(res!.constraintsVersioning).toBeDefined(); expect(versioningName).toBeDefined(); expect(versioning).toBeDefined(); }); it(`${constraint} is a valid constraint`, () => { expect(versioning.isValid(constraint)).toBeTrue(); }); it('matches version 1.19, even though it is not valid SemVer', () => { expect(versioning.matches('1.19', constraint)).toBeTrue(); }); it('matches the current SemVer minor', () => { expect(versioning.matches('1.19.0', constraint)).toBeTrue(); expect(versioning.matches('1.19.10', constraint)).toBeTrue(); }); it('does not match the next SemVer minor', () => { expect(versioning.matches('1.20.0', constraint)).toBeFalse(); expect(versioning.matches('1.20.10', constraint)).toBeFalse(); }); it('does not match the previous SemVer minor', () => { expect(versioning.matches('1.18.0', constraint)).toBeFalse(); expect(versioning.matches('1.18.5', constraint)).toBeFalse(); }); }); describe('convertGoDirectiveToSemVerRange()', () => { it('handles undefined go directive', () => { const goDirective = undefined; const semVerRange = convertGoDirectiveToSemVerRange(goDirective); expect(semVerRange.version).toBeUndefined(); }); }); });