Skip to content

Commit

Permalink
Adjusted Memento to generate the memento child class when missing (wa…
Browse files Browse the repository at this point in the history
…s not possible before Metalama 2024.2)

Fixed and adjusted tests
Added a unit test case for when we have an autogenerated memento child class that the user is trying to use in a MementoCreateHook or MementoRestoreHook
  • Loading branch information
niklasstich committed Oct 9, 2024
1 parent 2672348 commit 288e8d7
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 17 deletions.
31 changes: 27 additions & 4 deletions Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Metalama.Framework.Advising;
using Metalama.Framework.Code.DeclarationBuilders;
using Moyou.Diagnostics;
using Moyou.Extensions;

Expand Down Expand Up @@ -41,6 +44,19 @@ public class MementoAttribute : TypeAspect
Warnings.Memento.NonSupportedMemberInStrictModeMessageFormat,
Warnings.Memento.NonSupportedMemberInStrictModeTitle,
Warnings.Memento.NonSupportedMemberInStrictModeCategory);

/// <summary>
/// MOYOU1002
/// </summary>
/// <remarks>
/// INamedType should be target originator type
/// </remarks>
private static readonly DiagnosticDefinition<INamedType>
WarningNoMementoNestedClass =
new(Warnings.Memento.NoMementoNestedClassId, Severity.Warning,
Warnings.Memento.NoMementoNestedClassMessageFormat,
Warnings.Memento.NoMementoNestedClassTitle,
Warnings.Memento.NoMementoNestedClassCategory);

public override void BuildAspect(IAspectBuilder<INamedType> builder)
{
Expand All @@ -63,7 +79,14 @@ public override void BuildAspect(IAspectBuilder<INamedType> builder)
relevantMembers = relevantMembers.Except(membersWithUnsupportedTypes).ToList();
}

var nestedMementoType = builder.Target.NestedTypes.First(NestedTypeIsEligible);
var res = builder.Advice.IntroduceClass(builder.Target, "Memento", OverrideStrategy.Ignore);
if (res.Outcome == AdviceOutcome.Default)
{
builder.Diagnostics.Report(WarningNoMementoNestedClass.WithArguments(builder.Target), builder.Target);
}
var nestedMementoType = res.Outcome == AdviceOutcome.Default
? res.Declaration
: builder.Target.NestedTypes.First(NestedTypeIsEligible);

Check warning on line 89 in Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest with .NET Core 8.x

'INamedType.NestedTypes' is obsolete: 'Use the Types property.'

Check warning on line 89 in Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest with .NET Core 8.x

'INamedType.NestedTypes' is obsolete: 'Use the Types property.'

Check warning on line 89 in Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest with .NET Core 8.x

'INamedType.NestedTypes' is obsolete: 'Use the Types property.'

//introduce relevant fields and properties to the memento type
var introducedFieldsOnMemento = IntroduceMementoTypeFields().ToList();
Expand Down Expand Up @@ -145,9 +168,9 @@ bool IsTypeOfMemberSupported(IFieldOrProperty member)
public override void BuildEligibility(IEligibilityBuilder<INamedType> builder)
{
base.BuildEligibility(builder);
builder.MustSatisfy(type => type.NestedTypes.Any(NestedTypeIsEligible),
type =>
$"{type.Description} must contain a nested private class, (struct) record or struct called 'Memento''");
// builder.MustSatisfy(type => type.NestedTypes.Any(NestedTypeIsEligible),
// type =>
// $"{type.Description} must contain a nested private class, (struct) record or struct called 'Memento''");
builder.MustNotBeAbstract();
builder.MustNotBeInterface();
}
Expand Down
6 changes: 6 additions & 0 deletions Moyou.Diagnostics/Warnings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public static class Memento

public static string NonSupportedMemberInStrictModeTitle => "Non-supported member in strict mode";
public static string NonSupportedMemberInStrictModeCategory => "Memento";

public static string NoMementoNestedClassId => "MOYOU1002";
public static string NoMementoNestedClassMessageFormat =>
"Class {0} does not have a nested class named Memento. One will be automatically generated. Please note that if you plan on using MementoCreateHook and MementoRestoreHook attributes, you need to either define the nested class yourself or use a fully qualified name (e.g. {0}.Memento) in the signature of your hook methods.";
public static string NoMementoNestedClassTitle => "Autogenerated Memento nested class";
public static string NoMementoNestedClassCategory => "Memento";
}

/// <summary>
Expand Down
11 changes: 6 additions & 5 deletions Moyou.Extensions/MementoExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Metalama.Framework.Aspects;
using System.Diagnostics;
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Eligibility;

Expand All @@ -13,15 +14,15 @@ public static void HasExactlyOneParameterOfTypeNestedMemento(this IEligibilityBu
builder.MustSatisfyAll(innerBuilder =>
{
innerBuilder.MustSatisfy(method => method.Parameters.Count == 1,
method => $"{method.Description} must have exactly one parameter");
method => $"{method.Description} must have exactly one parameter of the Memento type");
innerBuilder.MustSatisfy(method =>
{
var mementoType = method.DeclaringType.NestedTypes.FirstOrDefault(type => type.Name == "Memento");
return mementoType != null && method.Parameters[0].Type.Is(mementoType);
var parameterType = (INamedType)method.Parameters[0].Type;
return parameterType.Name.Split('.').Last() == "Memento" && parameterType.DeclaringType == method.DeclaringType;
}, method =>
{
var mementoType =
method.Object.DeclaringType.NestedTypes.FirstOrDefault(type => type.Name == "Memento");
method.Object.DeclaringType.Types.FirstOrDefault(type => type.Name == "Memento");
return $"{method.Description} must have exactly one parameter of type {mementoType?.FullName}";
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Error LAMA0037 on `MementoCreateHook`: `The aspect 'MementoCreateHook' cannot be applied to the method 'HasNoParameters.CreateMementoHook()' because 'HasNoParameters.CreateMementoHook()' must have exactly one parameter.`
// Error LAMA0037 on `MementoCreateHook`: `The aspect 'MementoCreateHook' cannot be applied to the method 'HasNoParameters.CreateMementoHook()' because 'HasNoParameters.CreateMementoHook()' must have exactly one parameter of the Memento type.`
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Error LAMA0037 on `MementoCreateHook`: `The aspect 'MementoCreateHook' cannot be applied to the method 'HasTooManyParameters.CreateMementoHook(int, int)' because 'HasTooManyParameters.CreateMementoHook(int, int)' must have exactly one parameter.`
// Error LAMA0037 on `MementoCreateHook`: `The aspect 'MementoCreateHook' cannot be applied to the method 'HasTooManyParameters.CreateMementoHook(int, int)' because 'HasTooManyParameters.CreateMementoHook(int, int)' must have exactly one parameter of the Memento type.`
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
// Error LAMA0037 on `Memento`: `The aspect 'Memento' cannot be applied to the type 'NoMementoNestedClass' because 'NoMementoNestedClass' must contain a nested private class, (struct) record or struct called 'Memento''.`
// Warning MOYOU1002 on `NoMementoNestedClass`: `Class NoMementoNestedClass does not have a nested class named Memento. One will be automatically generated. Please note that if you plan on using MementoCreateHook and MementoRestoreHook attributes, you need to either define the nested class yourself or use a fully qualified name (e.g. NoMementoNestedClass.Memento) in the signature of your hook methods.`
using Moyou.Aspects.Memento;
namespace Moyou.CompileTimeTest.MementoTests.MementoAttributeTests;
[Memento]
internal class NoMementoNestedClass : global::Moyou.Aspects.Memento.IOriginator
{
string A { get; set; }
public global::Moyou.Aspects.Memento.IMemento CreateMemento()
{
return (global::Moyou.Aspects.Memento.IMemento)this.CreateMementoImpl();
}
private global::Moyou.Aspects.Memento.IMemento CreateMementoImpl()
{
var memento = new global::Moyou.CompileTimeTest.MementoTests.MementoAttributeTests.NoMementoNestedClass.Memento();
memento.A = this.A;
return (global::Moyou.Aspects.Memento.IMemento)memento;
}
public void RestoreMemento(global::Moyou.Aspects.Memento.IMemento memento)
{
this.RestoreMementoImpl(memento);
}
private void RestoreMementoImpl(global::Moyou.Aspects.Memento.IMemento memento)
{
try
{
var cast = ((global::Moyou.CompileTimeTest.MementoTests.MementoAttributeTests.NoMementoNestedClass.Memento)memento);
this.A = ((global::Moyou.CompileTimeTest.MementoTests.MementoAttributeTests.NoMementoNestedClass.Memento)cast!).A;
}
catch (global::System.InvalidCastException icex)
{
throw new global::System.ArgumentException("Incorrect memento type", nameof(memento), icex);
}
}
class Memento : global::Moyou.Aspects.Memento.IMemento
{
public global::System.String A;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@ namespace Moyou.CompileTimeTest.MementoTests.RestoreHookAttributeTests;
[Memento]
internal class HasNoParameters
{
[MementoRestoreHook]
public void RestoreMementoHook()
{
}

private record Memento
{

}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Error LAMA0037 on `Memento`: `The aspect 'Memento' cannot be applied to the type 'HasNoParameters' because 'HasNoParameters' must contain a nested private class, (struct) record or struct called 'Memento''.`
// Error LAMA0037 on `MementoRestoreHook`: `The aspect 'MementoRestoreHook' cannot be applied to the method 'HasNoParameters.RestoreMementoHook()' because 'HasNoParameters.RestoreMementoHook()' must have exactly one parameter of the Memento type.`
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace Moyou.CompileTimeTest.MementoTests.RestoreHookAttributeTests;
[Memento]
internal class HasParameterOfWrongType
{
[MementoCreateHook]
public void CreateMementoHook(int parameter)
[MementoRestoreHook]
public void RestoreMementoHook(int parameter)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Error LAMA0037 on `MementoCreateHook`: `The aspect 'MementoCreateHook' cannot be applied to the method 'HasParameterOfWrongType.CreateMementoHook(int)' because 'HasParameterOfWrongType.CreateMementoHook(int)' must have exactly one parameter of type Moyou.CompileTimeTest.MementoTests.RestoreHookAttributeTests.HasParameterOfWrongType.Memento.`
// Error LAMA0037 on `MementoRestoreHook`: `The aspect 'MementoRestoreHook' cannot be applied to the method 'HasParameterOfWrongType.RestoreMementoHook(int)' because 'HasParameterOfWrongType.RestoreMementoHook(int)' must have exactly one parameter of type Moyou.CompileTimeTest.MementoTests.RestoreHookAttributeTests.HasParameterOfWrongType.Memento.`
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Error LAMA0037 on `MementoRestoreHook`: `The aspect 'MementoRestoreHook' cannot be applied to the method 'HasTooManyParameters.RestoreMementoHook(int, int, int)' because 'HasTooManyParameters.RestoreMementoHook(int, int, int)' must have exactly one parameter.`
// Error LAMA0037 on `MementoRestoreHook`: `The aspect 'MementoRestoreHook' cannot be applied to the method 'HasTooManyParameters.RestoreMementoHook(int, int, int)' because 'HasTooManyParameters.RestoreMementoHook(int, int, int)' must have exactly one parameter of the Memento type.`

0 comments on commit 288e8d7

Please sign in to comment.