From 6cccd8700ec6a6ebdd11aa64f097952800b9a9f7 Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:59:43 +0100 Subject: [PATCH 1/8] PartTypeDefinitionExistsCoCo (#32) * CoCo1 Every type used in "part name:type" has to be an existing Part-Definition * Test for MKPXCoCo1 * rename and changed error code --- .../cocos/PartTypeDefinitionExistsCoCo.java | 39 +++++++++ .../PartTypeDefinitionExistsCoCoTest.java | 80 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java create mode 100644 language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java new file mode 100644 index 00000000..faced490 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java @@ -0,0 +1,39 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTSpecialization; +import de.monticore.lang.sysmlparts._ast.ASTPartUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; +import de.monticore.types.mcbasictypes._ast.ASTMCType; +import de.se_rwth.commons.logging.Log; + +import java.util.stream.Collectors; + +/** + * CoCo1: Jeder in "part name:Typ" verwendete Typ muss eine existierende Part-Definition sein. + */ +public class PartTypeDefinitionExistsCoCo implements SysMLPartsASTPartUsageCoCo { + + protected String printPartType(ASTMCType type) { + return type.printType(); + } + + @Override + public void check(ASTPartUsage node) { + var nonExistent = node.streamSpecializations() + .flatMap(ASTSpecialization::streamSuperTypes) + .filter(t -> + node.getEnclosingScope().resolvePartDef(printPartType(t)).isEmpty() + ) + .collect(Collectors.toList()); + + for (var problem : nonExistent) { + Log.error( + "0x10AA1 The type referenced in a PartUsage \"" + printPartType(problem) + + "\" does not exist as a part definition.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } +} diff --git a/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java new file mode 100644 index 00000000..3a86deed --- /dev/null +++ b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java @@ -0,0 +1,80 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.PartTypeDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PartTypeDefinitionExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class PartTypeDefinitionExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def SubComponent1;" + + "part def SubComponent2;" + + "part def MainComponent{" + + "part subcomp1: SubComponent1;" + + "part subcomp2: SubComponent2;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTPartUsageCoCo) new PartTypeDefinitionExistsCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "part def SubComponent1;" + + "part def MainComponent{" + + "part subcomp1: SubComponent1;" + + "part subcomp2: UndefinedComponent;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTPartUsageCoCo) new PartTypeDefinitionExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA1"))); + } + } + +} From a7af0293af8b7ac048d6ff11cc19805abb7cf9ba Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:00:08 +0100 Subject: [PATCH 2/8] RefinementTargetDefinitionExistsCoCo (#33) * CoCo2 Every name used in "part def X refines Name" has to be an existing Part-Definition. * add Test for MKPX_CoCo2 * renamed and changed error codes --- .../RefinementTargetDefinitionExistsCoCo.java | 41 +++++++++++ ...inementTargetDefinitionExistsCoCoTest.java | 71 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java create mode 100644 language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java new file mode 100644 index 00000000..3b7338d3 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java @@ -0,0 +1,41 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTSpecialization; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLRefinement; +import de.monticore.lang.sysmlparts._ast.ASTPartDef; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; +import de.monticore.types.mcbasictypes._ast.ASTMCType; +import de.se_rwth.commons.logging.Log; + +import java.util.stream.Collectors; + +/** + * CoCo2: Jeder im "part def X refines Name" verwendete Name muss eine existierende Part-Definition sein. + */ +public class RefinementTargetDefinitionExistsCoCo implements SysMLPartsASTPartDefCoCo { + + protected String printPartType(ASTMCType type) { + return type.printType(); + } + + @Override + public void check(ASTPartDef node) { + var nonExistent = node.streamSpecializations() + .filter(s -> s instanceof ASTSysMLRefinement) + .flatMap(ASTSpecialization::streamSuperTypes) + .filter(t -> + node.getEnclosingScope().resolvePartDef(printPartType(t)).isEmpty() + ) + .collect(Collectors.toList()); + + for (var problem : nonExistent) { + Log.error( + "0x10AA2 The name used in 'refines' \"" + printPartType(problem) + + "\" does not exist as a part definition.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } +} diff --git a/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java new file mode 100644 index 00000000..550587ac --- /dev/null +++ b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java @@ -0,0 +1,71 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.RefinementTargetDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RefinementTargetDefinitionExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class RefinementTargetDefinitionExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def BasePart;" + + "part def Refining refines BasePart;"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTPartDefCoCo) new RefinementTargetDefinitionExistsCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = "part def Refining refines UndefinedBasePart;"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTPartDefCoCo) new RefinementTargetDefinitionExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA2"))); + } + } + +} From e902c56b93b4b67d208a1be6e6d6a148c41d526a Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:00:26 +0100 Subject: [PATCH 3/8] UniqueSubPartNamesInConnectionCoCo (#34) * CoCo3 * Test for MKPX_CoCo3 * wrong import * rennamed and changed error codes --- .../UniqueSubPartNamesInConnectionCoCo.java | 72 +++++++++++++ ...niqueSubPartNamesInConnectionCoCoTest.java | 102 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java create mode 100644 language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java new file mode 100644 index 00000000..70161fe8 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java @@ -0,0 +1,72 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +/** + * CoCo3: Jeder in "connect a.b to c.d" verwendete Name von Subkomponenten (a und c) + * muss eindeutig im Modell vorhanden sein. + */ +public class UniqueSubPartNamesInConnectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; // keine Verbindung + } + + var scope = node.getEnclosingScope(); + + checkSubcomponentQualifier(scope, node.getSrc(), node); + checkSubcomponentQualifier(scope, node.getTgt(), node); + } + + protected void checkSubcomponentQualifier(ISysMLPartsScope scope, + ASTEndpoint endpoint, + ASTConnectionUsage node) { + String qname = endpointQName(endpoint); + String subName = extractFirstSegment(qname); + + if (subName == null) { + return; + } + + int matches = scope + .resolvePartUsageLocallyMany(false, subName, + AccessModifier.ALL_INCLUSION, p -> true) + .size(); + + if (matches > 1) { + Log.error( + "0x10AA3 The subcomponent name used in 'connect' \"" + subName + + "\" is not unique in the model.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } else if (matches != 1) { /** matches = 0 */ + Log.error( + "0x10AA3 The subcomponent name used in 'connect' \"" + subName + + "\" does not exist in the model.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + protected String endpointQName(ASTEndpoint ep) { + return ep.getMCQualifiedName() != null ? ep.getMCQualifiedName().toString() : ""; + } + + protected String extractFirstSegment(String qname) { + int dot = qname.indexOf('.'); + if (dot > 0) { + return qname.substring(0, dot); + } + return null; + } +} diff --git a/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java new file mode 100644 index 00000000..d7d11bca --- /dev/null +++ b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java @@ -0,0 +1,102 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.UniqueSubPartNamesInConnectionCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UniqueSubPartNamesInConnectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class UniqueSubPartNamesInConnectionCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.p to b.q;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new UniqueSubPartNamesInConnectionCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalidUndefined() throws IOException { + String invalidModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "connect a.p to c.q;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new UniqueSubPartNamesInConnectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA3"))); + } + + @Test + public void testInvalidDuplicateName() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part a: A;" + + "part a: A;" + + "connect a.p to a.p;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new UniqueSubPartNamesInConnectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA3"))); + } + } +} From 5dcd4250ad66a5bfbf1703ef40fa07d12e0dbd3f Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:01:20 +0100 Subject: [PATCH 4/8] QualifiedPortNameExistsCoCo (#35) * CoCo4 * not responsible for undefined parts covered in MKPX_CoCo3 * Test for MKPX_CoCo4 * add parser and Model Path * Missing Import SysMLv2Parser * Missing import assertFalse * Update MKPXCoCo4Test.java * Update MKPXCoCo4Test.java * Update MKPXCoCo4Test.java * Update MKPXCoCo4Test.java * syntax error fixed * renamed and changed error code --- .../cocos/QualifiedPortNameExistsCoCo.java | 96 +++++++++++++++++++ .../QualifiedPortNameExistsCoCoTest.java | 84 ++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java create mode 100644 language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java new file mode 100644 index 00000000..f63d428e --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java @@ -0,0 +1,96 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.lang.sysmlparts._symboltable.PartDefSymbol; +import de.monticore.lang.sysmlparts._symboltable.PartUsageSymbol; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * QualifiedPortNameExistsCoCo: + * In einer Verbindung "connect a.b to c.d" muss jeder verwendete (qualifizierte) Portname existieren. + */ +public class QualifiedPortNameExistsCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; // keine Verbindung + } + + ISysMLPartsScope scope = node.getEnclosingScope(); + + checkEndpoint(scope, node.getSrc(), node); + checkEndpoint(scope, node.getTgt(), node); + } + + protected void checkEndpoint(ISysMLPartsScope scope, + ASTEndpoint endpoint, + ASTConnectionUsage conn) { + String qname = endpointQName(endpoint); + if (qname.isEmpty()) { + return; + } + + String[] parts = qname.split("\\."); + + // Qualifizierter Name: a.b -> resolve a (PartUsage), dann b in PartDef + if (parts.length >= 2) { + String partName = parts[0]; + String portName = parts[parts.length - 1]; + + Optional partOpt = scope.resolvePartUsageLocally(partName); + + if (partOpt.isEmpty()) { + // Existenz der Subkomponente wird bereits in MKPX_CoCo3 geprüft. + return; + } + + var partDefOpt = partOpt.get().getPartDef(); + if (partDefOpt.isEmpty()) { + // CoCo3 + return; + } + + PartDefSymbol partDef = partDefOpt.get(); + boolean portExistsInDef = partDef.getSpannedScope() + .resolvePortUsageLocallyMany(false, portName, AccessModifier.ALL_INCLUSION, p -> true) + .size() == 1; + + if (!portExistsInDef) { + Log.error( + "0x10AA4 The port '" + portName + "' does not exist in the definition of subcomponent '" + + partName + "'.", + conn.get_SourcePositionStart(), + conn.get_SourcePositionEnd() + ); + } + } + else { + // Unqualifiziert: Port der Oberkomponente muss lokal existieren + String portName = parts[0]; + List localPorts = scope.resolvePortUsageLocallyMany( + false, portName, AccessModifier.ALL_INCLUSION, p -> true); + if (localPorts.isEmpty()) { + Log.error( + "0x10AA4 The port used in 'connect' '" + portName + + "' does not exist in the parent component.", + conn.get_SourcePositionStart(), + conn.get_SourcePositionEnd() + ); + } + } + } + + /** Liefert den qualifizierten Namen des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + return ep.getMCQualifiedName() != null ? ep.getMCQualifiedName().toString() : ""; + } +} diff --git a/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java new file mode 100644 index 00000000..8598a274 --- /dev/null +++ b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java @@ -0,0 +1,84 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.QualifiedPortNameExistsCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class QualifiedPortNameExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class QualifiedPortNameExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.p to b.q;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new QualifiedPortNameExistsCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.wrongName to b.q;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new QualifiedPortNameExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA4"))); + } + } +} From 70b4201b8f966101326942f38916ac00a2b796e9 Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:55:31 +0100 Subject: [PATCH 5/8] ParentComponentInputConnectionDirectionCoCo (#37) * CoCo6 * clean up * Update MKPX_CoCo6.java * Test for MKPX_CoCo6 * add parser and model path * Missing Import SysMLv2Parser * Missing import assertFalse * Update MKPXCoCo6Test.java * Update MKPXCoCo6Test.java * Update MKPXCoCo6Test.java * explicit port direction * fixed resolving issue * added warnings for inout ports * rename and new error codes * conjugated Ports allowed and Tests added --- ...ComponentInputConnectionDirectionCoCo.java | 197 ++++++++++++++++++ ...onentInputConnectionDirectionCoCoTest.java | 134 ++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java create mode 100644 language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java new file mode 100644 index 00000000..49e05c85 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java @@ -0,0 +1,197 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTModifier; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLTyping; +import de.monticore.lang.sysmlparts._ast.ASTAttributeUsage; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.lang.sysmlparts._symboltable.PartDefSymbol; +import de.monticore.lang.sysmlparts._symboltable.PartUsageSymbol; +import de.monticore.lang.sysmlparts._symboltable.PortUsageSymbol; +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * ParentComponentInputConnectionDirectionCoCo + * Inputs von Oberkomponenten können nur zu Inputs von Subkomponenten oder Outputs der Oberkomponenten verbunden werden + * (alternative Formulierung): + * Inputs von Oberkomponenten können nicht zu Outputs von Subkomponenten verbunden werden. + */ +public class ParentComponentInputConnectionDirectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + // Skip validation if endpoints are missing + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; + } + + ASTEndpoint src = node.getSrc(); + ASTEndpoint tgt = node.getTgt(); + + String srcQName = endpointQName(src); + String tgtQName = endpointQName(tgt); + + ISysMLPartsScope scope = node.getEnclosingScope(); + + // Determine if endpoints reference subcomponents + boolean tgtIsSub = isSubcomponentEndpoint(tgtQName); + boolean srcIsSub = isSubcomponentEndpoint(srcQName); + + // Resolve port symbols + PortUsageSymbol tgtPort = resolvePortSymbol(scope, tgtQName, tgtIsSub); + PortUsageSymbol srcPort = resolvePortSymbol(scope, srcQName, srcIsSub); + + // If any port is unresolvable, let other CoCos handle it + if (srcPort == null || tgtPort == null) { + return; + } + + // Classify ports + boolean tgtIsInput = portIsInput(tgtPort); + boolean tgtIsOutput = portIsOutput(tgtPort); + + // Allowed connections: + // 1. Subcomponent with Input ports + // 2. Parent component with Output ports + boolean allowed = (tgtIsSub && tgtIsInput) || (!tgtIsSub && tgtIsOutput); + + if(srcIsSub || !portIsInput(srcPort)){ + // Source is neither input nor ParentComponent + // CoCo does not apply + return; + } + if( + (portIsInOutput(srcPort)) || + (portIsInOutput(tgtPort)) + ) { + Log.warn("0x10AA6 Warning: Connection involves an 'inout' port which may have ambiguous directionality.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd()); + } + + if (!allowed) { + Log.error( + "0x10AA6 Illegal connection: inputs of parent components can only be " + + "connected to inputs of subcomponents or outputs of the parent component.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + // -------- Hilfsmethoden -------- + + /** Qualified Name des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + if (ep.getMCQualifiedName() != null) { + return ep.getMCQualifiedName().toString(); + } + return ""; + } + + /** + * Heuristik: ein Name mit '.' steht für einen Port einer Subkomponente, + * z.B. "a.out". Ohne Punkt = Port der Oberkomponente. + */ + protected boolean isSubcomponentEndpoint(String qname) { + return qname.contains("."); + } + + /** Resolve port symbol based on whether it's in a subcomponent */ + protected PortUsageSymbol resolvePortSymbol(ISysMLPartsScope scope, + String qname, boolean isSub) { + if (isSub) { + return resolvePortOfSubPart(scope, qname); + } else { + return resolvePort(scope, qname); + } + } + + /** Resolve PortUsageSymbol by qualified name */ + protected PortUsageSymbol resolvePort(ISysMLPartsScope scope, String qname) { + List result = scope.resolvePortUsageSubKinds( + true, // search in enclosing scopes + qname, // qualified name, e.g., "a.out" + AccessModifier.ALL_INCLUSION, // ignore visibility + p -> true // no additional filtering + ); + + if (result.isEmpty()) { + return scope.resolvePortUsage(qname).orElse(null); + } + // If multiple candidates, take the first (should be unique in your models) + return result.get(0); + } + + /** Resolve port within a subcomponent */ + protected PortUsageSymbol resolvePortOfSubPart(ISysMLPartsScope scope, + String qname) { + // Split "a.out" into part "a" and port "out" + int lastDot = qname.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } + + String partName = qname.substring(0, lastDot); + String portName = qname.substring(lastDot + 1); + + // Resolve the subcomponent + Optional partSymbol = scope.resolvePartUsage(partName); + if (partSymbol.isEmpty()) { + return null; + } + + Optional partDef = partSymbol.get().getPartDef(); + if (partDef.isPresent()) { + ISysMLPartsScope partScope = partDef.get().getSpannedScope(); + return resolvePort(partScope, portName); + } + + return null; + } + + /** Extract modifiers from PortUsageSymbol */ + protected ASTModifier getModifiersFromPortUsageSymbol(PortUsageSymbol symbol) { + ASTAttributeUsage portAttributeUsageAST = (ASTAttributeUsage) + symbol.getAstNode() + .getPortDefs() + .get(0) + .getSysMLElementList() + .get(0); + return portAttributeUsageAST.getModifier(); + } + + protected boolean portIsInput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsInAndNotConjugated = mods.isIn() && !portIsConjugated(symbol); + boolean portIsOutAndConjugated = mods.isOut() && portIsConjugated(symbol); + return (portIsInAndNotConjugated || portIsOutAndConjugated); + } + + protected boolean portIsOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsOutAndNotConjugated = mods.isOut() && !portIsConjugated(symbol); + boolean portIsInAndConjugated = mods.isIn() && portIsConjugated(symbol); + return (portIsOutAndNotConjugated || portIsInAndConjugated); + } + + protected boolean portIsInOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + return mods.isInout(); + } + + protected boolean portIsConjugated(PortUsageSymbol symbol) { + return + ((ASTSysMLTyping) symbol + .getAstNode() + .getSpecialization(0)) + .isConjugated(); + } +} diff --git a/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java new file mode 100644 index 00000000..45b1dfc0 --- /dev/null +++ b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java @@ -0,0 +1,134 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.ParentComponentInputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ParentComponentInputConnectionDirectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class InputConnectionTests { + @Test + public void testValid() throws IOException { + String validModel = + "port def InPort { in attribute data: int; }" + + "port def OutPort { out attribute data: int; }" + + "part def A { port input: InPort; }" + + "part def B { port output: OutPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "port sysInAnother: InPort;" + + "port sysOut: OutPort;" + + "part a: A;" + + "part b: B;" + + "connect sysIn to a.input;" + + "connect sysInAnother to sysOut;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testValidConjugatedModel() throws IOException { + String validModel = + "port def InPort { in attribute data: int; }" + + "part def A { port input: InPort; }" + + "part def B { port output: ~InPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "port sysInAnother: InPort;" + + "port sysOut: ~InPort;" + + "part a: A;" + + "part b: B;" + + "connect sysIn to a.input;" + + "connect sysInAnother to sysOut;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "port def InPort { in attribute data: int; }" + + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "part a: A;" + + "connect sysIn to a.output;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA6"))); + } + + @Test + public void testInvalidConjugatedModel() throws IOException { + String invalidModel = + "port def InPort { in attribute data: int; }" + + "part def A { port output: ~InPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "part a: A;" + + "connect sysIn to a.output;" + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA6"))); + } + } +} From 26354f4548932c39ad8f7e1f3a0b3d205d35ac6b Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:55:45 +0100 Subject: [PATCH 6/8] SubcomponentOutputConnectionDirectionCoCo (#36) * CoCo5 * wrong dir and clean up * add Test for MKPX_CoCo5 * add parser and model path * Missing Import SysMLv2Parser * Missing import assertFalse * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPX_CoCo5.java * Revert "Update MKPX_CoCo5.java" This reverts commit ae76ff63630a7c01eb9602fc1d2fd0e3103888b2. * Update MKPX_CoCo5.java * Revert "Update MKPX_CoCo5.java" This reverts commit 710e2b812b7d20161129b66eafb7a84dfaeb30ae. * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * Update MKPX_CoCo5.java * Update MKPX_CoCo5.java * Update MKPXCoCo5Test.java * Update MKPX_CoCo5.java * explicit port def from (0_valid.sysml) * syntax error * Update MKPXCoCo5Test.java * Update MKPXCoCo5Test.java * fixed resolving method * added warning for inout ports * extended warning for target port * renamed and new error codes * allow conjugated ports * added Tests for conjuagted Ports * clean up * Update SubcomponentOutputConnectionDirectionCoCo.java --- ...omponentOutputConnectionDirectionCoCo.java | 194 ++++++++++++++++++ ...nentOutputConnectionDirectionCoCoTest.java | 155 ++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java create mode 100644 language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java new file mode 100644 index 00000000..8a3389c3 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java @@ -0,0 +1,194 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTModifier; +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLTyping; +import de.monticore.lang.sysmlparts._ast.ASTAttributeUsage; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.*; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * SubcomponentOutputConnectionDirectionCoCo + * Checks that outputs of subcomponents can only be connected to: + * 1. Inputs of subcomponents, or + * 2. Outputs of the parent component. + */ +public class SubcomponentOutputConnectionDirectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + // Skip validation if endpoints are missing + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; + } + + ASTEndpoint src = node.getSrc(); + ASTEndpoint tgt = node.getTgt(); + + String srcQName = endpointQName(src); + String tgtQName = endpointQName(tgt); + + ISysMLPartsScope scope = node.getEnclosingScope(); + + // Determine if endpoints reference subcomponents + boolean tgtIsSub = isSubcomponentEndpoint(tgtQName); + boolean srcIsSub = isSubcomponentEndpoint(srcQName); + + // Resolve port symbols + PortUsageSymbol tgtPort = resolvePortSymbol(scope, tgtQName, tgtIsSub); + PortUsageSymbol srcPort = resolvePortSymbol(scope, srcQName, srcIsSub); + + // If any port is unresolvable, let other CoCos handle it + if (srcPort == null || tgtPort == null) { + return; + } + + // Classify ports + boolean tgtIsInput = portIsInput(tgtPort); + boolean tgtIsOutput = portIsOutput(tgtPort); + + // Allowed connections: + // 1. Subcomponent with Input ports + // 2. Parent component with Output ports + boolean allowed = (tgtIsSub && tgtIsInput) || (!tgtIsSub && tgtIsOutput); + + if(!srcIsSub || !portIsOutput(srcPort)){ + // Source is neither output nor Subcomponent + // CoCo does not apply + return; + } + if( + (portIsInOutput(srcPort)) || + (portIsInOutput(tgtPort)) + ) { + Log.warn("0x10AA5 Warning: Connection involves an 'inout' port which may have ambiguous directionality.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd()); + } + + if (!allowed) { + Log.error( + "0x10AA5 Illegal connection: outputs of subcomponents can only be " + + "connected to inputs of subcomponents or outputs of the parent component.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + // -------- Hilfsmethoden -------- + + /** Qualified Name des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + if (ep.getMCQualifiedName() != null) { + return ep.getMCQualifiedName().toString(); + } + return ""; + } + + /** + * Heuristik: ein Name mit '.' steht für einen Port einer Subkomponente, + * z.B. "a.out". Ohne Punkt = Port der Oberkomponente. + */ + protected boolean isSubcomponentEndpoint(String qname) { + return qname.contains("."); + } + + /** Resolve port symbol based on whether it's in a subcomponent */ + protected PortUsageSymbol resolvePortSymbol(ISysMLPartsScope scope, + String qname, boolean isSub) { + if (isSub) { + return resolvePortOfSubPart(scope, qname); + } else { + return resolvePort(scope, qname); + } + } + + /** Resolve PortUsageSymbol by qualified name */ + protected PortUsageSymbol resolvePort(ISysMLPartsScope scope, String qname) { + List result = scope.resolvePortUsageSubKinds( + true, // search in enclosing scopes + qname, // qualified name, e.g., "a.out" + AccessModifier.ALL_INCLUSION, // ignore visibility + p -> true // no additional filtering + ); + + if (result.isEmpty()) { + return scope.resolvePortUsage(qname).orElse(null); + } + // If multiple candidates, take the first (should be unique in your models) + return result.get(0); + } + + /** Resolve port within a subcomponent */ + protected PortUsageSymbol resolvePortOfSubPart(ISysMLPartsScope scope, + String qname) { + // Split "a.out" into part "a" and port "out" + int lastDot = qname.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } + + String partName = qname.substring(0, lastDot); + String portName = qname.substring(lastDot + 1); + + // Resolve the subcomponent + Optional partSymbol = scope.resolvePartUsage(partName); + if (partSymbol.isEmpty()) { + return null; + } + + Optional partDef = partSymbol.get().getPartDef(); + if (partDef.isPresent()) { + ISysMLPartsScope partScope = partDef.get().getSpannedScope(); + return resolvePort(partScope, portName); + } + + return null; + } + + /** Extract modifiers from PortUsageSymbol */ + protected ASTModifier getModifiersFromPortUsageSymbol(PortUsageSymbol symbol) { + ASTAttributeUsage portAttributeUsageAST = (ASTAttributeUsage) + symbol.getAstNode() + .getPortDefs() + .get(0) + .getSysMLElementList() + .get(0); + return portAttributeUsageAST.getModifier(); + } + + protected boolean portIsInput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsInAndNotConjugated = mods.isIn() && !portIsConjugated(symbol); + boolean portIsOutAndConjugated = mods.isOut() && portIsConjugated(symbol); + return (portIsInAndNotConjugated || portIsOutAndConjugated); + } + + protected boolean portIsOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsOutAndNotConjugated = mods.isOut() && !portIsConjugated(symbol); + boolean portIsInAndConjugated = mods.isIn() && portIsConjugated(symbol); + return (portIsOutAndNotConjugated || portIsInAndConjugated); + } + + protected boolean portIsInOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + return mods.isInout(); + } + + protected boolean portIsConjugated(PortUsageSymbol symbol) { + return + ((ASTSysMLTyping) symbol + .getAstNode() + .getSpecialization(0)) + .isConjugated(); + } +} diff --git a/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java new file mode 100644 index 00000000..3e706be7 --- /dev/null +++ b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java @@ -0,0 +1,155 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2.cocos.SubcomponentOutputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SubcomponentOutputConnectionDirectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + Log.getFindings().clear(); + Log.enableFailQuick(true); + } + + @Nested + public class OutputConnectionTests { + + @Test + public void testValid() throws IOException { + String validModel = + "port def OutPort { out attribute data: int; }" + + "port def InPort { in attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port input: InPort; }" + + "part def C { port output: OutPort; }" + + "part def System {" + + "port sysOutput: OutPort;" + + "part a: A;" + + "part b: B;" + + "part c: C;" + + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + + "}"; + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testValidConjugatedModel() throws IOException { + String validModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port input: ~OutPort; }" + + "part def C { port output: OutPort; }" + + "part def System {" + + "port sysOutput: OutPort;" + + "part a: A;" + + "part b: B;" + + "part c: C;" + + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + + "}"; + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + checker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testInvalidSubOutToSubOut() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port output: OutPort; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.output to b.output;" // (Sub) Output -> (Sub) Output + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + } + + @Test + public void testInvalidSubOutToMainInConjugatedModel() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysInput: ~OutPort;" + + "part a: A;" + + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + } + + @Test + public void testInvalidSubOutToMainIn() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "port def InPort { in attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysInput: InPort;" + + "part a: A;" + + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + + "}"; + + ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); + SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + assertTrue(Log.getFindings().stream() + .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + } + } +} From a825cd090be313c1d94beecbf7d832cfc7575502 Mon Sep 17 00:00:00 2001 From: Mike <127297267+MKZaito@users.noreply.github.com> Date: Sat, 10 Jan 2026 14:33:47 +0100 Subject: [PATCH 7/8] Clean Up Tests (#42) use SysMLv2Tool for scope genration and symboltable creation --- ...onentInputConnectionDirectionCoCoTest.java | 83 +++++++++----- .../PartTypeDefinitionExistsCoCoTest.java | 61 +++++++--- .../QualifiedPortNameExistsCoCoTest.java | 60 +++++++--- ...inementTargetDefinitionExistsCoCoTest.java | 61 +++++++--- ...nentOutputConnectionDirectionCoCoTest.java | 96 ++++++++++------ ...niqueSubPartNamesInConnectionCoCoTest.java | 108 ++++++++++++++---- 6 files changed, 326 insertions(+), 143 deletions(-) diff --git a/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java index 45b1dfc0..4374e2b7 100644 --- a/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java +++ b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java @@ -1,22 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.ParentComponentInputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class ParentComponentInputConnectionDirectionCoCoTest { @@ -33,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -56,12 +61,10 @@ public void testValid() throws IOException { + "connect sysInAnother to sysOut;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -80,12 +83,10 @@ public void testValidConjugatedModel() throws IOException { + "connect sysInAnother to sysOut;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -100,14 +101,11 @@ public void testInvalid() throws IOException { + "connect sysIn to a.output;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); - Log.enableFailQuick(false); - checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA6"))); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA6"); } @Test @@ -121,14 +119,39 @@ public void testInvalidConjugatedModel() throws IOException { + "connect sysIn to a.output;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA6"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new ParentComponentInputConnectionDirectionCoCo()); + checker.addCoCo(new ParentComponentInputConnectionDirectionCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA6"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); } } } diff --git a/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java index 3a86deed..a5a0f380 100644 --- a/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java +++ b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java @@ -1,22 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.PartTypeDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class PartTypeDefinitionExistsCoCoTest { @@ -33,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -49,12 +54,10 @@ public void testValid() throws IOException { + "part subcomp2: SubComponent2;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTPartUsageCoCo) new PartTypeDefinitionExistsCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -66,15 +69,39 @@ public void testInvalid() throws IOException { + "part subcomp2: UndefinedComponent;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA1"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTPartUsageCoCo) new PartTypeDefinitionExistsCoCo()); + checker.addCoCo(new PartTypeDefinitionExistsCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA1"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); } - } + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } } diff --git a/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java index 8598a274..b55a6e8e 100644 --- a/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java +++ b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java @@ -1,24 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.QualifiedPortNameExistsCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class QualifiedPortNameExistsCoCoTest { @@ -35,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -52,12 +55,10 @@ public void testValid() throws IOException { + "connect a.p to b.q;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo(new QualifiedPortNameExistsCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -71,14 +72,39 @@ public void testInvalid() throws IOException { + "connect a.wrongName to b.q;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA4"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); checker.addCoCo(new QualifiedPortNameExistsCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA4"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); } } } diff --git a/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java index 550587ac..a9e924de 100644 --- a/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java +++ b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java @@ -1,22 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.RefinementTargetDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class RefinementTargetDefinitionExistsCoCoTest { @@ -33,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -45,27 +50,49 @@ public void testValid() throws IOException { "part def BasePart;" + "part def Refining refines BasePart;"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTPartDefCoCo) new RefinementTargetDefinitionExistsCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test public void testInvalid() throws IOException { String invalidModel = "part def Refining refines UndefinedBasePart;"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA2"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTPartDefCoCo) new RefinementTargetDefinitionExistsCoCo()); + checker.addCoCo(new RefinementTargetDefinitionExistsCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA2"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); } - } + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } } diff --git a/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java index 3e706be7..18c74360 100644 --- a/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java +++ b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java @@ -1,22 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.SubcomponentOutputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class SubcomponentOutputConnectionDirectionCoCoTest { @@ -33,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -56,12 +61,10 @@ public void testValid() throws IOException { + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -79,12 +82,10 @@ public void testValidConjugatedModel() throws IOException { + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -99,14 +100,11 @@ public void testInvalidSubOutToSubOut() throws IOException { + "connect a.output to b.output;" // (Sub) Output -> (Sub) Output + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); - Log.enableFailQuick(false); - checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); } @Test @@ -120,14 +118,11 @@ public void testInvalidSubOutToMainInConjugatedModel() throws IOException { + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); - Log.enableFailQuick(false); - checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); } @Test @@ -142,14 +137,39 @@ public void testInvalidSubOutToMainIn() throws IOException { + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new SubcomponentOutputConnectionDirectionCoCo()); + checker.addCoCo(new SubcomponentOutputConnectionDirectionCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA5"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); } } } diff --git a/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java index d7d11bca..38913d6b 100644 --- a/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java +++ b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java @@ -1,23 +1,26 @@ /* (c) https://github.com/MontiCore/monticore */ package cocos; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; -import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; import de.monticore.lang.sysmlv2.cocos.UniqueSubPartNamesInConnectionCoCo; +import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class UniqueSubPartNamesInConnectionCoCoTest { @@ -34,8 +37,9 @@ public static void init() { @BeforeEach public void reset() { SysMLv2Mill.globalScope().clear(); - Log.getFindings().clear(); - Log.enableFailQuick(true); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); } @Nested @@ -51,12 +55,10 @@ public void testValid() throws IOException { + "connect a.p to b.q;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(validModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new UniqueSubPartNamesInConnectionCoCo()); - checker.checkAll(ast); - assertTrue(Log.getFindings().isEmpty()); + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); } @Test @@ -69,14 +71,11 @@ public void testInvalidUndefined() throws IOException { + "connect a.p to c.q;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); - var checker = new SysMLv2CoCoChecker(); - checker.addCoCo((SysMLPartsASTConnectionUsageCoCo) new UniqueSubPartNamesInConnectionCoCo()); - Log.enableFailQuick(false); - checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA3"))); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); } @Test @@ -89,14 +88,75 @@ public void testInvalidDuplicateName() throws IOException { + "connect a.p to a.p;" + "}"; - ASTSysMLModel ast = SysMLv2Mill.parser().parse_String(invalidModel).get(); - SysMLv2Mill.scopesGenitorDelegator().createFromAST(ast); + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + @Test + public void testInvalidBothUndefined() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part a: A;" + + "connect undefined1.p to undefined2.p;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + @Test + public void testInvalidUndefinedAndDuplicateName() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part duplicate1: A;" + + "part duplicate1: ~A;" + + "connect duplicate1.p to undefined3.p;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { var checker = new SysMLv2CoCoChecker(); checker.addCoCo(new UniqueSubPartNamesInConnectionCoCo()); Log.enableFailQuick(false); checker.checkAll(ast); - assertTrue(Log.getFindings().stream() - .anyMatch(f -> f.getMsg().contains("0x10AA3"))); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); } } } From 8a71680f477f64c8e73c74a0f046fcf05cac91f5 Mon Sep 17 00:00:00 2001 From: MKZaito <127297267+MKZaito@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:57:41 +0100 Subject: [PATCH 8/8] version bump to 7.8.3 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cda01f97..4cf7d37a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ assertj_version = 3.21.0 junit_version = 5.8.2 # Version of published artifacts -version = 7.8.2 +version = 7.8.3