import is from '@sindresorhus/is';
import { codeBlock } from 'common-tags';
import {
  parseGradle,
  parseJavaToolchainVersion,
  parseKotlinSource,
  parseProps,
} from './parser';
import {
  GRADLE_PLUGINS,
  GRADLE_TEST_SUITES,
  REGISTRY_URLS,
} from './parser/common';
import { Fixtures } from '~test/fixtures';
import { fs, logger } from '~test/util';

vi.mock('../../../util/fs');

function mockFs(files: Record<string, string>): void {
  fs.getSiblingFileName.mockImplementation(
    (existingFileNameWithPath: string, otherFileName: string) => {
      return existingFileNameWithPath
        .slice(0, existingFileNameWithPath.lastIndexOf('/') + 1)
        .concat(otherFileName);
    },
  );
}

describe('modules/manager/gradle/parser', () => {
  it('handles end of input', () => {
    expect(parseGradle('version = ').deps).toBeEmpty();
    expect(parseGradle('id "foo.bar" version').deps).toBeEmpty();
  });

  describe('variables', () => {
    describe('Groovy: single var assignments', () => {
      it.each`
        input                                | name                 | value
        ${'foo = "1.2.3"'}                   | ${'foo'}             | ${'1.2.3'}
        ${'foo.bar = "1.2.3"'}               | ${'foo.bar'}         | ${'1.2.3'}
        ${'foo.bar.baz = "1.2.3"'}           | ${'foo.bar.baz'}     | ${'1.2.3'}
        ${'ext.foobar = "1.2.3"'}            | ${'foobar'}          | ${'1.2.3'}
        ${'foo["bar"] = "1.2.3"'}            | ${'foo.bar'}         | ${'1.2.3'}
        ${'foo["bar"]["baz"] = "1.2.3"'}     | ${'foo.bar.baz'}     | ${'1.2.3'}
        ${'foo["bar"]["baz.qux"] = "1.2.3"'} | ${'foo.bar.baz.qux'} | ${'1.2.3'}
        ${'foo.bar["baz"]["qux"] = "1.2.3"'} | ${'foo.bar.baz.qux'} | ${'1.2.3'}
        ${'ext["foo"] = "1.2.3"'}            | ${'foo'}             | ${'1.2.3'}
        ${'ext["foo"]["bar"] = "1.2.3"'}     | ${'foo.bar'}         | ${'1.2.3'}
        ${'extra["foo"] = "1.2.3"'}          | ${'foo'}             | ${'1.2.3'}
        ${'project.foobar = "1.2.3"'}        | ${'foobar'}          | ${'1.2.3'}
        ${'project.ext.foo.bar = "1.2.3"'}   | ${'foo.bar'}         | ${'1.2.3'}
        ${'rootProject.foobar = "1.2.3"'}    | ${'foobar'}          | ${'1.2.3'}
        ${'rootProject.foo.bar = "1.2.3"'}   | ${'foo.bar'}         | ${'1.2.3'}
      `('$input', ({ input, name, value }) => {
        const { vars } = parseGradle(input);
        expect(vars).toContainKey(name);
        expect(vars[name]).toMatchObject({ key: name, value });
      });
    });

    describe('Groovy: single var assignments (non-match)', () => {
      it.each`
        input
        ${'foo[["bar"]] = "baz"'}
        ${'foo["bar", "invalid"] = "1.2.3"'}
        ${'foo.bar["baz", "invalid"] = "1.2.3"'}
      `('$input', ({ input }) => {
        const { vars } = parseGradle(input);
        expect(vars).toBeEmpty();
      });
    });

    describe('Groovy: multi var assignments', () => {
      it('simple map', () => {
        const input = codeBlock`
        ext {
          versions = [
            spotbugs_annotations  : '4.5.3',
            core                  : '1.7.0',
          ]

          ignored = [ 'asdf' ]

          libraries = [:]
          libraries += [
            guava: "com.google.guava:guava:31.1-jre",
            detekt: '1.18.1',
            core2: versions.core
          ]
        }
        `;

        const { vars } = parseGradle(input);
        expect(vars).toMatchObject({
          'versions.spotbugs_annotations': {
            key: 'versions.spotbugs_annotations',
            value: '4.5.3',
          },
          'versions.core': {
            key: 'versions.core',
            value: '1.7.0',
          },
          'libraries.guava': {
            key: 'libraries.guava',
            value: 'com.google.guava:guava:31.1-jre',
          },
          'libraries.detekt': {
            key: 'libraries.detekt',
            value: '1.18.1',
          },
          'libraries.core2': {
            key: 'versions.core',
            value: '1.7.0',
          },
        });
      });

      it('nested map', () => {
        const input = codeBlock`
          project.ext.versions = [
            some: invalidsymbol,
            android: [
              buildTools: '30.0.3'
            ],
            kotlin: '1.4.30',
            'spring-boot': '2.7.3',
            androidx: [
              paging: '2.1.2',
              kotlin: [
                stdlib: '1.4.20',
                coroutines: '1.3.7',
              ],
            ],
            'androidx': [
              'appcompat': '1.6.1',
              'compose': [
                'ui': '1.6.0',
                'runtime': '1.6.0',
              ],
            ],
            espresso: '3.2.0'
          ]
        `;

        const { vars } = parseGradle(input);
        expect(vars).toMatchObject({
          'versions.android.buildTools': {
            key: 'versions.android.buildTools',
            value: '30.0.3',
          },
          'versions.kotlin': {
            key: 'versions.kotlin',
            value: '1.4.30',
          },
          'versions.spring-boot': {
            key: 'versions.spring-boot',
            value: '2.7.3',
          },
          'versions.androidx.paging': {
            key: 'versions.androidx.paging',
            value: '2.1.2',
          },
          'versions.androidx.kotlin.stdlib': {
            key: 'versions.androidx.kotlin.stdlib',
            value: '1.4.20',
          },
          'versions.androidx.kotlin.coroutines': {
            key: 'versions.androidx.kotlin.coroutines',
            value: '1.3.7',
          },
          'versions.androidx.appcompat': {
            key: 'versions.androidx.appcompat',
            value: '1.6.1',
          },
          'versions.androidx.compose.ui': {
            key: 'versions.androidx.compose.ui',
            value: '1.6.0',
          },
          'versions.androidx.compose.runtime': {
            key: 'versions.androidx.compose.runtime',
            value: '1.6.0',
          },
          'versions.espresso': {
            key: 'versions.espresso',
            value: '3.2.0',
          },
        });
      });

      it('map with interpolated dependency strings', () => {
        const input = codeBlock`
          def slfj4Version = "2.0.0"
          def lifecycle_version = "2.5.1"
          libraries = [
            jcl: "org.slf4j:jcl-over-slf4j:\${slfj4Version}",
            releaseCoroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1-eap13"
            api: "org.slf4j:slf4j-api:$slfj4Version",
            lifecycle: [
                "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version",
                "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
            ]
          ]
          foo = [ group: "org.slf4j", name: "slf4j-ext", version: slfj4Version ]
        `;

        const { deps } = parseGradle(input);
        expect(deps).toMatchObject([
          {
            depName: 'org.slf4j:jcl-over-slf4j',
            sharedVariableName: 'slfj4Version',
            currentValue: '2.0.0',
          },
          {
            depName: 'org.jetbrains.kotlinx:kotlinx-coroutines-core',
            sharedVariableName: 'libraries.releaseCoroutines',
            currentValue: '0.26.1-eap13',
          },
          {
            depName: 'org.slf4j:slf4j-api',
            sharedVariableName: 'slfj4Version',
            currentValue: '2.0.0',
          },
          {
            depName: 'androidx.lifecycle:lifecycle-runtime-ktx',
            sharedVariableName: 'lifecycle_version',
            currentValue: '2.5.1',
          },
          {
            depName: 'androidx.lifecycle:lifecycle-viewmodel-ktx',
            sharedVariableName: 'lifecycle_version',
            currentValue: '2.5.1',
          },
          {
            depName: 'org.slf4j:slf4j-ext',
            sharedVariableName: 'slfj4Version',
            currentValue: '2.0.0',
          },
        ]);
      });
    });

    describe('Kotlin: single var assignments', () => {
      it.each`
        input                        | name     | value
        ${'set("foo", "1.2.3")'}     | ${'foo'} | ${'1.2.3'}
        ${'version("foo", "1.2.3")'} | ${'foo'} | ${'1.2.3'}
      `('$input', ({ input, name, value }) => {
        const { vars } = parseGradle(input);
        expect(vars).toContainKey(name);
        expect(vars[name]).toMatchObject({ key: name, value });
      });
    });

    describe('Kotlin: single var assignments (non-match)', () => {
      it.each`
        input
        ${'set(["foo", "bar"])'}
        ${'set("foo", "bar", "baz", "qux"])'}
      `('$input', ({ input }) => {
        const { vars } = parseGradle(input);
        expect(vars).toBeEmpty();
      });
    });

    describe('Kotlin: single extra var assignments', () => {
      it.each`
        input                                     | name     | value
        ${'val foo by extra("1.2.3")'}            | ${'foo'} | ${'1.2.3'}
        ${'val foo by extra { "1.2.3" }'}         | ${'foo'} | ${'1.2.3'}
        ${'val foo: String by extra { "1.2.3" }'} | ${'foo'} | ${'1.2.3'}
      `('$input', ({ input, name, value }) => {
        const { vars } = parseGradle(input);
        expect(vars).toContainKey(name);
        expect(vars[name]).toMatchObject({ key: name, value });
      });
    });

    describe('Kotlin: multi var assignments', () => {
      it('simple map', () => {
        const input =
          'val versions = mapOf("foo1" to "bar1", "foo2" to "bar2", "foo3" to "bar3")';

        const { vars } = parseGradle(input);
        expect(vars).toMatchObject({
          'versions.foo1': {
            key: 'versions.foo1',
            value: 'bar1',
          },
          'versions.foo2': {
            key: 'versions.foo2',
            value: 'bar2',
          },
          'versions.foo3': {
            key: 'versions.foo3',
            value: 'bar3',
          },
        });
      });

      it('nested map', () => {
        const input = codeBlock`
          val junitPlatformVersion: String by extra { "1.0.1" }
          ext["deps"] = mapOf(
            "support" to mapOf(
              "appCompat" to "com.android.support:appcompat-v7:26.0.2",
              "invalid" to whatever,
              "junit" to mapOf(
                "jupiter" to "5.0.1",
                "platform" to "1.0.1",
              )
              "design" to "com.android.support:design:26.0.2"
            ),
            "support2" to mapOfInvalid(
              "design2" to "com.android.support:design:26.0.2"
            ),
            "picasso" to "com.squareup.picasso:picasso:2.5.2",
            "junit-platform-runner" to junitPlatformVersion
          )
        `;

        const { vars } = parseGradle(input);
        expect(vars).toMatchObject({
          'deps.support.appCompat': {
            key: 'deps.support.appCompat',
            value: 'com.android.support:appcompat-v7:26.0.2',
          },
          'deps.support.junit.jupiter': {
            key: 'deps.support.junit.jupiter',
            value: '5.0.1',
          },
          'deps.support.junit.platform': {
            key: 'deps.support.junit.platform',
            value: '1.0.1',
          },
          'deps.support.design': {
            key: 'deps.support.design',
            value: 'com.android.support:design:26.0.2',
          },
          'deps.picasso': {
            key: 'deps.picasso',
            value: 'com.squareup.picasso:picasso:2.5.2',
          },
          'deps.junit-platform-runner': {
            key: 'junitPlatformVersion',
            value: '1.0.1',
          },
        });
      });

      it('map with interpolated dependency strings', () => {
        const input = codeBlock`
          val slfj4Version = "2.0.0"
          libraries = mapOf(
            "jcl" to "org.slf4j:jcl-over-slf4j:\${slfj4Version}",
            "releaseCoroutines" to "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1-eap13"
            "api" to "org.slf4j:slf4j-api:$slfj4Version",
          )
        `;

        const { deps } = parseGradle(input);
        expect(deps).toMatchObject([
          {
            depName: 'org.slf4j:jcl-over-slf4j',
            sharedVariableName: 'slfj4Version',
            currentValue: '2.0.0',
          },
          {
            depName: 'org.jetbrains.kotlinx:kotlinx-coroutines-core',
            sharedVariableName: 'libraries.releaseCoroutines',
            currentValue: '0.26.1-eap13',
          },
          {
            depName: 'org.slf4j:slf4j-api',
            sharedVariableName: 'slfj4Version',
            currentValue: '2.0.0',
          },
        ]);
      });
    });
  });

  describe('dependencies', () => {
    describe('simple dependency strings', () => {
      it.each`
        input                          | output
        ${'"foo:bar:1.2.3"'}           | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${'"foo:bar:1.2+"'}            | ${{ depName: 'foo:bar', currentValue: '1.2+' }}
        ${'"foo:bar:1.2.3@zip"'}       | ${{ depName: 'foo:bar', currentValue: '1.2.3', dataType: 'zip' }}
        ${'"foo:bar:1.2.3:docs@jar"'}  | ${{ depName: 'foo:bar', currentValue: '1.2.3', dataType: 'jar' }}
        ${'"foo:bar1:1"'}              | ${{ depName: 'foo:bar1', currentValue: '1', managerData: { fileReplacePosition: 10 } }}
        ${'"foo:bar:[1.2.3, )"'}       | ${{ depName: 'foo:bar', currentValue: '[1.2.3, )', managerData: { fileReplacePosition: 9 } }}
        ${'"foo:bar:[1.2.3, 1.2.4)"'}  | ${{ depName: 'foo:bar', currentValue: '[1.2.3, 1.2.4)', managerData: { fileReplacePosition: 9 } }}
        ${'"foo:bar:[,1.2.4)"'}        | ${{ depName: 'foo:bar', currentValue: '[,1.2.4)', managerData: { fileReplacePosition: 9 } }}
        ${'"foo:bar:x86@x86"'}         | ${{ depName: 'foo:bar', currentValue: 'x86', managerData: { fileReplacePosition: 9 } }}
        ${'foo.bar = "foo:bar:1.2.3"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      `('$input', ({ input, output }) => {
        const { deps } = parseGradle(input);
        expect(deps).toMatchObject([output]);
      });
    });

    describe('interpolated dependency strings', () => {
      it.each`
        def                                  | str                                    | output
        ${'foo = "1.2.3"'}                   | ${'"foo:bar:$foo@@@"'}                 | ${null}
        ${''}                                | ${'"foo:bar:$baz"'}                    | ${null}
        ${'foo = "1"; bar = "2"; baz = "3"'} | ${'"foo:bar:$foo.$bar.$baz"'}          | ${{ depName: 'foo:bar', currentValue: '1.2.3', skipReason: 'contains-variable' }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:$baz"'}                    | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'foo.bar = "1.2.3"'}               | ${'"foo:bar:$foo.bar"'}                | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'foo.bar' }}
        ${'foo = "1.2.3"'}                   | ${'"foo:bar_$foo:4.5.6"'}              | ${{ depName: 'foo:bar_1.2.3', managerData: { fileReplacePosition: 28 } }}
        ${'foo = "bar"'}                     | ${'"foo:${foo}1:1"'}                   | ${{ depName: 'foo:bar1', currentValue: '1', managerData: { fileReplacePosition: 25 } }}
        ${'bar = "bar:1.2.3"'}               | ${'"foo:$bar"'}                        | ${{ depName: 'foo:bar', currentValue: '1.2.3', skipReason: 'contains-variable' }}
        ${'baz = "1.2.3"'}                   | ${'foobar = "foo:bar:$baz"'}           | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'foo = "${bar}"; baz = "1.2.3"'}   | ${'"foo:bar:${baz}"'}                  | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:${ext[\'baz\']}"'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:${ext.baz}"'}              | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:${project.ext[\'baz\']}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${property("c")}"'}      | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'c' }}
        ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${properties["c"]}"'}    | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'c' }}
      `('$def | $str', ({ def, str, output }) => {
        const { deps } = parseGradle([def, str].join('\n'));
        expect(deps).toMatchObject([output].filter(is.truthy));
      });
    });

    describe('concatenated dependency strings', () => {
      it.each`
        def                                  | str                               | output
        ${''}                                | ${'"foo:bar" + ":1.2.3"'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 15 } }}
        ${''}                                | ${'"foo:bar:" + "1.2.3"'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 15 } }}
        ${''}                                | ${'"foo:bar:" + "1.2.3@zip"'}     | ${{ depName: 'foo:bar', currentValue: '1.2.3', dataType: 'zip', managerData: { fileReplacePosition: 15 } }}
        ${''}                                | ${'"foo:" + "bar:1.2.3"'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 15 } }}
        ${''}                                | ${'"foo:bar:1." + "2.3"'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', skipReason: 'contains-variable' }}
        ${''}                                | ${'"foo:bar1.2.3:" + "1.2.3"'}    | ${{ depName: 'foo:bar1.2.3', currentValue: '1.2.3', managerData: { fileReplacePosition: 20 } }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:" + baz'}             | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 7 } }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:" + property("baz")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 7 } }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:" + baz + "456"'}     | ${{ depName: 'foo:bar', currentValue: '1.2.3456', skipReason: 'contains-variable' }}
        ${'baz = "1.2.3"'}                   | ${'"foo:bar:" + baz + "@zip"'}    | ${{ depName: 'foo:bar', currentValue: '1.2.3', dataType: 'zip', managerData: { fileReplacePosition: 7 } }}
        ${'foo.bar = "bar:"; baz = "1.2.3"'} | ${'"foo:" + foo.bar + "${baz}"'}  | ${{ depName: 'foo:bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 25 } }}
        ${'foo.bar = "bar"; baz = "1.2.3"'}  | ${'"foo:bar_${foo.bar}:" + baz'}  | ${{ depName: 'foo:bar_bar', currentValue: '1.2.3', managerData: { fileReplacePosition: 24 } }}
      `('$def | $str', ({ def, str, output }) => {
        const { deps } = parseGradle([def, str].join('\n'));
        expect(deps).toMatchObject([output]);
      });
    });

    describe('property accessors', () => {
      it.each`
        accessor
        ${'property'}
        ${'getProperty'}
        ${'ext.getProperty'}
        ${'extra.get'}
        ${'project.property'}
        ${'project.getProperty'}
        ${'project.ext.getProperty'}
        ${'project.ext.get'}
        ${'project.extra.get'}
        ${'rootProject.property'}
        ${'rootProject.getProperty'}
        ${'rootProject.ext.getProperty'}
        ${'rootProject.extra.get'}
      `('$accessor', ({ accessor }) => {
        const input = `
          baz = "1.2.3"
          api("foo:bar:$\{${String(accessor)}("baz") as String}")
        `;
        const { deps } = parseGradle(input);
        expect(deps).toMatchObject([
          {
            depName: 'foo:bar',
            currentValue: '1.2.3',
            sharedVariableName: 'baz',
          },
        ]);
      });
    });

    describe('kotlin() short notation dependencies', () => {
      const output = {
        depName: 'foo',
        packageName: 'org.jetbrains.kotlin:kotlin-foo',
        currentValue: '1.2.3',
      };

      it.each`
        def                | str                                   | output
        ${''}              | ${'kotlin("foo", "1.2.3")'}           | ${output}
        ${''}              | ${'kotlin("foo", version = "1.2.3")'} | ${output}
        ${'some = "foo"'}  | ${'kotlin(some, version = "1.2.3")'}  | ${output}
        ${'some = "foo"'}  | ${'kotlin("${some}", "1.2.3")'}       | ${output}
        ${'baz = "1.2.3"'} | ${'kotlin("foo", baz)'}               | ${{ ...output, sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'} | ${'kotlin("foo", version = baz)'}     | ${output}
        ${'baz = "1.2.3"'} | ${'kotlin("foo", property("baz"))'}   | ${output}
        ${'baz = "1.2.3"'} | ${'kotlin("foo", "${baz}456")'}       | ${{ skipReason: 'unspecified-version' }}
        ${'baz = "1.2.3"'} | ${'kotlin("foo", baz + "456")'}       | ${{ skipReason: 'unspecified-version' }}
        ${''}              | ${'kotlin("foo", some)'}              | ${null}
        ${''}              | ${'kotlin(["foo", "1.2.3"])'}         | ${null}
        ${''}              | ${'kotlin("foo", "1.2.3", "4.5.6")'}  | ${null}
        ${''}              | ${'kotlin("foo", "1.2.3@@@")'}        | ${null}
      `('$def | $str', ({ def, str, output }) => {
        const { deps } = parseGradle([def, str].join('\n'));
        expect(deps).toMatchObject([output].filter(is.truthy));
      });
    });

    describe('map notation dependencies', () => {
      it.each`
        def                | str                                                                               | output
        ${''}              | ${'group: "foo", name: "bar", version: "1.2.3"'}                                  | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${''}              | ${'group: "foo", name: "bar", version: baz'}                                      | ${null}
        ${''}              | ${'group: "foo", name: "bar", version: "1.2.3@@@"'}                               | ${null}
        ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: baz'}                                      | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'some = "foo"'}  | ${'group: property("some"), name: property("some"), version: "1.2.3"'}            | ${{ depName: 'foo:foo', currentValue: '1.2.3' }}
        ${'some = "foo"'}  | ${'group: some, name: some, version: "1.2.3"'}                                    | ${{ depName: 'foo:foo', currentValue: '1.2.3' }}
        ${'some = "foo"'}  | ${'group: "${some}", name: "${some}", version: "1.2.3"'}                          | ${{ depName: 'foo:foo', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: "${baz}"'}                                 | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: "${baz}456"'}                              | ${{ depName: 'foo:bar', skipReason: 'unspecified-version' }}
        ${''}              | ${'(group: "foo", name: "bar", version: "1.2.3", classifier: "sources")'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${''}              | ${'(group: "foo", name: "bar", version: "1.2.3") {exclude module: "spring-jcl"}'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${''}              | ${"implementation platform(group: 'foo', name: 'bar', version: '1.2.3')"}         | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${''}              | ${'(group = "foo", name = "bar", version = "1.2.3")'}                             | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = baz)'}                                 | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'some = "foo"'}  | ${'(group = some, name = some, version = "1.2.3")'}                               | ${{ depName: 'foo:foo', currentValue: '1.2.3' }}
        ${'some = "foo"'}  | ${'(group = "${some}", name = "${some}", version = "1.2.3")'}                     | ${{ depName: 'foo:foo', currentValue: '1.2.3' }}
        ${'some = "foo"'}  | ${'(group = "${some}" + some, name = some + "bar" + some, version = "1.2.3")'}    | ${{ depName: 'foofoo:foobarfoo', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = "${baz}")'}                            | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = "${baz}456")'}                         | ${{ depName: 'foo:bar', currentValue: '1.2.3456', skipReason: 'unspecified-version' }}
        ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = baz + "456")'}                         | ${{ depName: 'foo:bar', currentValue: '1.2.3456', skipReason: 'unspecified-version' }}
        ${''}              | ${'(group = "foo", name = "bar", version = "1.2.3", changing: true)'}             | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      `('$def | $str', ({ def, str, output }) => {
        const { deps } = parseGradle([def, str].join('\n'));
        expect(deps).toMatchObject([output].filter(is.truthy));
      });
    });

    describe('dependencySet dependencies', () => {
      it('simple dependencySet', () => {
        const input = codeBlock`
          ext.activemq_version = "5.8.0"
          dependencySet(group: 'org.apache.activemq', version: activemq_version) {
            entry 'activemq-broker'
            entry('activemq-kahadb-store') {
              exclude group: "org.springframework", name: "spring-context"
            }
            entry 'activemq-stomp'
          }
        `;

        const { deps } = parseGradle(input);
        expect(deps).toMatchObject([
          {
            depName: 'org.apache.activemq:activemq-broker',
            currentValue: '5.8.0',
            sharedVariableName: 'activemq_version',
          },
          {
            depName: 'org.apache.activemq:activemq-kahadb-store',
            currentValue: '5.8.0',
            sharedVariableName: 'activemq_version',
          },
          {
            depName: 'org.apache.activemq:activemq-stomp',
            currentValue: '5.8.0',
            sharedVariableName: 'activemq_version',
          },
        ]);
      });

      describe('dependencySet variants', () => {
        const validOutput = [
          {
            depName: 'foo:bar1',
            currentValue: '1.2.3',
            sharedVariableName: 'foo:1.2.3',
          },
          {
            depName: 'foo:bar2',
            currentValue: '1.2.3',
            sharedVariableName: 'foo:1.2.3',
          },
        ];
        const validOutput1 = validOutput.map((dep) => {
          return { ...dep, sharedVariableName: 'baz' };
        });

        it.each`
          def                               | str                                                                                                 | output
          ${''}                             | ${'dependencySet([group: "foo", version: "1.2.3"]) { entry "bar1" }'}                               | ${{}}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3", group: "foo", version: "1.2.3") { entry "bar1" }'} | ${{}}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3") { { entry "bar1" } }'}                             | ${{}}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3") { entry(["bar1"]) }'}                              | ${{}}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3") { entry("bar", "baz") }'}                          | ${{}}
          ${''}                             | ${'dependencySet(group: "${nonexistingvar}", version: "1.2.3") { entry "bar1"; entry "bar2" }'}     | ${{}}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3") { entry "bar1"; entry "bar2" }'}                   | ${validOutput}
          ${''}                             | ${'dependencySet(group: "foo", version: "1.2.3") { entry "bar1"; entry ("bar2") }'}                 | ${validOutput}
          ${'baz = "1.2.3"'}                | ${'dependencySet(group: "foo", version: baz) { entry "bar1"; entry ("bar2") }'}                     | ${validOutput1}
          ${'baz = "1.2.3"'}                | ${'dependencySet(group: "foo", version: "${baz}") { entry "bar1"; entry ("bar2") }'}                | ${validOutput1}
          ${'baz = "1.2.3"'}                | ${'dependencySet(group: "foo", version: property("baz")) { entry "bar1"; entry ("bar2") }'}         | ${validOutput1}
          ${'some = "foo"; other = "bar1"'} | ${'dependencySet(group: some, version: "1.2.3") { entry other; entry "bar2" }'}                     | ${validOutput}
          ${'some = "foo"; baz = "1.2.3"'}  | ${'dependencySet(group: some, version: "${baz}456") { entry "bar1"; entry "bar2" }'}                | ${{}}
          ${'some = "foo"; other = "bar1"'} | ${'dependencySet(group: some, version: "1.2.3") { entry(other); entry "bar2" }'}                    | ${validOutput}
          ${'some = "foo"; other = "bar1"'} | ${'dependencySet(group: "${some}", version: "1.2.3") { entry "${other}"; entry "bar2" }'}           | ${validOutput}
          ${'some = "foo"; other = "bar1"'} | ${'dependencySet(group: "${some}", version: "1.2.3") { entry("${other}"); entry "bar2" }'}          | ${validOutput}
          ${''}                             | ${'dependencySet(group = "foo", version = "1.2.3") { entry "bar1"; entry "bar2" }'}                 | ${validOutput}
        `('$def | $str', ({ def, str, output }) => {
          const { deps } = parseGradle([def, str].join('\n'));
          expect(deps).toMatchObject(output);
        });
      });
    });

    describe('plugins', () => {
      it.each`
        def                 | input                                      | output
        ${''}               | ${'id "foo.bar" version "1.2.3"'}          | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id("foo.bar").version("1.2.3")'}        | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id(["foo.bar"]) version "1.2.3"'}       | ${null}
        ${''}               | ${'id("foo", "bar") version "1.2.3"'}      | ${null}
        ${''}               | ${'id "foo".version("1.2.3")'}             | ${null}
        ${''}               | ${'id("foo.bar") version("1.2.3")'}        | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id("foo.bar") version "1.2.3"'}         | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id "foo.bar" version "$baz"'}           | ${{ depName: 'foo.bar', skipReason: 'unspecified-version', currentValue: 'baz' }}
        ${'baz = "1.2.3"'}  | ${'id "foo.bar" version "$baz"'}           | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3', sharedVariableName: 'baz' }}
        ${'baz = "1.2.3"'}  | ${'id("foo.bar") version "$baz"'}          | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id "foo.bar" version "x${ab}cd"'}       | ${{ depName: 'foo.bar', skipReason: 'unspecified-version' }}
        ${''}               | ${'id("foo.bar") version "$baz"'}          | ${{ depName: 'foo.bar', skipReason: 'unspecified-version', currentValue: 'baz' }}
        ${''}               | ${'id("foo.bar") version "x${ab}cd"'}      | ${{ depName: 'foo.bar', skipReason: 'unspecified-version' }}
        ${''}               | ${'id("foo.bar") version "1" + "2.3"'}     | ${{ depName: 'foo.bar', skipReason: 'unspecified-version' }}
        ${''}               | ${'id("foo.bar") version property("qux")'} | ${{ depName: 'foo.bar', skipReason: 'unspecified-version' }}
        ${'baz = "1.2.3"'}  | ${'id("foo.bar") version property("baz")'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'id "foo.bar" version baz'}              | ${{ depName: 'foo.bar', currentValue: 'baz', skipReason: 'unspecified-version' }}
        ${'baz = "1.2.3"'}  | ${'id "foo.bar" version baz'}              | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'}  | ${'id("foo.bar") version baz'}             | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${'baz = "1.2.3"'}  | ${'id("foo.bar").version(baz)'}            | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }}
        ${''}               | ${'kotlin("jvm") version "1.3.71"'}        | ${{ depName: 'org.jetbrains.kotlin.jvm', packageName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71' }}
        ${'baz = "1.3.71"'} | ${'kotlin("jvm") version baz'}             | ${{ depName: 'org.jetbrains.kotlin.jvm', packageName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71', sharedVariableName: 'baz' }}
      `('$def | $input', ({ def, input, output }) => {
        const { deps } = parseGradle([def, input].join('\n'));
        expect(deps).toMatchObject([output].filter(is.truthy));
      });
    });
  });

  describe('registries', () => {
    describe('predefined registries', () => {
      it.each`
        input                                          | output
        ${'mavenCentral()'}                            | ${REGISTRY_URLS.mavenCentral}
        ${'google()'}                                  | ${REGISTRY_URLS.google}
        ${'google { content { includeGroup "foo" } }'} | ${REGISTRY_URLS.google}
        ${'gradlePluginPortal()'}                      | ${REGISTRY_URLS.gradlePluginPortal}
        ${'jcenter()'}                                 | ${REGISTRY_URLS.jcenter}
      `('$input', ({ input, output }) => {
        const { urls } = parseGradle(input);
        expect(urls).toMatchObject([{ registryUrl: output }]);
      });
    });

    describe('custom registries', () => {
      it.each`
        def                         | input                                                            | url
        ${''}                       | ${'maven("")'}                                                   | ${null}
        ${''}                       | ${'maven(["https://foo.bar/baz/qux"])'}                          | ${null}
        ${''}                       | ${'maven("foo", "bar")'}                                         | ${null}
        ${''}                       | ${'maven("https://foo.bar/baz")'}                                | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven("${base}/baz")'}                                        | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven(base)'}                                                 | ${'https://foo.bar'}
        ${''}                       | ${'maven(url = "https://foo.bar/baz")'}                          | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven(url = uri("https://foo.bar/baz"))'}                     | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven(uri("https://foo.bar/baz"))'}                           | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven(uri("${base}/baz"))'}                                   | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven(uri(property("base")))'}                                | ${'https://foo.bar'}
        ${'base="https://foo.bar"'} | ${'maven(uri(base))'}                                            | ${'https://foo.bar'}
        ${'base="https://foo.bar"'} | ${'maven(uri(base + "/baz"))'}                                   | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven(uri(["https://foo.bar/baz"]))'}                         | ${null}
        ${''}                       | ${'maven { ["https://foo.bar/baz"] }'}                           | ${null}
        ${''}                       | ${'maven { url "https://foo.bar/baz" }'}                         | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url base + "/baz" }'}                                 | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url "${base}/baz" }'}                                 | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven { url uri("https://foo.bar/baz") }'}                    | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url uri("${base}/baz") }'}                            | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven { url = "https://foo.bar/baz" }'}                       | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url = "${base}/baz" }'}                               | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url = property("base") }'}                            | ${'https://foo.bar'}
        ${'base="https://foo.bar"'} | ${'maven { url = base }'}                                        | ${'https://foo.bar'}
        ${''}                       | ${'maven { url = uri("https://foo.bar/baz") }'}                  | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url = uri("${base}/baz") }'}                          | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { url = uri(base) }'}                                   | ${'https://foo.bar'}
        ${''}                       | ${'maven { uri(["https://foo.bar/baz"]) }'}                      | ${null}
        ${'base="https://foo.bar"'} | ${'maven { name "baz"\nurl = "${base}/${name}" }'}               | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { name "${base}" + "/baz"\nurl = "${name}" + "/qux" }'} | ${'https://foo.bar/baz/qux'}
        ${'base="https://foo.bar"'} | ${'maven { name = "baz"\nurl = "${base}/${name}" }'}             | ${'https://foo.bar/baz'}
        ${'some="baz"'}             | ${'maven { name = "${some}"\nurl = "https://foo.bar/${name}" }'} | ${'https://foo.bar/baz'}
        ${'some="foo.bar/baz"'}     | ${'maven { name = property("some")\nurl = "https://${name}" }'}  | ${'https://foo.bar/baz'}
        ${'some="baz"'}             | ${'maven { name = some\nurl = "https://foo.bar/${name}" }'}      | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven { setUrl("https://foo.bar/baz") }'}                     | ${'https://foo.bar/baz'}
        ${''}                       | ${'maven { setUrl(uri("https://foo.bar/baz")) }'}                | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { setUrl("${base}/baz") }'}                             | ${'https://foo.bar/baz'}
        ${'base="https://foo.bar"'} | ${'maven { setUrl(project.property("base")) }'}                  | ${'https://foo.bar'}
        ${'base="https://foo.bar"'} | ${'maven { setUrl(base) }'}                                      | ${'https://foo.bar'}
        ${''}                       | ${'maven { setUrl(["https://foo.bar/baz"]) }'}                   | ${null}
        ${''}                       | ${'maven { setUrl("foo", "bar") }'}                              | ${null}
        ${'base="https://foo.bar"'} | ${'publishing { repositories { maven("${base}/baz") } }'}        | ${null}
      `('$def | $input', ({ def, input, url }) => {
        const expected = url ? [{ registryUrl: url }] : [];
        const { urls } = parseGradle([def, input].join('\n'));
        expect(urls).toMatchObject(expected);
      });
    });

    it('pluginManagement', () => {
      const input = codeBlock`
          pluginManagement {
            def fooVersion = "1.2.3"
            repositories {
              mavenLocal()
              maven { url = "https://foo.bar/plugins" }
              gradlePluginPortal()
            }
            plugins {
              id("foo.bar") version "$fooVersion"
            }
          }
          dependencyResolutionManagement {
            repositories {
              maven { url = "https://foo.bar/deps" }
              mavenCentral()
            }
          }
        `;

      const { deps, urls } = parseGradle(input);
      expect(deps).toMatchObject([
        {
          depType: 'plugin',
          depName: 'foo.bar',
          currentValue: '1.2.3',
        },
      ]);
      expect(urls).toMatchObject([
        {
          registryUrl: 'https://foo.bar/plugins',
          scope: 'plugin',
        },
        {
          registryUrl: REGISTRY_URLS.gradlePluginPortal,
          scope: 'plugin',
        },
        {
          registryUrl: 'https://foo.bar/deps',
          scope: 'dep',
        },
        {
          registryUrl: REGISTRY_URLS.mavenCentral,
          scope: 'dep',
        },
      ]);
    });

    describe('content descriptors', () => {
      it('valid combinations', () => {
        const input = codeBlock`
          maven(url = "https://foo.bar/baz") {
            content {
              excludeGroup("baz.qux")
            }
          }
          mavenCentral().content {
            includeGroup("foo.bar")
          }
          maven {
            url = "https://foo.bar/deps"
            content {
              includeGroupAndSubgroups("foo.bar")
            }
          }
          maven {
            url = "https://some.foo"
            content {
              includeModule("foo", "bar")
              excludeModule("baz", "qux")
              includeVersion("foo", "bar", "1.2.3")
              excludeVersion("baz", "qux", "4.5.6")
              includeGroupByRegex("org\\\\.jetbrains\\\\.kotlin.*")
              excludeGroupByRegex(".*google.*")
              includeModuleByRegex(".*foo.*", ".*bar.*")
              excludeModuleByRegex(".*baz.*", ".*qux.*")
              includeVersionByRegex(".*foo.*", ".*bar.*", "1.2.3")
              excludeVersionByRegex(".*baz.*", ".*qux.*", ".*4.5.*")
            }
          }
        `;

        const { urls } = parseGradle(input);
        expect(urls).toStrictEqual([
          {
            registryUrl: 'https://foo.bar/baz',
            registryType: 'regular',
            scope: 'dep',
            content: [
              { mode: 'exclude', matcher: 'simple', groupId: 'baz.qux' },
            ],
          },
          {
            registryUrl: REGISTRY_URLS.mavenCentral,
            registryType: 'regular',
            scope: 'dep',
            content: [
              { mode: 'include', matcher: 'simple', groupId: 'foo.bar' },
            ],
          },
          {
            registryUrl: 'https://foo.bar/deps',
            registryType: 'regular',
            scope: 'dep',
            content: [
              { mode: 'include', matcher: 'subgroup', groupId: 'foo.bar' },
            ],
          },
          {
            registryUrl: 'https://some.foo',
            registryType: 'regular',
            scope: 'dep',
            content: [
              {
                mode: 'include',
                matcher: 'simple',
                groupId: 'foo',
                artifactId: 'bar',
              },
              {
                mode: 'exclude',
                matcher: 'simple',
                groupId: 'baz',
                artifactId: 'qux',
              },
              {
                mode: 'include',
                matcher: 'simple',
                groupId: 'foo',
                artifactId: 'bar',
                version: '1.2.3',
              },
              {
                mode: 'exclude',
                matcher: 'simple',
                groupId: 'baz',
                artifactId: 'qux',
                version: '4.5.6',
              },
              {
                mode: 'include',
                matcher: 'regex',
                groupId: '^org\\.jetbrains\\.kotlin.*$',
              },
              { mode: 'exclude', matcher: 'regex', groupId: '^.*google.*$' },
              {
                mode: 'include',
                matcher: 'regex',
                groupId: '^.*foo.*$',
                artifactId: '^.*bar.*$',
              },
              {
                mode: 'exclude',
                matcher: 'regex',
                groupId: '^.*baz.*$',
                artifactId: '^.*qux.*$',
              },
              {
                mode: 'include',
                matcher: 'regex',
                groupId: '^.*foo.*$',
                artifactId: '^.*bar.*$',
                version: '^1.2.3$',
              },
              {
                mode: 'exclude',
                matcher: 'regex',
                groupId: '^.*baz.*$',
                artifactId: '^.*qux.*$',
                version: '^.*4.5.*$',
              },
            ],
          },
        ]);
      });

      describe('invalid or unsupported regEx patterns', () => {
        it.each`
          fieldName    | pattern
          ${'group'}   | ${'includeGroupByRegex(".*so\\me.invalid.pattern.*")'}
          ${'group'}   | ${'includeModuleByRegex(".*so\\me.invalid.pattern.*", ".*bar.*")'}
          ${'module'}  | ${'includeModuleByRegex(".*foo.*", ".*so\\me.invalid.pattern.*")'}
          ${'module'}  | ${'excludeModuleByRegex(".*baz.*", "(?!(foo|bar).*)")'}
          ${'version'} | ${'includeVersionByRegex(".*foo.*", ".*bar.*", "(?!(foo|bar).*)")'}
          ${'version'} | ${'excludeVersionByRegex(".*baz.*", ".*qux.*", "(?!(foo|bar).*)")'}
        `('$pattern', ({ fieldName, pattern }) => {
          const input = codeBlock`
            mavenCentral {
              content {
                ${pattern}
              }
            }
          `;
          parseGradle(input);
          expect(logger.logger.debug).toHaveBeenCalledWith(
            expect.stringContaining(
              `Skipping content descriptor with unsupported regExp pattern for ${fieldName}`,
            ),
          );
        });
      });
    });

    it('exclusiveContent', () => {
      const input = codeBlock`
        pluginManagement {
          repositories {
            exclusiveContent {
              forRepository {
                maven {
                  name = "some maven registry"
                  setUrl("https://foo.bar.com/repository/public/")
                }
              }
              filter {
                includeGroup("com.foo.bar")
                includeModule("foo", "bar")
              }
            }
            gradlePluginPortal()
          }
        }
        exclusiveContent {
          filter {
            includeGroup("foo")
          }
          forRepository {
            mavenCentral()
          }
        }
      `;

      const { urls } = parseGradle(input);
      expect(urls).toMatchObject([
        {
          registryUrl: 'https://foo.bar.com/repository/public/',
          registryType: 'exclusive',
          scope: 'plugin',
          content: [
            {
              mode: 'include',
              matcher: 'simple',
              groupId: 'com.foo.bar',
            },
            {
              mode: 'include',
              matcher: 'simple',
              groupId: 'foo',
              artifactId: 'bar',
            },
          ],
        },
        {
          registryUrl: REGISTRY_URLS.gradlePluginPortal,
          registryType: 'regular',
          scope: 'plugin',
        },
        {
          registryUrl: REGISTRY_URLS.mavenCentral,
          registryType: 'exclusive',
          scope: 'dep',
          content: [
            {
              mode: 'include',
              matcher: 'simple',
              groupId: 'foo',
            },
          ],
        },
      ]);
    });
  });

  describe('version catalog', () => {
    it.each`
      def                                           | str                                                             | output
      ${''}                                         | ${'library("foo.bar", "foo", "bar").version("1.2.3")'}          | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'}                            | ${'library("foo.bar", "foo", "bar").version(baz)'}              | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'}                            | ${'library("foo.bar", "foo", "bar").version("${baz}")'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'}                            | ${'library("foo.bar", "foo", "bar").version(property("baz"))'}  | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'}                            | ${'library("foo.bar", "foo", "bar").version("${baz}xy")'}       | ${{ depName: 'foo:bar', currentValue: '1.2.3xy', skipReason: 'unspecified-version' }}
      ${'baz = "1.2.3"'}                            | ${'library("foo.bar", "foo", "bar").version(baz + ".45")'}      | ${{ depName: 'foo:bar', currentValue: '1.2.3.45', skipReason: 'unspecified-version' }}
      ${'group = "foo"; artifact = "bar"'}          | ${'library("foo.bar", group, artifact).version("1.2.3")'}       | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"'}                     | ${'library("foo.bar", "${f}", "${b}").version("1.2.3")'}        | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"; v = "1.2.3"'}        | ${'library("foo.bar", property("f"), "${b}").version(v)'}       | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"'}                     | ${'library("foo.bar", "${f}" + f, "${b}").version("1.2.3")'}    | ${{ depName: 'foofoo:bar', currentValue: '1.2.3' }}
      ${'version("baz", "1.2.3")'}                  | ${'library("foo.bar", "foo", "bar").versionRef("baz")'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }}
      ${'library("foo-bar_baz-qux", "foo", "bar")'} | ${'"${libs.foo.bar.baz.qux}:1.2.3"'}                            | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${''}                                         | ${'library(["foo.bar", "foo", "bar"]).version("1.2.3")'}        | ${null}
      ${''}                                         | ${'library("foo", "bar", "baz", "qux").version("1.2.3")'}       | ${null}
      ${''}                                         | ${'library("foo.bar", "foo", "bar").version("1.2.3", "4.5.6")'} | ${null}
      ${''}                                         | ${'library("foo", bar, "baz").version("1.2.3")'}                | ${null}
      ${''}                                         | ${'plugin("foo.bar", "foo")'}                                   | ${null}
      ${''}                                         | ${'plugin("foo.bar", "foo").version("1.2.3")'}                  | ${{ depName: 'foo', currentValue: '1.2.3' }}
      ${''}                                         | ${'alias("foo.bar").to("foo", "bar").version("1.2.3")'}         | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'version("baz", "1.2.3")'}                  | ${'alias("foo.bar").to("foo", "bar").versionRef("baz")'}        | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'version("baz", "1.2.3")'}                  | ${'alias("foo.bar").to("foo", "bar").version("${baz}")'}        | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"; v = "1.2.3"'}        | ${'alias("foo.bar").to(f, b).version(v)'}                       | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"; v = "1.2.3"'}        | ${'alias("foo.bar").to(f + b, b + f).version(v)'}               | ${{ depName: 'foobar:barfoo', currentValue: '1.2.3' }}
      ${'f = "foo"; b = "bar"; v = "1.2.3"'}        | ${'alias("foo.bar").to("${f}", "${b}").version("$v")'}          | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${''}                                         | ${'alias(["foo.bar"]).to("foo", "bar").version("1.2.3")'}       | ${null}
    `('$def | $str', ({ def, str, output }) => {
      const input = [def, str].join('\n');
      const { deps } = parseGradle(input);
      expect(deps).toMatchObject([output].filter(is.truthy));
    });
  });

  describe('heuristic dependency matching', () => {
    it.each`
      input                                                                    | output
      ${'("foo", "bar", "1.2.3")'}                                             | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'("foo", "bar", "1.2.3", "4.5.6")'}                                    | ${null}
      ${'(["foo", "bar", "1.2.3"])'}                                           | ${null}
      ${'someMethod("foo", "bar", "1.2.3")'}                                   | ${{ depName: 'foo:bar', currentValue: '1.2.3' }}
      ${'listOf("foo", "bar", "baz")'}                                         | ${null}
      ${'java { registerFeature(foo) { capability("foo", "bar", "1.2.3") } }'} | ${null}
    `('$input', ({ input, output }) => {
      const { deps } = parseGradle(input);
      expect(deps).toMatchObject([output].filter(is.truthy));
    });

    it('handles 3 independent dependencies mismatched as groupId, artifactId, version', () => {
      const { deps } = parseGradle(
        'someConfig("foo:bar:1.2.3", "foo:baz:4.5.6", "foo:qux:7.8.9")',
      );
      expect(deps).toMatchObject([
        { depName: 'foo:bar', currentValue: '1.2.3' },
        { depName: 'foo:baz', currentValue: '4.5.6' },
        { depName: 'foo:qux', currentValue: '7.8.9' },
      ]);
    });
  });

  describe('calculations', () => {
    it('calculates offset', () => {
      const content = "'foo:bar:1.2.3'";
      const { deps } = parseGradle(content);
      const [res] = deps;
      const idx = content
        // TODO #22198
        .slice(res.managerData!.fileReplacePosition)
        .indexOf('1.2.3');
      expect(idx).toBe(0);
    });

    it('parses fixture from "gradle" manager', () => {
      const content = Fixtures.get('build.gradle.example1');
      const { deps } = parseGradle(content, {}, 'build.gradle');
      const replacementIndices = deps.map(({ managerData, currentValue }) =>
        // TODO #22198
        content.slice(managerData!.fileReplacePosition).indexOf(currentValue!),
      );
      expect(replacementIndices.every((idx) => idx === 0)).toBeTrue();
      expect(deps).toMatchObject([
        {
          currentValue: '1.5.2.RELEASE',
          depName: 'org.springframework.boot:spring-boot-gradle-plugin',
          sharedVariableName: 'springBootVersion',
          managerData: {
            fileReplacePosition: 53,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '1.2.3',
          depName: 'com.github.jengelman.gradle.plugins:shadow',
          managerData: {
            fileReplacePosition: 417,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '0.1',
          depName: 'com.fkorotkov:gradle-libraries-plugin',
          managerData: {
            fileReplacePosition: 481,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '0.2.3',
          depName:
            'gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin',
          managerData: {
            fileReplacePosition: 568,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '3.1.1',
          depName: 'org.apache.openjpa:openjpa',
          managerData: {
            fileReplacePosition: 621,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '0.13.0',
          depName: 'com.gradle.publish:plugin-publish-plugin',
          managerData: {
            fileReplacePosition: 688,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '6.0.9.RELEASE',
          depName: 'org.grails:gorm-hibernate5-spring-boot',
          managerData: {
            fileReplacePosition: 1882,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '6.0.5',
          depName: 'mysql:mysql-connector-java',
          managerData: {
            fileReplacePosition: 1938,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '1.0-groovy-2.4',
          depName: 'org.spockframework:spock-spring',
          managerData: {
            fileReplacePosition: 1996,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '1.3',
          depName: 'org.hamcrest:hamcrest-core',
          managerData: {
            fileReplacePosition: 2101,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '3.1',
          depName: 'cglib:cglib-nodep',
          managerData: {
            fileReplacePosition: 2189,
            packageFile: 'build.gradle',
          },
        },
        {
          currentValue: '3.1.1',
          depName: 'org.apache.openjpa:openjpa',
          managerData: {
            fileReplacePosition: 2295,
            packageFile: 'build.gradle',
          },
        },
      ]);
    });
  });

  describe('gradle.properties', () => {
    it.each`
      input            | key          | value    | fileReplacePosition
      ${'foo=bar'}     | ${'foo'}     | ${'bar'} | ${4}
      ${' foo = bar '} | ${'foo'}     | ${'bar'} | ${7}
      ${'foo.bar=baz'} | ${'foo.bar'} | ${'baz'} | ${8}
      ${'foo.bar:baz'} | ${'foo.bar'} | ${'baz'} | ${8}
      ${'foo.bar baz'} | ${'foo.bar'} | ${'baz'} | ${8}
    `('$input', ({ input, key, value, fileReplacePosition }) => {
      expect(parseProps(input)).toMatchObject({
        vars: { [key]: { key, value, fileReplacePosition } },
      });
    });

    it('handles multi-line file', () => {
      expect(parseProps('foo=foo\nbar=bar')).toMatchObject({
        vars: {
          foo: { key: 'foo', value: 'foo', fileReplacePosition: 4 },
          bar: { key: 'bar', value: 'bar', fileReplacePosition: 12 },
        },
        deps: [],
      });
    });

    it('attaches packageFile', () => {
      expect(
        parseProps('foo = bar', 'foo/bar/gradle.properties'),
      ).toMatchObject({
        vars: { foo: { packageFile: 'foo/bar/gradle.properties' } },
      });
    });

    it('parses dependencies', () => {
      const res = parseProps('dep = foo:bar:1.2.3');

      expect(res).toMatchObject({
        deps: [
          {
            currentValue: '1.2.3',
            depName: 'foo:bar',
            managerData: { fileReplacePosition: 14 },
          },
        ],
      });
    });
  });

  describe('apply from', () => {
    const key = 'version';
    const value = '1.2.3';
    const validOutput = {
      version: {
        key,
        value,
        fileReplacePosition: 11,
        packageFile: 'foo/bar.gradle',
      },
    };

    const fileContents = {
      'foo/bar.gradle': key + ' = "' + value + '"',
    };

    it.each`
      def                        | input                                                     | output
      ${''}                      | ${'apply from: ""'}                                       | ${{}}
      ${''}                      | ${'apply from: "foo/invalid.gradle"'}                     | ${{}}
      ${''}                      | ${'apply from: "${base}"'}                                | ${{}}
      ${''}                      | ${'apply from: "foo/invalid.non-gradle"'}                 | ${{}}
      ${''}                      | ${'apply from: "https://someurl.com/file.gradle"'}        | ${{}}
      ${''}                      | ${'apply from: "foo/bar.gradle"'}                         | ${validOutput}
      ${'base="foo"'}            | ${'apply from: "${base}/bar.gradle"'}                     | ${validOutput}
      ${'path="foo/bar.gradle"'} | ${'apply from: path'}                                     | ${validOutput}
      ${'path="bar.gradle"'}     | ${'apply from: "foo/" + path'}                            | ${validOutput}
      ${'path="foo/bar.gradle"'} | ${'apply from: property("path")'}                         | ${validOutput}
      ${''}                      | ${'apply from: file("foo/bar.gradle")'}                   | ${validOutput}
      ${'base="foo"'}            | ${'apply from: file("${base}/bar.gradle")'}               | ${validOutput}
      ${''}                      | ${'apply from: project.file("foo/bar.gradle")'}           | ${validOutput}
      ${''}                      | ${'apply from: rootProject.file("foo/bar.gradle")'}       | ${validOutput}
      ${''}                      | ${'apply from: new File("foo/bar.gradle")'}               | ${validOutput}
      ${'base="foo"'}            | ${'apply from: new File("${base}/bar.gradle")'}           | ${validOutput}
      ${''}                      | ${'apply from: new File("foo", "bar.gradle")'}            | ${validOutput}
      ${'base="foo"'}            | ${'apply from: new File(base, "bar.gradle")'}             | ${validOutput}
      ${'base="foo"'}            | ${'apply from: new File("${base}", "bar.gradle")'}        | ${validOutput}
      ${'path="bar.gradle"'}     | ${'apply from: new File("foo", "${path}")'}               | ${validOutput}
      ${'e="o"; b="gradle" '}    | ${'apply from: new File("f" + e + e, "bar." + b)'}        | ${validOutput}
      ${'a="bar"; b="gradle"'}   | ${'apply from: new File("foo", a + "." + "${b}")'}        | ${validOutput}
      ${'path="bar.gradle"'}     | ${'apply from: new File("foo", property("path"))'}        | ${validOutput}
      ${'base="foo"'}            | ${'apply from: new File(property("base"), "bar.gradle")'} | ${validOutput}
      ${''}                      | ${'apply(from = "foo/bar.gradle"))'}                      | ${validOutput}
      ${'base="foo"'}            | ${'apply(from = "${base}/bar.gradle"))'}                  | ${validOutput}
      ${''}                      | ${'apply(from = File("foo/bar.gradle"))'}                 | ${validOutput}
      ${''}                      | ${'apply(from = File("foo", "bar", "baz"))'}              | ${{}}
      ${''}                      | ${'apply(from = File(["${somedir}/foo.gradle"]))'}        | ${{}}
      ${'base="foo"'}            | ${'apply(from = File("${base}/bar.gradle"))'}             | ${validOutput}
      ${''}                      | ${'apply(from = File("foo", "bar.gradle"))'}              | ${validOutput}
      ${'base="foo"'}            | ${'apply(from = File(base, "bar.gradle"))'}               | ${validOutput}
      ${'base="foo"'}            | ${'apply(from = File("${base}", "bar.gradle"))'}          | ${validOutput}
    `('$def | $input', ({ def, input, output }) => {
      mockFs(fileContents);
      const { vars } = parseGradle(
        [def, input].join('\n'),
        {},
        '',
        fileContents,
      );
      expect(vars).toMatchObject(output);
    });

    it('recursion check', () => {
      const { vars } = parseGradle(
        'apply from: "foo/bar.gradle"',
        {},
        '',
        fileContents,
        3,
      );
      expect(logger.logger.debug).toHaveBeenCalledWith(
        'Max recursion depth reached in script file: foo/bar.gradle',
      );
      expect(vars).toBeEmpty();
    });
  });

  describe('implicit gradle plugins', () => {
    it.each`
      def                | input                                                            | output
      ${'baz = "1.2.3"'} | ${'checkstyle { toolVersion = "${baz}" }'}                       | ${{ depName: 'checkstyle', packageName: GRADLE_PLUGINS.checkstyle[1], currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'} | ${'checkstyle { toolVersion "${baz}" }'}                         | ${{ depName: 'checkstyle', packageName: GRADLE_PLUGINS.checkstyle[1], currentValue: '1.2.3' }}
      ${''}              | ${'codenarc { toolVersion = "1.2.3" }'}                          | ${{ depName: 'codenarc', packageName: GRADLE_PLUGINS.codenarc[1], currentValue: '1.2.3' }}
      ${''}              | ${'detekt { toolVersion = "1.2.3" }'}                            | ${{ depName: 'detekt', packageName: GRADLE_PLUGINS.detekt[1], currentValue: '1.2.3' }}
      ${''}              | ${'findbugs { toolVersion = "1.2.3" }'}                          | ${{ depName: 'findbugs', packageName: GRADLE_PLUGINS.findbugs[1], currentValue: '1.2.3' }}
      ${''}              | ${'googleJavaFormat { toolVersion = "1.2.3" }'}                  | ${{ depName: 'googleJavaFormat', packageName: GRADLE_PLUGINS.googleJavaFormat[1], currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = baz }'}                                | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS.jacoco[1], currentValue: '1.2.3', sharedVariableName: 'baz' }}
      ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = property("baz") }'}                    | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS.jacoco[1], currentValue: '1.2.3' }}
      ${''}              | ${'lombok { version = "1.2.3" }'}                                | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS.lombok[1], currentValue: '1.2.3' }}
      ${''}              | ${'lombok { version.set("1.2.3") }'}                             | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS.lombok[1], currentValue: '1.2.3' }}
      ${''}              | ${'lombok { version.value("1.2.3") }'}                           | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS.lombok[1], currentValue: '1.2.3' }}
      ${''}              | ${'pmd { toolVersion = "1.2.3" }'}                               | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS.pmd[1], currentValue: '1.2.3' }}
      ${''}              | ${'pmd { toolVersion.set("1.2.3") }'}                            | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS.pmd[1], currentValue: '1.2.3' }}
      ${''}              | ${'pmd { toolVersion.value("1.2.3") }'}                          | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS.pmd[1], currentValue: '1.2.3' }}
      ${''}              | ${'pmd { foo = "bar"; toolVersion = "1.2.3" }'}                  | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS.pmd[1], currentValue: '1.2.3' }}
      ${''}              | ${'spotbugs { toolVersion = "1.2.3" }'}                          | ${{ depName: 'spotbugs', packageName: GRADLE_PLUGINS.spotbugs[1], currentValue: '1.2.3' }}
      ${''}              | ${'pmd { toolVersion = "@@@" }'}                                 | ${null}
      ${''}              | ${'pmd { toolVersion = "${baz}" }'}                              | ${null}
      ${'baz = "1.2.3"'} | ${'pmd { toolVersion = "${baz}.456" }'}                          | ${{ depName: 'pmd', currentValue: '1.2.3.456', skipReason: 'unspecified-version' }}
      ${'baz = "1.2.3"'} | ${'pmd { toolVersion = baz + ".456" }'}                          | ${{ depName: 'pmd', currentValue: '1.2.3.456', skipReason: 'unspecified-version' }}
      ${''}              | ${'pmd { [toolVersion = "6.36.0"] }'}                            | ${null}
      ${''}              | ${'unknown { toolVersion = "1.2.3" }'}                           | ${null}
      ${''}              | ${'composeOptions { kotlinCompilerExtensionVersion = "1.2.3" }'} | ${{ depName: 'composeOptions', packageName: GRADLE_PLUGINS.composeOptions[1], currentValue: '1.2.3' }}
      ${''}              | ${'jmh { jmhVersion = "1.2.3" }'}                                | ${{ depName: 'jmh', packageName: GRADLE_PLUGINS.jmh[1], currentValue: '1.2.3' }}
      ${''}              | ${'micronaut { version = "1.2.3" }'}                             | ${{ depName: 'micronaut', packageName: GRADLE_PLUGINS.micronaut[1], currentValue: '1.2.3' }}
    `('$def | $input', ({ def, input, output }) => {
      const { deps } = parseGradle([def, input].join('\n'));
      expect(deps).toMatchObject([output].filter(is.truthy));
    });
  });

  describe('implicit gradle test suite dependencies', () => {
    it.each`
      def                | input                                                   | output
      ${''}              | ${'testing.suites.test { useJunit("1.2.3") } } }'}      | ${{ depName: 'useJunit', packageName: GRADLE_TEST_SUITES.useJunit, currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'} | ${'testing.suites.test { useJUnitJupiter(baz) } } }'}   | ${{ depName: 'useJUnitJupiter', packageName: GRADLE_TEST_SUITES.useJUnitJupiter, currentValue: '1.2.3' }}
      ${''}              | ${'testing.suites.test { useKotlinTest("1.2.3") } } }'} | ${{ depName: 'useKotlinTest', packageName: GRADLE_TEST_SUITES.useKotlinTest, currentValue: '1.2.3' }}
      ${'baz = "1.2.3"'} | ${'testing { suites { test { useSpock(baz) } } }'}      | ${{ depName: 'useSpock', packageName: GRADLE_TEST_SUITES.useSpock, currentValue: '1.2.3' }}
      ${''}              | ${'testing.suites.test { useSpock("1.2.3") } } }'}      | ${{ depName: 'useSpock', packageName: GRADLE_TEST_SUITES.useSpock, currentValue: '1.2.3' }}
      ${''}              | ${'testing.suites.test { useTestNG("1.2.3") } } }'}     | ${{ depName: 'useTestNG', packageName: GRADLE_TEST_SUITES.useTestNG, currentValue: '1.2.3' }}
    `('$def | $input', ({ def, input, output }) => {
      const { deps } = parseGradle([def, input].join('\n'));
      expect(deps).toMatchObject([output]);
    });
  });

  describe('Kotlin object notation', () => {
    it('simple objects', () => {
      const input = codeBlock`
        object Versions {
          const val baz = "1.2.3"
        }

        object Libraries {
          val deps = mapOf("api" to "org.slf4j:slf4j-api:\${Versions.baz}")
          val deps2 = listOf(
            "androidx.appcompat:appcompat:4.5.6",
            "androidx.core:core-ktx:\${Versions.baz}",
            listOf("androidx.webkit:webkit:\${Versions.baz}")
          )
          val dep: String = "foo:bar:" + Versions.baz
        }
      `;

      const res = parseKotlinSource(input);
      expect(res).toMatchObject({
        vars: {
          'Versions.baz': {
            key: 'Versions.baz',
            value: '1.2.3',
          },
        },
        deps: [
          {
            depName: 'org.slf4j:slf4j-api',
            sharedVariableName: 'Versions.baz',
            currentValue: '1.2.3',
          },
          {
            depName: 'androidx.appcompat:appcompat',
            currentValue: '4.5.6',
          },
          {
            depName: 'androidx.core:core-ktx',
            sharedVariableName: 'Versions.baz',
            currentValue: '1.2.3',
          },
          {
            depName: 'androidx.webkit:webkit',
            sharedVariableName: 'Versions.baz',
            currentValue: '1.2.3',
          },
          {
            depName: 'foo:bar',
            sharedVariableName: 'Versions.baz',
            currentValue: '1.2.3',
          },
        ],
      });
    });

    it('nested objects', () => {
      const input = codeBlock`
        object Deps {
          const val kotlinVersion = "1.5.31"

          object Kotlin {
            val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:\${Deps.kotlinVersion}"
          }

          object Test {
            private const val version = "1.3.0-rc01"
            const val core = "androidx.test:core:\${Deps.Test.version}"

            object Espresso {
              object Release {
                private const val version = "3.3.0-rc01"
                const val espressoCore = "androidx.test.espresso:espresso-core:$version"
              }
            }

            object Androidx {
              const val coreKtx = "androidx.test:core-ktx:$version"
            }
          }
        }
      `;

      const res = parseKotlinSource(input);
      expect(res).toMatchObject({
        vars: {
          'Deps.kotlinVersion': {
            key: 'Deps.kotlinVersion',
            value: '1.5.31',
          },
          'Deps.Test.version': {
            key: 'Deps.Test.version',
            value: '1.3.0-rc01',
          },
          'Deps.Test.Espresso.Release.version': {
            key: 'Deps.Test.Espresso.Release.version',
            value: '3.3.0-rc01',
          },
        },
        deps: [
          {
            depName: 'org.jetbrains.kotlin:kotlin-stdlib-jdk7',
            currentValue: '1.5.31',
            sharedVariableName: 'Deps.kotlinVersion',
          },
          {
            depName: 'androidx.test:core',
            currentValue: '1.3.0-rc01',
            sharedVariableName: 'Deps.Test.version',
          },
          {
            depName: 'androidx.test.espresso:espresso-core',
            currentValue: '3.3.0-rc01',
            sharedVariableName: 'Deps.Test.Espresso.Release.version',
          },
          {
            depName: 'androidx.test:core-ktx',
            currentValue: '1.3.0-rc01',
            sharedVariableName: 'Deps.Test.version',
          },
        ],
      });
    });

    it('imported objects', () => {
      const input = codeBlock`
        object ModuleConfiguration {
          object Build {
            object Database {
              const val h2Version = "2.0.206"
            }
          }
        }
      `;

      const gradleKtsInput = codeBlock`
        import ModuleConfiguration.Build.Database
        dependencies {
          runtimeOnly("com.h2database:h2:\${Database.h2Version}")
        }
      `;

      const { vars } = parseKotlinSource(input);
      const res = parseGradle(gradleKtsInput, vars);
      expect(res).toMatchObject({
        vars: {
          'ModuleConfiguration.Build.Database.h2Version': {
            key: 'ModuleConfiguration.Build.Database.h2Version',
            value: '2.0.206',
          },
        },
        deps: [
          {
            depName: 'com.h2database:h2',
            currentValue: '2.0.206',
            sharedVariableName: 'ModuleConfiguration.Build.Database.h2Version',
          },
        ],
      });
    });
  });

  describe('Java language version', () => {
    it.each`
      input                                                                            | output
      ${'java { toolchain { languageVersion = JavaLanguageVersion.of(22) } }'}         | ${'22'}
      ${'java { toolchain.languageVersion.set(JavaLanguageVersion.of(16)) }'}          | ${'16'}
      ${'java.toolchain { languageVersion.set(JavaLanguageVersion.of(17)) }'}          | ${'17'}
      ${'java.toolchain.languageVersion = JavaLanguageVersion.of(21)'}                 | ${'21'}
      ${'kotlin { jvmToolchain { languageVersion = JavaLanguageVersion.of(17) } }'}    | ${'17'}
      ${'kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }'} | ${'17'}
      ${'kotlin.jvmToolchain { languageVersion.set(JavaLanguageVersion.of(8)) }'}      | ${'8'}
      ${'kotlin { jvmToolchain(11) }'}                                                 | ${'11'}
      ${'kotlin.jvmToolchain(16)'}                                                     | ${'16'}
      ${'dependencies { implementation "com.google.protobuf:protobuf-java:2.17.0" }'}  | ${null}
    `('$input', ({ input, output }) => {
      const javaLanguageVersion = parseJavaToolchainVersion(input);
      expect(javaLanguageVersion).toBe(output);
    });
  });
});
