From 793edf06a5dc1ba5b4f6e82774c26d7645013df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Tue, 3 Mar 2026 21:49:12 +0100 Subject: [PATCH] fix: derive a valid package for shallow export model URIs in ExportGeneratorX ExportGeneratorX.getPrefix derives the target package from raw URI segment arithmetic and misbehaves on every short URI shape: 1 segment (e.g. ParseHelper's __synthetic0.export) crashes with IndexOutOfBoundsException (subList(-1, 0)); 2 segments emit the literal segment "resource"; project-root URIs emit the raw project name (uncompilable generated code when the project is not named like a Java package); source-folder-root URIs emit an empty package, producing leading-dot qualified names. Keep the derivation for URIs with five or more segments byte for byte (segments between position 3 and the file) and make every shorter shape fall back to the project name when it already is a valid Java package name (SourceVersion.isName) or "generated" otherwise. No existing test ever failed on this: the builder participant catches the exception per resource and logs it (the long-standing "Error during compilation of '....export'" noise in master test runs), and export tests never assert generated output. ExportGeneratorXTest pins the full shape table with synthetic resource URIs. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018ANGM7L3BaEGfGUmPVKRt4 --- .../generator/ExportGeneratorXTest.java | 73 +++++++++++++++++++ .../xtext/test/export/ExportTestSuite.java | 3 +- .../export/generator/ExportGeneratorX.xtend | 13 +++- 3 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java diff --git a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java new file mode 100644 index 000000000..37cabe8c4 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2026 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.impl.ResourceImpl; +import org.junit.jupiter.api.Test; + +import com.avaloq.tools.ddk.xtext.export.export.ExportFactory; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.test.export.util.ExportTestUtil; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest; + + +/** + * Regression tests for URI-based package derivation in {@link ExportGeneratorX}. + */ +@SuppressWarnings("nls") +public class ExportGeneratorXTest extends AbstractXtextTest { + + private static final String DERIVED_PACKAGE_PROVIDER = "com.avaloq.naming.fooExportedNamesProvider"; + private static final String PROJECT_FALLBACK_PROVIDER = "MyProject.naming.fooExportedNamesProvider"; + private static final String DEFAULT_FALLBACK_PROVIDER = "generated.naming.fooExportedNamesProvider"; + + private final ExportGeneratorX exportGeneratorX = getXtextTestUtil().get(ExportGeneratorX.class); + + @Override + protected ExportTestUtil getXtextTestUtil() { + return ExportTestUtil.getInstance(); + } + + @Override + protected String getTestSourceFileName() { + return null; // all tests fabricate synthetic resource URIs + } + + @Test + public void testNestedSourceFolderUrisKeepDerivedPackage() { + assertEquals(DERIVED_PACKAGE_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src/com/avaloq/foo.export")); + assertEquals(DERIVED_PACKAGE_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src-gen/com/avaloq/foo.export")); + assertEquals("main.java.com.avaloq.naming.fooExportedNamesProvider", exportedNamesProviderFor("platform:/resource/MyProject/src/main/java/com/avaloq/foo.export")); + } + + @Test + public void testShortUrisFallBackToValidPackage() { + assertEquals(PROJECT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/foo.export")); + assertEquals(PROJECT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src/foo.export")); + assertEquals(DEFAULT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/1My-Project/foo.export")); + } + + @Test + public void testSingleSegmentUriFallsBackToDefaultPackage() { + assertEquals(DEFAULT_FALLBACK_PROVIDER, exportedNamesProviderFor("foo.export")); + } + + private String exportedNamesProviderFor(final String uri) { + final Resource resource = new ResourceImpl(URI.createURI(uri)); + final ExportModel model = ExportFactory.eINSTANCE.createExportModel(); + resource.getContents().add(model); + return exportGeneratorX.getExportedNamesProvider(model); + } +} diff --git a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java index ea8a86330..a7de215fc 100644 --- a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java +++ b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java @@ -13,6 +13,7 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; +import com.avaloq.tools.ddk.xtext.export.generator.ExportGeneratorXTest; import com.avaloq.tools.ddk.xtext.export.exporting.ExportExportingTest; import com.avaloq.tools.ddk.xtext.export.formatting.ExportFormattingTest; import com.avaloq.tools.ddk.xtext.export.scoping.ExportScopingTest; @@ -24,6 +25,6 @@ * Empty class serving only as holder for JUnit 5 suite annotations. */ @Suite -@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class}) +@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class, ExportGeneratorXTest.class}) public class ExportTestSuite { } diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend index 23325a04a..601cae9a5 100644 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend @@ -24,6 +24,7 @@ import com.google.inject.Inject import java.util.Collection import java.util.List import java.util.Map +import javax.lang.model.SourceVersion import org.eclipse.emf.ecore.EAttribute import org.eclipse.emf.ecore.EClass import org.eclipse.emf.ecore.EClassifier @@ -53,11 +54,15 @@ class ExportGeneratorX { def List getPrefix(URI uri) { // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above - if (uri.segmentsList().size > 3) { - return uri.segmentsList().subList(3, uri.segmentCount() - 1); - } else { - return uri.segmentsList().subList(uri.segmentCount() - 2, uri.segmentCount() - 1); + val segments = uri.segmentsList + if (segments.size > 4) { + return segments.subList(3, segments.size - 1) } + // shorter shapes have no package segments: fall back to a valid package name + if (segments.size > 2 && SourceVersion.isName(segments.get(1))) { + return #[segments.get(1)] + } + return #['generated'] } def String getExportedNamesProvider(ExportModel model) {