diff --git a/.claude/commands/implement-extensions.md b/.claude/commands/implement-extensions.md new file mode 100644 index 00000000..75ed7790 --- /dev/null +++ b/.claude/commands/implement-extensions.md @@ -0,0 +1,190 @@ +--- +description: Spawn researcher/implementer/tester/reviewer team to fill in stub Compute* methods in a SysML2.NET Extend file +argument-hint: +--- + +# /implement-extensions + +Spawn the 4-role agent team (researcher → implementer → tester → reviewer, all +Opus) for **$ARGUMENTS** — a `*Extensions.cs` file under `SysML2.NET/Extend/` +whose `Compute*` methods are still stubs throwing `NotSupportedException`. + +The team template is at `C:\Users\atheate\.claude\team-templates\extension-impl.md` +(v2). Read it first — its role prompts are the source of truth; this command body +is the orchestration glue. + +## Hard scope rule + +**Only modify**: +- The named production file `$ARGUMENTS` +- Its corresponding test fixture (auto-derived in step 2) +- The researcher's notes file (auto-derived in step 2) +- Sibling test fixtures whose `Throws.TypeOf()` assertions + now fail because of regressions caused by the new implementations (regression + sweep in step 8) + +**MUST NOT modify**: +- Any other production file in `SysML2.NET/Extend/`, `SysML2.NET/Core/`, etc. +- Auto-generated POCOs and interfaces. +- Code-generator templates. + +This is the user-memory `feedback_scope_discipline.md` rule. Even when an adjacent +stub blocks dependent test coverage, surface the blocker; do not silently expand +scope. Use the stub-blocker test pattern (see template). + +## Workflow + +### 1. Validate input + +Confirm `$ARGUMENTS` is: +- An absolute Windows path with backslashes. +- A file matching `C:\CODE\SysML2.NET\SysML2.NET\Extend\*Extensions.cs`. +- The file exists. + +If validation fails, stop and ask the user to re-invoke with a corrected path. + +### 2. Auto-derive paths + +From `$ARGUMENTS = C:\CODE\SysML2.NET\SysML2.NET\Extend\Extensions.cs`: + +- **Production file**: `$ARGUMENTS` itself. +- **Test fixture**: `C:\CODE\SysML2.NET\SysML2.NET.Tests\Extend\ExtensionsTestFixture.cs`. + If it does not exist, surface that to the user — likely scope mismatch. +- **Reference production file**: `C:\CODE\SysML2.NET\SysML2.NET\Extend\NamespaceExtensions.cs`. +- **Reference test file**: `C:\CODE\SysML2.NET\SysML2.NET.Tests\Extend\NamespaceExtensionsTestFixture.cs`. +- **Target interface**: `I` — find via Glob `SysML2.NET\Core\AutoGenPoco\**\I.cs`. +- **Target metaclass name**: ``. +- **Subject param name**: lowercase first char of `` + `[1..]` + `Subject` (e.g. `Type` → `typeSubject`, `Feature` → `featureSubject`). +- **Notes file**: `C:\CODE\SysML2.NET\.team-notes\-extensions-spec.md` (kebab-case ``). Create the `.team-notes\` directory if it doesn't exist (`mkdir -p`). +- **Team name**: `-extensions-impl`. + +Print the derived paths back to the user as a sanity check. + +### 3. Enumerate stub methods + +Grep the production file for `throw new NotSupportedException`. List the enclosing +methods. These are the stubs to implement. + +If the count is 0, stop — the file has no stubs left. + +### 4. Sanity check with the user + +Use `AskUserQuestion` to present: +- The auto-derived paths (test fixture, interface, reference template, notes file). +- The list of stub methods (or a count if there are many). +- Two options: "Implement all" or "Implement a subset" (let the user paste a method + list as a custom answer). + +If they pick subset, narrow the method list. Otherwise proceed with all. + +### 5. Spawn the researcher (FIRST role — produces the notes file the others read) + +Read the v2 team template at `C:\Users\atheate\.claude\team-templates\extension-impl.md` +to refresh the role prompts. Substitute the placeholders from step 2 + the method +list from step 4. + +Spawn the **researcher** as `Agent({subagent_type: "general-purpose", model: "opus"})` +with the v2 researcher prompt. Foreground (not `run_in_background`) — the next +roles depend on the notes file. + +The researcher MUST: +- Treat the OCL ``/`` body in the XMI as the canonical + source of truth. +- Fall back to the OCL block in the production file's `` (mirrored from + XMI by codegen). +- For methods with NO OCL body (e.g. `Type::isConjugated`), record a short prose + derivation rule + spec citation, and EXPLICITLY FLAG the spec-text-only origin + in the notes. +- Flag any method whose OCL transitively reads a still-stubbed sibling + `Compute*` so the tester knows to use the stub-blocker pattern. + +After the researcher finishes, read `{{NOTES_FILE}}` yourself to verify it +covers all methods + flags spec-text-only and stub-blocker cases. + +### 6. Spawn the implementer + +`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 implementer +prompt. Foreground. The prompt MUST instruct the implementer to read +`{{NOTES_FILE}}` first. + +After the implementer finishes: +```bash +dotnet build C:\CODE\SysML2.NET\SysML2.NET\SysML2.NET.csproj --nologo --verbosity quiet +``` + +If the build fails, dispatch the implementer back to fix its own bugs (do not +delegate to a fresh agent unless the original is non-responsive). + +### 7. Spawn the tester + +`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 tester +prompt. Foreground. Instruct the tester to read `{{NOTES_FILE}}` first (each +method has a "Test plan" section there). + +After the tester finishes: +```bash +dotnet test C:\CODE\SysML2.NET\SysML2.NET.Tests\SysML2.NET.Tests.csproj --filter "FullyQualifiedName~ExtensionsTestFixture" --nologo --verbosity quiet +``` + +Goal: 0 failures in the targeted fixture. + +### 8. Regression sweep (mandatory) + +Run the full solution test suite: + +```bash +dotnet test C:\CODE\SysML2.NET\SysML2.NET.sln --no-build --nologo --verbosity quiet +``` + +If failures exist, identify those of the form: + +> Expected: `` But was: `no exception thrown` + +These are pre-existing tests in sibling fixtures that asserted the stubs throw — +they now fail because our new implementations make those paths succeed. Dispatch +the tester back (via `SendMessage` to the still-running tester if available, else +a fresh `Agent` call) with the failing-test list and instructions to update those +assertions to assert real behavior. The regression sweep is in-scope per the +template. + +Iterate until 100% green or the user opts out. + +### 9. Spawn the reviewer (LAST role — verdict only) + +`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 reviewer +prompt. Foreground. The reviewer cross-checks `{{PRODUCTION_FILE}}` and +`{{TEST_FILE}}` against `{{NOTES_FILE}}` and produces an "OK / NEEDS FIX" verdict. + +If the verdict is "NEEDS FIX", dispatch the implementer or tester back to +action the findings (the reviewer never edits). + +### 10. Final summary + +Report to the user: +- Modified files (production + test fixture + notes + any regression-sweep test fixtures). +- Test counts (X/Y green for the targeted fixture; A/B green for the full solution). +- Reviewer verdict + any unresolved findings. +- Out-of-scope blockers surfaced (e.g. "5 populated cases use the stub-blocker + pattern because `` in `.cs` is still a stub — + consider a follow-up issue"). +- Spec-text-only methods (e.g. `IsConjugated`) — flag separately so the user + knows the implementation is grounded in spec prose rather than OCL. + +Do NOT auto-commit. The user reviews and commits. + +## Notes for the orchestrator (you, the main agent) + +- Use the **Opus** model for all four roles — they handle OCL→C# translation + best and the user has explicitly preferred Opus for this workflow. +- Spawn each role **foreground** (not `run_in_background`) because each step + depends on the previous's output. +- The researcher runs **FIRST and is mandatory** — even when the production file's + `` already carries OCL, the researcher's notes file gives the + implementer/tester/reviewer a single shared contract document, AND it's the only + role that handles spec-text-only methods cleanly. +- The reviewer is **mandatory** even when in past runs it caught no bugs the + tester missed — the user explicitly wants it as cheap insurance against subtle + OCL mistranslation. +- If a build error involves an explicit-interface-impl loop (e.g. `(INamespace)x` + cast not bypassing virtual dispatch), call the static extension method directly + rather than via interface dispatch — pattern from the TypeExtensions task fix. diff --git a/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs index b6fed013..1153c7a7 100644 --- a/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs @@ -24,9 +24,11 @@ namespace SysML2.NET.Tests.Extend using NUnit.Framework; + using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Extensions; using Type = SysML2.NET.Core.POCO.Core.Types.Type; @@ -587,7 +589,22 @@ public void VerifyComputeDirectionForOperation() var feature = new Feature(); - Assert.That(() => feature.ComputeDirectionForOperation(new Type()), Throws.TypeOf()); + using (Assert.EnterMultipleScope()) + { + // null type → result is null per the OCL guard. + Assert.That(feature.ComputeDirectionForOperation(null), Is.Null); + + // a Type that has no relationship to the feature returns null direction. + Assert.That(feature.ComputeDirectionForOperation(new Type()), Is.Null); + } + + // populated: a feature owned by a Type via FeatureMembership returns its declared Direction. + var directedFeature = new Feature { Direction = FeatureDirectionKind.In }; + var owningType = new Type(); + var featureMembership = new FeatureMembership(); + owningType.AssignOwnership(featureMembership, directedFeature); + + Assert.That(directedFeature.ComputeDirectionForOperation(owningType), Is.EqualTo(FeatureDirectionKind.In)); } [Test] @@ -710,12 +727,30 @@ public void VerifyComputeIsFeaturedWithinOperation() { Assert.That(() => ((IFeature)null).ComputeIsFeaturedWithinOperation(null), Throws.TypeOf()); - // all body cases blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation - // (reached via ResolveGlobal path even for type=null and empty featuringTypes, - // because the impl resolves Base::Anything to check implicit featuring). var feature = new Feature(); - Assert.That(() => feature.ComputeIsFeaturedWithinOperation(null), Throws.TypeOf()); + using (Assert.EnterMultipleScope()) + { + // type = null: with no featuringType entries, forAll over the empty set is vacuously true. + Assert.That(feature.ComputeIsFeaturedWithinOperation(null), Is.True); + + // type non-null: with no featuringType entries, type.IsCompatibleWith forall is vacuously + // true → returns true. + Assert.That(feature.ComputeIsFeaturedWithinOperation(new Type()), Is.True); + } + + // a feature whose featuringType is a specific Type is featured within that Type. + var owningType = new Type(); + var owningTypeFeaturing = new TypeFeaturing { FeatureOfType = feature, FeaturingType = owningType }; + feature.AssignOwnership(owningTypeFeaturing); + + using (Assert.EnterMultipleScope()) + { + Assert.That(feature.ComputeIsFeaturedWithinOperation(owningType), Is.True); + + // and not within an unrelated Type. + Assert.That(feature.ComputeIsFeaturedWithinOperation(new Type()), Is.False); + } } [Test] @@ -723,11 +758,22 @@ public void VerifyComputeCanAccessOperation() { Assert.That(() => ((IFeature)null).ComputeCanAccessOperation(null), Throws.TypeOf()); - // all body cases blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation - // (reached via IsFeaturedWithin → ResolveGlobal path even for empty featuringTypes). var feature = new Feature(); - Assert.That(() => feature.ComputeCanAccessOperation(new Feature()), Throws.TypeOf()); + // empty case: no featuringType on the subject feature → visited stays empty → returns false. + Assert.That(feature.ComputeCanAccessOperation(new Feature()), Is.False); + + // populated: subject feature is featured by a Type, and the target feature is featured within + // that same Type — subject can access target. + var sharedType = new Type(); + var subjectTypeFeaturing = new TypeFeaturing { FeatureOfType = feature, FeaturingType = sharedType }; + feature.AssignOwnership(subjectTypeFeaturing); + + var target = new Feature(); + var targetTypeFeaturing = new TypeFeaturing { FeatureOfType = target, FeaturingType = sharedType }; + target.AssignOwnership(targetTypeFeaturing); + + Assert.That(feature.ComputeCanAccessOperation(target), Is.True); } [Test] @@ -780,7 +826,24 @@ public void VerifyComputeRedefinedSupertypesOperation() var feature = new Feature(); - Assert.That(() => feature.ComputeRedefinedSupertypesOperation(false), Throws.TypeOf()); + // empty case: no specializations, no chaining → empty supertypes list. + Assert.That(feature.ComputeRedefinedSupertypesOperation(false), Has.Count.EqualTo(0)); + + // populated: a Specialization adds its General to supertypes; an implied Specialization + // is filtered when excludeImplied = true. + var general = new Type(); + var specialization = new Specialization { Specific = feature, General = general }; + feature.AssignOwnership(specialization); + + var impliedGeneral = new Type(); + var impliedSpecialization = new Specialization { Specific = feature, General = impliedGeneral, IsImplied = true }; + feature.AssignOwnership(impliedSpecialization); + + using (Assert.EnterMultipleScope()) + { + Assert.That(feature.ComputeRedefinedSupertypesOperation(false), Is.EquivalentTo([general, impliedGeneral])); + Assert.That(feature.ComputeRedefinedSupertypesOperation(true), Is.EquivalentTo([general])); + } } [Test] @@ -788,12 +851,22 @@ public void VerifyComputeTypingFeaturesOperation() { Assert.That(() => ((IFeature)null).ComputeTypingFeaturesOperation(), Throws.TypeOf()); - // body cases (non-conjugated subsetting filtering, CrossSubsetting exclusion, - // chainingFeature append) are blocked by TypeExtensions.ComputeIsConjugated stub - // which is called via featureSubject.isConjugated at the start of the implementation var feature = new Feature(); - Assert.That(() => feature.ComputeTypingFeaturesOperation(), Throws.TypeOf()); + // empty case: not conjugated, no Subsetting, no chainingFeature → empty list. + Assert.That(feature.ComputeTypingFeaturesOperation(), Has.Count.EqualTo(0)); + + // populated (non-conjugated): a Subsetting contributes its SubsettedFeature; CrossSubsetting + // is excluded. + var subsettedFeature = new Feature(); + var subsetting = new Subsetting { SubsettedFeature = subsettedFeature }; + feature.AssignOwnership(subsetting); + + var crossSubsettedFeature = new Feature(); + var crossSubsetting = new CrossSubsetting { CrossedFeature = crossSubsettedFeature }; + feature.AssignOwnership(crossSubsetting); + + Assert.That(feature.ComputeTypingFeaturesOperation(), Is.EquivalentTo([subsettedFeature])); } [Test] @@ -802,8 +875,29 @@ public void VerifyComputeSubsetsChainOperation() Assert.That(() => ((IFeature)null).ComputeSubsetsChainOperation(null, null), Throws.TypeOf()); var feature = new Feature(); + var first = new Feature(); + var second = new Feature(); + + // empty case: bare feature with no specializations and no chainingFeature → false. + Assert.That(feature.ComputeSubsetsChainOperation(first, second), Is.False); + + // populated: a feature with chaining [first, second] satisfies the chain match. + var matching = new Feature(); + var chaining1 = new FeatureChaining { ChainingFeature = first }; + matching.AssignOwnership(chaining1); - Assert.That(() => feature.ComputeSubsetsChainOperation(new Feature(), new Feature()), Throws.TypeOf()); + var chaining2 = new FeatureChaining { ChainingFeature = second }; + matching.AssignOwnership(chaining2); + + // The subject specializes the matching feature so it shows up in the BFS visited set. + var specialization = new Specialization { Specific = feature, General = matching }; + feature.AssignOwnership(specialization); + + using (Assert.EnterMultipleScope()) + { + Assert.That(feature.ComputeSubsetsChainOperation(first, second), Is.True); + Assert.That(feature.ComputeSubsetsChainOperation(second, first), Is.False); + } } [Test] @@ -813,7 +907,16 @@ public void VerifyComputeRedefinedIsCompatibleWithOperation() var feature = new Feature(); - Assert.That(() => feature.ComputeRedefinedIsCompatibleWithOperation(new Type()), Throws.TypeOf()); + // empty case: bare feature has no Specialization to an unrelated Type and the otherType is not + // a Feature → returns false. + Assert.That(feature.ComputeRedefinedIsCompatibleWithOperation(new Type()), Is.False); + + // populated: a feature that specializes a Type is compatible with that Type. + var supertype = new Type(); + var specialization = new Specialization { Specific = feature, General = supertype }; + feature.AssignOwnership(specialization); + + Assert.That(feature.ComputeRedefinedIsCompatibleWithOperation(supertype), Is.True); } [Test] @@ -821,12 +924,51 @@ public void VerifyComputeRedefinesFromLibraryOperation() { Assert.That(() => ((IFeature)null).ComputeRedefinesFromLibraryOperation(null), Throws.TypeOf()); - // positive verification of library redefinition requires a loaded library model - // and is blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation stub - // (via ResolveGlobal → VisibleMemberships chain) - var feature = new Feature(); + var bareFeature = new Feature(); + + using (Assert.EnterMultipleScope()) + { + // null/blank library names short-circuit through ResolveGlobal to false. + Assert.That(bareFeature.ComputeRedefinesFromLibraryOperation(null), Is.False); + Assert.That(bareFeature.ComputeRedefinesFromLibraryOperation(" "), Is.False); + + // a name that does not resolve in this fixture (no library wired) returns false. + Assert.That(bareFeature.ComputeRedefinesFromLibraryOperation("Some::Library::Feature"), Is.False); + } + + // positive case: build a small library namespace, place the subject feature in it, + // and have the subject redefine the library feature. + var rootNamespace = new Namespace(); + + var libraryFeature = new Feature { DeclaredName = "LibFeature" }; + var libraryMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(libraryMembership, libraryFeature); + + var subject = new Feature(); + var subjectMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(subjectMembership, subject); + + var redefinition = new Redefinition { RedefinedFeature = libraryFeature }; + subject.AssignOwnership(redefinition); - Assert.That(() => feature.ComputeRedefinesFromLibraryOperation("Some::Library::Feature"), Throws.TypeOf()); + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeRedefinesFromLibraryOperation("LibFeature"), Is.True); + + // resolves to a Feature the subject does NOT redefine → false. + var unrelatedFeature = new Feature { DeclaredName = "UnrelatedFeature" }; + var unrelatedMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(unrelatedMembership, unrelatedFeature); + + Assert.That(subject.ComputeRedefinesFromLibraryOperation("UnrelatedFeature"), Is.False); + + // resolves to a non-Feature element → false. + var libraryType = new Type { DeclaredName = "LibType" }; + var libraryTypeMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(libraryTypeMembership, libraryType); + + Assert.That(subject.ComputeRedefinesFromLibraryOperation("LibType"), Is.False); + } } } } diff --git a/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs index e2a51dca..0886d1c7 100644 --- a/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs @@ -210,7 +210,10 @@ public void VerifyComputeVisibleMembershipsOperation() using (Assert.EnterMultipleScope()) { Assert.That(namespaceElement.ComputeVisibleMembershipsOperation([], false, false), Has.Count.EqualTo(2)); - Assert.That(() => namespaceElement.ComputeVisibleMembershipsOperation([], true, false), Throws.TypeOf()); + + // recursive case: public ownedMemberships of the outer namespace, plus visible + // memberships harvested from each public nested INamespace. + Assert.That(namespaceElement.ComputeVisibleMembershipsOperation([], true, false), Is.EquivalentTo(new[] { publicMembership, nestedOwning, nestedMembership })); } } diff --git a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs index bccf6ef1..6533519c 100644 --- a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs @@ -1,164 +1,875 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 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.Tests.Extend { using System; - + using System.Collections.Generic; + using NUnit.Framework; - + + using SysML2.NET.Core.Core.Types; + using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.Root.Namespaces; + using SysML2.NET.Extensions; + + using Type = SysML2.NET.Core.POCO.Core.Types.Type; [TestFixture] public class TypeExtensionsTestFixture { [Test] - public void ComputeDifferencingType_ThrowsNotSupportedException() + public void VerifyComputeOwnedDifferencing() { - Assert.That(() => ((IType)null).ComputeDifferencingType(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedDifferencing(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedDifferencing(), Has.Count.EqualTo(0)); + + var differencingType = new Type(); + var differencing = new Differencing { DifferencingType = differencingType }; + subject.AssignOwnership(differencing); + + var unrelatedSpecialization = new Specialization(); + subject.AssignOwnership(unrelatedSpecialization); + + Assert.That(subject.ComputeOwnedDifferencing(), Is.EquivalentTo([differencing])); } - + [Test] - public void ComputeDirectedFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedDisjoining() { - Assert.That(() => ((IType)null).ComputeDirectedFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedDisjoining(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedDisjoining(), Has.Count.EqualTo(0)); + + var disjoining = new Disjoining(); + subject.AssignOwnership(disjoining); + + var unrelatedSpecialization = new Specialization(); + subject.AssignOwnership(unrelatedSpecialization); + + Assert.That(subject.ComputeOwnedDisjoining(), Is.EquivalentTo([disjoining])); } - + [Test] - public void ComputeEndFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedFeatureMembership() { - Assert.That(() => ((IType)null).ComputeEndFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedFeatureMembership(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedFeatureMembership(), Has.Count.EqualTo(0)); + + var feature = new Feature(); + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, feature); + + var owningMembership = new OwningMembership(); + subject.AssignOwnership(owningMembership, new Feature()); + + Assert.That(subject.ComputeOwnedFeatureMembership(), Is.EquivalentTo([featureMembership])); } - + [Test] - public void ComputeFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedIntersecting() { - Assert.That(() => ((IType)null).ComputeFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedIntersecting(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedIntersecting(), Has.Count.EqualTo(0)); + + var intersectingType = new Type(); + var intersecting = new Intersecting { IntersectingType = intersectingType }; + subject.AssignOwnership(intersecting); + + var unrelatedSpecialization = new Specialization(); + subject.AssignOwnership(unrelatedSpecialization); + + Assert.That(subject.ComputeOwnedIntersecting(), Is.EquivalentTo([intersecting])); } - + [Test] - public void ComputeFeatureMembership_ThrowsNotSupportedException() + public void VerifyComputeOwnedUnioning() { - Assert.That(() => ((IType)null).ComputeFeatureMembership(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedUnioning(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedUnioning(), Has.Count.EqualTo(0)); + + var unioningType = new Type(); + var unioning = new Unioning { UnioningType = unioningType }; + subject.AssignOwnership(unioning); + + var unrelatedSpecialization = new Specialization(); + subject.AssignOwnership(unrelatedSpecialization); + + Assert.That(subject.ComputeOwnedUnioning(), Is.EquivalentTo([unioning])); } - + [Test] - public void ComputeInheritedFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedSpecialization() { - Assert.That(() => ((IType)null).ComputeInheritedFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedSpecialization(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedSpecialization(), Has.Count.EqualTo(0)); + + var general = new Type(); + var specialization = new Specialization { Specific = subject, General = general }; + subject.AssignOwnership(specialization); + + // a Specialization owned by subject but whose Specific points elsewhere must be filtered out + var elsewhere = new Type(); + var foreignSpecialization = new Specialization { Specific = elsewhere, General = new Type() }; + subject.AssignOwnership(foreignSpecialization); + + Assert.That(subject.ComputeOwnedSpecialization(), Is.EquivalentTo([specialization])); } - + [Test] - public void ComputeInheritedMembership_ThrowsNotSupportedException() + public void VerifyComputeOwnedConjugator() { - Assert.That(() => ((IType)null).ComputeInheritedMembership(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedConjugator(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedConjugator(), Is.Null); + + var originalType = new Type(); + var conjugation = new Conjugation { ConjugatedType = subject, OriginalType = originalType }; + subject.AssignOwnership(conjugation); + + Assert.That(subject.ComputeOwnedConjugator(), Is.SameAs(conjugation)); } - + [Test] - public void ComputeInput_ThrowsNotSupportedException() + public void VerifyComputeMultiplicity() { - Assert.That(() => ((IType)null).ComputeInput(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeMultiplicity(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeMultiplicity(), Is.Null); + + var multiplicity = new Multiplicity(); + var owningMembership = new OwningMembership(); + subject.AssignOwnership(owningMembership, multiplicity); + + Assert.That(subject.ComputeMultiplicity(), Is.SameAs(multiplicity)); } - + [Test] - public void ComputeIntersectingType_ThrowsNotSupportedException() + public void VerifyComputeIsConjugated() { - Assert.That(() => ((IType)null).ComputeIntersectingType(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeIsConjugated(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeIsConjugated(), Is.False); + + var originalType = new Type(); + var conjugation = new Conjugation { ConjugatedType = subject, OriginalType = originalType }; + subject.AssignOwnership(conjugation); + + Assert.That(subject.ComputeIsConjugated(), Is.True); } - + [Test] - public void ComputeIsConjugated_ThrowsNotSupportedException() + public void VerifyComputeDifferencingType() { - Assert.That(() => ((IType)null).ComputeIsConjugated(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeDifferencingType(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeDifferencingType(), Has.Count.EqualTo(0)); + + var differencingType = new Type(); + var differencing = new Differencing { DifferencingType = differencingType }; + subject.AssignOwnership(differencing); + + var nullDifferencing = new Differencing(); + subject.AssignOwnership(nullDifferencing); + + Assert.That(subject.ComputeDifferencingType(), Is.EquivalentTo([differencingType])); } - + [Test] - public void ComputeMultiplicity_ThrowsNotSupportedException() + public void VerifyComputeIntersectingType() { - Assert.That(() => ((IType)null).ComputeMultiplicity(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeIntersectingType(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeIntersectingType(), Has.Count.EqualTo(0)); + + var intersectingType = new Type(); + var intersecting = new Intersecting { IntersectingType = intersectingType }; + subject.AssignOwnership(intersecting); + + var nullIntersecting = new Intersecting(); + subject.AssignOwnership(nullIntersecting); + + Assert.That(subject.ComputeIntersectingType(), Is.EquivalentTo([intersectingType])); } - + [Test] - public void ComputeOutput_ThrowsNotSupportedException() + public void VerifyComputeUnioningType() { - Assert.That(() => ((IType)null).ComputeOutput(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeUnioningType(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeUnioningType(), Has.Count.EqualTo(0)); + + var unioningType = new Type(); + var unioning = new Unioning { UnioningType = unioningType }; + subject.AssignOwnership(unioning); + + var nullUnioning = new Unioning(); + subject.AssignOwnership(nullUnioning); + + Assert.That(subject.ComputeUnioningType(), Is.EquivalentTo([unioningType])); } - + [Test] - public void ComputeOwnedConjugator_ThrowsNotSupportedException() + public void VerifyComputeOwnedFeature() { - Assert.That(() => ((IType)null).ComputeOwnedConjugator(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedFeature(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var feature = new Feature(); + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, feature); + + Assert.That(() => subject.ComputeOwnedFeature(), Throws.TypeOf()); } - + [Test] - public void ComputeOwnedDifferencing_ThrowsNotSupportedException() + public void VerifyComputeFeature() { - Assert.That(() => ((IType)null).ComputeOwnedDifferencing(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeFeature(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var feature = new Feature(); + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, feature); + + Assert.That(() => subject.ComputeFeature(), Throws.TypeOf()); } - + [Test] - public void ComputeOwnedDisjoining_ThrowsNotSupportedException() + public void VerifyComputeOwnedEndFeature() { - Assert.That(() => ((IType)null).ComputeOwnedDisjoining(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeOwnedEndFeature(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOwnedEndFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var endFeature = new Feature { IsEnd = true }; + var endMembership = new FeatureMembership(); + subject.AssignOwnership(endMembership, endFeature); + + Assert.That(() => subject.ComputeOwnedEndFeature(), Throws.TypeOf()); } - + [Test] - public void ComputeOwnedEndFeature_ThrowsNotSupportedException() + public void VerifyComputeEndFeature() { - Assert.That(() => ((IType)null).ComputeOwnedEndFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeEndFeature(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeEndFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var endFeature = new Feature { IsEnd = true }; + var endMembership = new FeatureMembership(); + subject.AssignOwnership(endMembership, endFeature); + + Assert.That(() => subject.ComputeEndFeature(), Throws.TypeOf()); } - + [Test] - public void ComputeOwnedFeature_ThrowsNotSupportedException() + public void VerifyComputeSupertypesOperation() { - Assert.That(() => ((IType)null).ComputeOwnedFeature(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeSupertypesOperation(false), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeSupertypesOperation(false), Has.Count.EqualTo(0)); + + var general = new Type(); + var specialization = new Specialization { Specific = subject, General = general }; + subject.AssignOwnership(specialization); + + var impliedGeneral = new Type(); + var impliedSpecialization = new Specialization { Specific = subject, General = impliedGeneral, IsImplied = true }; + subject.AssignOwnership(impliedSpecialization); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeSupertypesOperation(false), Is.EquivalentTo([general, impliedGeneral])); + Assert.That(subject.ComputeSupertypesOperation(true), Is.EquivalentTo([general])); + } + + // conjugated branch returns the OriginalType of the conjugator. + var conjugatedSubject = new Type(); + var originalType = new Type(); + var conjugation = new Conjugation { ConjugatedType = conjugatedSubject, OriginalType = originalType }; + conjugatedSubject.AssignOwnership(conjugation); + + Assert.That(conjugatedSubject.ComputeSupertypesOperation(false), Is.EquivalentTo([originalType])); } - + [Test] - public void ComputeOwnedFeatureMembership_ThrowsNotSupportedException() + public void VerifyComputeAllSupertypesOperation() { - Assert.That(() => ((IType)null).ComputeOwnedFeatureMembership(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeAllSupertypesOperation(), Throws.TypeOf()); + + var subject = new Type(); + + using (Assert.EnterMultipleScope()) + { + // OCL: OrderedSet{self}->closure(supertypes(false)) — result includes self. + Assert.That(subject.ComputeAllSupertypesOperation(), Has.Count.EqualTo(1)); + Assert.That(subject.ComputeAllSupertypesOperation(), Does.Contain(subject)); + } + + var middle = new Type(); + var top = new Type(); + + var subjectToMiddle = new Specialization { Specific = subject, General = middle }; + subject.AssignOwnership(subjectToMiddle); + + var middleToTop = new Specialization { Specific = middle, General = top }; + middle.AssignOwnership(middleToTop); + + // 3-level chain plus a self-loop to demonstrate cycle safety. + var cycleSpecialization = new Specialization { Specific = top, General = subject }; + top.AssignOwnership(cycleSpecialization); + + var result = subject.ComputeAllSupertypesOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Is.EquivalentTo([subject, middle, top])); + Assert.That(result, Has.Count.EqualTo(3)); + } } - + [Test] - public void ComputeOwnedIntersecting_ThrowsNotSupportedException() + public void VerifyComputeSpecializesOperation() { - Assert.That(() => ((IType)null).ComputeOwnedIntersecting(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeSpecializesOperation(new Type()), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeSpecializesOperation(null), Throws.TypeOf()); + + var unrelated = new Type(); + + Assert.That(subject.ComputeSpecializesOperation(unrelated), Is.False); + + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeSpecializesOperation(supertype), Is.True); + Assert.That(subject.ComputeSpecializesOperation(unrelated), Is.False); + } + + // conjugated branch: subject specializes whatever its OriginalType specializes. + var conjugatedSubject = new Type(); + var originalType = new Type(); + var conjugation = new Conjugation { ConjugatedType = conjugatedSubject, OriginalType = originalType }; + conjugatedSubject.AssignOwnership(conjugation); + + var originalSupertype = new Type(); + var originalSpecialization = new Specialization { Specific = originalType, General = originalSupertype }; + originalType.AssignOwnership(originalSpecialization); + + Assert.That(conjugatedSubject.ComputeSpecializesOperation(originalSupertype), Is.True); } - + [Test] - public void ComputeOwnedSpecialization_ThrowsNotSupportedException() + public void VerifyComputeIsCompatibleWithOperation() { - Assert.That(() => ((IType)null).ComputeOwnedSpecialization(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeIsCompatibleWithOperation(new Type()), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeIsCompatibleWithOperation(null), Throws.TypeOf()); + + var unrelated = new Type(); + + Assert.That(subject.ComputeIsCompatibleWithOperation(unrelated), Is.False); + + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + Assert.That(subject.ComputeIsCompatibleWithOperation(supertype), Is.True); } - + [Test] - public void ComputeOwnedUnioning_ThrowsNotSupportedException() + public void VerifyComputeDirectionOfOperation() { - Assert.That(() => ((IType)null).ComputeOwnedUnioning(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeDirectionOfOperation(new Feature()), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeDirectionOfOperation(null), Throws.TypeOf()); + + // a feature whose owningType is unrelated and unreachable returns null direction. + var unrelatedFeature = new Feature { Direction = FeatureDirectionKind.In }; + + Assert.That(subject.ComputeDirectionOfOperation(unrelatedFeature), Is.Null); + + // populated branch: feature owned via FeatureMembership has owningType == subject and direction is propagated. + var ownedFeature = new Feature { Direction = FeatureDirectionKind.In }; + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, ownedFeature); + + Assert.That(subject.ComputeDirectionOfOperation(ownedFeature), Is.EqualTo(FeatureDirectionKind.In)); } - + [Test] - public void ComputeUnioningType_ThrowsNotSupportedException() + public void VerifyComputeDirectionOfExcludingOperation() { - Assert.That(() => ((IType)null).ComputeUnioningType(), Throws.TypeOf()); + Assert.That(() => ((IType)null).ComputeDirectionOfExcludingOperation(new Feature(), []), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeDirectionOfExcludingOperation(null, []), Throws.TypeOf()); + + // unrelated feature with unrelated owningType: result is null. + var unrelatedFeature = new Feature { Direction = FeatureDirectionKind.In }; + + Assert.That(subject.ComputeDirectionOfExcludingOperation(unrelatedFeature, null), Is.Null); + + // direct branch: feature.owningType == subject → returns feature.Direction. + var ownedFeature = new Feature { Direction = FeatureDirectionKind.In }; + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, ownedFeature); + + Assert.That(subject.ComputeDirectionOfExcludingOperation(ownedFeature, []), Is.EqualTo(FeatureDirectionKind.In)); + + // conjugation flip: a conjugated subject inherits the feature direction from its OriginalType + // and flips In↔Out at the current level. + var conjugatedSubject = new Type(); + var originalType = new Type(); + var conjugation = new Conjugation { ConjugatedType = conjugatedSubject, OriginalType = originalType }; + conjugatedSubject.AssignOwnership(conjugation); + + var originalSpecialization = new Specialization { Specific = originalType, General = subject }; + originalType.AssignOwnership(originalSpecialization); + + // For conjugatedSubject: + // supertypes(false) = [originalType] + // originalType.directionOfExcluding(ownedFeature, ...) traverses to subject and yields In + // then conjugatedSubject is conjugated → flip In to Out. + Assert.That(conjugatedSubject.ComputeDirectionOfExcludingOperation(ownedFeature, []), Is.EqualTo(FeatureDirectionKind.Out)); + } + + [Test] + public void VerifyComputeDirectedFeature() + { + Assert.That(() => ((IType)null).ComputeDirectedFeature(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeDirectedFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var directedFeature = new Feature { Direction = FeatureDirectionKind.In }; + var directedMembership = new FeatureMembership(); + subject.AssignOwnership(directedMembership, directedFeature); + + Assert.That(() => subject.ComputeDirectedFeature(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeInput() + { + Assert.That(() => ((IType)null).ComputeInput(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeInput(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var inFeature = new Feature { Direction = FeatureDirectionKind.In }; + var inMembership = new FeatureMembership(); + subject.AssignOwnership(inMembership, inFeature); + + Assert.That(() => subject.ComputeInput(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeOutput() + { + Assert.That(() => ((IType)null).ComputeOutput(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeOutput(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var outFeature = new Feature { Direction = FeatureDirectionKind.Out }; + var outMembership = new FeatureMembership(); + subject.AssignOwnership(outMembership, outFeature); + + Assert.That(() => subject.ComputeOutput(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeAllRedefinedFeaturesOfOperation() + { + Assert.That(() => ((IType)null).ComputeAllRedefinedFeaturesOfOperation(new OwningMembership()), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeAllRedefinedFeaturesOfOperation(null), Throws.TypeOf()); + + // membership whose memberElement is not a Feature returns an empty collection. + var nonFeatureMembership = new Membership { MemberElement = new Type() }; + + Assert.That(subject.ComputeAllRedefinedFeaturesOfOperation(nonFeatureMembership), Has.Count.EqualTo(0)); + + // populated case: a Feature with a direct redefinition chain — result includes the Feature + // itself plus the redefined target (per IFeature.AllRedefinedFeatures()). + var redefinedTarget = new Feature(); + var redefiningFeature = new Feature(); + var redefinition = new Redefinition { RedefinedFeature = redefinedTarget }; + redefiningFeature.AssignOwnership(redefinition); + + var featureMembership = new Membership { MemberElement = redefiningFeature }; + + var result = subject.ComputeAllRedefinedFeaturesOfOperation(featureMembership); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Does.Contain(redefiningFeature)); + Assert.That(result, Does.Contain(redefinedTarget)); + } + } + + [Test] + public void VerifyComputeRemoveRedefinedFeaturesOperation() + { + Assert.That(() => ((IType)null).ComputeRemoveRedefinedFeaturesOperation([]), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(() => subject.ComputeRemoveRedefinedFeaturesOperation(null), Throws.TypeOf()); + + // empty input → empty output. + Assert.That(subject.ComputeRemoveRedefinedFeaturesOperation([]), Has.Count.EqualTo(0)); + + // populated: two memberships where one carries a Feature that redefines the Feature in the other. + // The redefining-feature membership should drop the redefined-feature membership. + var baseFeature = new Feature(); + var derivedFeature = new Feature(); + var derivedRedefinesBase = new Redefinition { RedefinedFeature = baseFeature }; + derivedFeature.AssignOwnership(derivedRedefinesBase); + + var baseMembership = new Membership { MemberElement = baseFeature }; + var derivedMembership = new Membership { MemberElement = derivedFeature }; + + var result = subject.ComputeRemoveRedefinedFeaturesOperation([baseMembership, derivedMembership]); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result, Does.Contain(derivedMembership)); + Assert.That(result, Does.Not.Contain(baseMembership)); + } + } + + [Test] + public void VerifyComputeInheritableMembershipsOperation() + { + Assert.That(() => ((IType)null).ComputeInheritableMembershipsOperation(null, null, false), Throws.TypeOf()); + + var subject = new Type(); + + // empty case: no supertypes → no inheritable memberships. + Assert.That(subject.ComputeInheritableMembershipsOperation(null, null, false), Has.Count.EqualTo(0)); + + // populated: subject specializes a supertype that has a public Type membership → + // the public membership surfaces; private members are excluded. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var publicElement = new Type(); + var publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(publicMembership, publicElement); + + var privateElement = new Type(); + var privateMembership = new OwningMembership { Visibility = VisibilityKind.Private }; + supertype.AssignOwnership(privateMembership, privateElement); + + var result = subject.ComputeInheritableMembershipsOperation(null, null, false); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Does.Contain(publicMembership)); + Assert.That(result, Does.Not.Contain(privateMembership)); + } + } + + [Test] + public void VerifyComputeInheritedMembershipsOperation() + { + Assert.That(() => ((IType)null).ComputeInheritedMembershipsOperation(null, null, false), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeInheritedMembershipsOperation(null, null, false), Has.Count.EqualTo(0)); + + // populated: subject specializes a supertype that exposes a public Type membership. + // RemoveRedefinedFeatures (which wraps InheritableMemberships) leaves Type-typed memberships + // alone, so the public membership surfaces. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var publicElement = new Type(); + var publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(publicMembership, publicElement); + + Assert.That(subject.ComputeInheritedMembershipsOperation(null, null, false), Does.Contain(publicMembership)); + } + + [Test] + public void VerifyComputeNonPrivateMembershipsOperation() + { + Assert.That(() => ((IType)null).ComputeNonPrivateMembershipsOperation(null, null, false), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeNonPrivateMembershipsOperation(null, null, false), Has.Count.EqualTo(0)); + + var publicElement = new Type(); + var publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + subject.AssignOwnership(publicMembership, publicElement); + + var privateElement = new Type(); + var privateMembership = new OwningMembership { Visibility = VisibilityKind.Private }; + subject.AssignOwnership(privateMembership, privateElement); + + // private memberships must be excluded; the public one remains. + var result = subject.ComputeNonPrivateMembershipsOperation(null, null, false); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Does.Contain(publicMembership)); + Assert.That(result, Does.Not.Contain(privateMembership)); + } + } + + [Test] + public void VerifyComputeRedefinedVisibleMembershipsOperation() + { + Assert.That(() => ((IType)null).ComputeRedefinedVisibleMembershipsOperation(null, false, false), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeRedefinedVisibleMembershipsOperation(null, false, false), Has.Count.EqualTo(0)); + + var publicElement = new Type(); + var publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + subject.AssignOwnership(publicMembership, publicElement); + + var privateElement = new Type(); + var privateMembership = new OwningMembership { Visibility = VisibilityKind.Private }; + subject.AssignOwnership(privateMembership, privateElement); + + var includeAllResult = subject.ComputeRedefinedVisibleMembershipsOperation(null, false, true); + var publicOnlyResult = subject.ComputeRedefinedVisibleMembershipsOperation(null, false, false); + + using (Assert.EnterMultipleScope()) + { + // includeAll = true → both public and private surface. + Assert.That(includeAllResult, Does.Contain(publicMembership)); + Assert.That(includeAllResult, Does.Contain(privateMembership)); + + // includeAll = false → only public. + Assert.That(publicOnlyResult, Does.Contain(publicMembership)); + Assert.That(publicOnlyResult, Does.Not.Contain(privateMembership)); + } + } + + [Test] + public void VerifyComputeFeatureMembership() + { + Assert.That(() => ((IType)null).ComputeFeatureMembership(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeFeatureMembership(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. + var feature = new Feature(); + var featureMembership = new FeatureMembership(); + subject.AssignOwnership(featureMembership, feature); + + Assert.That(() => subject.ComputeFeatureMembership(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeInheritedFeature() + { + Assert.That(() => ((IType)null).ComputeInheritedFeature(), Throws.TypeOf()); + + var subject = new Type(); + + // empty case: no inherited memberships → no inherited features. + Assert.That(subject.ComputeInheritedFeature(), Has.Count.EqualTo(0)); + + // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still + // a stub: the OCL is `inheritedMembership->selectByKind(FeatureMembership).memberFeature`, and + // resolving `memberFeature` on a populated FeatureMembership reads `ownedMemberFeature` → stub. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var inheritedFeature = new Feature(); + var inheritedFeatureMembership = new FeatureMembership(); + supertype.AssignOwnership(inheritedFeatureMembership, inheritedFeature); + + Assert.That(() => subject.ComputeInheritedFeature(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeInheritedMembership() + { + Assert.That(() => ((IType)null).ComputeInheritedMembership(), Throws.TypeOf()); + + var subject = new Type(); + + Assert.That(subject.ComputeInheritedMembership(), Has.Count.EqualTo(0)); + + // populated: subject specializes a supertype with a public Type membership → + // the public membership surfaces in inheritedMembership. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var publicElement = new Type(); + var publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(publicMembership, publicElement); + + Assert.That(subject.ComputeInheritedMembership(), Does.Contain(publicMembership)); + } + + [Test] + public void VerifyComputeSpecializesFromLibraryOperation() + { + Assert.That(() => ((IType)null).ComputeSpecializesFromLibraryOperation("Some::Type"), Throws.TypeOf()); + + var bareSubject = new Type(); + + using (Assert.EnterMultipleScope()) + { + // null/blank library names short-circuit to false. + Assert.That(bareSubject.ComputeSpecializesFromLibraryOperation(null), Is.False); + Assert.That(bareSubject.ComputeSpecializesFromLibraryOperation(" "), Is.False); + + // unresolvable name → ResolveGlobal returns null → false. + Assert.That(bareSubject.ComputeSpecializesFromLibraryOperation("Nonexistent::Type"), Is.False); + } + + // positive case: build a small library namespace, place the subject type in it, + // and have the subject specialize the library type. + var rootNamespace = new Namespace(); + + var libraryType = new Type { DeclaredName = "LibType" }; + var libraryMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(libraryMembership, libraryType); + + var subject = new Type(); + var subjectMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(subjectMembership, subject); + + var specialization = new Specialization { Specific = subject, General = libraryType }; + subject.AssignOwnership(specialization); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeSpecializesFromLibraryOperation("LibType"), Is.True); + + // resolves to a Type the subject does NOT specialize → false. + var unrelatedType = new Type { DeclaredName = "UnrelatedType" }; + var unrelatedMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + rootNamespace.AssignOwnership(unrelatedMembership, unrelatedType); + + Assert.That(subject.ComputeSpecializesFromLibraryOperation("UnrelatedType"), Is.False); + } + } + + [Test] + public void VerifyComputeMultiplicitiesOperation() + { + Assert.That(() => ((IType)null).ComputeMultiplicitiesOperation(), Throws.TypeOf()); + + var subject = new Type(); + + // empty case: no own multiplicity, no supertypes → empty result. + Assert.That(subject.ComputeMultiplicitiesOperation(), Has.Count.EqualTo(0)); + + // populated case: subject owns a Multiplicity directly → result is just that one. + var multiplicity = new Multiplicity(); + var multiplicityMembership = new OwningMembership(); + subject.AssignOwnership(multiplicityMembership, multiplicity); + + var result = subject.ComputeMultiplicitiesOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(multiplicity)); + } } } } diff --git a/SysML2.NET/Extend/TypeExtensions.cs b/SysML2.NET/Extend/TypeExtensions.cs index 7d228815..4baa046a 100644 --- a/SysML2.NET/Extend/TypeExtensions.cs +++ b/SysML2.NET/Extend/TypeExtensions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // // // Copyright (C) 2022-2026 Starion Group S.A. @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Core.Types { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -51,10 +52,11 @@ internal static class TypeExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeDifferencingType(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedDifferencing.Select(x => x.DifferencingType).Where(x => x != null)]; } /// @@ -72,10 +74,11 @@ internal static List ComputeDifferencingType(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeDirectedFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.feature.Where(memberFeature => typeSubject.DirectionOf(memberFeature) != null)]; } /// @@ -93,10 +96,11 @@ internal static List ComputeDirectedFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeEndFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.feature.Where(memberFeature => memberFeature.IsEnd)]; } /// @@ -114,10 +118,11 @@ internal static List ComputeEndFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.featureMembership.Select(membership => membership.ownedMemberFeature).Where(x => x != null)]; } /// @@ -136,10 +141,11 @@ internal static List ComputeFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeFeatureMembership(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedFeatureMembership.Union(typeSubject.inheritedMembership.OfType())]; } /// @@ -158,10 +164,11 @@ internal static List ComputeFeatureMembership(this IType typ /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeInheritedFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.inheritedMembership.OfType().Select(membership => membership.ownedMemberFeature).Where(x => x != null)]; } /// @@ -179,10 +186,11 @@ internal static List ComputeInheritedFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeInheritedMembership(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : typeSubject.InheritedMemberships([], [], false); } /// @@ -203,10 +211,11 @@ internal static List ComputeInheritedMembership(this IType typeSubj /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeInput(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.feature.Where(memberFeature => typeSubject.DirectionOf(memberFeature) is FeatureDirectionKind.In or FeatureDirectionKind.Inout)]; } /// @@ -224,10 +233,11 @@ internal static List ComputeInput(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeIntersectingType(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedIntersecting.Select(intersecting => intersecting.IntersectingType).Where(x => x != null)]; } /// @@ -239,10 +249,11 @@ internal static List ComputeIntersectingType(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsConjugated(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : typeSubject.ownedConjugator != null; } /// @@ -265,10 +276,11 @@ internal static bool ComputeIsConjugated(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IMultiplicity ComputeMultiplicity(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : typeSubject.ownedMember.OfType().FirstOrDefault(); } /// @@ -289,10 +301,11 @@ internal static IMultiplicity ComputeMultiplicity(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOutput(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.feature.Where(memberFeature => typeSubject.DirectionOf(memberFeature) is FeatureDirectionKind.Out or FeatureDirectionKind.Inout)]; } /// @@ -314,10 +327,11 @@ internal static List ComputeOutput(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IConjugation ComputeOwnedConjugator(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : typeSubject.OwnedRelationship.OfType().FirstOrDefault(); } /// @@ -336,10 +350,11 @@ internal static IConjugation ComputeOwnedConjugator(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedDifferencing(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType()]; } /// @@ -358,10 +373,11 @@ internal static List ComputeOwnedDifferencing(this IType typeSubj /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedDisjoining(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType()]; } /// @@ -379,10 +395,11 @@ internal static List ComputeOwnedDisjoining(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedEndFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedFeature.Where(memberFeature => memberFeature.IsEnd)]; } /// @@ -400,10 +417,11 @@ internal static List ComputeOwnedEndFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedFeature(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedFeatureMembership.Select(membership => membership.ownedMemberFeature).Where(x => x != null)]; } /// @@ -421,10 +439,11 @@ internal static List ComputeOwnedFeature(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedFeatureMembership(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType()]; } /// @@ -442,10 +461,11 @@ internal static List ComputeOwnedFeatureMembership(this ITyp /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedIntersecting(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType()]; } /// @@ -464,10 +484,11 @@ internal static List ComputeOwnedIntersecting(this IType typeSubj /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedSpecialization(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType().Where(specialization => specialization.Specific == typeSubject)]; } /// @@ -486,10 +507,11 @@ internal static List ComputeOwnedSpecialization(this IType type /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedUnioning(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.OwnedRelationship.OfType()]; } /// @@ -507,10 +529,11 @@ internal static List ComputeOwnedUnioning(this IType typeSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeUnioningType(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return typeSubject == null + ? throw new ArgumentNullException(nameof(typeSubject)) + : [..typeSubject.ownedUnioning.Select(unioning => unioning.UnioningType).Where(x => x != null)]; } /// @@ -543,10 +566,25 @@ internal static List ComputeUnioningType(this IType typeSubject) /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRedefinedVisibleMembershipsOperation(this IType typeSubject, List excluded, bool isRecursive, bool includeAll) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + var safeExcluded = excluded ?? []; + + // Call NamespaceExtensions directly: Type.VisibleMemberships is wired back to this method, + // so going through INamespace.VisibleMemberships would recurse infinitely. + var visibleMemberships = typeSubject.ComputeVisibleMembershipsOperation(safeExcluded, isRecursive, includeAll); + + var excludedWithSelf = new List(safeExcluded) { typeSubject }; + + var visibleInheritedMemberships = typeSubject.InheritedMemberships(excludedWithSelf, [], isRecursive) + .Where(membership => includeAll || membership.Visibility == VisibilityKind.Public); + + return [..visibleMemberships.Union(visibleInheritedMemberships)]; } /// @@ -576,10 +614,16 @@ internal static List ComputeRedefinedVisibleMembershipsOperation(th /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeInheritedMembershipsOperation(this IType typeSubject, List excludedNamespaces, List excludedTypes, bool excludeImplied) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + var inheritable = typeSubject.InheritableMemberships(excludedNamespaces ?? [], excludedTypes ?? [], excludeImplied); + + return typeSubject.RemoveRedefinedFeatures(inheritable); } /// @@ -610,10 +654,31 @@ internal static List ComputeInheritedMembershipsOperation(this ITyp /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeInheritableMembershipsOperation(this IType typeSubject, List excludedNamespaces, List excludedTypes, bool excludeImplied) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + var safeExcludedNamespaces = excludedNamespaces ?? []; + var safeExcludedTypes = excludedTypes ?? []; + + var excludingSelf = new List(safeExcludedTypes) { typeSubject }; + + var result = new List(); + + foreach (var supertype in typeSubject.Supertypes(excludeImplied)) + { + if (supertype == null || excludingSelf.Contains(supertype)) + { + continue; + } + + result.AddRange(supertype.NonPrivateMemberships(safeExcludedNamespaces, excludingSelf, excludeImplied)); + } + + return result; } /// @@ -651,18 +716,29 @@ internal static List ComputeInheritableMembershipsOperation(this IT /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeNonPrivateMembershipsOperation(this IType typeSubject, List excludedNamespaces, List excludedTypes, bool excludeImplied) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + var safeExcludedNamespaces = excludedNamespaces ?? []; + var safeExcludedTypes = excludedTypes ?? []; + + var publicMemberships = typeSubject.MembershipsOfVisibility(VisibilityKind.Public, safeExcludedNamespaces); + var protectedMemberships = typeSubject.MembershipsOfVisibility(VisibilityKind.Protected, safeExcludedNamespaces); + var inheritedMemberships = typeSubject.InheritedMemberships(safeExcludedNamespaces, safeExcludedTypes, excludeImplied); + + return [..publicMemberships.Union(protectedMemberships).Union(inheritedMemberships)]; } /// /// Return a subset of memberships, removing those Memberships whose memberElements are Features and for - /// which either of the following two conditions holds:
    + /// which either of the following two conditions holds:
      ///
    1. The memberElement of the Membership is included in redefined Features of another /// Membership in memberships.
    2. One of the redefined Features of the - /// Membership is a directly redefinedFeature of an ownedFeature of this Type.
    3. + /// Membership is a directly redefinedFeature of an ownedFeature of this Type. ///
    For this purpose, the redefined Features of a Membership /// whose memberElement is a Feature includes the memberElement and all Features directly or indirectly /// redefined by the memberElement. @@ -690,10 +766,35 @@ internal static List ComputeNonPrivateMembershipsOperation(this ITy /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRemoveRedefinedFeaturesOperation(this IType typeSubject, List memberships) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (memberships == null) + { + throw new ArgumentNullException(nameof(memberships)); + } + + var reducedMemberships = memberships + .Where(currentMembership => !memberships.Any(otherMembership => + otherMembership != currentMembership + && typeSubject.AllRedefinedFeaturesOf(otherMembership).Contains(currentMembership.MemberElement as IFeature))) + .ToList(); + + var redefinedFeatures = typeSubject.ownedFeature + .SelectMany(ownedFeature => ownedFeature.OwnedRelationship.OfType()) + .Select(redefinition => redefinition.RedefinedFeature) + .Where(x => x != null) + .ToHashSet(); + + return + [ + ..reducedMemberships.Where(membership => + !typeSubject.AllRedefinedFeaturesOf(membership).Any(redefinedFeatures.Contains)) + ]; } /// @@ -717,10 +818,21 @@ internal static List ComputeRemoveRedefinedFeaturesOperation(this I /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAllRedefinedFeaturesOfOperation(this IType typeSubject, IMembership membership) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (membership == null) + { + throw new ArgumentNullException(nameof(membership)); + } + + return membership.MemberElement is IFeature memberFeature + ? memberFeature.AllRedefinedFeatures() + : []; } /// @@ -742,10 +854,19 @@ internal static List ComputeAllRedefinedFeaturesOfOperation(this IType /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static FeatureDirectionKind? ComputeDirectionOfOperation(this IType typeSubject, IFeature feature) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (feature == null) + { + throw new ArgumentNullException(nameof(feature)); + } + + return typeSubject.DirectionOfExcluding(feature, []); } /// @@ -785,10 +906,56 @@ internal static List ComputeAllRedefinedFeaturesOfOperation(this IType /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static FeatureDirectionKind? ComputeDirectionOfExcludingOperation(this IType typeSubject, IFeature feature, List excluded) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (feature == null) + { + throw new ArgumentNullException(nameof(feature)); + } + + var excludedWithSelf = new List(excluded ?? []) { typeSubject }; + + if (feature.owningType == typeSubject) + { + return feature.Direction; + } + + FeatureDirectionKind? direction = null; + + foreach (var supertype in typeSubject.Supertypes(false)) + { + if (supertype == null || excludedWithSelf.Contains(supertype)) + { + continue; + } + + var supertypeDirection = supertype.DirectionOfExcluding(feature, excludedWithSelf); + + if (supertypeDirection != null) + { + direction = supertypeDirection; + break; + } + } + + if (direction == null) + { + return null; + } + + return typeSubject.isConjugated + ? direction switch + { + FeatureDirectionKind.In => FeatureDirectionKind.Out, + FeatureDirectionKind.Out => FeatureDirectionKind.In, + _ => direction + } + : direction; } /// @@ -815,10 +982,25 @@ internal static List ComputeAllRedefinedFeaturesOfOperation(this IType /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeSupertypesOperation(this IType typeSubject, bool excludeImplied) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (typeSubject.isConjugated) + { + var originalType = typeSubject.ownedConjugator?.OriginalType; + + return originalType == null ? [] : [originalType]; + } + + var specializations = excludeImplied + ? typeSubject.ownedSpecialization.Where(specialization => !specialization.IsImplied) + : typeSubject.ownedSpecialization; + + return [..specializations.Select(specialization => specialization.General).Where(x => x != null)]; } /// @@ -837,10 +1019,29 @@ internal static List ComputeSupertypesOperation(this IType typeSubject, b /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAllSupertypesOperation(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + var visited = new List { typeSubject }; + var queue = new Queue(); + queue.Enqueue(typeSubject); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + foreach (var supertype in current.Supertypes(false).Where(supertype => supertype != null && !visited.Contains(supertype))) + { + visited.Add(supertype); + queue.Enqueue(supertype); + } + } + + return visited; } /// @@ -865,10 +1066,26 @@ internal static List ComputeAllSupertypesOperation(this IType typeSubject /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeSpecializesOperation(this IType typeSubject, IType supertype) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (supertype == null) + { + throw new ArgumentNullException(nameof(supertype)); + } + + if (typeSubject.isConjugated) + { + var originalType = typeSubject.ownedConjugator?.OriginalType; + + return originalType != null && originalType.Specializes(supertype); + } + + return typeSubject.AllSupertypes().Contains(supertype); } /// @@ -893,10 +1110,21 @@ internal static bool ComputeSpecializesOperation(this IType typeSubject, IType s /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeSpecializesFromLibraryOperation(this IType typeSubject, string libraryTypeName) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (string.IsNullOrWhiteSpace(libraryTypeName)) + { + return false; + } + + var resolved = typeSubject.ResolveGlobal(libraryTypeName); + + return resolved?.MemberElement is IType libraryType && typeSubject.Specializes(libraryType); } /// @@ -918,14 +1146,23 @@ internal static bool ComputeSpecializesFromLibraryOperation(this IType typeSubje /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsCompatibleWithOperation(this IType typeSubject, IType otherType) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (otherType == null) + { + throw new ArgumentNullException(nameof(otherType)); + } + + return typeSubject.Specializes(otherType); } /// - /// Return the owned or inherited Multiplicities for this Type<./code>. + /// Return the owned or inherited Multiplicities for this Type /// /// /// OCL2.0: @@ -945,10 +1182,44 @@ internal static bool ComputeIsCompatibleWithOperation(this IType typeSubject, IT /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeMultiplicitiesOperation(this IType typeSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (typeSubject == null) + { + throw new ArgumentNullException(nameof(typeSubject)); + } + + if (typeSubject.multiplicity != null) + { + return [typeSubject.multiplicity]; + } + + var visited = new HashSet { typeSubject }; + var result = new List(); + var queue = new Queue(); + + foreach (var general in typeSubject.ownedSpecialization.Select(specialization => specialization.General).Where(x => x != null && visited.Add(x))) + { + queue.Enqueue(general); + } + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + if (current.multiplicity != null) + { + result.Add(current.multiplicity); + continue; + } + + foreach (var general in current.ownedSpecialization.Select(specialization => specialization.General).Where(x => x != null && visited.Add(x))) + { + queue.Enqueue(general); + } + } + + return result; } } }