diff --git a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrerTest.java b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrerTest.java new file mode 100644 index 0000000000..f5990ea105 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrerTest.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.format.jvmmodel; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.impl.ResourceImpl; +import org.eclipse.xtext.common.types.JvmGenericType; +import org.eclipse.xtext.common.types.TypesFactory; +import org.junit.jupiter.api.Test; + +import com.avaloq.tools.ddk.xtext.format.format.Constant; +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.avaloq.tools.ddk.xtext.format.format.FormatFactory; +import com.avaloq.tools.ddk.xtext.test.format.util.FormatTestUtil; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest; + + +/** + * Regression test for {@link FormatJvmModelInferrer#inferConstants(FormatConfiguration, JvmGenericType)}. + * + *

A {@link Constant} without an {@code intValue} or a {@code stringValue} makes + * {@link FormatJvmModelInferrer#createConstant(FormatConfiguration, Constant)} return {@code null}. The inferrer must + * skip such {@code null} members instead of leaking them into the JVM model (mirroring the original Xtend {@code +=} + * null-skip), otherwise an {@code IllegalArgumentException: element: null} surfaces downstream.

+ */ +@SuppressWarnings("nls") +public class FormatJvmModelInferrerTest extends AbstractXtextTest { + + private final FormatJvmModelInferrer inferrer = getXtextTestUtil().get(FormatJvmModelInferrer.class); + + @Override + protected FormatTestUtil getXtextTestUtil() { + return FormatTestUtil.getInstance(); + } + + @Override + protected String getTestSourceFileName() { + return null; // the model is fabricated in-memory, no source file is loaded + } + + /** + * A value-less constant must not leak a {@code null} member into the inferred type. + */ + @Test + public void testInferConstantsSkipsValuelessConstant() { + final FormatConfiguration format = FormatFactory.eINSTANCE.createFormatConfiguration(); + final Constant valuelessConstant = FormatFactory.eINSTANCE.createConstant(); + valuelessConstant.setName("VALUELESS"); // name only, both intValue and stringValue stay null + format.getConstants().add(valuelessConstant); + + final Resource resource = new ResourceImpl(URI.createURI("synthetic:/format/valueless.format")); + resource.getContents().add(format); + + // createConstant returns null for a value-less constant. + assertNull(inferrer.createConstant(format, valuelessConstant), "value-less constant should yield no JVM member"); + + final JvmGenericType type = TypesFactory.eINSTANCE.createJvmGenericType(); + assertDoesNotThrow(() -> inferrer.inferConstants(format, type), "inferConstants must not fail on a value-less constant"); + assertFalse(type.getMembers().contains(null), "the inferred type must not contain a null member"); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/test/format/FormatTestSuite.java b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/test/format/FormatTestSuite.java index 8b12ea9435..4a4ef63c53 100644 --- a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/test/format/FormatTestSuite.java +++ b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/test/format/FormatTestSuite.java @@ -16,6 +16,7 @@ import com.avaloq.tools.ddk.xtext.format.FormatParsingTest; import com.avaloq.tools.ddk.xtext.format.builder.FormatBuilderParticipantTest; import com.avaloq.tools.ddk.xtext.format.formatting.FormatFormattingTest; +import com.avaloq.tools.ddk.xtext.format.jvmmodel.FormatJvmModelInferrerTest; import com.avaloq.tools.ddk.xtext.format.scoping.FormatScopingTest; import com.avaloq.tools.ddk.xtext.format.validation.FormatValidationTest; @@ -24,7 +25,7 @@ * Empty class serving only as holder for JUnit5 annotations. */ @Suite -@SelectClasses({FormatParsingTest.class, FormatFormattingTest.class, FormatValidationTest.class, FormatScopingTest.class, FormatBuilderParticipantTest.class}) +@SelectClasses({FormatParsingTest.class, FormatFormattingTest.class, FormatValidationTest.class, FormatScopingTest.class, FormatBuilderParticipantTest.class, FormatJvmModelInferrerTest.class}) public class FormatTestSuite { } diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java index 3abd284dce..e42896966c 100644 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java @@ -211,7 +211,10 @@ public void inferClass(final FormatConfiguration format, final JvmGenericType it public void inferConstants(final FormatConfiguration format, final JvmGenericType it) { if (!FormatGeneratorUtil.getAllConstants(format).isEmpty()) { for (final Constant c : FormatGeneratorUtil.getAllConstants(format)) { - it.getMembers().add(createConstant(format, c)); + final JvmMember member = createConstant(format, c); + if (member != null) { // createConstant returns null for a value-less constant; the original Xtend += (operator_add) skipped nulls + it.getMembers().add(member); + } } } }