diff --git a/.claude/commands/implement-extensions-batch.md b/.claude/commands/implement-extensions-batch.md index a6a2bd7a..18a47a58 100644 --- a/.claude/commands/implement-extensions-batch.md +++ b/.claude/commands/implement-extensions-batch.md @@ -51,6 +51,38 @@ stub-blocker test pattern (see template) when an in-scope test would otherwise need to traverse a still-stubbed upstream method that is NOT part of the current batch. +## Pre-flight: detect orchestrator plan mode + +If the orchestrator session is itself in **plan mode** at the moment +`/implement-extensions-batch` runs, spawned sub-agents (researchers, implementers, +testers, reviewers) inherit that state. The Agent tool's `mode: "acceptEdits"` +parameter does NOT override the inherited plan-mode state on the current Claude +Code build — sub-agents will respect the `` that declares plan +mode and refuse to apply any edits, even though their prompts tell them to. + +Symptom: every sub-agent reports "ready to execute on exit from plan mode" and +writes its work to its own per-agent plan file at +`C:\Users\\.claude\plans\-agent-.md` instead of to the +target file. + +Before spawning any sub-agent, check whether plan mode is active in the +orchestrator session. If it is: + +1. **Stop** and surface the situation to the user with `AskUserQuestion`. Two + options: + - **Exit plan mode first** (user toggles their harness off plan mode, then + re-invokes the command). Cleanest. + - **Proceed in degraded mode**: spawn researchers as normal (they only need + to write to `.team-notes/`, which the orchestrator can copy into place + from their per-agent plan files if blocked). For Phase IT (implementers + + testers) and Phase RV (reviewers), the orchestrator applies the + production / test edits itself, reading each implementer's plan file to + extract the verbatim code. Reviewers can still run read-only. + +2. If the user picks degraded mode, set an internal `PLAN_MODE_DEGRADED=true` + flag for the run and follow the per-phase divergences in the **Notes for + the orchestrator** block below. + ## Workflow ### 1. Parse `$ARGUMENTS` and validate the batch @@ -310,6 +342,8 @@ Print to the user: | Sibling test failure in regression sweep | Step 11 | Dispatch a regression-sweep tester per touched fixture; in scope. | | Reviewer NEEDS FIX | Step 12 | Re-dispatch implementer or tester for that file only; other files' results still reported. | | One file's implementation fails after branch + assignment | Any step ≥ 6 | Keep the branch; surface in final summary; user decides whether to retry via `/implement-extensions` for that single file or revert. | +| Sub-agent inherits orchestrator plan mode and refuses to edit | Phases R / IT / RV | Surface to user via `AskUserQuestion`. Either exit plan mode and retry, or proceed in degraded mode (orchestrator copies researcher specs from agent plan files into `.team-notes/`, applies implementer + tester code from agent plan files, runs reviewers as read-only). | +| Agent's `mode: "acceptEdits"` parameter does not override inherited plan mode | Phases IT / RV | Known limitation of this Claude Code build. The orchestrator must apply the edits itself in degraded mode (see "Pre-flight: detect orchestrator plan mode"). | ## Parallelism caps (orchestrator self-enforced) @@ -339,3 +373,25 @@ Print to the user: - If the user supplies a single file, route them to `/implement-extensions` with the same filename rather than creating a degenerate 1-file "batch" branch. +- **Plan-mode degraded mode** (`PLAN_MODE_DEGRADED=true`): + - **Phase R**: researchers will write to per-agent plan files instead of + `.team-notes/`. After each returns, copy its plan file from + `C:\Users\\.claude\plans\-agent-.md` into + `.team-notes/-extensions-spec.md` and verify content matches the + schema the template expects. + - **Phase IT**: implementers + testers will write to per-agent plan files, + NOT to the production / test fixtures. The orchestrator reads each + agent's plan file, extracts the verbatim code, and applies the edits + itself via `Edit` / `Write`. Build + targeted tests still run in + Phase V. Do NOT mark Phase IT complete on the sub-agent's "ready to + execute" message alone — only after the orchestrator has applied each + file's diffs and the build is green. + - **Phase RV**: reviewers operate read-only, so plan mode does not block + them. No degradation needed. + - **Sanity check**: in degraded mode, the orchestrator does roughly 2× the + work it would in normal mode. Budget for it — do not silently fall + behind on Phase V verification just because Phase IT cost more turns. +- The Agent tool's `mode` parameter cannot reliably escape inherited plan mode + on this Claude Code build. The orchestrator MUST detect plan mode at + pre-flight and pick the degraded-mode branch deliberately rather than + assuming `mode: "acceptEdits"` will work. diff --git a/.claude/commands/implement-extensions.md b/.claude/commands/implement-extensions.md index b051b87c..78cad4a5 100644 --- a/.claude/commands/implement-extensions.md +++ b/.claude/commands/implement-extensions.md @@ -45,6 +45,39 @@ This is the user-memory `feedback_scope_discipline.md` rule. Even when an adjace stub blocks dependent test coverage, surface the blocker; do not silently expand scope. Use the stub-blocker test pattern (see template). +## Pre-flight: detect orchestrator plan mode + +If the orchestrator session is itself in **plan mode** at the moment +`/implement-extensions` runs, spawned sub-agents (researcher, implementer, tester, +reviewer) inherit that state. The Agent tool's `mode: "acceptEdits"` parameter +does NOT override the inherited plan-mode state on the current Claude Code build +— sub-agents will respect the `` that declares plan mode and +refuse to apply any edits, even though their prompts tell them to. + +Symptom: every sub-agent reports "ready to execute on exit from plan mode" and +writes its work to its own per-agent plan file at +`C:\Users\\.claude\plans\-agent-.md` instead of to the +target file. + +Before spawning any sub-agent, check whether plan mode is active in the +orchestrator session. If it is: + +1. **Stop** and surface the situation to the user with `AskUserQuestion`. Two + options: + - **Exit plan mode first** (user toggles their harness off plan mode, then + re-invokes the command). Cleanest. + - **Proceed in degraded mode**: spawn the researcher as normal (it only + needs to write to `.team-notes/`, which the orchestrator can copy into + place from the agent's plan file if blocked). For the step-6 implementer + + tester pair and the step-9 reviewer, the orchestrator applies the + production / test edits itself, reading each sub-agent's plan file to + extract the verbatim code. The reviewer is already read-only and runs + unaffected. + +2. If the user picks degraded mode, set an internal `PLAN_MODE_DEGRADED=true` + flag for the run and follow the per-step divergences in the **Notes for + the orchestrator** block at the bottom of this file. + ## Workflow ### 1. Validate input @@ -421,3 +454,31 @@ unresolved findings are separately surfaced in the final-summary report. The implementation state of the file; unresolved findings are separately surfaced in the final-summary report. The `gh issue edit` push must touch ONLY the `### Checklist` section — verify with a re-fetch + diff before reporting "done". +- **Plan-mode degraded mode** (`PLAN_MODE_DEGRADED=true` — set in the pre-flight + step at the top of this file): + - **Step 5 (researcher)**: the researcher will write its spec to a per-agent + plan file under `C:\Users\\.claude\plans\-agent-.md` + instead of `.team-notes/-extensions-spec.md`. After it returns, copy + the agent plan file into `.team-notes/-extensions-spec.md` and verify + the content matches the schema in `.claude/team-templates/extension-impl.md`. + - **Step 6 (implementer + tester)**: both will write their final production + / test code to per-agent plan files, NOT to `{{PRODUCTION_FILE}}` / + `{{TEST_FILE}}`. The orchestrator reads each agent's plan file, extracts + the verbatim code, and applies the edits itself via `Edit` / `Write`. + Step 7's `dotnet build` + targeted `dotnet test` then runs against the + orchestrator-applied diffs as normal. Do NOT mark step 6 complete on the + sub-agent's "ready to execute" message alone — only after the orchestrator + has applied each file's diffs and the build is green. + - **Step 8 (regression sweep)**: same as step 6 — the regression-sweep + tester writes to a per-agent plan file; orchestrator applies the diffs to + each touched sibling fixture itself. + - **Step 9 (reviewer)**: read-only, so plan mode does not block it. No + degradation needed. + - **Sanity check**: in degraded mode, the orchestrator does roughly 2× the + work it would in normal mode (it now applies the edits the sub-agents + would otherwise apply themselves). Budget for it — do not silently fall + behind on step-7 verification just because step 6 cost more turns. +- The Agent tool's `mode` parameter cannot reliably escape inherited plan mode + on this Claude Code build. The orchestrator MUST detect plan mode at + pre-flight and pick the degraded-mode branch deliberately rather than + assuming `mode: "acceptEdits"` will work. diff --git a/.claude/team-templates/extension-impl.md b/.claude/team-templates/extension-impl.md index b3bb0f58..d8fa1a2d 100644 --- a/.claude/team-templates/extension-impl.md +++ b/.claude/team-templates/extension-impl.md @@ -80,6 +80,31 @@ Don't use this template when: fixtures that now fail get updated as part of the same PR. ``` +## Plan-mode-aware prompting (added 2026-05-28) + +If the orchestrator session is in plan mode when this template is used, sub-agents +will inherit it and cannot apply edits. The Agent-tool `mode: "acceptEdits"` +parameter does NOT override the inherited state on the current Claude Code build. + +Role prompts in this template are written so that the orchestrator can fall back +to applying edits itself when this happens: + +- **Researcher** prompts always direct the agent to write the spec to its + declared `{{NOTES_FILE}}` location. In plan-mode-degraded runs the agent will + write to a per-agent plan file under + `C:\Users\\.claude\plans\-agent-.md` instead; the + orchestrator then copies it to `.team-notes/`. +- **Implementer / tester** prompts must emit the verbatim production / test code + in their text response (or, equivalently, in their per-agent plan file) so it + survives a plan-mode block. The orchestrator extracts and applies it via + `Edit` / `Write`. +- **Reviewer** prompts are already read-only and unaffected by plan mode. + +The `/implement-extensions-batch` command body documents the degraded-mode flow +end-to-end in its "Pre-flight: detect orchestrator plan mode" section. The +single-file `/implement-extensions` command should also adopt the same flow — +see that file's notes section. + ## Hard scope-discipline rule (v2) **Honor user-memory `feedback_scope_discipline.md`**: when the task names a single diff --git a/CLAUDE.md b/CLAUDE.md index 1fa453d0..cd7a925a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,7 +154,7 @@ Auto-generated DTOs use structured namespaces reflecting the KerML/SysML package - Use 'StringBuilder.Append(char)' instead of 'StringBuilder.Append(string)' when the input is a constant unit string - Prefer 'string.IsNullOrWhiteSpace' over 'string.IsNullOrEmpty' when checking the non-nullable value of a string - Prefer switch expressions/statements over if-else chains when applicable -- Prefer indexer syntax (e.g., 'list[^1]') and range syntax (e.g., 'array[1..^1]') over LINQ methods (e.g., 'list.Last()', 'list.Skip(1).Take(n)') when applicable +- Prefer LINQ as much as possible — including for projection / filter / aggregation over collections (`items.Where(...).Select(...).ToList()`, `result.AddRange(items.Select(...))`, `items.Any(predicate)`, etc.) instead of hand-rolled `foreach` + `if` + `.Add()` loops. The ONE exception is straightforward positional or range access on a concrete `List`/array: `list[^1]` beats `list.Last()`, `array[1..^1]` beats `array.Skip(1).SkipLast(1)` — indexer/range syntax is more performant there. Outside that narrow exception, LINQ wins for clarity AND maintainability. - Prefer C# collection expressions (`[a, b, c]`, `[..xs]`, `[]`) over `new[] { ... }`, `new List { ... }`, `new T[] { ... }` when constructing a collection. Applies to both production code AND tests (e.g. `Is.EqualTo([classifier1, classifier2])` not `Is.EqualTo(new[] { classifier1, classifier2 })`, `return [];` not `return new List();`). Fall back to explicit construction only when type inference cannot pick the right collection type. - Use meaningful variable names instead of single-letter names in any context (e.g., 'charIndex' instead of 'i', 'currentChar' instead of 'c', 'element' instead of 'e') - Use 'NotSupportedException' (not 'NotImplementedException') for placeholder/stub methods that require manual implementation diff --git a/SysML2.NET.Tests/Extend/AnnotatingElementExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AnnotatingElementExtensionsTestFixture.cs index 86014a44..7cfb7045 100644 --- a/SysML2.NET.Tests/Extend/AnnotatingElementExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AnnotatingElementExtensionsTestFixture.cs @@ -1,7 +1,7 @@ // ------------------------------------------------------------------------------------------------- // // -// Copyright 2022-2026 Starion Group S.A. +// Copyright (C) 2022-2026 Starion Group S.A. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,36 +21,157 @@ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Kernel.Packages; using SysML2.NET.Core.POCO.Root.Annotations; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; [TestFixture] public class AnnotatingElementExtensionsTestFixture { [Test] - public void ComputeAnnotatedElement_ThrowsNotSupportedException() + public void Verify_ComputeAnnotatedElement() { - Assert.That(() => ((IAnnotatingElement)null).ComputeAnnotatedElement(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotatingElement)null).ComputeAnnotatedElement(), + Throws.TypeOf()); + + // No annotations, no owningNamespace → result is [null] (singleton containing null). + var orphanSubject = new Comment(); + + Assert.That(orphanSubject.ComputeAnnotatedElement(), Is.EqualTo(new IElement[] { null })); + + // No annotations, but subject placed inside a Package → result is [package]. + var ownedNamespaceSubject = new Comment(); + var hostPackage = new Package(); + hostPackage.AssignOwnership(new OwningMembership(), ownedNamespaceSubject); + + Assert.That(ownedNamespaceSubject.ComputeAnnotatedElement(), Is.EqualTo(new[] { hostPackage })); + + // Single owning annotation: annotation owns subject; annotation.AnnotatedElement = annotated. + var owningAnnotatedSubject = new Comment(); + var owningAnnotation = new Annotation(); + var annotated = new Package(); + owningAnnotation.AnnotatedElement = annotated; + ((IContainedRelationship)owningAnnotation).OwnedRelatedElement.Add(owningAnnotatedSubject); + + Assert.That(owningAnnotatedSubject.ComputeAnnotatedElement(), Is.EqualTo(new[] { annotated })); + + // Multiple owned annotations, none target self. + var multiOwnedSubject = new Comment(); + var elementA = new Package(); + var elementB = new Package(); + var ownedAnnotationA = new Annotation { AnnotatedElement = elementA }; + var ownedAnnotationB = new Annotation { AnnotatedElement = elementB }; + ((IContainedElement)multiOwnedSubject).OwnedRelationship.Add(ownedAnnotationA); + ((IContainedElement)multiOwnedSubject).OwnedRelationship.Add(ownedAnnotationB); + + Assert.That(multiOwnedSubject.ComputeAnnotatedElement(), Is.EqualTo(new[] { elementA, elementB })); + + // Owning + 2 owned. + var mixedSubject = new Comment(); + var owningTarget = new Package(); + var owningAnnotation2 = new Annotation { AnnotatedElement = owningTarget }; + ((IContainedRelationship)owningAnnotation2).OwnedRelatedElement.Add(mixedSubject); + + var ownedTarget1 = new Package(); + var ownedTarget2 = new Package(); + var ownedAnn1 = new Annotation { AnnotatedElement = ownedTarget1 }; + var ownedAnn2 = new Annotation { AnnotatedElement = ownedTarget2 }; + ((IContainedElement)mixedSubject).OwnedRelationship.Add(ownedAnn1); + ((IContainedElement)mixedSubject).OwnedRelationship.Add(ownedAnn2); + + Assert.That(mixedSubject.ComputeAnnotatedElement(), Is.EqualTo(new[] { owningTarget, ownedTarget1, ownedTarget2 })); } - + [Test] - public void ComputeAnnotation_ThrowsNotSupportedException() + public void Verify_ComputeAnnotation() { - Assert.That(() => ((IAnnotatingElement)null).ComputeAnnotation(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotatingElement)null).ComputeAnnotation(), + Throws.TypeOf()); + + var emptySubject = new Comment(); + + Assert.That(emptySubject.ComputeAnnotation(), Is.Empty); + + // Only owned annotations (no owning). + var ownedOnlySubject = new Comment(); + var owned1 = new Annotation(); + var owned2 = new Annotation(); + ((IContainedElement)ownedOnlySubject).OwnedRelationship.Add(owned1); + ((IContainedElement)ownedOnlySubject).OwnedRelationship.Add(owned2); + + Assert.That(ownedOnlySubject.ComputeAnnotation(), Is.EqualTo(new[] { owned1, owned2 })); + + // Owning + 2 owned → owning prepended. + var bothSubject = new Comment(); + var owningAnn = new Annotation(); + ((IContainedRelationship)owningAnn).OwnedRelatedElement.Add(bothSubject); + var bothOwned1 = new Annotation(); + var bothOwned2 = new Annotation(); + ((IContainedElement)bothSubject).OwnedRelationship.Add(bothOwned1); + ((IContainedElement)bothSubject).OwnedRelationship.Add(bothOwned2); + + Assert.That(bothSubject.ComputeAnnotation(), Is.EqualTo(new[] { owningAnn, bothOwned1, bothOwned2 })); } - + [Test] - public void ComputeOwnedAnnotatingRelationship_ThrowsNotSupportedException() + public void Verify_ComputeOwnedAnnotatingRelationship() { - Assert.That(() => ((IAnnotatingElement)null).ComputeOwnedAnnotatingRelationship(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotatingElement)null).ComputeOwnedAnnotatingRelationship(), + Throws.TypeOf()); + + var emptySubject = new Comment(); + + Assert.That(emptySubject.ComputeOwnedAnnotatingRelationship(), Is.Empty); + + // Annotation targeting self → filtered out (self is the annotated side, not annotating). + var selfTargetSubject = new Comment(); + var selfAnnotation = new Annotation { AnnotatedElement = selfTargetSubject }; + ((IContainedElement)selfTargetSubject).OwnedRelationship.Add(selfAnnotation); + + Assert.That(selfTargetSubject.ComputeOwnedAnnotatingRelationship(), Is.Empty); + + // Two annotations: one targeting self (filtered), one targeting other (kept). + var mixedSubject = new Comment(); + var selfTargetingAnn = new Annotation { AnnotatedElement = mixedSubject }; + var otherTargetingAnn = new Annotation { AnnotatedElement = new Package() }; + ((IContainedElement)mixedSubject).OwnedRelationship.Add(selfTargetingAnn); + ((IContainedElement)mixedSubject).OwnedRelationship.Add(otherTargetingAnn); + + Assert.That(mixedSubject.ComputeOwnedAnnotatingRelationship(), Is.EqualTo(new[] { otherTargetingAnn })); } - + [Test] - public void ComputeOwningAnnotatingRelationship_ThrowsNotSupportedException() + public void Verify_ComputeOwningAnnotatingRelationship() { - Assert.That(() => ((IAnnotatingElement)null).ComputeOwningAnnotatingRelationship(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotatingElement)null).ComputeOwningAnnotatingRelationship(), + Throws.TypeOf()); + + var noOwningSubject = new Comment(); + + Assert.That(noOwningSubject.ComputeOwningAnnotatingRelationship(), Is.Null); + + // Owning IS an Annotation → returns it. + var annotationOwnedSubject = new Comment(); + var owningAnnotation = new Annotation(); + ((IContainedRelationship)owningAnnotation).OwnedRelatedElement.Add(annotationOwnedSubject); + + Assert.That(annotationOwnedSubject.ComputeOwningAnnotatingRelationship(), Is.SameAs(owningAnnotation)); + + // Owning is a non-Annotation IRelationship (e.g., OwningMembership) → null. + var membershipOwnedSubject = new Comment(); + var hostPackage = new Package(); + hostPackage.AssignOwnership(new OwningMembership(), membershipOwnedSubject); + + Assert.That(membershipOwnedSubject.ComputeOwningAnnotatingRelationship(), Is.Null); } } } diff --git a/SysML2.NET.Tests/Extend/AnnotationExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AnnotationExtensionsTestFixture.cs index fdbf68e5..28f7a9d5 100644 --- a/SysML2.NET.Tests/Extend/AnnotationExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AnnotationExtensionsTestFixture.cs @@ -1,7 +1,7 @@ // ------------------------------------------------------------------------------------------------- // // -// Copyright 2022-2026 Starion Group S.A. +// Copyright (C) 2022-2026 Starion Group S.A. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,36 +21,117 @@ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Kernel.Packages; using SysML2.NET.Core.POCO.Root.Annotations; + using SysML2.NET.Core.POCO.Root.Elements; [TestFixture] public class AnnotationExtensionsTestFixture { [Test] - public void ComputeAnnotatingElement_ThrowsNotSupportedException() + public void Verify_ComputeAnnotatingElement() { - Assert.That(() => ((IAnnotation)null).ComputeAnnotatingElement(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotation)null).ComputeAnnotatingElement(), + Throws.TypeOf()); + + var ownedSubject = new Annotation(); + var ownedAnnotating = new Comment(); + ((IContainedRelationship)ownedSubject).OwnedRelatedElement.Add(ownedAnnotating); + + Assert.That(ownedSubject.ComputeAnnotatingElement(), Is.SameAs(ownedAnnotating)); + + var owningSubject = new Annotation(); + var owningAnnotating = new Comment(); + ((IContainedRelationship)owningSubject).OwningRelatedElement = owningAnnotating; + + Assert.That(owningSubject.ComputeAnnotatingElement(), Is.SameAs(owningAnnotating)); + + var emptySubject = new Annotation(); + + Assert.That(emptySubject.ComputeAnnotatingElement(), Is.Null); } - + [Test] - public void ComputeOwnedAnnotatingElement_ThrowsNotSupportedException() + public void Verify_ComputeOwnedAnnotatingElement() { - Assert.That(() => ((IAnnotation)null).ComputeOwnedAnnotatingElement(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotation)null).ComputeOwnedAnnotatingElement(), + Throws.TypeOf()); + + var emptySubject = new Annotation(); + + Assert.That(emptySubject.ComputeOwnedAnnotatingElement(), Is.Null); + + var nonAnnotatingOnlySubject = new Annotation(); + ((IContainedRelationship)nonAnnotatingOnlySubject).OwnedRelatedElement.Add(new Package()); + + Assert.That(nonAnnotatingOnlySubject.ComputeOwnedAnnotatingElement(), Is.Null); + + var mixedSubject = new Annotation(); + ((IContainedRelationship)mixedSubject).OwnedRelatedElement.Add(new Package()); + var ownedComment = new Comment(); + ((IContainedRelationship)mixedSubject).OwnedRelatedElement.Add(ownedComment); + + // Proves selectByKind, not positional [0]. + Assert.That(mixedSubject.ComputeOwnedAnnotatingElement(), Is.SameAs(ownedComment)); } - + [Test] - public void ComputeOwningAnnotatedElement_ThrowsNotSupportedException() + public void Verify_ComputeOwningAnnotatedElement() { - Assert.That(() => ((IAnnotation)null).ComputeOwningAnnotatedElement(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotation)null).ComputeOwningAnnotatedElement(), + Throws.TypeOf()); + + var noOwningSubject = new Annotation(); + + Assert.That(noOwningSubject.ComputeOwningAnnotatedElement(), Is.Null); + + var equalSubject = new Annotation(); + var annotated = new Package(); + equalSubject.AnnotatedElement = annotated; + ((IContainedRelationship)equalSubject).OwningRelatedElement = annotated; + + Assert.That(equalSubject.ComputeOwningAnnotatedElement(), Is.SameAs(annotated)); + + var unequalSubject = new Annotation(); + unequalSubject.AnnotatedElement = new Package(); + ((IContainedRelationship)unequalSubject).OwningRelatedElement = new Comment(); + + // Load-bearing negative: OwningRelatedElement is set but does not equal AnnotatedElement → null. + Assert.That(unequalSubject.ComputeOwningAnnotatedElement(), Is.Null); + + var nullAnnotatedSubject = new Annotation(); + ((IContainedRelationship)nullAnnotatedSubject).OwningRelatedElement = new Package(); + + Assert.That(nullAnnotatedSubject.ComputeOwningAnnotatedElement(), Is.Null); } - + [Test] - public void ComputeOwningAnnotatingElement_ThrowsNotSupportedException() + public void Verify_ComputeOwningAnnotatingElement() { - Assert.That(() => ((IAnnotation)null).ComputeOwningAnnotatingElement(), Throws.TypeOf()); + Assert.That( + () => ((IAnnotation)null).ComputeOwningAnnotatingElement(), + Throws.TypeOf()); + + var noOwningSubject = new Annotation(); + + Assert.That(noOwningSubject.ComputeOwningAnnotatingElement(), Is.Null); + + var annotatingOwningSubject = new Annotation(); + var owningComment = new Comment(); + ((IContainedRelationship)annotatingOwningSubject).OwningRelatedElement = owningComment; + + Assert.That(annotatingOwningSubject.ComputeOwningAnnotatingElement(), Is.SameAs(owningComment)); + + var nonAnnotatingOwningSubject = new Annotation(); + ((IContainedRelationship)nonAnnotatingOwningSubject).OwningRelatedElement = new Package(); + + Assert.That(nonAnnotatingOwningSubject.ComputeOwningAnnotatingElement(), Is.Null); } } } diff --git a/SysML2.NET.Tests/Extend/AssociationExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AssociationExtensionsTestFixture.cs index c52f4bb0..8137fd57 100644 --- a/SysML2.NET.Tests/Extend/AssociationExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AssociationExtensionsTestFixture.cs @@ -1,7 +1,7 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // // -// Copyright 2022-2026 Starion Group S.A. +// Copyright (C) 2022-2026 Starion Group S.A. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,36 +21,163 @@ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Extensions; [TestFixture] public class AssociationExtensionsTestFixture { [Test] - public void ComputeAssociationEnd_ThrowsNotSupportedException() + public void Verify_ComputeAssociationEnd() { - Assert.That(() => ((IAssociation)null).ComputeAssociationEnd(), Throws.TypeOf()); + Assert.That( + () => ((IAssociation)null).ComputeAssociationEnd(), + Throws.TypeOf()); + + var emptyAssociation = new Association(); + + Assert.That(emptyAssociation.ComputeAssociationEnd(), Is.Empty); + + var nonEndOnlyAssociation = new Association(); + var nonEndFeature = new Feature { IsEnd = false }; + nonEndOnlyAssociation.AssignOwnership(new FeatureMembership(), nonEndFeature); + + Assert.That(nonEndOnlyAssociation.ComputeAssociationEnd(), Is.Empty); + + var mixedAssociation = new Association(); + var mixedNonEnd = new Feature { IsEnd = false }; + var mixedEnd = new Feature { IsEnd = true }; + mixedAssociation.AssignOwnership(new FeatureMembership(), mixedNonEnd); + mixedAssociation.AssignOwnership(new FeatureMembership(), mixedEnd); + + Assert.That(mixedAssociation.ComputeAssociationEnd(), Is.EqualTo(new[] { mixedEnd })); + + var binaryAssociation = new Association(); + var firstEnd = new Feature { IsEnd = true }; + var secondEnd = new Feature { IsEnd = true }; + binaryAssociation.AssignOwnership(new FeatureMembership(), firstEnd); + binaryAssociation.AssignOwnership(new FeatureMembership(), secondEnd); + + Assert.That(binaryAssociation.ComputeAssociationEnd(), Is.EqualTo(new[] { firstEnd, secondEnd })); } - + [Test] - public void ComputeRelatedType_ThrowsNotSupportedException() + public void Verify_ComputeRelatedType() { - Assert.That(() => ((IAssociation)null).ComputeRelatedType(), Throws.TypeOf()); + Assert.That( + () => ((IAssociation)null).ComputeRelatedType(), + Throws.TypeOf()); + + var emptyAssociation = new Association(); + + Assert.That(emptyAssociation.ComputeRelatedType(), Is.Empty); + + var oneEndAssociation = new Association(); + var soleType = new Classifier(); + oneEndAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(soleType)); + + Assert.That(oneEndAssociation.ComputeRelatedType(), Is.EqualTo(new[] { soleType })); + + var twoEndAssociation = new Association(); + var firstType = new Classifier(); + var secondType = new Classifier(); + twoEndAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(firstType)); + twoEndAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(secondType)); + + Assert.That(twoEndAssociation.ComputeRelatedType(), Is.EqualTo(new[] { firstType, secondType })); + + var sharedTypeAssociation = new Association(); + var sharedType = new Classifier(); + sharedTypeAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(sharedType)); + sharedTypeAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(sharedType)); + + // isUnique=false on relatedType — duplicates are retained. + Assert.That(sharedTypeAssociation.ComputeRelatedType(), Is.EqualTo(new[] { sharedType, sharedType })); } - + [Test] - public void ComputeSourceType_ThrowsNotSupportedException() + public void Verify_ComputeSourceType() { - Assert.That(() => ((IAssociation)null).ComputeSourceType(), Throws.TypeOf()); + Assert.That( + () => ((IAssociation)null).ComputeSourceType(), + Throws.TypeOf()); + + var emptyAssociation = new Association(); + + Assert.That(emptyAssociation.ComputeSourceType(), Is.Null); + + var oneRelatedAssociation = new Association(); + var oneRelatedType = new Classifier(); + oneRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(oneRelatedType)); + + Assert.That(oneRelatedAssociation.ComputeSourceType(), Is.SameAs(oneRelatedType)); + + var twoRelatedAssociation = new Association(); + var sourceType = new Classifier(); + var targetType = new Classifier(); + twoRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(sourceType)); + twoRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(targetType)); + + Assert.That(twoRelatedAssociation.ComputeSourceType(), Is.SameAs(sourceType)); } - + [Test] - public void ComputeTargetType_ThrowsNotSupportedException() + public void Verify_ComputeTargetType() + { + Assert.That( + () => ((IAssociation)null).ComputeTargetType(), + Throws.TypeOf()); + + var emptyAssociation = new Association(); + + Assert.That(emptyAssociation.ComputeTargetType(), Is.Empty); + + var oneRelatedAssociation = new Association(); + oneRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(new Classifier())); + + Assert.That(oneRelatedAssociation.ComputeTargetType(), Is.Empty); + + var twoRelatedAssociation = new Association(); + var twoRelatedTargetType = new Classifier(); + twoRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(new Classifier())); + twoRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(twoRelatedTargetType)); + + Assert.That(twoRelatedAssociation.ComputeTargetType(), Is.EqualTo(new[] { twoRelatedTargetType })); + + var threeRelatedAssociation = new Association(); + var threeMiddleType = new Classifier(); + var threeTargetType = new Classifier(); + threeRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(new Classifier())); + threeRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(threeMiddleType)); + threeRelatedAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(threeTargetType)); + + Assert.That(threeRelatedAssociation.ComputeTargetType(), Is.EqualTo(new[] { threeMiddleType, threeTargetType })); + + var dupTailAssociation = new Association(); + var dupSharedType = new Classifier(); + dupTailAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(new Classifier())); + dupTailAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(dupSharedType)); + dupTailAssociation.AssignOwnership(new FeatureMembership(), CreateEndFeature(dupSharedType)); + + // asOrderedSet() in OCL → Distinct() in C#. + Assert.That(dupTailAssociation.ComputeTargetType(), Is.EqualTo(new[] { dupSharedType })); + } + + /// + /// Build an end Feature with a FeatureTyping pointing to . + /// + private static Feature CreateEndFeature(IType featureType) { - Assert.That(() => ((IAssociation)null).ComputeTargetType(), Throws.TypeOf()); + var feature = new Feature { IsEnd = true }; + feature.AssignOwnership(new FeatureTyping { Type = featureType }); + + return feature; } } } diff --git a/SysML2.NET.Tests/Extend/InterfaceDefinitionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/InterfaceDefinitionExtensionsTestFixture.cs index b6ce1d3a..288c1e14 100644 --- a/SysML2.NET.Tests/Extend/InterfaceDefinitionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/InterfaceDefinitionExtensionsTestFixture.cs @@ -38,7 +38,7 @@ public void Verify_ComputeInterfaceEnd() { Assert.That( () => ((IInterfaceDefinition)null).ComputeInterfaceEnd(), - Throws.TypeOf().With.Property("ParamName").EqualTo("interfaceDefinitionSubject")); + Throws.TypeOf()); var emptyInterfaceDefinition = new InterfaceDefinition(); diff --git a/SysML2.NET.Tests/Extend/InterfaceUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/InterfaceUsageExtensionsTestFixture.cs index 003bde51..b3b30a04 100644 --- a/SysML2.NET.Tests/Extend/InterfaceUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/InterfaceUsageExtensionsTestFixture.cs @@ -35,7 +35,7 @@ public class InterfaceUsageExtensionsTestFixture [Test] public void Verify_ComputeInterfaceDefinition() { - Assert.That(() => ((IInterfaceUsage)null).ComputeInterfaceDefinition(), Throws.TypeOf().With.Property("ParamName").EqualTo("interfaceUsageSubject")); + Assert.That(() => ((IInterfaceUsage)null).ComputeInterfaceDefinition(), Throws.TypeOf()); // Empty InterfaceUsage: no FeatureTyping in OwnedRelationship -> empty list. var emptyInterfaceUsage = new InterfaceUsage(); diff --git a/SysML2.NET.Tests/Extend/MetadataAccessExpressionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/MetadataAccessExpressionExtensionsTestFixture.cs index 97af60a2..e2e0e051 100644 --- a/SysML2.NET.Tests/Extend/MetadataAccessExpressionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/MetadataAccessExpressionExtensionsTestFixture.cs @@ -1,7 +1,7 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // // -// Copyright 2022-2026 Starion Group S.A. +// Copyright (C) 2022-2026 Starion Group S.A. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,18 +21,99 @@ namespace SysML2.NET.Tests.Extend { using System; - + using System.Collections.Generic; + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Kernel.Packages; + using SysML2.NET.Core.POCO.Root.Annotations; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Exceptions; + using SysML2.NET.Extensions; [TestFixture] public class MetadataAccessExpressionExtensionsTestFixture { [Test] - public void ComputeReferencedElement_ThrowsNotSupportedException() + public void ComputeMetaclassFeatureOperation_ThrowsNotSupportedException() + { + // Blocked: requires MOF metaclass reflection registry (out of scope for this batch). + Assert.That( + () => ((IMetadataAccessExpression)null).ComputeMetaclassFeatureOperation(), + Throws.TypeOf()); + } + + [Test] + public void Verify_ComputeRedefinedEvaluateOperation() + { + Assert.That( + () => ((IMetadataAccessExpression)null).ComputeRedefinedEvaluateOperation(null), + Throws.TypeOf()); + + // referencedElement is [1..1] derived; an empty subject propagates the IncompleteModelException + // from ComputeReferencedElement before the metaclassFeature() tail is reached. + var emptySubject = new MetadataAccessExpression(); + + Assert.That(() => emptySubject.ComputeRedefinedEvaluateOperation(null), Throws.TypeOf()); + + // For any well-formed subject, the trailing `->including(metaclassFeature())` step calls + // ComputeMetaclassFeatureOperation, which is intentionally left as a stub (MOF reflection + // infrastructure is out of scope). The call therefore propagates NotSupportedException — + // this verifies the calling chain is wired correctly per OCL. + var validSubject = new MetadataAccessExpression(); + validSubject.AssignOwnership(new OwningMembership(), new Package()); + + Assert.That(() => validSubject.ComputeRedefinedEvaluateOperation(null), Throws.TypeOf()); + } + + [Test] + public void Verify_ComputeRedefinedModelLevelEvaluableOperation() { - Assert.That(() => ((IMetadataAccessExpression)null).ComputeReferencedElement(), Throws.TypeOf()); + Assert.That( + () => ((IMetadataAccessExpression)null).ComputeRedefinedModelLevelEvaluableOperation(null), + Throws.TypeOf()); + + var subject = new MetadataAccessExpression(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeRedefinedModelLevelEvaluableOperation(null), Is.True); + Assert.That(subject.ComputeRedefinedModelLevelEvaluableOperation(new List()), Is.True); + } + } + + [Test] + public void Verify_ComputeReferencedElement() + { + Assert.That( + () => ((IMetadataAccessExpression)null).ComputeReferencedElement(), + Throws.TypeOf()); + + var emptySubject = new MetadataAccessExpression(); + + Assert.That(() => emptySubject.ComputeReferencedElement(), Throws.TypeOf()); + + var singleOwningMembershipSubject = new MetadataAccessExpression(); + var referenced = new Comment(); + singleOwningMembershipSubject.AssignOwnership(new OwningMembership(), referenced); + + Assert.That(singleOwningMembershipSubject.ComputeReferencedElement(), Is.SameAs(referenced)); + + var featureMembershipOnlySubject = new MetadataAccessExpression(); + featureMembershipOnlySubject.AssignOwnership(new FeatureMembership(), new Feature()); + + Assert.That(() => featureMembershipOnlySubject.ComputeReferencedElement(), Throws.TypeOf()); + + var mixedSubject = new MetadataAccessExpression(); + mixedSubject.AssignOwnership(new FeatureMembership(), new Feature()); + var mixedReferenced = new Package(); + mixedSubject.AssignOwnership(new OwningMembership(), mixedReferenced); + + // Proves the filter is "not IFeatureMembership", not positional [0]. + Assert.That(mixedSubject.ComputeReferencedElement(), Is.SameAs(mixedReferenced)); } } } diff --git a/SysML2.NET.Tests/Extend/PortDefinitionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/PortDefinitionExtensionsTestFixture.cs index f35d4222..c48ff6cb 100644 --- a/SysML2.NET.Tests/Extend/PortDefinitionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/PortDefinitionExtensionsTestFixture.cs @@ -37,7 +37,7 @@ public void Verify_ComputeConjugatedPortDefinition() { Assert.That( () => ((IPortDefinition)null).ComputeConjugatedPortDefinition(), - Throws.TypeOf().With.Property("ParamName").EqualTo("portDefinitionSubject")); + Throws.TypeOf()); var emptyPortDefinition = new PortDefinition(); Assert.That(emptyPortDefinition.ComputeConjugatedPortDefinition(), Is.Null); diff --git a/SysML2.NET.Tests/Extend/PortUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/PortUsageExtensionsTestFixture.cs index d590c69f..1c65bc2e 100644 --- a/SysML2.NET.Tests/Extend/PortUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/PortUsageExtensionsTestFixture.cs @@ -37,7 +37,7 @@ public void Verify_ComputePortDefinition() { Assert.That( () => ((IPortUsage)null).ComputePortDefinition(), - Throws.TypeOf().With.Property("ParamName").EqualTo("portUsageSubject")); + Throws.TypeOf()); var emptySubject = new PortUsage(); diff --git a/SysML2.NET/Extend/AnnotatingElementExtensions.cs b/SysML2.NET/Extend/AnnotatingElementExtensions.cs index 9e1f79a1..226b6a77 100644 --- a/SysML2.NET/Extend/AnnotatingElementExtensions.cs +++ b/SysML2.NET/Extend/AnnotatingElementExtensions.cs @@ -1,20 +1,20 @@ // ------------------------------------------------------------------------------------------------- // -// -// Copyright (C) 2022-2026 Starion Group S.A. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// +// +// Copyright (C) 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ @@ -22,13 +22,13 @@ namespace SysML2.NET.Core.POCO.Root.Annotations { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.POCO.Root.Elements; - using SysML2.NET.Core.POCO.Root.Namespaces; /// - /// The class provides extensions methods for - /// the interface + /// The class provides extensions methods for + /// the interface /// internal static class AnnotatingElementExtensions { @@ -44,15 +44,29 @@ internal static class AnnotatingElementExtensions /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAnnotatedElement(this IAnnotatingElement annotatingElementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (annotatingElementSubject == null) + { + throw new ArgumentNullException(nameof(annotatingElementSubject)); + } + + var annotations = annotatingElementSubject.annotation; + + if (annotations.Count != 0) + { + var result = new List(annotations.Count); + result.AddRange(annotations.Select(annotation => annotation.AnnotatedElement)); + + return result; + } + + return [annotatingElementSubject.owningNamespace]; } /// @@ -68,15 +82,27 @@ internal static List ComputeAnnotatedElement(this IAnnotatingElement a /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAnnotation(this IAnnotatingElement annotatingElementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (annotatingElementSubject == null) + { + throw new ArgumentNullException(nameof(annotatingElementSubject)); + } + + var owning = annotatingElementSubject.owningAnnotatingRelationship; + var owned = annotatingElementSubject.ownedAnnotatingRelationship; + + if (owning == null) + { + return [..owned]; + } + + return [owning, ..owned]; } /// @@ -91,31 +117,46 @@ internal static List ComputeAnnotation(this IAnnotatingElement anno /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedAnnotatingRelationship(this IAnnotatingElement annotatingElementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (annotatingElementSubject == null) + { + throw new ArgumentNullException(nameof(annotatingElementSubject)); + } + + var result = new List(); + + foreach (var relationship in annotatingElementSubject.OwnedRelationship) + { + if (relationship is IAnnotation annotation + && !ReferenceEquals(annotation.AnnotatedElement, annotatingElementSubject)) + { + result.Add(annotation); + } + } + + return result; } /// /// Computes the derived property. /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IAnnotation ComputeOwningAnnotatingRelationship(this IAnnotatingElement annotatingElementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return annotatingElementSubject == null + ? throw new ArgumentNullException(nameof(annotatingElementSubject)) + : annotatingElementSubject.OwningRelationship as IAnnotation; } - } } diff --git a/SysML2.NET/Extend/AnnotationExtensions.cs b/SysML2.NET/Extend/AnnotationExtensions.cs index 9d673a5b..dddecfe3 100644 --- a/SysML2.NET/Extend/AnnotationExtensions.cs +++ b/SysML2.NET/Extend/AnnotationExtensions.cs @@ -1,34 +1,33 @@ // ------------------------------------------------------------------------------------------------- // -// -// Copyright (C) 2022-2026 Starion Group S.A. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// +// +// Copyright (C) 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Core.POCO.Root.Annotations { using System; - using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.POCO.Root.Elements; - using SysML2.NET.Core.POCO.Root.Namespaces; /// - /// The class provides extensions methods for - /// the interface + /// The class provides extensions methods for + /// the interface /// internal static class AnnotationExtensions { @@ -45,15 +44,16 @@ internal static class AnnotationExtensions /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IAnnotatingElement ComputeAnnotatingElement(this IAnnotation annotationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return annotationSubject == null + ? throw new ArgumentNullException(nameof(annotationSubject)) + : annotationSubject.ownedAnnotatingElement ?? annotationSubject.owningAnnotatingElement; } /// @@ -71,46 +71,56 @@ internal static IAnnotatingElement ComputeAnnotatingElement(this IAnnotation ann /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IAnnotatingElement ComputeOwnedAnnotatingElement(this IAnnotation annotationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return annotationSubject == null + ? throw new ArgumentNullException(nameof(annotationSubject)) + : annotationSubject.OwnedRelatedElement.OfType().FirstOrDefault(); } /// /// Computes the derived property. /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeOwningAnnotatedElement(this IAnnotation annotationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (annotationSubject == null) + { + throw new ArgumentNullException(nameof(annotationSubject)); + } + + var owningRelatedElement = annotationSubject.OwningRelatedElement; + + return owningRelatedElement != null + && ReferenceEquals(owningRelatedElement, annotationSubject.AnnotatedElement) + ? owningRelatedElement + : null; } /// /// Computes the derived property. /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IAnnotatingElement ComputeOwningAnnotatingElement(this IAnnotation annotationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return annotationSubject == null + ? throw new ArgumentNullException(nameof(annotationSubject)) + : annotationSubject.OwningRelatedElement as IAnnotatingElement; } - } } diff --git a/SysML2.NET/Extend/AssociationExtensions.cs b/SysML2.NET/Extend/AssociationExtensions.cs index f9ec1b81..f67b18c4 100644 --- a/SysML2.NET/Extend/AssociationExtensions.cs +++ b/SysML2.NET/Extend/AssociationExtensions.cs @@ -1,20 +1,20 @@ // ------------------------------------------------------------------------------------------------- // -// -// Copyright (C) 2022-2026 Starion Group S.A. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// +// +// Copyright (C) 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ @@ -22,19 +22,14 @@ namespace SysML2.NET.Core.POCO.Kernel.Associations { using System; using System.Collections.Generic; + using System.Linq; - using SysML2.NET.Core.Core.Types; - using SysML2.NET.Core.Root.Namespaces; - using SysML2.NET.Core.POCO.Core.Classifiers; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; - using SysML2.NET.Core.POCO.Root.Annotations; - using SysML2.NET.Core.POCO.Root.Elements; - using SysML2.NET.Core.POCO.Root.Namespaces; /// - /// The class provides extensions methods for - /// the interface + /// The class provides extensions methods for + /// the interface /// internal static class AssociationExtensions { @@ -42,15 +37,16 @@ internal static class AssociationExtensions /// Computes the derived property. /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAssociationEnd(this IAssociation associationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return associationSubject == null + ? throw new ArgumentNullException(nameof(associationSubject)) + : [..associationSubject.feature.Where(memberFeature => memberFeature.IsEnd)]; } /// @@ -63,15 +59,16 @@ internal static List ComputeAssociationEnd(this IAssociation associati /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRelatedType(this IAssociation associationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return associationSubject == null + ? throw new ArgumentNullException(nameof(associationSubject)) + : [..associationSubject.associationEnd.SelectMany(end => end.type)]; } /// @@ -86,15 +83,21 @@ internal static List ComputeRelatedType(this IAssociation associationSubj /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IType ComputeSourceType(this IAssociation associationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (associationSubject == null) + { + throw new ArgumentNullException(nameof(associationSubject)); + } + + var relatedTypes = associationSubject.relatedType; + + return relatedTypes.Count == 0 ? null : relatedTypes[0]; } /// @@ -113,16 +116,21 @@ internal static IType ComputeSourceType(this IAssociation associationSubject) /// /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeTargetType(this IAssociation associationSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); - } + if (associationSubject == null) + { + throw new ArgumentNullException(nameof(associationSubject)); + } + var relatedTypes = associationSubject.relatedType; + + return relatedTypes.Count < 2 ? [] : [..relatedTypes.Skip(1).Distinct()]; + } } } diff --git a/SysML2.NET/Extend/MetadataAccessExpressionExtensions.cs b/SysML2.NET/Extend/MetadataAccessExpressionExtensions.cs index 2c6600f0..a7bed30e 100644 --- a/SysML2.NET/Extend/MetadataAccessExpressionExtensions.cs +++ b/SysML2.NET/Extend/MetadataAccessExpressionExtensions.cs @@ -1,20 +1,20 @@ // ------------------------------------------------------------------------------------------------- // -// -// Copyright (C) 2022-2026 Starion Group S.A. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// +// +// Copyright (C) 2022-2026 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ @@ -22,21 +22,19 @@ namespace SysML2.NET.Core.POCO.Kernel.Expressions { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; - using SysML2.NET.Core.Core.Types; - using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; - using SysML2.NET.Core.POCO.Kernel.Behaviors; - using SysML2.NET.Core.POCO.Kernel.Functions; using SysML2.NET.Core.POCO.Kernel.Metadata; - using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Exceptions; + using SysML2.NET.Extensions; /// - /// The class provides extensions methods for - /// the interface + /// The class provides extensions methods for + /// the interface /// internal static class MetadataAccessExpressionExtensions { @@ -44,15 +42,30 @@ internal static class MetadataAccessExpressionExtensions /// Computes the derived property. /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeReferencedElement(this IMetadataAccessExpression metadataAccessExpressionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (metadataAccessExpressionSubject == null) + { + throw new ArgumentNullException(nameof(metadataAccessExpressionSubject)); + } + + var ownedRelationships = metadataAccessExpressionSubject.OwnedRelationship; + + foreach (var ownedRelationship in ownedRelationships) + { + if (ownedRelationship is IOwningMembership owningMembership and not IFeatureMembership) + { + return owningMembership.OwnedRelatedElement.RequireSingleOfType(nameof(owningMembership)); + } + } + + throw new IncompleteModelException( + $"{nameof(IMetadataAccessExpression)}.referencedElement is [1..1] but no non-FeatureMembership OwningMembership was found on '{nameof(metadataAccessExpressionSubject)}'."); } /// @@ -65,7 +78,7 @@ internal static IElement ComputeReferencedElement(this IMetadataAccessExpression /// /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -73,10 +86,14 @@ internal static IElement ComputeReferencedElement(this IMetadataAccessExpression /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeRedefinedModelLevelEvaluableOperation(this IMetadataAccessExpression metadataAccessExpressionSubject, List visited) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (metadataAccessExpressionSubject == null) + { + throw new ArgumentNullException(nameof(metadataAccessExpressionSubject)); + } + + return true; } /// @@ -96,7 +113,7 @@ internal static bool ComputeRedefinedModelLevelEvaluableOperation(this IMetadata /// /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -104,10 +121,29 @@ internal static bool ComputeRedefinedModelLevelEvaluableOperation(this IMetadata /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRedefinedEvaluateOperation(this IMetadataAccessExpression metadataAccessExpressionSubject, IElement target) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (metadataAccessExpressionSubject == null) + { + throw new ArgumentNullException(nameof(metadataAccessExpressionSubject)); + } + + var referencedElement = metadataAccessExpressionSubject.referencedElement; + + var result = new List(); + + foreach (var ownedElement in referencedElement.ownedElement) + { + if (ownedElement is IMetadataFeature metadataFeature + && metadataFeature.annotatedElement.Contains(referencedElement)) + { + result.Add(metadataFeature); + } + } + + result.Add(metadataAccessExpressionSubject.MetaclassFeature()); + + return result; } /// @@ -116,12 +152,12 @@ internal static List ComputeRedefinedEvaluateOperation(this IMetadataA /// are bound to the MOF properties of the referencedElement. /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] internal static IMetadataFeature ComputeMetaclassFeatureOperation(this IMetadataAccessExpression metadataAccessExpressionSubject) { throw new NotSupportedException("Create a GitHub issue when this method is required");