diff --git a/lib/modules/manager/bazel/parser.spec.ts b/lib/modules/manager/bazel/parser.spec.ts index 7c0de48a14a874335ad02bf34b344f6a9daaf0f6..3f7075bda638e02081255dc3e61cedd7ecc7c1b8 100644 --- a/lib/modules/manager/bazel/parser.spec.ts +++ b/lib/modules/manager/bazel/parser.spec.ts @@ -218,4 +218,44 @@ describe('modules/manager/bazel/parser', () => { }, ]); }); + + it('parses Maven', () => { + const input = codeBlock` + maven_install( + artifacts = [ + "com.example1:foo:1.1.1", + maven.artifact( + group = "com.example2", + artifact = "bar", + version = "2.2.2", + ) + ], + repositories = [ + "https://example1.com/maven2", + "https://example2.com/maven2", + ] + ) + `; + + const res = parse(input); + + expect(res?.map(extract)).toEqual([ + { + rule: 'maven_install', + artifacts: [ + 'com.example1:foo:1.1.1', + { + _function: 'maven.artifact', + artifact: 'bar', + group: 'com.example2', + version: '2.2.2', + }, + ], + repositories: [ + 'https://example1.com/maven2', + 'https://example2.com/maven2', + ], + }, + ]); + }); }); diff --git a/lib/modules/manager/bazel/parser.ts b/lib/modules/manager/bazel/parser.ts index 776f38673d3c0e599a7e23654732f1737196fd20..c6a8f77828cb7f827214ae7f946de94c9ed8c75a 100644 --- a/lib/modules/manager/bazel/parser.ts +++ b/lib/modules/manager/bazel/parser.ts @@ -10,6 +10,7 @@ interface Ctx { results: RecordFragment[]; stack: NestedFragment[]; recordKey?: string; + subRecordKey?: string; } function emptyCtx(source: string): Ctx { @@ -45,9 +46,20 @@ function extractTreeValue( * - `tag = "1.2.3"` * - `name = "foobar"` * - `deps = ["foo", "bar"]` + * - ` + * artifacts = [ + maven.artifact( + group = "com.example1", + artifact = "foobar", + version = "1.2.3", + ) + ] + ` **/ const kwParams = q - .sym<Ctx>((ctx, { value: recordKey }) => ({ ...ctx, recordKey })) + .sym<Ctx>((ctx, { value: recordKey }) => { + return { ...ctx, recordKey }; + }) .op('=') .alt( // string @@ -59,10 +71,12 @@ const kwParams = q } return ctx; }), - // array of strings + // array of strings or calls q.tree({ type: 'wrapped-tree', maxDepth: 1, + startsWith: '[', + endsWith: ']', preHandler: (ctx, tree) => { const parentRecord = currentFragment(ctx) as RecordFragment; if ( @@ -80,17 +94,93 @@ const kwParams = q } return ctx; }, - search: q.str<Ctx>((ctx, { value, offset }) => { - const parentRecord = currentFragment(ctx); - if (parentRecord.type === 'record' && ctx.recordKey) { - const key = ctx.recordKey; - const array = parentRecord.children[key]; - if (array.type === 'array') { - array.children.push({ type: 'string', value, offset }); + search: q.alt( + q.str<Ctx>((ctx, { value, offset }) => { + const parentRecord = currentFragment(ctx); + if (parentRecord.type === 'record' && ctx.recordKey) { + const key = ctx.recordKey; + const array = parentRecord.children[key]; + if (array.type === 'array') { + array.children.push({ type: 'string', value, offset }); + } } - } - return ctx; - }), + return ctx; + }), + q + .sym<Ctx>() + .handler(recordStartHandler) + .handler((ctx, { value, offset }) => { + const ruleFragment = currentFragment(ctx); + if (ruleFragment.type === 'record') { + ruleFragment.children._function = { + type: 'string', + value, + offset, + }; + } + return ctx; + }) + .many( + q.op<Ctx>('.').sym((ctx, { value }) => { + const ruleFragment = currentFragment(ctx); + if ( + ruleFragment.type === 'record' && + ruleFragment.children._function + ) { + ruleFragment.children._function.value += `.${value}`; + } + return ctx; + }), + 0, + 3 + ) + .tree({ + type: 'wrapped-tree', + maxDepth: 1, + startsWith: '(', + endsWith: ')', + search: q + .sym<Ctx>((ctx, { value: subRecordKey }) => ({ + ...ctx, + subRecordKey, + })) + .op('=') + .str((ctx, { value: subRecordValue, offset }) => { + const subRecordKey = ctx.subRecordKey!; + const ruleFragment = currentFragment(ctx); + if (ruleFragment.type === 'record') { + ruleFragment.children[subRecordKey] = { + type: 'string', + value: subRecordValue, + offset, + }; + } + delete ctx.subRecordKey; + return ctx; + }), + postHandler: (ctx, tree) => { + const callFrag = currentFragment(ctx); + ctx.stack.pop(); + if (callFrag.type === 'record' && tree.type === 'wrapped-tree') { + callFrag.value = extractTreeValue( + ctx.source, + tree, + callFrag.offset + ); + + const parentRecord = currentFragment(ctx); + if (parentRecord.type === 'record' && ctx.recordKey) { + const key = ctx.recordKey; + const array = parentRecord.children[key]; + if (array.type === 'array') { + array.children.push(callFrag); + } + } + } + return ctx; + }, + }) + ), postHandler: (ctx, tree) => { const parentRecord = currentFragment(ctx); if ( @@ -140,7 +230,7 @@ function ruleCall( }); } -function ruleStartHandler(ctx: Ctx, { offset }: lexer.Token): Ctx { +function recordStartHandler(ctx: Ctx, { offset }: lexer.Token): Ctx { ctx.stack.push({ type: 'record', value: '', @@ -166,7 +256,7 @@ function ruleNameHandler(ctx: Ctx, { value, offset }: lexer.Token): Ctx { */ const regularRule = q .sym<Ctx>(supportedRulesRegex, (ctx, token) => - ruleNameHandler(ruleStartHandler(ctx, token), token) + ruleNameHandler(recordStartHandler(ctx, token), token) ) .join(ruleCall(kwParams)); @@ -176,7 +266,7 @@ const regularRule = q * - `maybe(go_repository, ...)` */ const maybeRule = q - .sym<Ctx>('maybe', ruleStartHandler) + .sym<Ctx>('maybe', recordStartHandler) .join( ruleCall( q.alt( diff --git a/lib/modules/manager/bazel/rules/index.spec.ts b/lib/modules/manager/bazel/rules/index.spec.ts index c133e7a4f869eca633cb3276a68fd620757a63b5..761222f6eabfb484357ee3c3f1f752f9803d08fb 100644 --- a/lib/modules/manager/bazel/rules/index.spec.ts +++ b/lib/modules/manager/bazel/rules/index.spec.ts @@ -348,4 +348,48 @@ describe('modules/manager/bazel/rules/index', () => { ]); }); }); + + describe('maven', () => { + it('extracts maven dependencies', () => { + expect( + extractDepsFromFragmentData({ + rule: 'maven_install', + artifacts: [ + 'com.example1:foo:1.1.1', + { + artifact: 'bar', + function: 'maven.artifact', + group: 'com.example2', + version: '2.2.2', + }, + ], + repositories: [ + 'https://example1.com/maven2', + 'https://example2.com/maven2', + ], + }) + ).toEqual([ + { + currentValue: '1.1.1', + datasource: 'maven', + depType: 'maven_install', + depName: 'com.example1:foo', + registryUrls: [ + 'https://example1.com/maven2', + 'https://example2.com/maven2', + ], + }, + { + currentValue: '2.2.2', + datasource: 'maven', + depType: 'maven_install', + depName: 'com.example2:bar', + registryUrls: [ + 'https://example1.com/maven2', + 'https://example2.com/maven2', + ], + }, + ]); + }); + }); }); diff --git a/lib/modules/manager/bazel/rules/index.ts b/lib/modules/manager/bazel/rules/index.ts index 149ff09e742ce57672609bb24ba70f685204f178..3fa644cad51050a715ac7e10ff52705f63d3a0d6 100644 --- a/lib/modules/manager/bazel/rules/index.ts +++ b/lib/modules/manager/bazel/rules/index.ts @@ -6,14 +6,27 @@ import { DockerTarget, dockerRules } from './docker'; import { GitTarget, gitRules } from './git'; import { GoTarget, goRules } from './go'; import { HttpTarget, httpRules } from './http'; +import { MavenTarget, mavenRules } from './maven'; -const Target = z.union([DockerTarget, GitTarget, GoTarget, HttpTarget]); +const Target = z.union([ + DockerTarget, + GitTarget, + GoTarget, + HttpTarget, + MavenTarget, +]); /** * Gather all rule names supported by Renovate in order to speed up parsing * by filtering out other syntactically correct rules we don't support yet. */ -const supportedRules = [...dockerRules, ...gitRules, ...goRules, ...httpRules]; +const supportedRules = [ + ...dockerRules, + ...gitRules, + ...goRules, + ...httpRules, + ...mavenRules, +]; export const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`); export function extractDepsFromFragmentData( diff --git a/lib/modules/manager/bazel/rules/maven.ts b/lib/modules/manager/bazel/rules/maven.ts new file mode 100644 index 0000000000000000000000000000000000000000..82fcdd3934fa01e103d0413590ae5359f9ccb33e --- /dev/null +++ b/lib/modules/manager/bazel/rules/maven.ts @@ -0,0 +1,51 @@ +import is from '@sindresorhus/is'; +import { z } from 'zod'; +import { MavenDatasource } from '../../../datasource/maven'; +import type { PackageDependency } from '../../types'; + +export const mavenRules = ['maven_install'] as const; + +const ArtifactSpec = z.object({ + group: z.string(), + artifact: z.string(), + version: z.string(), +}); +type ArtifactSpec = z.infer<typeof ArtifactSpec>; + +export const MavenTarget = z + .object({ + rule: z.enum(mavenRules), + artifacts: z + .union([z.string(), ArtifactSpec]) + .array() + .transform((xs) => { + const result: ArtifactSpec[] = []; + for (const x of xs) { + if (is.string(x)) { + const [group, artifact, version] = x.split(':'); + if (group && artifact && version) { + result.push({ group, artifact, version }); + } + } else { + result.push(x); + } + } + + return result; + }), + repositories: z.array(z.string()).optional(), + }) + .transform( + ({ + rule: depType, + artifacts, + repositories: registryUrls, + }): PackageDependency[] => + artifacts.map(({ group, artifact, version: currentValue }) => ({ + datasource: MavenDatasource.id, + depName: `${group}:${artifact}`, + currentValue, + depType, + registryUrls, + })) + ); diff --git a/lib/modules/manager/bazel/types.ts b/lib/modules/manager/bazel/types.ts index ce1233b12f1aa77c2f0f01dbdb34b4fa705ffa81..ea94a32848bf1551cec586c796406c33067cd6d0 100644 --- a/lib/modules/manager/bazel/types.ts +++ b/lib/modules/manager/bazel/types.ts @@ -56,6 +56,7 @@ export type FragmentData = export type FragmentPath = | [number] | [number, string] - | [number, string, number]; + | [number, string, number] + | [number, string, number, string]; export type FragmentUpdater = string | ((_: string) => string);