diff --git a/lib/modules/manager/gradle/parser.spec.ts b/lib/modules/manager/gradle/parser.spec.ts index 037799c81c6e3b..faa099f3e4345b 100644 --- a/lib/modules/manager/gradle/parser.spec.ts +++ b/lib/modules/manager/gradle/parser.spec.ts @@ -8,7 +8,11 @@ import { parseKotlinSource, parseProps, } from './parser'; -import { GRADLE_PLUGINS, REGISTRY_URLS } from './parser/common'; +import { + GRADLE_PLUGINS, + GRADLE_TEST_SUITES, + REGISTRY_URLS, +} from './parser/common'; jest.mock('../../../util/fs'); @@ -1068,6 +1072,21 @@ describe('modules/manager/gradle/parser', () => { }); }); + 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` diff --git a/lib/modules/manager/gradle/parser/common.ts b/lib/modules/manager/gradle/parser/common.ts index 2c201804407bce..0a9c0c8082edf2 100644 --- a/lib/modules/manager/gradle/parser/common.ts +++ b/lib/modules/manager/gradle/parser/common.ts @@ -37,6 +37,14 @@ export const GRADLE_PLUGINS = { spotbugs: ['toolVersion', 'com.github.spotbugs:spotbugs'], }; +export const GRADLE_TEST_SUITES = { + useJunit: 'junit:junit', + useJUnitJupiter: 'org.junit.jupiter:junit-jupiter', + useKotlinTest: 'org.jetbrains.kotlin:kotlin-test-junit', + useSpock: 'org.spockframework:spock-core', + useTestNG: 'org.testng:testng', +}; + export function storeVarToken(ctx: Ctx, node: lexer.Token): Ctx { ctx.varTokens.push(node); return ctx; diff --git a/lib/modules/manager/gradle/parser/dependencies.ts b/lib/modules/manager/gradle/parser/dependencies.ts index e9fb2b1265fe66..edf48a3fe323e0 100644 --- a/lib/modules/manager/gradle/parser/dependencies.ts +++ b/lib/modules/manager/gradle/parser/dependencies.ts @@ -3,6 +3,7 @@ import { regEx } from '../../../../util/regex'; import type { Ctx } from '../types'; import { GRADLE_PLUGINS, + GRADLE_TEST_SUITES, cleanupTempVars, qArtifactId, qDotOrBraceExpr, @@ -15,7 +16,7 @@ import { } from './common'; import { handleDepString, - handleImplicitGradlePlugin, + handleImplicitDep, handleKotlinShortNotationDep, handleLongFormDep, } from './handlers'; @@ -155,10 +156,10 @@ export const qLongFormDep = q // pmd { toolVersion = "1.2.3" } const qImplicitGradlePlugin = q .alt( - ...Object.keys(GRADLE_PLUGINS).map((pluginName) => + ...Object.keys(GRADLE_PLUGINS).map((implicitDepName) => q - .sym(pluginName, storeVarToken) - .handler((ctx) => storeInTokenMap(ctx, 'pluginName')) + .sym(implicitDepName, storeVarToken) + .handler((ctx) => storeInTokenMap(ctx, 'implicitDepName')) .tree({ type: 'wrapped-tree', maxDepth: 1, @@ -167,7 +168,7 @@ const qImplicitGradlePlugin = q endsWith: '}', search: q .sym( - GRADLE_PLUGINS[pluginName as keyof typeof GRADLE_PLUGINS][0], + GRADLE_PLUGINS[implicitDepName as keyof typeof GRADLE_PLUGINS][0], ) .alt( // toolVersion = "1.2.3" @@ -186,7 +187,34 @@ const qImplicitGradlePlugin = q }), ), ) - .handler(handleImplicitGradlePlugin) + .handler(handleImplicitDep) + .handler(cleanupTempVars); + +// testing { suites { test { useSpock("1.2.3") } } } +const qImplicitTestSuites = qDotOrBraceExpr( + 'testing', + qDotOrBraceExpr( + 'suites', + qDotOrBraceExpr( + 'test', + q + .sym( + regEx(`^(?:${Object.keys(GRADLE_TEST_SUITES).join('|')})$`), + storeVarToken, + ) + .handler((ctx) => storeInTokenMap(ctx, 'implicitDepName')) + .tree({ + type: 'wrapped-tree', + maxDepth: 1, + maxMatches: 1, + startsWith: '(', + endsWith: ')', + search: q.begin().join(qVersion).end(), + }), + ), + ), +) + .handler(handleImplicitDep) .handler(cleanupTempVars); export const qDependencies = q.alt( @@ -196,6 +224,7 @@ export const qDependencies = q.alt( qKotlinShortNotationDependencies, qKotlinMapNotationDependencies, qImplicitGradlePlugin, + qImplicitTestSuites, // avoid heuristic matching of gradle feature variant capabilities qDotOrBraceExpr('java', q.sym('registerFeature').tree()), ); diff --git a/lib/modules/manager/gradle/parser/handlers.ts b/lib/modules/manager/gradle/parser/handlers.ts index 5aaba92f773cf7..5d983abbd07f31 100644 --- a/lib/modules/manager/gradle/parser/handlers.ts +++ b/lib/modules/manager/gradle/parser/handlers.ts @@ -9,6 +9,7 @@ import type { Ctx, GradleManagerData } from '../types'; import { isDependencyString, parseDependencyString } from '../utils'; import { GRADLE_PLUGINS, + GRADLE_TEST_SUITES, REGISTRY_URLS, findVariable, interpolateString, @@ -401,22 +402,25 @@ export function handleApplyFrom(ctx: Ctx): Ctx { return ctx; } -export function handleImplicitGradlePlugin(ctx: Ctx): Ctx { - const pluginName = loadFromTokenMap(ctx, 'pluginName')[0].value; +export function handleImplicitDep(ctx: Ctx): Ctx { + const implicitDepName = loadFromTokenMap(ctx, 'implicitDepName')[0].value; const versionTokens = loadFromTokenMap(ctx, 'version'); const versionValue = interpolateString(versionTokens, ctx); if (!versionValue) { return ctx; } - const groupIdArtifactId = - GRADLE_PLUGINS[pluginName as keyof typeof GRADLE_PLUGINS][1]; + const isImplicitGradlePlugin = implicitDepName in GRADLE_PLUGINS; + const groupIdArtifactId = isImplicitGradlePlugin + ? GRADLE_PLUGINS[implicitDepName as keyof typeof GRADLE_PLUGINS][1] + : GRADLE_TEST_SUITES[implicitDepName as keyof typeof GRADLE_TEST_SUITES]; + const dep = parseDependencyString(`${groupIdArtifactId}:${versionValue}`); if (!dep) { return ctx; } - dep.depName = pluginName; + dep.depName = implicitDepName; dep.packageName = groupIdArtifactId; dep.managerData = { fileReplacePosition: versionTokens[0].offset,