diff --git a/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap index a8c31bc1a278b76..e69de29bb2d1d64 100644 --- a/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap @@ -1,640 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`modules/manager/sbt/extract extractFile() extract deps from native scala file with private variables 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.13.0-RC5", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.7.1", - "datasource": "sbt-package", - "depName": "com.example:foo", - "fileReplacePosition": 9, - "packageName": "com.example:foo_2.13.0-RC5", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - Object { - "currentValue": "1.2.3", - "datasource": "sbt-package", - "depName": "com.abc:abc", - "editFile": undefined, - "fileReplacePosition": 7, - "groupName": "abcVersion", - "packageName": "com.abc:abc", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.13.0-RC5", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extract deps from native scala file with variables 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.13.0-RC5", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.7.1", - "datasource": "sbt-package", - "depName": "com.example:foo", - "fileReplacePosition": 9, - "packageName": "com.example:foo_2.13.0-RC5", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - Object { - "currentValue": "1.2.3", - "datasource": "sbt-package", - "depName": "com.abc:abc", - "editFile": undefined, - "fileReplacePosition": 7, - "groupName": "abcVersion", - "packageName": "com.abc:abc", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - Object { - "currentValue": "1.2.3", - "datasource": "sbt-package", - "depName": "com.abc:abc-a", - "editFile": undefined, - "fileReplacePosition": 7, - "groupName": "abcVersion", - "packageName": "com.abc:abc-a", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - Object { - "currentValue": "1.2.3", - "datasource": "sbt-package", - "depName": "com.abc:abc-b", - "editFile": undefined, - "fileReplacePosition": 7, - "groupName": "abcVersion", - "packageName": "com.abc:abc-b", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - Object { - "currentValue": "1.2.3", - "datasource": "sbt-package", - "depName": "com.abc:abc-c", - "editFile": undefined, - "fileReplacePosition": 7, - "groupName": "abcVersion", - "packageName": "com.abc:abc-c", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.13.0-RC5", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts addCompilerPlugin 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "0.9.9", - "datasource": "sbt-plugin", - "depName": "org.spire-math:kind-projector", - "depType": "plugin", - "fileReplacePosition": 0, - "packageName": "org.spire-math:kind-projector", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://dl.bintray.com/sbt/sbt-plugin-releases", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": null, -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts addCompilerPlugin 2`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.1.1", - "datasource": "sbt-plugin", - "depName": "org.scalamacros:paradise", - "depType": "plugin", - "fileReplacePosition": 0, - "packageName": "org.scalamacros:paradise", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://dl.bintray.com/sbt/sbt-plugin-releases", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": null, -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps for generic use-cases 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.9.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.1", - "datasource": "sbt-package", - "depName": "org.example:foo", - "fileReplacePosition": 5, - "packageName": "org.example:foo", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 6, - "packageName": "org.example:bar_2.9.10", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.3", - "datasource": "sbt-package", - "depName": "org.example:baz", - "fileReplacePosition": 8, - "packageName": "org.example:baz_2.9.10", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.4", - "datasource": "sbt-package", - "depName": "org.example:qux", - "fileReplacePosition": 9, - "packageName": "org.example:qux", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "2.13.3", - "datasource": "sbt-package", - "depName": "org.scala-lang:scala-library", - "depType": "sources", - "fileReplacePosition": 11, - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.5", - "datasource": "sbt-package", - "depName": "org.example:quux", - "fileReplacePosition": 13, - "packageName": "org.example:quux", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.6", - "datasource": "sbt-package", - "depName": "org.example:quuz", - "depType": "test", - "fileReplacePosition": 20, - "packageName": "org.example:quuz_2.9.10", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.7", - "datasource": "sbt-package", - "depName": "org.example:corge", - "depType": "Provided", - "fileReplacePosition": 21, - "packageName": "org.example:corge", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.8", - "datasource": "sbt-package", - "depName": "org.example:grault", - "depType": "Test", - "editFile": undefined, - "fileReplacePosition": 17, - "groupName": "versionExample", - "packageName": "org.example:grault", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.9", - "datasource": "sbt-plugin", - "depName": "org.example:waldo", - "depType": "plugin", - "fileReplacePosition": 34, - "packageName": "org.example:waldo", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - "https://dl.bintray.com/sbt/sbt-plugin-releases", - ], - }, - Object { - "currentValue": "(,8.4.0]", - "datasource": "sbt-package", - "depName": "org.example:fred", - "fileReplacePosition": 36, - "packageName": "org.example:fred", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - ], - "packageFileVersion": "1.0", - "scalaVersion": "2.9.10", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps when scala version is defined in a variable 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.12.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.1", - "datasource": "sbt-package", - "depName": "org.example:foo", - "fileReplacePosition": 8, - "packageName": "org.example:foo", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 9, - "packageName": "org.example:bar_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.3", - "datasource": "sbt-package", - "depName": "org.example:baz", - "fileReplacePosition": 11, - "packageName": "org.example:baz_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.4", - "datasource": "sbt-package", - "depName": "org.example:qux", - "fileReplacePosition": 12, - "packageName": "org.example:qux", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.5", - "datasource": "sbt-package", - "depName": "org.example:quux", - "fileReplacePosition": 15, - "packageName": "org.example:quux", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.6", - "datasource": "sbt-package", - "depName": "org.example:quuz", - "depType": "test", - "fileReplacePosition": 21, - "packageName": "org.example:quuz_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.7", - "datasource": "sbt-package", - "depName": "org.example:corge", - "depType": "Provided", - "fileReplacePosition": 22, - "packageName": "org.example:corge", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.8", - "datasource": "sbt-package", - "depName": "org.example:grault", - "depType": "Test", - "editFile": undefined, - "fileReplacePosition": 1, - "groupName": "versionExample", - "packageName": "org.example:grault", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - ], - }, - Object { - "currentValue": "0.0.9", - "datasource": "sbt-plugin", - "depName": "org.example:waldo", - "depType": "plugin", - "fileReplacePosition": 35, - "packageName": "org.example:waldo", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - "https://example.com/repos/1/", - "https://example.com/repos/2/", - "https://example.com/repos/3/", - "https://example.com/repos/4/", - "https://example.com/repos/5/", - "https://dl.bintray.com/sbt/sbt-plugin-releases", - ], - }, - ], - "packageFileVersion": "3.2.1", - "scalaVersion": "2.12", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps when scala version is defined in a variable with ThisBuild scope 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.12.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 3, - "packageName": "org.example:bar_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.12", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps when scala version is defined in a variable with a trailing comma 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.12.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 5, - "packageName": "org.example:bar_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.12", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps when scala version is defined with ThisBuild scope 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.12.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 2, - "packageName": "org.example:bar_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.12", -} -`; - -exports[`modules/manager/sbt/extract extractFile() extracts deps when scala version is defined with a trailing comma 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.12.10", - "datasource": "maven", - "depName": "scala", - "packageName": "org.scala-lang:scala-library", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - "separateMinorPatch": true, - }, - Object { - "currentValue": "0.0.2", - "datasource": "sbt-package", - "depName": "org.example:bar", - "fileReplacePosition": 4, - "packageName": "org.example:bar_2.12", - "registryUrls": Array [ - "https://repo.maven.apache.org/maven2", - ], - }, - ], - "packageFileVersion": undefined, - "scalaVersion": "2.12", -} -`; diff --git a/lib/modules/manager/sbt/extract.spec.ts b/lib/modules/manager/sbt/extract.spec.ts index 37d339cad08d74c..e69de29bb2d1d64 100644 --- a/lib/modules/manager/sbt/extract.spec.ts +++ b/lib/modules/manager/sbt/extract.spec.ts @@ -1,621 +0,0 @@ -import { Fixtures } from '../../../../test/fixtures'; -import { GlobalConfig } from '../../../config/global'; -import type { RepoGlobalConfig } from '../../../config/types'; -import type { ExtractConfig, PackageFile } from '../types'; -import { extractFile } from './extract'; -import { extractAllPackageFiles } from '.'; - -const fixturesDir = 'lib/modules/manager/sbt/__fixtures__'; - -const sbt = Fixtures.get(`sample.sbt`); -const sbtScalaVersionVariable = Fixtures.get(`scala-version-variable.sbt`); -const sbtMissingScalaVersion = Fixtures.get(`missing-scala-version.sbt`); -const sbtDependencyFile = Fixtures.get(`dependency-file.scala`); -const sbtPrivateVariableDependencyFile = Fixtures.get( - `private-variable-dependency-file.scala` -); - -describe('modules/manager/sbt/extract', () => { - describe('extractFile()', () => { - it('returns null for empty', () => { - expect(extractFile('')).toBeNull(); - expect(extractFile('non-sense')).toBeNull(); - expect( - extractFile('libraryDependencies += "foo" % "bar" % ???') - ).toBeNull(); - expect( - extractFile('libraryDependencies += "foo" % "bar" %% "baz"') - ).toBeNull(); - expect( - extractFile('libraryDependencies += ??? % "bar" % "baz"') - ).toBeNull(); - expect( - extractFile('libraryDependencies += "foo" % ??? % "baz"') - ).toBeNull(); - - expect(extractFile('libraryDependencies += ')).toBeNull(); - expect(extractFile('libraryDependencies += "foo"')).toBeNull(); - expect(extractFile('libraryDependencies += "foo" % "bar" %')).toBeNull(); - expect( - extractFile('libraryDependencies += "foo" % "bar" % "baz" %%') - ).toBeNull(); - }); - - it('extracts deps for generic use-cases', () => { - expect(extractFile(sbt)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.9.10', - }, - { packageName: 'org.example:foo', currentValue: '0.0.1' }, - { packageName: 'org.example:bar_2.9.10', currentValue: '0.0.2' }, - { packageName: 'org.example:baz_2.9.10', currentValue: '0.0.3' }, - { packageName: 'org.example:qux', currentValue: '0.0.4' }, - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.13.3', - }, - { packageName: 'org.example:quux', currentValue: '0.0.5' }, - { packageName: 'org.example:quuz_2.9.10', currentValue: '0.0.6' }, - { packageName: 'org.example:corge', currentValue: '0.0.7' }, - { packageName: 'org.example:grault', currentValue: '0.0.8' }, - { packageName: 'org.example:waldo', currentValue: '0.0.9' }, - { packageName: 'org.example:fred', currentValue: '(,8.4.0]' }, - ], - packageFileVersion: '1.0', - }); - }); - - it('extracts addCompilerPlugin', () => { - expect( - extractFile( - `addCompilerPlugin(("org.spire-math" % "kind-projector" % "0.9.9").cross(CrossVersion.binary))` - ) - ).toMatchSnapshot({ - deps: [ - { - packageName: 'org.spire-math:kind-projector', - currentValue: '0.9.9', - }, - ], - }); - expect( - extractFile( - `addCompilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.full))` - ) - ).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scalamacros:paradise', - currentValue: '2.1.1', - }, - ], - }); - }); - - it('extracts deps when scala version is defined in a variable', () => { - expect(extractFile(sbtScalaVersionVariable)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.12.10', - }, - { packageName: 'org.example:foo', currentValue: '0.0.1' }, - { packageName: 'org.example:bar_2.12', currentValue: '0.0.2' }, - { packageName: 'org.example:baz_2.12', currentValue: '0.0.3' }, - { packageName: 'org.example:qux', currentValue: '0.0.4' }, - { packageName: 'org.example:quux', currentValue: '0.0.5' }, - { packageName: 'org.example:quuz_2.12', currentValue: '0.0.6' }, - { packageName: 'org.example:corge', currentValue: '0.0.7' }, - { packageName: 'org.example:grault', currentValue: '0.0.8' }, - { packageName: 'org.example:waldo', currentValue: '0.0.9' }, - ], - - packageFileVersion: '3.2.1', - }); - }); - - it('skips deps when scala version is missing', () => { - expect(extractFile(sbtMissingScalaVersion)).toEqual({ - deps: [ - { - currentValue: '3.0.0', - datasource: 'sbt-package', - depName: 'org.scalatest:scalatest', - fileReplacePosition: 3, - packageName: 'org.scalatest:scalatest', - registryUrls: ['https://repo.maven.apache.org/maven2'], - }, - { - currentValue: '1.0.11', - datasource: 'sbt-plugin', - depName: 'com.github.gseitz:sbt-release', - depType: 'plugin', - editFile: undefined, - fileReplacePosition: 6, - groupName: 'sbtReleaseVersion', - packageName: 'com.github.gseitz:sbt-release', - registryUrls: [ - 'https://repo.maven.apache.org/maven2', - 'https://dl.bintray.com/sbt/sbt-plugin-releases', - ], - }, - ], - packageFileVersion: '1.0.1', - scalaVersion: null, - }); - }); - - it('extract deps from native scala file with variables', () => { - expect(extractFile(sbtDependencyFile)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.13.0-RC5', - }, - { - packageName: 'com.example:foo_2.13.0-RC5', - currentValue: '0.7.1', - }, - { packageName: 'com.abc:abc', currentValue: '1.2.3' }, - { packageName: 'com.abc:abc-a', currentValue: '1.2.3' }, - { packageName: 'com.abc:abc-b', currentValue: '1.2.3' }, - { packageName: 'com.abc:abc-c', currentValue: '1.2.3' }, - ], - }); - }); - - it('extracts deps when scala version is defined with a trailing comma', () => { - const content = ` - lazy val commonSettings = Seq( - scalaVersion := "2.12.10", - ) - libraryDependencies += "org.example" %% "bar" % "0.0.2" - `; - expect(extractFile(content)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.12.10', - }, - { - packageName: 'org.example:bar_2.12', - currentValue: '0.0.2', - }, - ], - }); - }); - - it('extracts deps when scala version is defined in a variable with a trailing comma', () => { - const content = ` - val ScalaVersion = "2.12.10" - lazy val commonSettings = Seq( - scalaVersion := ScalaVersion, - ) - libraryDependencies += "org.example" %% "bar" % "0.0.2" - `; - expect(extractFile(content)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.12.10', - }, - { packageName: 'org.example:bar_2.12', currentValue: '0.0.2' }, - ], - }); - }); - - it('extracts deps when scala version is defined with ThisBuild scope', () => { - const content = ` - ThisBuild / scalaVersion := "2.12.10" - libraryDependencies += "org.example" %% "bar" % "0.0.2" - `; - expect(extractFile(content)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.12.10', - }, - { - packageName: 'org.example:bar_2.12', - currentValue: '0.0.2', - }, - ], - }); - }); - - it('extracts deps when scala version is defined in a variable with ThisBuild scope', () => { - const content = ` - val ScalaVersion = "2.12.10" - ThisBuild / scalaVersion := ScalaVersion - libraryDependencies += "org.example" %% "bar" % "0.0.2" - `; - expect(extractFile(content)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.12.10', - }, - { - packageName: 'org.example:bar_2.12', - currentValue: '0.0.2', - }, - ], - }); - }); - - it('extract deps from native scala file with private variables', () => { - expect(extractFile(sbtPrivateVariableDependencyFile)).toMatchSnapshot({ - deps: [ - { - packageName: 'org.scala-lang:scala-library', - currentValue: '2.13.0-RC5', - }, - { - packageName: 'com.example:foo_2.13.0-RC5', - currentValue: '0.7.1', - }, - { - packageName: 'com.abc:abc', - currentValue: '1.2.3', - }, - ], - packageFileVersion: undefined, - }); - }); - - it('extract deps when they are defined in a new line', () => { - const content = ` - name := "service" - scalaVersion := "2.13.8" - - lazy val compileDependencies = - Seq( - "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4", - "ch.qos.logback" % "logback-classic" % "1.2.10" - ) - - libraryDependencies ++= compileDependencies`; - expect(extractFile(content)).toMatchObject({ - deps: [ - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - datasource: 'maven', - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - currentValue: '2.13.8', - separateMinorPatch: true, - }, - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - depName: 'com.typesafe.scala-logging:scala-logging', - packageName: 'com.typesafe.scala-logging:scala-logging_2.13', - currentValue: '3.9.4', - datasource: 'sbt-package', - }, - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - depName: 'ch.qos.logback:logback-classic', - packageName: 'ch.qos.logback:logback-classic', - currentValue: '1.2.10', - datasource: 'sbt-package', - }, - ], - packageFileVersion: undefined, - }); - }); - - it('extract deps line with force withSource exclude excludeAll', () => { - const content = ` - name := "renovatebot-sbt-example" - - scalaVersion := "2.13.8" - - libraryDependencies ++= Seq( - "com.lightbend.akka" %% "akka-stream-alpakka-csv" % "2.0.0" excludeAll ExclusionRule(organization = "com.typesafe.akka"), - "com.lightbend.akka" %% "akka-stream-alpakka-s3" % "2.0.1" force(), - )`; - expect(extractFile(content)).toMatchObject({ - deps: [ - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - datasource: 'maven', - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - currentValue: '2.13.8', - separateMinorPatch: true, - }, - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - depName: 'com.lightbend.akka:akka-stream-alpakka-csv', - fileReplacePosition: 6, - packageName: 'com.lightbend.akka:akka-stream-alpakka-csv_2.13', - currentValue: '2.0.0', - datasource: 'sbt-package', - }, - { - registryUrls: ['https://repo.maven.apache.org/maven2'], - depName: 'com.lightbend.akka:akka-stream-alpakka-s3', - packageName: 'com.lightbend.akka:akka-stream-alpakka-s3_2.13', - currentValue: '2.0.1', - datasource: 'sbt-package', - }, - ], - packageFileVersion: undefined, - }); - }); - }); - - describe('extractAllPackageFiles()', () => { - const config: ExtractConfig = {}; - - afterEach(() => { - GlobalConfig.reset(); - }); - - it('extract simple-project with Versions.scala variable file', async () => { - const adminConfig: RepoGlobalConfig = { - localDir: `${fixturesDir}/simple-project`, - }; - GlobalConfig.set(adminConfig); - const registryUrls = ['https://repo.maven.apache.org/maven2']; - expect( - await extractAllPackageFiles(config, [ - `build.sbt`, - `project/plugins.sbt`, - `project/Versions.scala`, - `submodule/build.sbt`, - ]) - ).toEqual([ - { - deps: [ - { - currentValue: '2.13.8', - datasource: 'maven', - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - registryUrls, - separateMinorPatch: true, - }, - { - currentValue: '1.2.11', - datasource: 'sbt-package', - depName: 'ch.qos.logback:logback-classic', - fileReplacePosition: 35, - packageName: 'ch.qos.logback:logback-classic', - registryUrls, - }, - ], - packageFile: 'build.sbt', - }, - { - deps: [ - { - currentValue: '0.13.0', - datasource: 'sbt-package', - depName: 'io.circe:circe-generic', - editFile: `project/Versions.scala`, - fileReplacePosition: 7, - groupName: 'Versions.circe', - packageName: 'io.circe:circe-generic_2.13', - registryUrls, - }, - { - currentValue: '10.2.6', - datasource: 'sbt-package', - depName: 'com.typesafe.akka:akka-http', - editFile: `project/Versions.scala`, - fileReplacePosition: 3, - groupName: 'Versions.akkaHttp', - packageName: 'com.typesafe.akka:akka-http_2.13', - registryUrls, - }, - { - currentValue: '2.6.18', - datasource: 'sbt-package', - depName: 'com.typesafe.akka:akka-stream', - editFile: `project/Versions.scala`, - fileReplacePosition: 2, - groupName: 'Versions.akka', - packageName: 'com.typesafe.akka:akka-stream_2.13', - registryUrls, - }, - { - currentValue: '1.3.1', - datasource: 'sbt-package', - depName: 'org.sangria-graphql:sangria-circe', - editFile: `project/Versions.scala`, - fileReplacePosition: 6, - groupName: 'Versions.sangriacirce', - packageName: 'org.sangria-graphql:sangria-circe_2.13', - registryUrls, - }, - { - currentValue: '3.2.11', - datasource: 'sbt-package', - depName: 'org.scalatest:scalatest-wordspec', - depType: 'Test', - editFile: `project/Versions.scala`, - fileReplacePosition: 13, - groupName: 'Versions.Tests.scalaTest', - packageName: 'org.scalatest:scalatest-wordspec_2.13', - registryUrls, - }, - { - currentValue: '3.2.11', - datasource: 'sbt-package', - depName: 'org.scalatest:scalatest-funsuite', - depType: 'Test', - editFile: `project/Versions.scala`, - fileReplacePosition: 13, - groupName: 'Versions.Tests.scalaTest', - packageName: 'org.scalatest:scalatest-funsuite_2.13', - registryUrls, - }, - { - currentValue: '1.17.5', - datasource: 'sbt-package', - depName: 'org.mockito:mockito-scala-scalatest', - depType: 'Test', - editFile: `project/Versions.scala`, - fileReplacePosition: 14, - groupName: 'Versions.Tests.mockito', - packageName: 'org.mockito:mockito-scala-scalatest_2.13', - registryUrls, - }, - { - currentValue: '3.1.9', - datasource: 'sbt-package', - depName: - 'com.softwaremill.sttp.client3:async-http-client-backend-future', - depType: 'Test', - editFile: `project/Versions.scala`, - fileReplacePosition: 4, - groupName: 'Versions.sttp', - packageName: - 'com.softwaremill.sttp.client3:async-http-client-backend-future_2.13', - registryUrls, - }, - ], - packageFile: `project/Versions.scala`, - }, - { - deps: [ - { - currentValue: '1.9.3', - datasource: 'sbt-plugin', - depName: 'org.scoverage:sbt-scoverage', - depType: 'plugin', - fileReplacePosition: 2, - packageName: 'org.scoverage:sbt-scoverage', - registryUrls: [ - ...registryUrls, - 'https://dl.bintray.com/sbt/sbt-plugin-releases', - ], - }, - { - currentValue: '1.0.1', - datasource: 'sbt-plugin', - depName: 'com.typesafe:sbt-mima-plugin', - depType: 'plugin', - fileReplacePosition: 3, - packageName: 'com.typesafe:sbt-mima-plugin_2.13', - registryUrls: [ - ...registryUrls, - 'https://dl.bintray.com/sbt/sbt-plugin-releases', - ], - }, - { - currentValue: '0.4.3', - datasource: 'sbt-plugin', - depName: 'pl.project13.scala:sbt-jmh', - depType: 'plugin', - fileReplacePosition: 4, - packageName: 'pl.project13.scala:sbt-jmh', - registryUrls: [ - ...registryUrls, - 'https://dl.bintray.com/sbt/sbt-plugin-releases', - ], - }, - ], - packageFile: 'project/plugins.sbt', - }, - ] as PackageFile[]); - }); - - it('extract simple-project with maven resolver', async () => { - const adminConfig: RepoGlobalConfig = { - localDir: `${fixturesDir}/simple-project-with-resolver`, - }; - GlobalConfig.set(adminConfig); - - const registryUrls = [ - 'https://repo.maven.apache.org/maven2', - 'https://example.org.com/internal-maven', - ]; - expect( - await extractAllPackageFiles(config, [ - `build.sbt`, - `project/plugins.sbt`, - `project/Versions.scala`, - `submodule/build.sbt`, - ]) - ).toEqual([ - { - deps: [ - { - currentValue: '2.13.5', - datasource: 'maven', - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - registryUrls, - separateMinorPatch: true, - }, - { - currentValue: '1.2.11', - datasource: 'sbt-package', - depName: 'ch.qos.logback:logback-classic', - fileReplacePosition: 35, - packageName: 'ch.qos.logback:logback-classic', - registryUrls, - }, - ], - packageFile: `build.sbt`, - }, - { - deps: [ - { - currentValue: '0.13.0', - datasource: 'sbt-package', - depName: 'io.circe:circe-generic', - editFile: `project/Versions.scala`, - fileReplacePosition: 5, - groupName: 'Versions.circe', - packageName: 'io.circe:circe-generic_2.13', - registryUrls, - }, - { - currentValue: '10.2.6', - datasource: 'sbt-package', - depName: 'com.typesafe.akka:akka-http', - editFile: `project/Versions.scala`, - fileReplacePosition: 3, - groupName: 'Versions.akkaHttp', - packageName: 'com.typesafe.akka:akka-http_2.13', - registryUrls, - }, - { - currentValue: '2.6.18', - datasource: 'sbt-package', - depName: 'com.typesafe.akka:akka-stream', - editFile: `project/Versions.scala`, - fileReplacePosition: 2, - groupName: 'Versions.akka', - packageName: 'com.typesafe.akka:akka-stream_2.13', - registryUrls, - }, - ], - packageFile: `project/Versions.scala`, - }, - { - deps: [ - { - currentValue: '1.9.5', - datasource: 'sbt-plugin', - depName: 'org.scoverage:sbt-scoverage', - depType: 'plugin', - fileReplacePosition: 2, - packageName: 'org.scoverage:sbt-scoverage', - registryUrls: [ - ...registryUrls, - 'https://dl.bintray.com/sbt/sbt-plugin-releases', - ], - }, - ], - packageFile: `project/plugins.sbt`, - }, - ] as PackageFile[]); - }); - }); -}); diff --git a/lib/modules/manager/sbt/extract.ts b/lib/modules/manager/sbt/extract.ts index 93f30fbe92918d3..c62e379e208aa74 100644 --- a/lib/modules/manager/sbt/extract.ts +++ b/lib/modules/manager/sbt/extract.ts @@ -1,520 +1,413 @@ +import { lang, query as q } from 'good-enough-parser'; import upath from 'upath'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; -import { newlineRegex, regEx } from '../../../util/regex'; +import { regEx } from '../../../util/regex'; +import { parseUrl } from '../../../util/url'; +import { GithubReleasesDatasource } from '../../datasource/github-releases'; import { MavenDatasource } from '../../datasource/maven'; -import { MAVEN_REPO } from '../../datasource/maven/common'; import { SbtPackageDatasource } from '../../datasource/sbt-package'; import { + SBT_PLUGINS_REPO, SbtPluginDatasource, - defaultRegistryUrls as sbtPluginDefaultRegistries, } from '../../datasource/sbt-plugin'; import { get } from '../../versioning'; import * as mavenVersioning from '../../versioning/maven'; +import * as semverVersioning from '../../versioning/semver'; +import { REGISTRY_URLS } from '../gradle/parser/common'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; import type { GroupFilenameContent, - ParseContext, ParseOptions, VariableContext, + Variables, } from './types'; +import { normalizeScalaVersion } from './util'; -const stripComment = (str: string): string => - str.replace(regEx(/(^|\s+)\/\/.*$/), '').replace(regEx(/\/\*\*.*\*\*\//), ''); +// type Vars = Record; -const isSingleLineDep = (str: string): boolean => - regEx(/^\s*(libraryDependencies|dependencyOverrides)\s*\+=\s*/).test(str); +interface Ctx { + globalVars: Variables; + localVars: Variables; + deps: PackageDependency[]; + registryUrls: string[]; + + scalaVersion?: string; + packageFileVersion?: string; -const isDepsBegin = (str: string): boolean => - regEx(/^\s*(libraryDependencies|dependencyOverrides)\s*\+\+=\s*/).test(str) || - regEx(/\s*Seq\(\s*$/).test(str); + groupId?: string; + artifactId?: string; + currentValue?: string; + currentValueInfo?: VariableContext; -const isPluginDep = (str: string): boolean => - regEx(/^\s*(addSbtPlugin|addCompilerPlugin)\s*\(.*\)\s*$/).test(str); + currentVarName?: string; + depType?: string; + useScalaVersion?: boolean; + variableName?: string; + + packageFile: string; +} -const isStringLiteral = (str: string): boolean => regEx(/^"[^"]*"$/).test(str); +const scala = lang.createLang('scala'); + +const sbtVersionRegex = regEx( + 'sbt\\.version *= *(?\\d+\\.\\d+\\.\\d+)' +); + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const nestedVariableLiteral = (handler: q.SymMatcherHandler) => + q.sym(handler).alt(q.many(q.op('.').sym(handler))); + +const scalaVersionMatch = q + .sym('scalaVersion') + .op(':=') + .alt( + q.str((ctx, { value: scalaVersion }) => ({ ...ctx, scalaVersion })), + nestedVariableLiteral((ctx, { value: varName }) => { + const scalaVersion = ctx.localVars[varName] ?? ctx.globalVars[varName]; + if (scalaVersion) { + ctx.scalaVersion = scalaVersion.val; + } + return ctx; + }) + ) + .handler((ctx) => { + if (ctx.scalaVersion) { + const version = get(mavenVersioning.id); + + let packageName = 'org.scala-lang:scala-library'; + if (version.getMajor(ctx.scalaVersion) === 3) { + packageName = 'org.scala-lang:scala3-library_3'; + } -const isScalaVersion = (str: string): boolean => - regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*"[^"]*"[\s,]*$/).test( - str + const dep: PackageDependency = { + datasource: MavenDatasource.id, + depName: 'scala', + packageName, + currentValue: ctx.scalaVersion, + separateMinorPatch: true, + }; + ctx.scalaVersion = normalizeScalaVersion(ctx.scalaVersion); + ctx.deps.push(dep); + } + return ctx; + }); + +const packageFileVersionMatch = q + .sym('version') + .op(':=') + .alt( + q.str((ctx, { value: packageFileVersion }) => ({ + ...ctx, + packageFileVersion, + })), + nestedVariableLiteral((ctx, { value: varName }) => { + const packageFileVersion = + ctx.localVars[varName] ?? ctx.globalVars[varName]; + if (packageFileVersion) { + ctx.packageFileVersion = packageFileVersion.val; + } + return ctx; + }) // support var1, var1.var2.var3 ); -const getScalaVersion = (str: string): string => - str - .replace(regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*"/), '') - .replace(regEx(/"[\s,]*$/), ''); - -const isPackageFileVersion = (str: string): boolean => - regEx(/^(version\s*:=\s*).*$/).test(str); - -const getPackageFileVersion = (str: string): string => - str - .replace(regEx(/^\s*version\s*:=\s*/), '') - .replace(regEx(/[\s,]*$/), '') - .replace(regEx(/"/g), ''); - -/* - https://www.scala-sbt.org/release/docs/Cross-Build.html#Publishing+conventions - */ -const normalizeScalaVersion = (str: string): string => { - // istanbul ignore if - if (!str) { - return str; - } - const versioning = get(mavenVersioning.id); - if (versioning.isVersion(str)) { - // Do not normalize unstable versions - if (!versioning.isStable(str)) { - return str; +const variableNameMatch = q + .sym((ctx, { value: varName }) => ({ + ...ctx, + currentVarName: varName, + })) + .opt(q.op(':').sym('String')); + +const variableValueMatch = q.str((ctx, { value, line }) => { + ctx.localVars[ctx.currentVarName!] = { + val: value, + sourceFile: ctx.packageFile, + lineIndex: line - 1, + }; + delete ctx.currentVarName; + return ctx; +}); + +const assignmentMatch = q.sym('val').join(variableNameMatch).op('='); + +const variableDefinitionMatch = q + .alt( + q.sym('lazy').join(assignmentMatch), + assignmentMatch, + variableNameMatch.op(':=') + ) + .join(variableValueMatch); + +const groupIdMatch = q.alt( + nestedVariableLiteral((ctx, { value: varName }) => { + const currentGroupId = ctx.localVars[varName] ?? ctx.globalVars[varName]; + if (currentGroupId) { + ctx.groupId = currentGroupId.val; } - // Do not normalize versions prior to 2.10 - if (!versioning.isGreaterThan(str, '2.10.0')) { - return str; + return ctx; + }), + q.str((ctx, { value: groupId }) => ({ ...ctx, groupId })) +); + +const artifactIdMatch = q.alt( + nestedVariableLiteral((ctx, { value: varName }) => { + const artifactId = ctx.localVars[varName] ?? ctx.globalVars[varName]; + if (artifactId) { + ctx.artifactId = artifactId.val; } + return ctx; + }), + q.str((ctx, { value: artifactId }) => ({ ...ctx, artifactId })) +); + +const resolveVariable: q.SymMatcherHandler = (ctx, { value: varName }) => { + const currentValue = ctx.localVars[varName] ?? ctx.globalVars[varName]; + if (currentValue) { + ctx.currentValue = currentValue.val; + ctx.currentValueInfo = currentValue; + ctx.variableName = varName; } - if (regEx(/^\d+\.\d+\.\d+$/).test(str)) { - return str.replace(regEx(/^(\d+)\.(\d+)\.\d+$/), '$1.$2'); - } - // istanbul ignore next - return str; -}; - -const isScalaVersionVariable = (str: string): boolean => - regEx( - /^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*[_a-zA-Z][_a-zA-Z0-9]*[\s,]*$/ - ).test(str); - -const getScalaVersionVariable = (str: string): string => - str - .replace(regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*/), '') - .replace(regEx(/[\s,]*$/), ''); - -const isResolver = (str: string): boolean => - regEx( - /^\s*(resolvers\s*\+\+?=\s*((Seq|List|Stream)\()?)?"[^"]*"\s*at\s*"[^"]*"[\s,)]*$/ - ).test(str); -const getResolverUrl = (str: string): string => - str - .replace( - regEx( - /^\s*(resolvers\s*\+\+?=\s*((Seq|List|Stream)\()?)?"[^"]*"\s*at\s*"/ - ), - '' - ) - .replace(regEx(/"[\s,)]*$/), ''); - -const isVarDependency = (str: string): boolean => - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=.*(%%?).*%.*/ - ).test(str); - -const isVarDef = (str: string): boolean => - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"[^"]*"\s*$/ - ).test(str); - -/** - * - * Check if variable definition is referencing another variable - * @param str line - * @returns {boolean} - */ -const isVarDefRefVar = (str: string): boolean => - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*[_a-zA-Z][_a-zA-Z0-9]*(\.[_a-zA-Z][_a-zA-Z0-9]*)*\s*$/ - ).test(str); - -const isVarSeqSingleLine = (str: string): boolean => - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*(Seq|List|Stream)\(.*\).*\s*$/ - ).test(str); - -const isVarSeqMultipleLine = (str: string): boolean => - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*(Seq|List|Stream)\(.*[^)]*.*$/ - ).test(str); - -const isObjectLine = (str: string): boolean => - regEx(/object\s+(\w+)\s+{/).test(str); - -const isObjectEndedLine = (str: string): boolean => - regEx(/^\s*}\s*$/).test(str); - -const getVarName = (str: string): string => - str.replace( - regEx(/^\s*(private\s*)?(lazy\s*)?val\s+([_a-zA-Z][_a-zA-Z0-9]*)\s*=.*$/), - '$3' - ); - -const isVarName = (str: string): boolean => - // allow dot annotation - regEx(/^[_a-zA-Z][_a-zA-Z0-9]*(\.[_a-zA-Z][_a-zA-Z0-9]*)*$/).test(str); - -const getVarInfo = ( - str: string, - { lookupVariableFile, lineIndex }: ParseContext -): VariableContext => { - const rightPart = str.replace( - regEx(/^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"/), - '' - ); - const val = rightPart.replace(regEx(/"\s*$/), ''); - return { val, sourceFile: lookupVariableFile!, lineIndex }; + return ctx; }; -function parseDepExpr( - expr: string, - ctx: ParseContext -): PackageDependency | null { +const versionMatch = q.alt( + nestedVariableLiteral(resolveVariable), // support var1, var1.var2.var3 + q.str((ctx, { value: currentValue }) => ({ ...ctx, currentValue })) // String literal "1.23.4" +); + +const simpleDependencyMatch = groupIdMatch + .op('%') + .join(artifactIdMatch) + .op('%') + .join(versionMatch); + +const versionedDependencyMatch = groupIdMatch + .op('%%') + .join(artifactIdMatch) + .handler((ctx) => ({ ...ctx, useScalaVersion: true })) + .op('%') + .join(versionMatch); + +const crossDependencyMatch = groupIdMatch + .op('%%%') + .join(artifactIdMatch) + .handler((ctx) => ({ ...ctx, useScalaVersion: true })) + .op('%') + .join(versionMatch); + +function depHandler(ctx: Ctx): Ctx { const { scalaVersion, - variables, - lineIndex, - globalVariables, - localVariables, + groupId, + artifactId, + currentValue, + useScalaVersion, + depType, + variableName, + currentValueInfo, } = ctx; - let { depType } = ctx; - - const isValidToken = (str: string): boolean => - isStringLiteral(str) || - (isVarName(str) && - (Boolean(localVariables[str]) || - Boolean(variables[str]) || - Boolean(globalVariables[str]))); - - const resolveToken = (str: string): string => { - if (isStringLiteral(str)) { - return str.replace(regEx(/^"/), '').replace(regEx(/"$/), ''); - } - if (localVariables[str]) { - ctx.lookupVariableFile = variables[str]?.sourceFile || ''; - return localVariables[str].val; - } - if (variables[str]) { - ctx.lookupVariableFile = variables[str]?.sourceFile || ''; - return variables[str].val; - } - if (globalVariables[str]) { - ctx.lookupVariableFile = globalVariables[str]?.sourceFile || ''; - return globalVariables[str].val; - } - return str; - }; - const tokens = expr - .trim() - .split(regEx(/("[^"]*")/g)) - .map((x) => (regEx(/"[^"]*"/).test(x) ? x : x.replace(regEx(/[()]+/g), ''))) - .join('') - .split(regEx(/\s*(%%?)\s*|\s*classifier\s*/)); - - const [ - rawGroupId, - groupOp, - rawArtifactId, - artifactOp, - rawVersion, - scopeOp, - rawScope, - ] = tokens; - - if (!rawGroupId) { - return null; - } - if (!isValidToken(rawGroupId)) { - return null; - } + delete ctx.groupId; + delete ctx.artifactId; + delete ctx.currentValue; + delete ctx.useScalaVersion; + delete ctx.depType; + delete ctx.variableName; + delete ctx.currentValueInfo; - if (!rawArtifactId) { - return null; - } - if (!isValidToken(rawArtifactId)) { - return null; - } - if (artifactOp !== '%') { - return null; - } - - if (!rawVersion) { - return null; - } - if (!isValidToken(rawVersion)) { - return null; - } - - if (scopeOp && scopeOp !== '%') { - return null; - } - const groupId = resolveToken(rawGroupId); - const depName = `${groupId}:${resolveToken(rawArtifactId)}`; - const artifactId = - groupOp === '%%' && scalaVersion - ? `${resolveToken(rawArtifactId)}_${scalaVersion}` - : resolveToken(rawArtifactId); - const packageName = `${groupId}:${artifactId}`; - const currentValue = resolveToken(rawVersion); - - if (!depType && rawScope) { - depType = rawScope.replace(regEx(/^"/), '').replace(regEx(/"$/), ''); - } + const depName = `${groupId!}:${artifactId!}`; - const result: PackageDependency = { + const dep: PackageDependency = { + datasource: SbtPackageDatasource.id, depName, - packageName, + packageName: + scalaVersion && useScalaVersion ? `${depName}_${scalaVersion}` : depName, currentValue, - fileReplacePosition: lineIndex, }; - const varDep = - localVariables[rawVersion] || - variables[rawVersion] || - globalVariables[rawVersion]; - if (varDep) { - result.groupName = `${rawVersion}`; - result.fileReplacePosition = varDep.lineIndex; - result.editFile = varDep.sourceFile; + if (depType) { + dep.depType = depType; + } + + if (depType === 'plugin') { + dep.datasource = SbtPluginDatasource.id; } - if (depType) { - result.depType = depType; + if (variableName) { + dep.groupName = variableName; + dep.variableName = variableName; + if (currentValueInfo) { + dep.fileReplacePosition = currentValueInfo.lineIndex; + dep.editFile = currentValueInfo.sourceFile; + } } - return result; + ctx.deps.push(dep); + + return ctx; } -function parseSbtLine( - acc: PackageFile & ParseOptions, - line: string, - lineIndex: number, - lines: string[] -): PackageFile & ParseOptions { - const { deps, registryUrls = [], variables = {}, globalVariables = {} } = acc; +function depTypeHandler(ctx: Ctx, { value: depType }: { value: string }): Ctx { + return { ...ctx, depType }; +} - let { - isMultiDeps, - scalaVersion, - packageFileVersion, - variableParentKey = '', - localVariables = {}, - } = acc; +const sbtPackageMatch = q + .opt(q.opt(q.sym('lazy')).sym('val').sym().op('=')) + .alt(crossDependencyMatch, simpleDependencyMatch, versionedDependencyMatch) + .opt( + q.alt( + q.sym('classifier').str(depTypeHandler), + q.op('%').sym(depTypeHandler), + q.op('%').str(depTypeHandler) + ) + ) + .handler(depHandler); + +const sbtPluginMatch = q + .sym(regEx(/^(?:addSbtPlugin|addCompilerPlugin)$/)) + .tree({ + type: 'wrapped-tree', + maxDepth: 1, + search: q + .begin() + .alt(simpleDependencyMatch, versionedDependencyMatch) + .end(), + }) + .handler((ctx) => ({ ...ctx, depType: 'plugin' })) + .handler(depHandler); + +const resolverMatch = q + .str() + .sym('at') + .str((ctx, { value }) => { + if (parseUrl(value)) { + ctx.registryUrls.push(value); + } + return ctx; + }); + +const addResolverMatch = q.sym('resolvers').alt( + q.op('+=').join(resolverMatch), + q.op('++=').sym('Seq').tree({ + type: 'wrapped-tree', + maxDepth: 1, + search: resolverMatch, + }) +); + +function registryUrlHandler(ctx: Ctx): Ctx { + for (const dep of ctx.deps) { + dep.registryUrls = [...ctx.registryUrls]; + if (dep.depType === 'plugin') { + dep.registryUrls.push(SBT_PLUGINS_REPO); + } + } + return ctx; +} - const ctx: ParseContext = { +const query = q.tree({ + type: 'root-tree', + maxDepth: 32, + search: q.alt( + scalaVersionMatch, + packageFileVersionMatch, + sbtPackageMatch, + sbtPluginMatch, + addResolverMatch, + variableDefinitionMatch + ), + postHandler: registryUrlHandler, +}); + +// Extract 1 file +export function extractPackageFile( + content: string, + { + packageFile, + registryUrls, + localVars, + globalVars, scalaVersion, - variables, - lookupVariableFile: acc.packageFile!, - lineIndex, - globalVariables, - localVariables, - }; + }: PackageFile & ParseOptions +): Ctx | null { + if ( + packageFile && + (packageFile === 'project/build.properties' || + packageFile.endsWith('/project/build.properties')) + ) { + const regexResult = sbtVersionRegex.exec(content); + const sbtVersion = regexResult?.groups?.version; + const matchString = regexResult?.[0]; + if (sbtVersion) { + const sbtDependency: PackageDependency = { + datasource: GithubReleasesDatasource.id, + depName: 'sbt/sbt', + packageName: 'sbt/sbt', + versioning: semverVersioning.id, + currentValue: sbtVersion, + replaceString: matchString, + extractVersion: '^v(?\\S+)', + editFile: packageFile, + }; - let dep: PackageDependency | null = null; - let scalaVersionVariable: string | null = null; - if (line !== '') { - if (isScalaVersion(line)) { - isMultiDeps = false; - const rawScalaVersion = getScalaVersion(line); - scalaVersion = normalizeScalaVersion(rawScalaVersion); - dep = { - datasource: MavenDatasource.id, - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - currentValue: rawScalaVersion, - separateMinorPatch: true, + return { + deps: [sbtDependency], + globalVars: {}, + localVars: {}, + packageFile, + registryUrls: [REGISTRY_URLS.mavenCentral], }; - } else if (isScalaVersionVariable(line)) { - isMultiDeps = false; - scalaVersionVariable = getScalaVersionVariable(line); - const scalaVar = - localVariables[scalaVersionVariable] ?? - variables[scalaVersionVariable] ?? - globalVariables[scalaVersionVariable]; - if (scalaVar) { - scalaVersion = normalizeScalaVersion(scalaVar.val); - dep = { - datasource: MavenDatasource.id, - depName: 'scala', - packageName: 'org.scala-lang:scala-library', - currentValue: scalaVar.val, - separateMinorPatch: true, - }; - } - } else if (isPackageFileVersion(line)) { - packageFileVersion = getPackageFileVersion(line); - } else if (isResolver(line)) { - isMultiDeps = false; - const url = getResolverUrl(line); - registryUrls.push(url); - } else if (isVarSeqSingleLine(line)) { - isMultiDeps = false; - const depExpr = line - .replace(regEx(/^.*(Seq|List|Stream)\(\s*/), '') - .replace(regEx(/\).*$/), ''); - dep = parseDepExpr(depExpr, { - ...ctx, - }); - } else if (isVarSeqMultipleLine(line)) { - isMultiDeps = true; - const depExpr = line.replace(regEx(/^.*(Seq|List|Stream)\(\s*/), ''); - dep = parseDepExpr(depExpr, { - ...ctx, - }); - } else if (isObjectLine(line)) { - const objectName = line.replace(regEx(/object\s+(\w+)\s+{/), '$1'); - variableParentKey = - variableParentKey === '' - ? objectName - : `${variableParentKey}.${objectName.trim()}`; - } else if (isObjectEndedLine(line)) { - variableParentKey = variableParentKey.split('.').slice(0, -1).join('.'); - localVariables = {}; - } else if (isVarDef(line)) { - const key = variableParentKey === '' ? '' : `${variableParentKey}.`; - localVariables[getVarName(line)] = getVarInfo(line, ctx); - variables[key + getVarName(line)] = getVarInfo(line, ctx); - } else if (isVarDefRefVar(line)) { - const rightPart = line - .replace( - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*(.*)\s*$/ - ), - '$3' - ) - .trim(); - const isVarExist = variables[rightPart] || globalVariables[rightPart]; - if (isVarExist) { - const key = - (variableParentKey === '' ? '' : `${variableParentKey}.`) + - getVarName(line); - variables[key] = isVarExist; - } - } else if (isVarDependency(line)) { - isMultiDeps = false; - const depExpr = line - .replace( - regEx( - /^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*/ - ), - '' - ) - .replace(/\s*(force|withSources|exclude(All)?).*$/, ''); - dep = parseDepExpr(depExpr, { - ...ctx, - }); - } else if (isSingleLineDep(line)) { - isMultiDeps = false; - const depExpr = line.replace(regEx(/^.*\+=\s*/), ''); - dep = parseDepExpr(depExpr, { - ...ctx, - }); - } else if (isPluginDep(line)) { - isMultiDeps = false; - const depExpr = line.replace( - regEx( - /^\s*(addSbtPlugin|addCompilerPlugin)\s*\(+(.*(%%?).*(%)\s*(\w+|".*")\s*)\)+.*/ - ), - '$2' - ); - dep = parseDepExpr(depExpr, { - ...ctx, - depType: 'plugin', - }); - } else if (isDepsBegin(line)) { - isMultiDeps = true; - } else if (isMultiDeps) { - const rightPart = line.replace(regEx(/^[\s,]*/), ''); - const depExpr = rightPart - .replace(regEx(/[\s,]*$/), '') - .replace(/\s*(force|withSources|exclude(All)?).*$/, ''); - dep = parseDepExpr(depExpr, { - ...ctx, - }); + } else { + return null; } } - if (dep) { - if (!dep.datasource) { - if (dep.depType === 'plugin') { - dep.datasource = SbtPluginDatasource.id; - dep.registryUrls = [...registryUrls, ...sbtPluginDefaultRegistries]; - } else { - dep.datasource = SbtPackageDatasource.id; - } - } - deps.push({ - registryUrls, - ...dep, + let parsedResult: Ctx | null = null; + + try { + parsedResult = scala.query(content, query, { + globalVars, + localVars, + deps: [], + registryUrls: [REGISTRY_URLS.mavenCentral, ...(registryUrls ?? [])], + packageFile, + scalaVersion, }); + } catch (err) /* istanbul ignore next */ { + logger.warn({ err, packageFile }, 'Sbt parsing error'); } - if (lineIndex + 1 < lines.length) { - return { - ...acc, - isMultiDeps, - variableParentKey, - scalaVersion: - scalaVersion || - (scalaVersionVariable && - variables[scalaVersionVariable] && - normalizeScalaVersion(variables[scalaVersionVariable].val)), - packageFileVersion, - }; - } - return { - deps, - packageFileVersion, - scalaVersion, - }; -} - -export function extractFile( - content: string, - defaultAcc?: PackageFile & ParseOptions -): (PackageFile & ParseOptions) | null { - if (!content) { + if (!parsedResult) { return null; } - const equalsToNewLineRe = regEx(/=\s*\n/, 'gm'); - const goodContentForParsing = content.replace(equalsToNewLineRe, '='); - const lines = goodContentForParsing.split(newlineRegex).map(stripComment); - - const acc: PackageFile & ParseOptions = { - registryUrls: [MAVEN_REPO], - deps: [], - isMultiDeps: false, - scalaVersion: null, - variables: {}, - globalVariables: {}, - localVariables: {}, - ...defaultAcc, - }; - // TODO: needs major refactoring? - const res = lines.reduce(parseSbtLine, acc); - return res.deps.length ? res : null; + return parsedResult; } function prepareLoadPackageFiles( packageFilesContent: { packageFile: string; content: string }[] ): { - globalVariables: ParseOptions['variables']; + globalVars: ParseOptions['globalVars']; registryUrls: string[]; scalaVersion: ParseOptions['scalaVersion']; } { // Return variable - let globalVariables: ParseOptions['variables'] = {}; - const registryUrls: string[] = [MAVEN_REPO]; - let scalaVersion: string | null = null; + let globalVars: Variables = {}; + const registryUrls: string[] = [REGISTRY_URLS.mavenCentral]; + let scalaVersion: string | undefined = undefined; for (const { packageFile, content } of packageFilesContent) { const acc: PackageFile & ParseOptions = { - registryUrls, deps: [], - variables: globalVariables, + registryUrls, + localVars: globalVars, + globalVars: {}, packageFile, }; - const res = extractFile(content, acc); + const res = extractPackageFile(content, acc); + if (res) { - globalVariables = { ...globalVariables, ...res.variables }; + globalVars = { ...globalVars, ...res.localVars }; if (res.registryUrls) { registryUrls.push(...res.registryUrls); } @@ -525,7 +418,7 @@ function prepareLoadPackageFiles( } return { - globalVariables, + globalVars, registryUrls, scalaVersion, }; @@ -540,22 +433,22 @@ export async function extractAllPackageFiles( const groupPackageFileContent: GroupFilenameContent = {}; for (const packageFile of packageFiles) { const content = await readLocalFile(packageFile, 'utf8'); - if (content) { - const group = upath.dirname(packageFile); - groupPackageFileContent[group] ??= []; - groupPackageFileContent[group].push({ packageFile, content }); + if (!content) { + logger.trace({ packageFile }, 'packageFile has no content'); + continue; } - logger.trace({ packageFile }, 'packageFile has no content'); + const group = upath.dirname(packageFile); + groupPackageFileContent[group] ??= []; + groupPackageFileContent[group].push({ packageFile, content }); } // 1. globalVariables from project/ and root package file // 2. registry from all package file // 3. Project's scalaVersion - use in parseDepExpr to add suffix eg. "_2.13" - const { globalVariables, registryUrls, scalaVersion } = - prepareLoadPackageFiles([ - ...(groupPackageFileContent['project'] ?? []), // in project/ folder - ...(groupPackageFileContent['.'] ?? []), // root - ]); + const { globalVars, registryUrls, scalaVersion } = prepareLoadPackageFiles([ + ...(groupPackageFileContent['project'] ?? []), // in project/ folder + ...(groupPackageFileContent['.'] ?? []), // root + ]); const mapDepsToPackageFile: Record = {}; // Start extract all package files @@ -563,29 +456,20 @@ export async function extractAllPackageFiles( // Extract package file by its group // local variable is share within its group for (const { packageFile, content } of packageFileContents) { - const res = extractFile(content, { + const res = extractPackageFile(content, { registryUrls, deps: [], packageFile, scalaVersion, - globalVariables, + localVars: {}, + globalVars, }); - - if (res?.deps) { - for (const dep of res.deps) { - // "dep?.editFile" is the source of variable that package version is referecing with - // "packageFile" is where package usage was found - const variableSourceFile = dep?.editFile ?? packageFile; - dep.registryUrls = [...new Set(dep.registryUrls)]; - if (!mapDepsToPackageFile[variableSourceFile]) { - mapDepsToPackageFile[variableSourceFile] = []; - } - const isExist = mapDepsToPackageFile[variableSourceFile].find( - (val) => - val.packageName === dep.packageName && - val.currentValue === dep.currentValue - ); - if (!isExist) { + if (res) { + if (res?.deps) { + for (const dep of res.deps) { + const variableSourceFile = dep?.editFile ?? packageFile; + dep.registryUrls = [...new Set(dep.registryUrls)]; + mapDepsToPackageFile[variableSourceFile] ??= []; mapDepsToPackageFile[variableSourceFile].push(dep); } } @@ -593,14 +477,25 @@ export async function extractAllPackageFiles( } } - // Format from Record - // to {packageFile:string, deps: Dependency[]}[] - const formatedDeps = Object.entries(mapDepsToPackageFile).map( + // Filter unique package + // As we merge all package to single package file + // Packages are counted in submodule but it's the same one + // by packageName and currentValue + const finalPackages = Object.entries(mapDepsToPackageFile).map( ([packageFile, deps]) => ({ packageFile, - deps, + deps: deps.filter( + (val, idx, self) => + idx === + self.findIndex( + (dep) => + dep.packageName === val.packageName && + dep.currentValue === val.currentValue + ) + ), }) ); + logger.debug('finalPackages ' + JSON.stringify(finalPackages)); - return formatedDeps.length ? formatedDeps : null; + return finalPackages.length > 0 ? finalPackages : null; } diff --git a/lib/modules/manager/sbt/index.ts b/lib/modules/manager/sbt/index.ts index 4e215ba727b83cb..aa14ed079975c6d 100644 --- a/lib/modules/manager/sbt/index.ts +++ b/lib/modules/manager/sbt/index.ts @@ -1,3 +1,4 @@ +import { GithubReleasesDatasource } from '../../datasource/github-releases'; import { MavenDatasource } from '../../datasource/maven'; import { SbtPackageDatasource } from '../../datasource/sbt-package'; import { SbtPluginDatasource } from '../../datasource/sbt-plugin'; @@ -10,9 +11,14 @@ export const supportedDatasources = [ MavenDatasource.id, SbtPackageDatasource.id, SbtPluginDatasource.id, + GithubReleasesDatasource.id, // For sbt itself ]; export const defaultConfig = { - fileMatch: ['\\.sbt$', 'project/[^/]*.scala$'], + fileMatch: [ + '\\.sbt$', + 'project/[^/]*\\.scala$', + 'project/build\\.properties$', + ], versioning: ivyVersioning.id, }; diff --git a/lib/modules/manager/sbt/types.ts b/lib/modules/manager/sbt/types.ts index b6b8f7806a65bf4..d44e334a430e4b9 100644 --- a/lib/modules/manager/sbt/types.ts +++ b/lib/modules/manager/sbt/types.ts @@ -3,28 +3,17 @@ export type VariableContext = { sourceFile: string; lineIndex: number; }; -type Variables = Record; - -export interface ParseContext { - scalaVersion?: string | null; - localVariables: Variables; // variable within "object" scope ex.scalaVersion - variables: Variables; // variable that can be use outside scope ex."Versions.ScalaVersion" - lineIndex: number; - lookupVariableFile?: string; - depType?: string; - readonly globalVariables: Variables; // variable from root and project/ folder -} - -export interface ParseOptions { - variableParentKey?: string; - isMultiDeps?: boolean; - scalaVersion?: string | null; - variables?: Variables; - localVariables?: Variables; - readonly globalVariables?: Variables; -} +export type Variables = Record; export type GroupFilenameContent = Record< string, { packageFile: string; content: string }[] >; + +export interface ParseOptions { + packageFile?: string; + isMultiDeps?: boolean; + scalaVersion?: string; + localVars: Variables; + readonly globalVars: Variables; +} diff --git a/lib/workers/repository/updates/branchify.ts b/lib/workers/repository/updates/branchify.ts index 4401fb4341640d5..189513a6c2216de 100644 --- a/lib/workers/repository/updates/branchify.ts +++ b/lib/workers/repository/updates/branchify.ts @@ -19,6 +19,7 @@ export async function branchifyUpgrades( packageFiles: Record ): Promise { logger.debug('branchifyUpgrades'); + logger.debug(JSON.stringify(packageFiles)); const updates = await flattenUpdates(config, packageFiles); logger.debug( `${updates.length} flattened updates found: ${updates