From 1df7ae95a5f3958e7e65a3db23e8bb116f954b00 Mon Sep 17 00:00:00 2001 From: Axel RICHARD Date: Thu, 12 Feb 2026 17:02:39 +0100 Subject: [PATCH] [1981] Escape special characters when exporting Expose elements Bug: https://github.com/eclipse-syson/syson/issues/1981 Signed-off-by: Axel RICHARD --- CHANGELOG.adoc | 2 + ...sioningRestControllerIntegrationTests.java | 34 ++++---- .../eclipse/syson/sysml/impl/ElementImpl.java | 64 ++++++++++----- .../sysml/textual/SysMLElementSerializer.java | 32 ++++---- ...{ElementTest.java => ElementImplTest.java} | 21 +++-- backend/tests/syson-tests/pom.xml | 2 +- .../AbstractCodingRulesTests.java | 79 +++++++++++++------ .../SysONServiceDependencyPredicate.java | 5 +- .../pages/release-notes/2026.3.0.adoc | 1 + 9 files changed, 152 insertions(+), 88 deletions(-) rename backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/{ElementTest.java => ElementImplTest.java} (88%) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 26a654f08..f7f4f7281 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -27,6 +27,7 @@ The property `SysONTestsProperties#ELASTICSEARCH` has been removed, tests that r - [releng] Update to https://github.com/eclipse-sirius/sirius-web[Sirius Web 2026.1.2] - [releng] Update to https://github.com/spring-projects/spring-boot/releases/tag/v3.5.10[Spring Boot 3.5.10] +- [releng] Update to archunit-junit5 1.3.0 === Bug fixes @@ -36,6 +37,7 @@ The property `SysONTestsProperties#ELASTICSEARCH` has been removed, tests that r - https://github.com/eclipse-syson/syson/issues/1973[#1973] [import] Fix an error during textual import when resolving the name of an unnamed redefined `Feature`. - https://github.com/eclipse-syson/syson/issues/1860[#1860] [diagrams] Add a precondition for compartment item node descriptions in order to filter out unwanted types. For example `ViewUsage` elements are no longer rendered in _parts_ compartments. +- https://github.com/eclipse-syson/syson/issues/1981[#1981] [export] Fix an error during textual export where `Expose` elements with apostrophes in their name were not properly escaped. === Improvements diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/projects/ProjectDataVersioningRestControllerIntegrationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/projects/ProjectDataVersioningRestControllerIntegrationTests.java index acd4f4a4f..506dd0a68 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/projects/ProjectDataVersioningRestControllerIntegrationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/projects/ProjectDataVersioningRestControllerIntegrationTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -16,11 +16,13 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; import org.eclipse.syson.AbstractIntegrationTests; +import org.eclipse.syson.GivenSysONServer; import org.eclipse.syson.application.data.SimpleProjectElementsTestProjectData; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -29,8 +31,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.io.ClassPathResource; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.transaction.annotation.Transactional; @@ -61,14 +61,13 @@ public void beforeEach() { this.givenInitialServerState.initialize(); } - @Test @DisplayName("GIVEN the SysON REST API, WHEN we ask for all changes, THEN all changes should be returned") - @Sql(scripts = { SimpleProjectElementsTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, - config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) - @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @GivenSysONServer({ SimpleProjectElementsTestProjectData.SCRIPT_PATH }) + @Test public void givenSysONRestAPIWhenWeAskForAllChangesThenAllChangesShouldBeReturned() { var webTestClient = WebTestClient.bindToServer() .baseUrl(this.getHTTPBaseUrl()) + .responseTimeout(Duration.ofSeconds(30)) .build(); String expectedJSON = null; @@ -89,14 +88,13 @@ public void givenSysONRestAPIWhenWeAskForAllChangesThenAllChangesShouldBeReturne .json(expectedJSON); } - @Test @DisplayName("GIVEN the SysON REST API, WHEN we ask for all changes in an unknown project, THEN it should return an error") - @Sql(scripts = { SimpleProjectElementsTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, - config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) - @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @GivenSysONServer({ SimpleProjectElementsTestProjectData.SCRIPT_PATH }) + @Test public void givenSysONRestAPIWhenWeAskForAllChangesInUnknownProjectThenItShouldReturnAnError() { var webTestClient = WebTestClient.bindToServer() .baseUrl(this.getHTTPBaseUrl()) + .responseTimeout(Duration.ofSeconds(30)) .build(); var uri = String.format("/api/rest/projects/%s/commits/%s/changes", INVALID_PROJECT, INVALID_PROJECT); @@ -107,14 +105,13 @@ public void givenSysONRestAPIWhenWeAskForAllChangesInUnknownProjectThenItShouldR .isNotFound(); } - @Test @DisplayName("GIVEN the SysON REST API, WHEN we ask for changes of a specific element, THEN those changes should be returned") - @Sql(scripts = { SimpleProjectElementsTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, - config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) - @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @GivenSysONServer({ SimpleProjectElementsTestProjectData.SCRIPT_PATH }) + @Test public void givenSysONRestAPIWhenWeAskForChangesOfASpecificElementThenThoseChangesShouldBeReturned() { var webTestClient = WebTestClient.bindToServer() .baseUrl(this.getHTTPBaseUrl()) + .responseTimeout(Duration.ofSeconds(30)) .build(); String expectedJSON = null; @@ -136,14 +133,13 @@ public void givenSysONRestAPIWhenWeAskForChangesOfASpecificElementThenThoseChang .json(expectedJSON); } - @Test @DisplayName("GIVEN the SysON REST API, WHEN we ask for specific changes in an unknown project, THEN it should return an error") - @Sql(scripts = { SimpleProjectElementsTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, - config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) - @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @GivenSysONServer({ SimpleProjectElementsTestProjectData.SCRIPT_PATH }) + @Test public void givenSysONRestAPIWhenWeAskForSpecificChangesInUnknownProjectThenItShouldReturnAnError() { var webTestClient = WebTestClient.bindToServer() .baseUrl(this.getHTTPBaseUrl()) + .responseTimeout(Duration.ofSeconds(30)) .build(); var computedChangeId = UUID.nameUUIDFromBytes((INVALID_PROJECT + SimpleProjectElementsTestProjectData.SemanticIds.PART_ID).getBytes()).toString(); diff --git a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/ElementImpl.java b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/ElementImpl.java index ecc7d158b..ab5f306af 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/ElementImpl.java +++ b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/ElementImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023, 2025 Obeo. + * Copyright (c) 2023, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -547,36 +547,64 @@ public void setOwningRelationship(Relationship newOwningRelationship) { * according to the KerML textual concrete syntax for qualified names (including use of unrestricted name notation * and escaped characters, as necessary). The qualifiedName is null if this Element has no owningNamespace or if * there is not a complete ownership chain of named Namespaces from a root Namespace to this Element. + * end-user-doc + *

+ * qualifiedName =
+ * if owningNamespace = null then null
+ * else if name <> null and owningNamespace.ownedMember-> select(m | m.name = name).indexOf(self) <> 1 then null + * else if owningNamespace.owner = null then escapedName()
+ * else if owningNamespace.qualifiedName = null or escapedName() = null then null
+ * else owningNamespace.qualifiedName + '::' + escapedName()
+ * endif
+ * endif
+ * endif
+ * endif + *

+ * --> * * @generated NOT */ @Override public String getQualifiedName() { - String selfName = NameHelper.toPrintableName(this.getName()); + // SysMLv2 specification compliant implementation + // at some point we will have to use this implementation + // String getQualifiedName = null; + // if (this.getOwningNamespace() == null) { + // getQualifiedName = null; + // } else if (this.getName() != null && this.getOwningNamespace().getOwnedMember().stream().filter(m -> + // m.getName() == this.getName()).toList().indexOf(this) != 0) { + // getQualifiedName = null; + // } else if (this.getOwningNamespace().getOwner() != null) { + // getQualifiedName = this.escapedName(); + // } else if (this.getOwningNamespace().getQualifiedName() == null || this.escapedName() == null) { + // getQualifiedName = null; + // } else { + // getQualifiedName = this.getOwningNamespace().getQualifiedName() + "::" + this.escapedName(); + // } + // return getQualifiedName; + + var selfName = NameHelper.toPrintableName(this.getName(), true); if (selfName.isBlank()) { return null; } - - StringBuilder qualifiedNameBuilder = new StringBuilder(); - Element container = this.getOwner(); - if (container != null && container instanceof Membership membership) { - Element membershipContainer = membership.getOwner(); - if (membershipContainer != null) { - String elementQN = membershipContainer.getQualifiedName(); + var qualifiedNameBuilder = new StringBuilder(); + var container = this.getOwner(); + if (container instanceof Membership membership) { + var membershipContainer = membership.getOwner(); + if (membershipContainer instanceof Element) { + var elementQN = membershipContainer.getQualifiedName(); if (elementQN != null && !elementQN.isBlank()) { qualifiedNameBuilder.append(elementQN); qualifiedNameBuilder.append("::"); } } } else if (container != null) { - String elementQN = container.getQualifiedName(); + var elementQN = container.getQualifiedName(); if (elementQN != null && !elementQN.isBlank()) { qualifiedNameBuilder.append(elementQN); qualifiedNameBuilder.append("::"); } } - qualifiedNameBuilder.append(selfName); return qualifiedNameBuilder.toString(); } @@ -630,17 +658,11 @@ public String effectiveShortName() { */ @Override public String escapedName() { - String escapedName = null; - String name = this.getName(); - if (name == null) { + String escapedName = this.getName(); + if (escapedName == null) { escapedName = this.getShortName(); - } else { - escapedName = this.getName(); - } - if (escapedName != null && escapedName.contains("\\S+")) { - escapedName = "'" + escapedName + "'"; } - return escapedName; + return NameHelper.toPrintableName(escapedName, true); } /** diff --git a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/SysMLElementSerializer.java b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/SysMLElementSerializer.java index 4e576c302..82a971882 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/SysMLElementSerializer.java +++ b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/SysMLElementSerializer.java @@ -178,7 +178,7 @@ public String caseAllocationUsage(AllocationUsage allocationUsage) { builder.appendWithSpaceIfNeeded(SysMLv2Keywords.ALLOCATION); builder.appendWithSpaceIfNeeded(declarationBuilder); } - + builder.appendWithSpaceIfNeeded(SysMLv2Keywords.ALLOCATE); this.appendConnectorPart(builder, allocationUsage); this.appendChildrenContent(builder, allocationUsage, allocationUsage.getOwnedMembership()); @@ -866,6 +866,21 @@ public String casePortUsage(PortUsage portUsage) { return builder.toString(); } + @Override + public String caseReferenceUsage(ReferenceUsage reference) { + Appender builder = new Appender(this.lineSeparator, this.indentation); + if (!this.isImplicit(reference)) { + this.appendBasicUsagePrefix(builder, reference); + this.appendUsageDeclaration(builder, reference); + this.appendUsageCompletion(builder, reference); + } + if (builder.toString().equals(";")) { + // This an EmptyUsage rule + return ""; + } + return builder.toString(); + } + @Override public String caseRenderingUsage(RenderingUsage rendering) { Appender builder = new Appender(this.lineSeparator, this.indentation); @@ -908,21 +923,6 @@ public String caseRequirementDefinition(RequirementDefinition requirement) { return builder.toString(); } - @Override - public String caseReferenceUsage(ReferenceUsage reference) { - Appender builder = new Appender(this.lineSeparator, this.indentation); - if (!this.isImplicit(reference)) { - this.appendBasicUsagePrefix(builder, reference); - this.appendUsageDeclaration(builder, reference); - this.appendUsageCompletion(builder, reference); - } - if (builder.toString().equals(";")) { - // This an EmptyUsage rule - return ""; - } - return builder.toString(); - } - @Override public String caseRequirementUsage(RequirementUsage usage) { Appender builder = this.newAppender(); diff --git a/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementTest.java b/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementImplTest.java similarity index 88% rename from backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementTest.java rename to backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementImplTest.java index fdaed3709..27bcecd5d 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementTest.java +++ b/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/ElementImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -34,7 +34,7 @@ * * @author gescande */ -public class ElementTest { +public class ElementImplTest { /** * Test model @@ -51,9 +51,10 @@ public class ElementTest { * } * private part def 'private def1x1'; * private part def 'def-10'; - * private part def 'éléphant; + * private part def 'éléphant'; * private part def def_11; * private part def _def_12; + * part def 'with escaped\'apostrophe'; * } * } * } @@ -70,14 +71,11 @@ private class TestModel { private Package p1x1; private PartDefinition def1x1; private PartDefinition privatedef1x1; - private PartDefinition def10; - private PartDefinition defElephant; - private PartDefinition def11; - private PartDefinition def12; + private PartDefinition withEscapedApostrophe; TestModel() { this.build(); @@ -97,6 +95,7 @@ private void build() { this.defElephant = this.builder.createInWithName(PartDefinition.class, this.p1x1, "éléphant"); this.def11 = this.builder.createInWithName(PartDefinition.class, this.p1x1, "def_11"); this.def12 = this.builder.createInWithName(PartDefinition.class, this.p1x1, "_def_12"); + this.withEscapedApostrophe = this.builder.createInWithName(PartDefinition.class, this.p1x1, "with escaped'apostrophe"); } } @@ -113,6 +112,7 @@ public void getQualifiedNameTest() { assertEquals("p1::'p1 x1'::'éléphant'", testModel.defElephant.getQualifiedName()); assertEquals("p1::'p1 x1'::def_11", testModel.def11.getQualifiedName()); assertEquals("p1::'p1 x1'::_def_12", testModel.def12.getQualifiedName()); + assertEquals("p1::'p1 x1'::'with escaped\\'apostrophe'", testModel.withEscapedApostrophe.getQualifiedName()); } @DisplayName("Check documentation feature") @@ -140,4 +140,11 @@ public void isLibraryElementNotInsideLibraryPackageTest() { assertThat(testModel.def1.isIsLibraryElement()).isFalse(); assertThat(testModel.def12.isIsLibraryElement()).isFalse(); } + + @DisplayName("Check espacedNamed derived operation") + @Test + public void escapedNameTest() { + var testModel = new TestModel(); + assertEquals("'with escaped\\'apostrophe'", testModel.withEscapedApostrophe.escapedName()); + } } diff --git a/backend/tests/syson-tests/pom.xml b/backend/tests/syson-tests/pom.xml index 4e8e8007f..8f9073cb1 100644 --- a/backend/tests/syson-tests/pom.xml +++ b/backend/tests/syson-tests/pom.xml @@ -47,7 +47,7 @@ com.tngtech.archunit archunit-junit5 - 0.19.0 + 1.3.0 diff --git a/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/AbstractCodingRulesTests.java b/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/AbstractCodingRulesTests.java index 14924d860..bf40b4268 100644 --- a/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/AbstractCodingRulesTests.java +++ b/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/AbstractCodingRulesTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -72,6 +72,8 @@ public abstract class AbstractCodingRulesTests { private static final String GUAVA_THIRDPARTY = "com.google.thirdparty.."; + private static final String ORG_ECLIPSE_CORE_RUNTIME = "org.eclipse.core.runtime.."; + private static final String SPRING_STRINGUTILS = "org.springframework.util.StringUtils"; private static final String TESTCASE_SUFFIX = "TestCases"; @@ -101,7 +103,8 @@ public void noClassesShouldUseGraphQLGuava() { .resideInAPackage(this.getProjectRootPackage()) .should() .dependOnClassesThat() - .resideInAnyPackage(GRAPHQL_GUAVA); + .resideInAnyPackage(GRAPHQL_GUAVA) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -129,7 +132,21 @@ public void noClassesShouldUseGuava() { GUAVA_UTIL, GUAVA_XML, GUAVA_THIRDPARTY - ); + ) + .allowEmptyShould(true); + + rule.check(this.getClasses()); + } + + @Test + public void noClassesShouldUseEclipseCoreRuntime() { + var rule = ArchRuleDefinition.noClasses() + .that() + .resideInAPackage(this.getProjectRootPackage()) + .should() + .dependOnClassesThat() + .resideInAnyPackage(ORG_ECLIPSE_CORE_RUNTIME) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -141,7 +158,8 @@ public void noClassesShouldUseJacksonAnnotations() { .resideInAPackage(this.getProjectRootPackage()) .should() .dependOnClassesThat() - .resideInAPackage(JACKSON_ANNOTATION); + .resideInAPackage(JACKSON_ANNOTATION) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -153,7 +171,8 @@ public void noClassesShouldUseJetbrainsImport() { .resideInAPackage(this.getProjectRootPackage()) .should() .dependOnClassesThat() - .resideInAPackage(JETBRAINS); + .resideInAPackage(JETBRAINS) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -167,7 +186,8 @@ public void noClassesShouldUseEMF() { .resideInAPackage(EMF) .orShould() .dependOnClassesThat() - .resideInAPackage(EMFJSON); + .resideInAPackage(EMFJSON) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -181,7 +201,9 @@ public void noClassesShouldUseEcoreUtilDelete() { .callMethod("org.eclipse.emf.ecore.util.EcoreUtil", "delete", "org.eclipse.emf.ecore.EObject") .orShould() .callMethod("org.eclipse.emf.ecore.util.EcoreUtil", "delete", "org.eclipse.emf.ecore.EObject", "boolean") - .because("EcoreUtil.delete doesn't work well with Sysml Standard Libraries in the ResourceSet, use DeleteService instead"); + .because("EcoreUtil.delete doesn't work well with Sysml Standard Libraries in the ResourceSet, use DeleteService instead") + .allowEmptyShould(true); + rule.check(this.getClasses()); } @@ -191,7 +213,8 @@ public void noClassesShouldUseApacheCommons() { .resideInAPackage(this.getProjectRootPackage()) .should() .dependOnClassesThat() - .resideInAPackage(APACHE_COMMONS); + .resideInAPackage(APACHE_COMMONS) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -203,7 +226,8 @@ public void noClassesShouldUseSpringStringUtils() { .resideInAPackage(this.getProjectRootPackage()) .should() .dependOnClassesThat() - .areAssignableTo(SPRING_STRINGUTILS); + .areAssignableTo(SPRING_STRINGUTILS) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -213,7 +237,8 @@ public void noClassesShouldUseStandardStreams() { ArchRule rule = ArchRuleDefinition.noClasses() .that() .resideInAPackage(this.getProjectRootPackage()) - .should(GeneralCodingRules.ACCESS_STANDARD_STREAMS); + .should(GeneralCodingRules.ACCESS_STANDARD_STREAMS) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -223,7 +248,8 @@ public void noClassesShouldThrowGenericExceptions() { ArchRule rule = ArchRuleDefinition.noClasses() .that() .resideInAPackage(this.getProjectRootPackage()) - .should(GeneralCodingRules.THROW_GENERIC_EXCEPTIONS); + .should(GeneralCodingRules.THROW_GENERIC_EXCEPTIONS) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -233,7 +259,8 @@ public void noClassesShouldUseJavaLogging() { ArchRule rule = ArchRuleDefinition.noClasses() .that() .resideInAPackage(this.getProjectRootPackage()) - .should(GeneralCodingRules.USE_JAVA_UTIL_LOGGING); + .should(GeneralCodingRules.USE_JAVA_UTIL_LOGGING) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -245,7 +272,9 @@ public void classesShouldUsePublicVisibility() { .resideInAPackage(this.getProjectRootPackage()) .should().bePackagePrivate() .andShould().bePrivate() - .andShould().beProtected(); + .andShould().beProtected() + .allowEmptyShould(true); + rule.check(this.getClasses()); } @@ -254,7 +283,9 @@ public void classesShouldHavePublicOrPrivateConstructors() { ArchRule rule = ArchRuleDefinition.noClasses() .that() .resideInAPackage(this.getProjectRootPackage()) - .should(this.haveProtectedOrPackageConstructor()); + .should(this.haveProtectedOrPackageConstructor()) + .allowEmptyShould(true); + rule.check(this.getClasses()); } @@ -294,7 +325,8 @@ public void interfacesShouldRespectNamingConventions() { .and() .areNotAnnotatedWith(Target.class) .should() - .haveSimpleNameStartingWith("I"); + .haveSimpleNameStartingWith("I") + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -309,7 +341,8 @@ public void abstractClassesShouldRespectNamingConventions() { .and() .haveModifier(ABSTRACT) .should() - .haveSimpleNameStartingWith("Abstract"); + .haveSimpleNameStartingWith("Abstract") + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -327,7 +360,8 @@ public void abstractClassesShouldNotContainBusinessCode() { .areNotInterfaces() .and() .haveModifier(ABSTRACT) - .should(this.notContainBusinessCode()); + .should(this.notContainBusinessCode()) + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -422,7 +456,8 @@ public void noMethodsShouldBeStatic() { .and(this.isNotSwitchTable()) .and(this.isNotRecordStaticBuilder()) .should() - .beStatic(); + .beStatic() + .allowEmptyShould(true); rule.check(this.getClasses()); } @@ -430,7 +465,7 @@ public void noMethodsShouldBeStatic() { private DescribedPredicate isNotTestCase() { return new DescribedPredicate<>("is not a test case") { @Override - public boolean apply(JavaClass javaClass) { + public boolean test(JavaClass javaClass) { return !javaClass.getName().endsWith(TESTCASE_SUFFIX); } }; @@ -444,7 +479,7 @@ public boolean apply(JavaClass javaClass) { private DescribedPredicate isNotLambda() { return new DescribedPredicate<>("is not a lambda") { @Override - public boolean apply(JavaMethod javaMethod) { + public boolean test(JavaMethod javaMethod) { return !javaMethod.getName().startsWith("lambda$") && !javaMethod.getName().startsWith("$deserializeLambda$"); } }; @@ -459,7 +494,7 @@ public boolean apply(JavaMethod javaMethod) { private DescribedPredicate isNotSwitchTable() { return new DescribedPredicate<>("is not a switch table (whatever that is...)") { @Override - public boolean apply(JavaMethod javaMethod) { + public boolean test(JavaMethod javaMethod) { return !javaMethod.getFullName().contains("$SWITCH_TABLE$"); } }; @@ -473,7 +508,7 @@ public boolean apply(JavaMethod javaMethod) { private DescribedPredicate isNotRecordStaticBuilder() { return new DescribedPredicate<>("is not an alternate builder in a record") { @Override - public boolean apply(JavaMethod javaMethod) { + public boolean test(JavaMethod javaMethod) { return !(javaMethod.getOwner().isRecord() && javaMethod.getRawReturnType().equals(javaMethod.getOwner())); } }; diff --git a/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/SysONServiceDependencyPredicate.java b/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/SysONServiceDependencyPredicate.java index 88296c4ab..15b899166 100644 --- a/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/SysONServiceDependencyPredicate.java +++ b/backend/tests/syson-tests/src/main/java/org/eclipse/syson/tests/architecture/SysONServiceDependencyPredicate.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.syson.tests.architecture; +import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import java.util.List; @@ -22,7 +23,7 @@ * * @author Arthur Daussy */ -public final class SysONServiceDependencyPredicate extends com.tngtech.archunit.base.DescribedPredicate { +public final class SysONServiceDependencyPredicate extends DescribedPredicate { private final String acceptableDependentServicePattern; @@ -33,7 +34,7 @@ public SysONServiceDependencyPredicate(List authorizedScopes, String ope } @Override - public boolean apply(JavaClass javaClass) { + public boolean test(JavaClass javaClass) { String simpleName = javaClass.getSimpleName(); if (!simpleName.endsWith("Service") || !javaClass.getName().contains("syson")) { diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc index 853d5e063..14671821f 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc @@ -23,6 +23,7 @@ It's not recommended for production use. ** Fix the textual export of `OccurrenceUsage` to avoid duplication of the _abstract_ keyword. ** Fix the textual export to properly escape names used in qualified names in some references. +** Fix an error during textual export where `Expose` elements with apostrophes in their name were not properly escaped. ** Fix the textual import to properly resolve the _payload_ argument of a `SendActionUsage` used in a `TransitionUsage`. The following model is now properly imported: +