diff --git a/lib/manager/gradle/shallow/extract.spec.ts b/lib/manager/gradle/shallow/extract.spec.ts index 951001c92c74ba609bf5f7584502648d23960ca4..c0a68d4b982ab7eeb1afaff2f4ac42d6fb23b08e 100644 --- a/lib/manager/gradle/shallow/extract.spec.ts +++ b/lib/manager/gradle/shallow/extract.spec.ts @@ -61,12 +61,12 @@ describe('manager/gradle/shallow/extract', () => { }, ], }, - { packageFile: 'build.gradle', deps: [] }, { datasource: 'maven', deps: [], packageFile: 'settings.gradle', }, + { packageFile: 'build.gradle', deps: [] }, ]); }); @@ -88,6 +88,11 @@ describe('manager/gradle/shallow/extract', () => { packageFile: 'gradle.properties', deps: [], }, + { + datasource: 'maven', + deps: [], + packageFile: 'settings.gradle', + }, { packageFile: 'build.gradle', deps: [ @@ -101,11 +106,6 @@ describe('manager/gradle/shallow/extract', () => { }, ], }, - { - datasource: 'maven', - deps: [], - packageFile: 'settings.gradle', - }, ]); }); @@ -164,12 +164,12 @@ describe('manager/gradle/shallow/extract', () => { }, ], }, - { packageFile: 'build.gradle', deps: [] }, { datasource: 'maven', deps: [], packageFile: 'settings.gradle', }, + { packageFile: 'build.gradle', deps: [] }, ]); }); diff --git a/lib/manager/gradle/shallow/extract.ts b/lib/manager/gradle/shallow/extract.ts index 3ed9208a144266f9395d053c8768922f58907d7d..d233e30f69dba4629de4c74a3d25a604ae5289bf 100644 --- a/lib/manager/gradle/shallow/extract.ts +++ b/lib/manager/gradle/shallow/extract.ts @@ -46,7 +46,8 @@ export async function extractAllPackageFiles( const registry: VariableRegistry = {}; const packageFilesByName: Record<string, PackageFile> = {}; const registryUrls = []; - for (const packageFile of reorderFiles(packageFiles)) { + const reorderedFiles = reorderFiles(packageFiles); + for (const packageFile of reorderedFiles) { packageFilesByName[packageFile] = { packageFile, datasource, diff --git a/lib/manager/gradle/shallow/parser.spec.ts b/lib/manager/gradle/shallow/parser.spec.ts index c3c439f0af916e106ed9f6d655d6bc4a77ce69e8..0437a3aef1eaf59568dc1150ae7082a10ceffd63 100644 --- a/lib/manager/gradle/shallow/parser.spec.ts +++ b/lib/manager/gradle/shallow/parser.spec.ts @@ -1,4 +1,5 @@ import { loadFixture } from '../../../../test/util'; +import { SkipReason } from '../../../types'; import { GOOGLE_REPO, GRADLE_PLUGIN_PORTAL_REPO, @@ -45,6 +46,52 @@ describe('manager/gradle/shallow/parser', () => { ({ deps } = parseGradle('version = "1.2.3"\n"foo:bar:$version@@@"')); expect(deps).toBeEmpty(); + + ({ deps } = parseGradle( + // eslint-disable-next-line no-template-curly-in-string + ['versions.foobar = "1.2.3"', '"foo:bar:${versions.foobar}"'].join('\n') + )); + expect(deps).toMatchObject([ + { + depName: 'foo:bar', + currentValue: '1.2.3', + groupName: 'versions.foobar', + }, + ]); + + ({ deps } = parseGradle( + ['versions.foobar = "1.2.3"', '"foo:bar:$versions.foobar"'].join('\n') + )); + expect(deps).toMatchObject([ + { + depName: 'foo:bar', + currentValue: '1.2.3', + groupName: 'versions.foobar', + }, + ]); + + expect( + parseGradle('foo.bar = "foo:bar:1.2.3"', {}, 'versions.gradle') + ).toMatchObject({ + vars: { + 'foo.bar': { + fileReplacePosition: 11, + key: 'foo.bar', + packageFile: 'versions.gradle', + value: 'foo:bar:1.2.3', + }, + }, + deps: [ + { + depName: 'foo:bar', + currentValue: '1.2.3', + groupName: 'foo.bar', + managerData: { + fileReplacePosition: 19, + }, + }, + ], + }); }); it('parses registryUrls', () => { let urls; @@ -148,6 +195,17 @@ describe('manager/gradle/shallow/parser', () => { currentValue: '1.2.3', }, ]); + + ({ deps } = parseGradle( + 'createXmlValueRemover("defaults", "integer", "integer")' + )); + expect(deps).toMatchObject([ + { + depName: 'defaults:integer', + currentValue: 'integer', + skipReason: SkipReason.Ignored, + }, + ]); }); it('parses plugin', () => { let deps; diff --git a/lib/manager/gradle/shallow/parser.ts b/lib/manager/gradle/shallow/parser.ts index 6c913197f089b42483417b1506d7d4080de857df..33c49dec9f376edb4eadf82fac5b86ff329cfa6d 100644 --- a/lib/manager/gradle/shallow/parser.ts +++ b/lib/manager/gradle/shallow/parser.ts @@ -113,14 +113,32 @@ function handleAssignment({ packageFile, tokenMap, }: SyntaxHandlerInput): SyntaxHandlerOutput { - const { keyToken, valToken } = tokenMap; - const variableData: VariableData = { - key: keyToken.value, + const { objectToken, keyToken, valToken } = tokenMap; + const obj = objectToken?.value; + const key = obj ? `${obj}.${keyToken.value}` : keyToken.value; + + const dep = parseDependencyString(valToken.value); + if (dep) { + dep.groupName = key; + dep.managerData = { + fileReplacePosition: valToken.offset + dep.depName.length + 1, + packageFile, + }; + } + + const varData: VariableData = { + key, value: valToken.value, fileReplacePosition: valToken.offset, packageFile, }; - return { vars: { [variableData.key]: variableData } }; + + const result: SyntaxHandlerOutput = { + vars: { [key]: varData }, + deps: dep ? [dep] : [], + }; + + return result; } function processDepString({ @@ -230,6 +248,8 @@ function processPredefinedRegistryUrl({ return { urls: [registryUrl] }; } +const annoyingMethods = new Set(['createXmlValueRemover']); + function processLongFormDep({ tokenMap, variables, @@ -253,12 +273,29 @@ function processLongFormDep({ packageFile, }; } + const methodName = tokenMap.methodName?.value; + if (annoyingMethods.has(methodName)) { + dep.skipReason = SkipReason.Ignored; + } + return { deps: [dep] }; } return null; } const matcherConfigs: SyntaxMatchConfig[] = [ + { + // foo.bar = 'baz' + matchers: [ + { matchType: TokenType.Word, tokenMapKey: 'objectToken' }, + { matchType: TokenType.Dot }, + { matchType: TokenType.Word, tokenMapKey: 'keyToken' }, + { matchType: TokenType.Assignment }, + { matchType: TokenType.String, tokenMapKey: 'valToken' }, + endOfInstruction, + ], + handler: handleAssignment, + }, { // foo = 'bar' matchers: [ @@ -465,6 +502,20 @@ const matcherConfigs: SyntaxMatchConfig[] = [ ], handler: processLongFormDep, }, + { + // fooBarBaz("com.example", "my.dependency", "1.2.3") + matchers: [ + { matchType: TokenType.Word, tokenMapKey: 'methodName' }, + { matchType: TokenType.LeftParen }, + { matchType: potentialStringTypes, tokenMapKey: 'groupId' }, + { matchType: TokenType.Comma }, + { matchType: potentialStringTypes, tokenMapKey: 'artifactId' }, + { matchType: TokenType.Comma }, + { matchType: potentialStringTypes, tokenMapKey: 'version' }, + { matchType: TokenType.RightParen }, + ], + handler: processLongFormDep, + }, { // ("com.example", "my.dependency", "1.2.3") matchers: [ diff --git a/lib/manager/gradle/shallow/utils.spec.ts b/lib/manager/gradle/shallow/utils.spec.ts index 5dfa16dec5d2600cc5d347e0040f154e0ad8772b..9801792639d1158f72223a24770f5680c9edccfc 100644 --- a/lib/manager/gradle/shallow/utils.spec.ts +++ b/lib/manager/gradle/shallow/utils.spec.ts @@ -98,23 +98,41 @@ describe('manager/gradle/shallow/utils', () => { }); it('reorderFiles', () => { - expect(reorderFiles(['a.gradle', 'b.gradle', 'a.gradle'])).toStrictEqual([ + expect( + reorderFiles([ + 'build.gradle', + 'a.gradle', + 'b.gradle', + 'a.gradle', + 'versions.gradle', + ]) + ).toStrictEqual([ + 'versions.gradle', 'a.gradle', 'a.gradle', 'b.gradle', + 'build.gradle', ]); expect( reorderFiles([ 'a/b/c/build.gradle', + 'a/b/versions.gradle', 'a/build.gradle', + 'versions.gradle', 'a/b/build.gradle', + 'a/versions.gradle', 'build.gradle', + 'a/b/c/versions.gradle', ]) ).toStrictEqual([ + 'versions.gradle', 'build.gradle', + 'a/versions.gradle', 'a/build.gradle', + 'a/b/versions.gradle', 'a/b/build.gradle', + 'a/b/c/versions.gradle', 'a/b/c/build.gradle', ]); @@ -146,8 +164,8 @@ describe('manager/gradle/shallow/utils', () => { 'gradle.properties', 'a.gradle', 'b.gradle', - 'build.gradle', 'c.gradle', + 'build.gradle', 'a/gradle.properties', 'a/build.gradle', 'a/b/gradle.properties', diff --git a/lib/manager/gradle/shallow/utils.ts b/lib/manager/gradle/shallow/utils.ts index c9c2a36a795520c332d42d21655b189b29113844..3c7e15171d4037a51ffb33a0759c9d6502d22c42 100644 --- a/lib/manager/gradle/shallow/utils.ts +++ b/lib/manager/gradle/shallow/utils.ts @@ -95,9 +95,23 @@ export function interpolateString( return resolvedSubstrings.join(''); } +const gradleVersionsFileRegex = regEx('^versions\\.gradle(?:\\.kts)?$', 'i'); +const gradleBuildFileRegex = regEx('^build\\.gradle(?:\\.kts)?$', 'i'); +const gradleFileRegex = regEx('\\.gradle(?:\\.kts)?$', 'i'); + +export function isGradleVersionsFile(path: string): boolean { + const filename = upath.basename(path); + return gradleVersionsFileRegex.test(filename); +} + +export function isGradleBuildFile(path: string): boolean { + const filename = upath.basename(path); + return gradleBuildFileRegex.test(filename); +} + export function isGradleFile(path: string): boolean { - const filename = upath.basename(path).toLowerCase(); - return filename.endsWith('.gradle') || filename.endsWith('.gradle.kts'); + const filename = upath.basename(path); + return gradleFileRegex.test(filename); } export function isPropsFile(path: string): boolean { @@ -114,6 +128,19 @@ export function toAbsolutePath(packageFile: string): string { return upath.join(packageFile.replace(regEx(/^[/\\]*/), '/')); } +function getFileRank(filename: string): number { + if (isPropsFile(filename)) { + return 0; + } + if (isGradleVersionsFile(filename)) { + return 1; + } + if (isGradleBuildFile(filename)) { + return 3; + } + return 2; +} + export function reorderFiles(packageFiles: string[]): string[] { return packageFiles.sort((x, y) => { const xAbs = toAbsolutePath(x); @@ -123,19 +150,18 @@ export function reorderFiles(packageFiles: string[]): string[] { const yDir = upath.dirname(yAbs); if (xDir === yDir) { - if ( - (isGradleFile(xAbs) && isGradleFile(yAbs)) || - (isPropsFile(xAbs) && isPropsFile(yAbs)) - ) { + const xRank = getFileRank(xAbs); + const yRank = getFileRank(yAbs); + if (xRank === yRank) { if (xAbs > yAbs) { return 1; } if (xAbs < yAbs) { return -1; } - } else if (isGradleFile(xAbs)) { + } else if (xRank > yRank) { return 1; - } else if (isGradleFile(yAbs)) { + } else if (yRank > xRank) { return -1; } } else if (xDir.startsWith(yDir)) {