diff --git a/lib/modules/manager/gradle/parser.spec.ts b/lib/modules/manager/gradle/parser.spec.ts index dfc1945fb37598714fc02af4bba3d16ec79c048a..4734a6cbca7e0c59495f50cb8979f550deb9400d 100644 --- a/lib/modules/manager/gradle/parser.spec.ts +++ b/lib/modules/manager/gradle/parser.spec.ts @@ -2,7 +2,7 @@ import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../test/fixtures'; import { fs, logger } from '../../../../test/util'; import { parseGradle, parseProps } from './parser'; -import { REGISTRY_URLS } from './parser/common'; +import { GRADLE_PLUGINS, REGISTRY_URLS } from './parser/common'; jest.mock('../../../util/fs'); @@ -788,4 +788,29 @@ describe('modules/manager/gradle/parser', () => { expect(vars).toBeEmpty(); }); }); + + describe('implicit gradle plugins', () => { + test.each` + def | input | output + ${'baz = "1.2.3"'} | ${'checkstyle { toolVersion = "${baz}" }'} | ${{ depName: 'checkstyle', packageName: GRADLE_PLUGINS['checkstyle'], currentValue: '1.2.3' }} + ${''} | ${'codenarc { toolVersion = "1.2.3" }'} | ${{ depName: 'codenarc', packageName: GRADLE_PLUGINS['codenarc'], currentValue: '1.2.3' }} + ${''} | ${'detekt { toolVersion = "1.2.3" }'} | ${{ depName: 'detekt', packageName: GRADLE_PLUGINS['detekt'], currentValue: '1.2.3' }} + ${''} | ${'findbugs { toolVersion = "1.2.3" }'} | ${{ depName: 'findbugs', packageName: GRADLE_PLUGINS['findbugs'], currentValue: '1.2.3' }} + ${''} | ${'googleJavaFormat { toolVersion = "1.2.3" }'} | ${{ depName: 'googleJavaFormat', packageName: GRADLE_PLUGINS['googleJavaFormat'], currentValue: '1.2.3' }} + ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = baz }'} | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS['jacoco'], currentValue: '1.2.3' }} + ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = property("baz") }'} | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS['jacoco'], currentValue: '1.2.3' }} + ${''} | ${'lombok { version = "1.2.3" }'} | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS['lombok'], currentValue: '1.2.3' }} + ${''} | ${'pmd { toolVersion = "1.2.3" }'} | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS['pmd'], currentValue: '1.2.3' }} + ${''} | ${'pmd { foo = "bar"; toolVersion = "1.2.3" }'} | ${{ depName: 'pmd', packageName: GRADLE_PLUGINS['pmd'], currentValue: '1.2.3' }} + ${''} | ${'spotbugs { toolVersion = "1.2.3" }'} | ${{ depName: 'spotbugs', packageName: GRADLE_PLUGINS['spotbugs'], 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: 'unknown-version' }} + ${''} | ${'pmd { [toolVersion = "6.36.0"] }'} | ${null} + ${''} | ${'unknown { toolVersion = "1.2.3" }'} | ${null} + `('$def | $input', ({ def, input, output }) => { + const { deps } = parseGradle([def, input].join('\n')); + expect(deps).toMatchObject([output].filter(Boolean)); + }); + }); }); diff --git a/lib/modules/manager/gradle/parser.ts b/lib/modules/manager/gradle/parser.ts index 53c843eff9f17f04e7bf0f3258913cf7fae7438d..cafbbb3b33e636289b8c2d7c0741677b11fdce0c 100644 --- a/lib/modules/manager/gradle/parser.ts +++ b/lib/modules/manager/gradle/parser.ts @@ -2,6 +2,7 @@ import { lang, lexer, query as q } from 'good-enough-parser'; import { newlineRegex, regEx } from '../../../util/regex'; import type { PackageDependency } from '../types'; import { + GRADLE_PLUGINS, REGISTRY_URLS, cleanupTempVars, coalesceVariable, @@ -15,6 +16,7 @@ import { handleCustomRegistryUrl, handleDepInterpolation, handleDepSimpleString, + handleImplicitGradlePlugin, handleKotlinShortNotationDep, handleLibraryDep, handleLongFormDep, @@ -736,6 +738,29 @@ const qApplyFrom = q .handler(handleApplyFrom) .handler(cleanupTempVars); +// pmd { toolVersion = "1.2.3" } +const qImplicitGradlePlugin = q + .sym(regEx(`^(?:${Object.keys(GRADLE_PLUGINS).join('|')})$`), storeVarToken) + .handler((ctx) => storeInTokenMap(ctx, 'pluginName')) + .tree({ + type: 'wrapped-tree', + maxDepth: 1, + maxMatches: 1, + startsWith: '{', + endsWith: '}', + search: q + .sym<Ctx>(regEx(/^(?:toolVersion|version)$/)) + .op('=') + .alt( + qTemplateString, + qPropertyAccessIdentifier, + qVariableAccessIdentifier + ), + }) + .handler((ctx) => storeInTokenMap(ctx, 'version')) + .handler(handleImplicitGradlePlugin) + .handler(cleanupTempVars); + export function parseGradle( input: string, initVars: PackageVariables = {}, @@ -766,7 +791,8 @@ export function parseGradle( qRegistryUrls, qVersionCatalogs, qLongFormDep, - qApplyFrom + qApplyFrom, + qImplicitGradlePlugin ), }); diff --git a/lib/modules/manager/gradle/parser/common.ts b/lib/modules/manager/gradle/parser/common.ts index 5bcc454a98564987ac440fafe60ed3e745ba84fd..9b6903361bdfa03e6bf1b7612b36c5aee6b999eb 100644 --- a/lib/modules/manager/gradle/parser/common.ts +++ b/lib/modules/manager/gradle/parser/common.ts @@ -8,6 +8,18 @@ export const REGISTRY_URLS = { mavenCentral: 'https://repo.maven.apache.org/maven2', }; +export const GRADLE_PLUGINS = { + checkstyle: 'com.puppycrawl.tools:checkstyle', + codenarc: 'org.codenarc:CodeNarc', + detekt: 'io.gitlab.arturbosch.detekt:detekt-core', + findbugs: 'com.google.code.findbugs:findbugs', + googleJavaFormat: 'com.google.googlejavaformat:google-java-format', + jacoco: 'org.jacoco:jacoco', + lombok: 'org.projectlombok:lombok', + pmd: 'net.sourceforge.pmd:pmd-java', + spotbugs: 'com.github.spotbugs:spotbugs', +}; + export const ANNOYING_METHODS: ReadonlySet<string> = new Set([ 'createXmlValueRemover', 'events', diff --git a/lib/modules/manager/gradle/parser/handlers.ts b/lib/modules/manager/gradle/parser/handlers.ts index 5a26a031e6c1fd723582c0aaf6e77d7550a643ca..c79315c9aef001982178abdd98f1039264fbe125 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, VariableData } from '../types'; import { parseDependencyString } from '../utils'; import { ANNOYING_METHODS, + GRADLE_PLUGINS, REGISTRY_URLS, interpolateString, loadFromTokenMap, @@ -374,3 +375,44 @@ export function handleApplyFrom(ctx: Ctx): Ctx { return ctx; } + +export function handleImplicitGradlePlugin(ctx: Ctx): Ctx { + const pluginName = loadFromTokenMap(ctx, 'pluginName')[0].value; + const versionTokens = loadFromTokenMap(ctx, 'version'); + const versionValue = interpolateString(versionTokens, ctx.globalVars); + if (!versionValue) { + return ctx; + } + + const groupIdArtifactId = + GRADLE_PLUGINS[pluginName as keyof typeof GRADLE_PLUGINS]; + const dep = parseDependencyString(`${groupIdArtifactId}:${versionValue}`); + if (!dep) { + return ctx; + } + + dep.depName = pluginName; + dep.packageName = groupIdArtifactId; + dep.managerData = { + fileReplacePosition: versionTokens[0].offset, + packageFile: ctx.packageFile, + }; + + if (versionTokens.length > 1) { + // = template string with multiple variables + dep.skipReason = 'unknown-version'; + } else if (versionTokens[0].type === 'symbol') { + const varData = ctx.globalVars[versionTokens[0].value]; + if (varData) { + dep.currentValue = varData.value; + dep.managerData = { + fileReplacePosition: varData.fileReplacePosition, + packageFile: varData.packageFile, + }; + } + } + + ctx.deps.push(dep); + + return ctx; +}