diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/AspectOrder.cs b/Moyou.Aspects/Moyou.Aspects.Factory/AspectOrder.cs new file mode 100644 index 0000000..36d1105 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/AspectOrder.cs @@ -0,0 +1,4 @@ +using Metalama.Framework.Aspects; +using Moyou.Aspects.Factory; + +[assembly: AspectOrder(AspectOrderDirection.CompileTime, typeof(FactoryMemberAspect), typeof(FactoryAttribute))] \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttribute.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttribute.cs new file mode 100644 index 0000000..dedc4d2 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttribute.cs @@ -0,0 +1,123 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Metalama.Framework.Advising; +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; +using Metalama.Framework.Diagnostics; +using Moyou.Diagnostics; +using Moyou.Extensions; + +namespace Moyou.Aspects.Factory; + +[AttributeUsage(AttributeTargets.Class)] +public class FactoryAttribute : TypeAspect +{ + private static readonly DiagnosticDefinition ErrorNoSuitableConstructor = + new(Errors.Factory.NoSuitableConstructorId, Severity.Error, + Errors.Factory.NoSuitableConstructorMessageFormat, + Errors.Factory.NoSuitableConstructorTitle, + Errors.Factory.NoSuitableConstructorCategory); + + private static readonly DiagnosticDefinition ErrorMultipleMarkedConstructors = + new(Errors.Factory.MultipleMarkedConstructorsId, Severity.Error, + Errors.Factory.MultipleMarkedConstructorsMessageFormat, + Errors.Factory.MultipleMarkedConstructorTitle, + Errors.Factory.MultipleMarkedConstructorCategory); + + [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly")] //property is argument + public override void BuildAspect(IAspectBuilder builder) + { + base.BuildAspect(builder); + + //read the annotations from FactoryMemberAspect and process all tuples + var annotations = builder.Target.Enhancements().GetAnnotations(); + var tuples = annotations.Select(annotation => annotation.AsTuple()).ToList(); + foreach (var tuple in tuples) + { + AddMemberToFactory(builder, tuple); + } + } + + private void AddMemberToFactory(IAspectBuilder builder, (INamedType, INamedType) tuple) + { + var memberType = tuple.Item1; + var primaryInterface = tuple.Item2; + var trimmedInterfaceName = primaryInterface.Name.StartsWith("I") + ? primaryInterface.Name[1..] + : primaryInterface.Name; + if (memberType.HasPublicDefaultConstructor()) + { + builder.IntroduceMethod(nameof(CreateTemplateDefaultConstructor), IntroductionScope.Instance, + buildMethod: methodBuilder => + { + //drop the leading 'I' from the interface in the method name + methodBuilder.Name = $"Create{trimmedInterfaceName}"; + methodBuilder.Accessibility = Accessibility.Public; + }, args: new { TInterface = primaryInterface, memberType }); + } + else + { + HandleNonDefaultConstructor(builder, memberType, trimmedInterfaceName, primaryInterface); + } + } + + private static void HandleNonDefaultConstructor(IAspectBuilder builder, INamedType memberType, + string trimmedInterfaceName, INamedType primaryInterface) + { + IConstructor? constructor; + if (memberType.Constructors.Count == 1) + { + constructor = memberType.Constructors.Single(); + } + else + { + try + { + constructor = + memberType.Constructors.SingleOrDefault(ctor => ctor.HasAttribute()); + } + catch (InvalidOperationException iox) + { + //only one constructor with attribute allowed + foreach (var markedCtor in memberType.Constructors.Where(ctor => ctor.HasAttribute())) + { + builder.Diagnostics.Report(ErrorMultipleMarkedConstructors.WithArguments(memberType), markedCtor); + } + + return; + } + } + + if (constructor == null) + { + //no constructor is marked + builder.Diagnostics.Report(ErrorNoSuitableConstructor.WithArguments(memberType), memberType); + return; + } + + builder.IntroduceMethod(nameof(CreateTemplate), IntroductionScope.Instance, buildMethod: builder => + { + builder.Name = $"Create{trimmedInterfaceName}"; + builder.Accessibility = Accessibility.Public; + //add all constructor parameters to factory method + foreach (var constructorParameter in constructor.Parameters) + { + builder.AddParameter(constructorParameter.Name, constructorParameter.Type); + } + }, args: new { TInterface = primaryInterface, constructor }); + } + + [Template] + public static TInterface CreateTemplateDefaultConstructor<[CompileTime] TInterface>( + [CompileTime] INamedType memberType) + { + var constructor = meta.CompileTime(memberType.Constructors.GetPublicDefaultConstructor()); + return constructor.Invoke()!; + } + + [Template] + public static TInterface CreateTemplate<[CompileTime] TInterface>([CompileTime] IConstructor constructor) + { + return constructor.Invoke(constructor.Parameters.Select(param => (IExpression)param.Value!)); + } +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttributeOptions.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttributeOptions.cs new file mode 100644 index 0000000..96a1158 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryAttributeOptions.cs @@ -0,0 +1,14 @@ +namespace Moyou.Aspects.Factory; + +// public class FactoryAttributeOptions : IHierarchicalOptions +// { +// public INamedType? AbstractFactoryType { get; set; } +// public object ApplyChanges(object changes, in ApplyChangesContext context) +// { +// var other = (FactoryAttributeOptions)changes; +// return new FactoryAttributeOptions +// { +// AbstractFactoryType = other.AbstractFactoryType ?? AbstractFactoryType +// }; +// } +// } \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryConstructorAttribute.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryConstructorAttribute.cs new file mode 100644 index 0000000..78ada82 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryConstructorAttribute.cs @@ -0,0 +1,10 @@ +using Metalama.Framework.Aspects; + +namespace Moyou.Aspects.Factory; + +[AttributeUsage(AttributeTargets.Constructor)] +[RunTimeOrCompileTime] +public class FactoryConstructorAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAnnotation.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAnnotation.cs new file mode 100644 index 0000000..83dfa5f --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAnnotation.cs @@ -0,0 +1,21 @@ +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; + +namespace Moyou.Aspects.Factory; + +[CompileTime] +public record FactoryMemberAnnotation : IAnnotation +{ + public IRef FactoryMemberType { get; } + public IRef PrimaryInterface { get; } + + public FactoryMemberAnnotation(IRef factoryMemberType, IRef primaryInterface) + { + FactoryMemberType = factoryMemberType; + PrimaryInterface = primaryInterface; + } + + public (INamedType, INamedType) AsTuple() => ( + (INamedType)FactoryMemberType.GetTarget(ReferenceResolutionOptions.Default), + (INamedType)PrimaryInterface.GetTarget(ReferenceResolutionOptions.Default)); +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAspect.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAspect.cs new file mode 100644 index 0000000..2b571f0 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAspect.cs @@ -0,0 +1,29 @@ +using System.Diagnostics.CodeAnalysis; +using Metalama.Framework.Advising; +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; + +namespace Moyou.Aspects.Factory; + +public class FactoryMemberAspect : IAspect +{ + public List<(INamedType, INamedType)> TargetTuples { get; } + + public FactoryMemberAspect(List<(INamedType, INamedType)> targetTuples) + { + TargetTuples = targetTuples; + } + + + [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly")] //property is argument + public void BuildAspect(IAspectBuilder builder) + { + //write an annotation on the target type containing the factory members and primary interface + var annotations = TargetTuples + .Select(tup => new FactoryMemberAnnotation(tup.Item1.ToRef(), tup.Item2.ToRef())); + foreach (var annotation in annotations) + { + builder.AddAnnotation(annotation, true); + } + } +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAttribute.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAttribute.cs new file mode 100644 index 0000000..243278f --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberAttribute.cs @@ -0,0 +1,11 @@ +using Metalama.Framework.Aspects; + +namespace Moyou.Aspects.Factory; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)] +[RunTimeOrCompileTime] +public class FactoryMemberAttribute : Attribute +{ + public Type TargetType { get; set; } + public Type? PrimaryInterface { get; set; } +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberFabric.cs b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberFabric.cs new file mode 100644 index 0000000..96eb6bf --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/FactoryMemberFabric.cs @@ -0,0 +1,163 @@ +using JetBrains.Annotations; +using Metalama.Framework.Code; +using Metalama.Framework.Diagnostics; +using Metalama.Framework.Fabrics; +using Moyou.Diagnostics; +using Moyou.Extensions; + +namespace Moyou.Aspects.Factory; + +[UsedImplicitly] +public class FactoryMemberFabric : TransitiveProjectFabric +{ + //MOYOU2201 + private static readonly DiagnosticDefinition ErrorNoTargetTypeInMemberAttribute = + new(Errors.Factory.NoTargetTypeInMemberAttributeId, Severity.Error, + Errors.Factory.NoTargetTypeInMemberAttributeMessageFormat, + Errors.Factory.NoTargetTypeInMemberAttributeTitle, + Errors.Factory.NoTargetTypeInMemberAttributeCategory); + + //MOYOU2202 + private static readonly DiagnosticDefinition ErrorTypeDoesntImplementAnyInterfaces = + new(Errors.Factory.TypeDoesntImplementAnyInterfacesId, Severity.Error, + Errors.Factory.TypeDoesntImplementAnyInterfacesMessageFormat, + Errors.Factory.TypeDoesntImplementAnyInterfacesTitle, + Errors.Factory.TypeDoesntImplementAnyInterfacesCategory); + + //MOYOU2203 + private static readonly DiagnosticDefinition ErrorAmbiguousInterfacesOnTargetType = + new(Errors.Factory.AmbiguousInterfacesOnTargetTypeId, Severity.Error, + Errors.Factory.AmbiguousInterfacesOnTargetTypeMessageFormat, + Errors.Factory.AmbiguousInterfacesOnTargetTypeTitle, + Errors.Factory.AmbiguousInterfacesOnTargetTypeCategory); + + //MOYOU2204 + private static readonly DiagnosticDefinition + ErrorTargetTypeDoesntImplementPrimaryInterface = + new(Errors.Factory.TargetTypeDoesntImplementPrimaryInterfaceId, Severity.Error, + Errors.Factory.TargetTypeDoesntImplementPrimaryInterfaceMessageFormat, + Errors.Factory.TargetTypeDoesntImplementPrimaryInterfaceTitle, + Errors.Factory.TargetTypeDoesntImplementPrimaryInterfaceCategory); + + public override void AmendProject(IProjectAmender amender) + { + var types = amender + .SelectTypes() + .Where(type => type.HasAttribute()); + + //MOYOU2201 no target type + types + .Where(type => type + .Attributes + .Where(IsFactoryMemberAttribute) + .Any(NoTargetTypeInAttribute) + ) + .ReportDiagnostic(type => ErrorNoTargetTypeInMemberAttribute.WithArguments(type)); + + //MOYOU2202 no implemented interfaces + types + .Where(type => type + .Attributes + .Where(IsFactoryMemberAttribute) + .Any(TargetTypeImplementsNoInterfaces) + ) + .ReportDiagnostic(type => ErrorTypeDoesntImplementAnyInterfaces.WithArguments(type)); + + //MOYOU2203 ambiguous interfaces + types + .Where(type => type + .Attributes + .Where(IsFactoryMemberAttribute) + .Where(TargetTypeInAttribute) + .Where(TargetTypeImplementsMultipleInterfaces) + .Any(NoPrimaryInterfaceInAttribute) + ) + .ReportDiagnostic(type => ErrorAmbiguousInterfacesOnTargetType.WithArguments(type)); + + //MOYOU2204 target type doesn't implement primary interface + types + .Where(type => type + .Attributes + .Where(IsFactoryMemberAttribute) + .Where(TargetTypeInAttribute) + .Any(TargetTypeDoesNotImplementPrimaryInterface) + ) + .ReportDiagnostic(type => ErrorTargetTypeDoesntImplementPrimaryInterface.WithArguments(type)); + + types.AddAspect(type => BuildAspect(type, amender)); + } + + private static bool TargetTypeImplementsMultipleInterfaces(IAttribute attribute) + { + var targetType = (INamedType)attribute.NamedArguments[nameof(FactoryMemberAttribute.TargetType)].Value!; + return targetType.ImplementedInterfaces.Count > 1; + } + + private static bool IsFactoryMemberAttribute(IAttribute attribute) + { + return attribute.Type.FullName == typeof(FactoryMemberAttribute).FullName; + } + + private static bool NoTargetTypeInAttribute(IAttribute attribute) + { + return !attribute.TryGetNamedArgument(nameof(FactoryMemberAttribute.TargetType), out _); + } + + private static bool NoPrimaryInterfaceInAttribute(IAttribute attribute) + { + return !attribute.TryGetNamedArgument(nameof(FactoryMemberAttribute.PrimaryInterface), out _); + } + + private static bool TargetTypeInAttribute(IAttribute attribute) + { + return attribute.TryGetNamedArgument(nameof(FactoryMemberAttribute.TargetType), out _); + } + + private static bool TargetTypeImplementsNoInterfaces(IAttribute attribute) + { + return attribute.TryGetNamedArgument(nameof(FactoryMemberAttribute.TargetType), out var targetType) && + ((INamedType)targetType.Value!).ImplementedInterfaces.Count == 0; + } + + private static bool TargetTypeDoesNotImplementPrimaryInterface(IAttribute attribute) + { + var targetType = (INamedType)attribute.NamedArguments[nameof(FactoryMemberAttribute.TargetType)].Value!; + return attribute.TryGetNamedArgument(nameof(FactoryMemberAttribute.PrimaryInterface), out var primaryInterface) && !targetType.ImplementedInterfaces.Contains((INamedType)primaryInterface.Value!); + } + + private static FactoryMemberAspect BuildAspect(INamedType type, IProjectAmender amender) + { + var memberAttributes = type + .Attributes + .Where(attr => attr.Type.FullName == typeof(FactoryMemberAttribute).FullName); + var targetTuples = GetTypeTuplesFromAttributes(type, memberAttributes); + var aspect = new FactoryMemberAspect(targetTuples); + return aspect; + } + + private static List<(INamedType, INamedType?)> GetTypeTuplesFromAttributes(INamedType factoryType, + IEnumerable memberAttributes) + { + return memberAttributes + .Select(GetTypeAndInterfaceTuple) + .Where(tuple => tuple.HasValue) + .Select(tuple => tuple.Value) + .ToList(); + + (INamedType, INamedType?)? GetTypeAndInterfaceTuple(IAttribute attr) + { + if (!attr.NamedArguments.TryGetValue(nameof(FactoryMemberAttribute.TargetType), out var targetTypeConstant)) + return null; //MOYOU2201 + var targetType = (INamedType)targetTypeConstant.Value!; + var implementedInterfaces = targetType.ImplementedInterfaces; + if (!attr.NamedArguments.TryGetValue(nameof(FactoryMemberAttribute.PrimaryInterface), + out var primaryInterface)) + return implementedInterfaces.Count == 1 ? (targetType, implementedInterfaces.First()) : null; //MOYOU2202 //MOYOU2203 + var primaryInterfaceType = (INamedType)primaryInterface.Value!; + if (implementedInterfaces.Contains(primaryInterfaceType)) + return (targetType, primaryInterface.Value as INamedType); + return null; //MOYOU2204 + + } + } +} \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Factory/Moyou.Aspects.Factory.csproj b/Moyou.Aspects/Moyou.Aspects.Factory/Moyou.Aspects.Factory.csproj new file mode 100644 index 0000000..3effd65 --- /dev/null +++ b/Moyou.Aspects/Moyou.Aspects.Factory/Moyou.Aspects.Factory.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Moyou.Aspects/Moyou.Aspects.Memento/AspectOrder.cs b/Moyou.Aspects/Moyou.Aspects.Memento/AspectOrder.cs index 7203923..6bf442c 100644 --- a/Moyou.Aspects/Moyou.Aspects.Memento/AspectOrder.cs +++ b/Moyou.Aspects/Moyou.Aspects.Memento/AspectOrder.cs @@ -1,6 +1,6 @@ using Metalama.Framework.Aspects; using Moyou.Aspects.Memento; -[assembly: AspectOrder(typeof(MementoCreateHookAttribute), typeof(MementoAttribute))] -[assembly: AspectOrder(typeof(MementoRestoreHookAttribute), typeof(MementoAttribute))] -[assembly: AspectOrder(typeof(MementoIgnoreAttribute), typeof(MementoAttribute))] \ No newline at end of file +[assembly: AspectOrder(AspectOrderDirection.RunTime, typeof(MementoCreateHookAttribute), typeof(MementoAttribute))] +[assembly: AspectOrder(AspectOrderDirection.RunTime, typeof(MementoRestoreHookAttribute), typeof(MementoAttribute))] +[assembly: AspectOrder(AspectOrderDirection.RunTime, typeof(MementoIgnoreAttribute), typeof(MementoAttribute))] \ No newline at end of file diff --git a/Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs b/Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs index 606a022..cc99411 100644 --- a/Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs +++ b/Moyou.Aspects/Moyou.Aspects.Memento/MementoAttribute.cs @@ -214,7 +214,6 @@ public IMemento CreateMementoImpl<[CompileTime] TMementoType>( //assign some fields var targetFieldOrProp = introducedFieldsOnMementoList .Single(memFieldOrProp => memFieldOrProp.Name == sourceFieldOrProp.Name).With(memento); - meta.DebugBreak(); if (!(sourceFieldOrProp.Type.IsReferenceType ?? false)) targetFieldOrProp.Value = sourceFieldOrProp.Value; else if (sourceFieldOrProp.Type.Is(SpecialType.String, ConversionKind.TypeDefinition)) //strings are immutable diff --git a/Moyou.Diagnostics/Errors.cs b/Moyou.Diagnostics/Errors.cs new file mode 100644 index 0000000..a22ab00 --- /dev/null +++ b/Moyou.Diagnostics/Errors.cs @@ -0,0 +1,77 @@ +using Metalama.Framework.Aspects; + +namespace Moyou.Diagnostics; + +[CompileTime] +public class Errors +{ + public static class Factory + { + // MOYOU2201: No TargetType in [FactoryMember] on factory {0}. + public static string NoTargetTypeInMemberAttributeId => "MOYOU2201"; + + public static string NoTargetTypeInMemberAttributeMessageFormat => + "No TargetType in [FactoryMember] on factory {0}."; + + public static string NoTargetTypeInMemberAttributeTitle => "No TargetType in [FactoryMember]."; + + public static string NoTargetTypeInMemberAttributeCategory => "Factory"; + + + // MOYOU2202: TargetType {0} doesn't implement any interfaces in [FactoryMember] on factory {1}. + public static string TypeDoesntImplementAnyInterfacesId => "MOYOU2202"; + + public static string TypeDoesntImplementAnyInterfacesMessageFormat => + "TargetType doesn't implement any interfaces in [FactoryMember] on factory {0}."; + + public static string TypeDoesntImplementAnyInterfacesTitle => "TargetType doesn't implement any interfaces."; + + public static string TypeDoesntImplementAnyInterfacesCategory => "Factory"; + + + // MOYOU2203: TargetType {0} in [FactoryMember] on factory {1} implements multiple interfaces. + // You must define which one to use for the return type of the factory by defining it via the PrimaryInterface property. + public static string AmbiguousInterfacesOnTargetTypeId => "MOYOU2203"; + + public static string AmbiguousInterfacesOnTargetTypeMessageFormat => + "TargetType in [FactoryMember] on factory {0} implements multiple interfaces. " + + "You must define which one to use for the return type of the factory by defining it via the PrimaryInterface property."; + + public static string AmbiguousInterfacesOnTargetTypeTitle => "Ambiguous interfaces on TargetType."; + + public static string AmbiguousInterfacesOnTargetTypeCategory => "Factory"; + + + // MOYOU2204: TargetType {0} in [FactoryMember] on factory {1} does not implement PrimaryInterface {2}. + public static string TargetTypeDoesntImplementPrimaryInterfaceId => "MOYOU2204"; + + public static string TargetTypeDoesntImplementPrimaryInterfaceMessageFormat => + "TargetType in [FactoryMember] on factory {0} does not implement PrimaryInterface."; + + public static string TargetTypeDoesntImplementPrimaryInterfaceTitle => + "TargetType doesn't implement PrimaryInterface."; + + public static string TargetTypeDoesntImplementPrimaryInterfaceCategory => "Factory"; + + + // MOYOU2205: Factory member {0} has no public default constructor or constructor marked with [FactoryConstructor]. + public static string NoSuitableConstructorId => "MOYOU2205"; + + public static string NoSuitableConstructorMessageFormat => + "Factory member {0} has no public default constructor or constructor marked with [FactoryConstructor]."; + + public static string NoSuitableConstructorTitle => "Factory member has no suitable constructor."; + + public static string NoSuitableConstructorCategory => "Factory"; + + + // MOYOU2206: Factory member {0} has more than one constructor marked with [FactoryConstructor]. + public static string MultipleMarkedConstructorsId => "MOYOU2206"; + + public static string MultipleMarkedConstructorsMessageFormat => + "Factory member {0} has more than one constructor marked with [FactoryConstructor]."; + + public static string MultipleMarkedConstructorTitle => "Factory member has more than one marked constructor."; + public static string MultipleMarkedConstructorCategory => "Factory"; + } +} \ No newline at end of file diff --git a/Moyou.Diagnostics/Warnings.cs b/Moyou.Diagnostics/Warnings.cs index 421adab..8a248c6 100644 --- a/Moyou.Diagnostics/Warnings.cs +++ b/Moyou.Diagnostics/Warnings.cs @@ -1,6 +1,4 @@ using Metalama.Framework.Aspects; -using Metalama.Framework.Code; -using Metalama.Framework.Diagnostics; namespace Moyou.Diagnostics; @@ -43,6 +41,7 @@ public static class Singleton "Singleton class {0} should have no accessible constructors. Found constructor with signature: {1}."; public static string HasAccessibleConstructorTitle => "Singleton class should have no accessible constructors"; public static string HasAccessibleConstructorCategory => "Singleton"; + public static string HasImplicitPublicConstructorId => "MOYOU1102"; public static string HasImplicitPublicConstructorMessageFormat => diff --git a/Moyou.Extensions/ConstructorCollectionExtensions.cs b/Moyou.Extensions/ConstructorCollectionExtensions.cs new file mode 100644 index 0000000..275fd68 --- /dev/null +++ b/Moyou.Extensions/ConstructorCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; +using Metalama.Framework.Code.Collections; + +namespace Moyou.Extensions; + +[CompileTime] +public static class ConstructorCollectionExtensions +{ + [CompileTime] + public static IConstructor GetPublicDefaultConstructor(this IConstructorCollection constructorCollection, + Accessibility accessibility = Accessibility.Public) => + constructorCollection.First(ctor => + ctor.Parameters.Count == 0 && + ctor.Accessibility == accessibility + ); + + public static IConstructor GetDefaultConstructor(this IConstructorCollection constructorCollection) => + constructorCollection.First(ctor => ctor.Parameters.Count == 0); +} \ No newline at end of file diff --git a/Moyou.Extensions/ConstructorExtensions.cs b/Moyou.Extensions/ConstructorExtensions.cs new file mode 100644 index 0000000..0a6349f --- /dev/null +++ b/Moyou.Extensions/ConstructorExtensions.cs @@ -0,0 +1,11 @@ +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; + +namespace Moyou.Extensions; + +[CompileTime] +public static class ConstructorExtensions +{ + public static bool HasAttribute(this IConstructor constructor) where TAttribute : Attribute => + constructor.Attributes.Any(attribute => attribute.Type.FullName == typeof(TAttribute).FullName); +} \ No newline at end of file diff --git a/Moyou.Extensions/NamedTypeExtensions.cs b/Moyou.Extensions/NamedTypeExtensions.cs new file mode 100644 index 0000000..2a40664 --- /dev/null +++ b/Moyou.Extensions/NamedTypeExtensions.cs @@ -0,0 +1,15 @@ +using Metalama.Framework.Aspects; +using Metalama.Framework.Code; + +namespace Moyou.Extensions; + +[CompileTime] +public static class NamedTypeExtensions +{ + [CompileTime] + public static bool HasAttribute(this INamedType type) where TAttribute : Attribute => + type.Attributes.Any(attr => attr.Type.FullName == typeof(TAttribute).FullName); + + public static bool HasPublicDefaultConstructor(this INamedType type) => + type.HasDefaultConstructor && type.Constructors.GetDefaultConstructor().Accessibility == Accessibility.Public; +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.cs b/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.cs new file mode 100644 index 0000000..6034c00 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.cs @@ -0,0 +1,24 @@ +using Moyou.Aspects.Factory; + +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; + +[FactoryMember(TargetType = typeof(TypeC))] +public class FactoryC +{ + +} + +public class TypeC : InterfaceA, InterfaceB +{ + +} + +public interface InterfaceA +{ + +} + +public interface InterfaceB +{ + +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.t.cs b/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.t.cs new file mode 100644 index 0000000..229ac1c --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/AmbiguousInterfacesOnTargetType.t.cs @@ -0,0 +1 @@ +// Error MOYOU2203 on `FactoryC`: `TargetType in [FactoryMember] on factory FactoryC implements multiple interfaces. You must define which one to use for the return type of the factory by defining it via the PrimaryInterface property.` \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.cs b/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.cs new file mode 100644 index 0000000..102ba62 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.cs @@ -0,0 +1,58 @@ +using Moyou.Aspects.Factory; + +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; + +[Factory] +[FactoryMember(TargetType = typeof(WindowsButton))] +[FactoryMember(TargetType = typeof(WindowsWindow))] +public class WindowsUIFactory +{ + +} + +[Factory] +[FactoryMember(TargetType = typeof(MacButton))] +[FactoryMember(TargetType = typeof(MacWindow))] +public class MacUIFactory +{ + +} + +public interface IButton +{ +} + +public interface IWindow +{ + +} + +public class WindowsButton : IButton +{ +} + +public class WindowsWindow : IWindow +{ + private WindowsWindow() + { + + } + + [FactoryConstructor] + public WindowsWindow(WindowsButton button) + { + + } +} + +public class MacButton : IButton +{ +} + +public class MacWindow : IWindow +{ + public MacWindow(int foobar, string barbaz, object bobo) + { + + } +} diff --git a/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.t.cs b/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.t.cs new file mode 100644 index 0000000..095972e --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/FullValidExample.t.cs @@ -0,0 +1,58 @@ +using Moyou.Aspects.Factory; +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; +[Factory] +[FactoryMember(TargetType = typeof(WindowsButton))] +[FactoryMember(TargetType = typeof(WindowsWindow))] +public class WindowsUIFactory +{ + public global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IButton CreateButton() + { + return (global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IButton)new global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.WindowsButton()!; + } + public global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IWindow CreateWindow(global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.WindowsButton button) + { + return new global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.WindowsWindow((global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.WindowsButton)button); + } +} +[Factory] +[FactoryMember(TargetType = typeof(MacButton))] +[FactoryMember(TargetType = typeof(MacWindow))] +public class MacUIFactory +{ + public global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IButton CreateButton() + { + return (global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IButton)new global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.MacButton()!; + } + public global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.IWindow CreateWindow(global::System.Int32 foobar, global::System.String barbaz, global::System.Object bobo) + { + return new global::Moyou.CompileTimeTest.Factory.FactoryAttributeTests.MacWindow((global::System.Int32)foobar, (global::System.String)barbaz, bobo); + } +} +public interface IButton +{ +} +public interface IWindow +{ +} +public class WindowsButton : IButton +{ +} +public class WindowsWindow : IWindow +{ + private WindowsWindow() + { + } + [FactoryConstructor] + public WindowsWindow(WindowsButton button) + { + } +} +public class MacButton : IButton +{ +} +public class MacWindow : IWindow +{ + public MacWindow(int foobar, string barbaz, object bobo) + { + } +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.cs b/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.cs new file mode 100644 index 0000000..8f85156 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.cs @@ -0,0 +1,14 @@ +using Moyou.Aspects.Factory; + +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; + +[FactoryMember] +public class FactoryA +{ + +} + +public class TypeA +{ + +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.t.cs b/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.t.cs new file mode 100644 index 0000000..5b1cd7c --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/NoTargetTypeInMemberAttribute.t.cs @@ -0,0 +1 @@ +// Error MOYOU2201 on `FactoryA`: `No TargetType in [FactoryMember] on factory FactoryA.` \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.cs b/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.cs new file mode 100644 index 0000000..aefe628 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.cs @@ -0,0 +1,19 @@ +using Moyou.Aspects.Factory; + +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; + +[FactoryMember(TargetType = typeof(TypeD), PrimaryInterface = typeof(InterfaceC))] +public class FactoryD +{ + +} + +public class TypeD +{ + +} + +public interface InterfaceC +{ + +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.t.cs b/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.t.cs new file mode 100644 index 0000000..5c0887f --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/TargetTypeDoesntImplementPrimaryInterface.t.cs @@ -0,0 +1,2 @@ +// Error MOYOU2202 on `FactoryD`: `TargetType doesn't implement any interfaces in [FactoryMember] on factory FactoryD.` +// Error MOYOU2204 on `FactoryD`: `TargetType in [FactoryMember] on factory FactoryD does not implement PrimaryInterface.` \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.cs b/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.cs new file mode 100644 index 0000000..a3582e3 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.cs @@ -0,0 +1,14 @@ +using Moyou.Aspects.Factory; + +namespace Moyou.CompileTimeTest.Factory.FactoryAttributeTests; + +[FactoryMember(TargetType = typeof(TypeB))] +public class FactoryB +{ + +} + +public class TypeB +{ + +} \ No newline at end of file diff --git a/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.t.cs b/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.t.cs new file mode 100644 index 0000000..1fe0751 --- /dev/null +++ b/Moyou.Test/Factory/FactoryAttributeTests/TypeDoesntImplementAnyInterfaces.t.cs @@ -0,0 +1 @@ +// Error MOYOU2202 on `FactoryB`: `TargetType doesn't implement any interfaces in [FactoryMember] on factory FactoryB.` \ No newline at end of file diff --git a/Moyou.Test/Moyou.CompileTimeTest.csproj b/Moyou.Test/Moyou.CompileTimeTest.csproj index 80b4c44..4bc5229 100644 --- a/Moyou.Test/Moyou.CompileTimeTest.csproj +++ b/Moyou.Test/Moyou.CompileTimeTest.csproj @@ -42,6 +42,7 @@ + diff --git a/Moyou.UnitTest/Moyou.UnitTest.csproj b/Moyou.UnitTest/Moyou.UnitTest.csproj index 9ab790e..966ae40 100644 --- a/Moyou.UnitTest/Moyou.UnitTest.csproj +++ b/Moyou.UnitTest/Moyou.UnitTest.csproj @@ -29,4 +29,8 @@ + + + + diff --git a/Moyou.sln b/Moyou.sln index ef7f35e..9b2cce8 100644 --- a/Moyou.sln +++ b/Moyou.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moyou.Aspects.Singleton", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moyou.Diagnostics", "Moyou.Diagnostics\Moyou.Diagnostics.csproj", "{243A9CB0-E58E-4A0D-AF02-4E4B18B24A5E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moyou.Aspects.Factory", "Moyou.Aspects\Moyou.Aspects.Factory\Moyou.Aspects.Factory.csproj", "{C6137E15-091A-4221-AB3D-218AD6BBE96B}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moyou.Aspects.UnsavedChanges", "Moyou.Aspects\Moyou.Aspects.UnsavedChanges\Moyou.Aspects.UnsavedChanges.csproj", "{2F05FD3A-8A3E-4560-95E5-70C428D176FC}" EndProject Global @@ -62,6 +64,12 @@ Global {243A9CB0-E58E-4A0D-AF02-4E4B18B24A5E}.LamaDebug|Any CPU.Build.0 = Debug|Any CPU {243A9CB0-E58E-4A0D-AF02-4E4B18B24A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {243A9CB0-E58E-4A0D-AF02-4E4B18B24A5E}.Release|Any CPU.Build.0 = Release|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.LamaDebug|Any CPU.ActiveCfg = Debug|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.LamaDebug|Any CPU.Build.0 = Debug|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6137E15-091A-4221-AB3D-218AD6BBE96B}.Release|Any CPU.Build.0 = Release|Any CPU {2F05FD3A-8A3E-4560-95E5-70C428D176FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F05FD3A-8A3E-4560-95E5-70C428D176FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F05FD3A-8A3E-4560-95E5-70C428D176FC}.LamaDebug|Any CPU.ActiveCfg = Debug|Any CPU @@ -78,6 +86,7 @@ Global GlobalSection(NestedProjects) = preSolution {5D94EA8C-2A35-49D7-A108-2712FDA6E573} = {12BCCD59-0094-404A-9EA9-B2799EA6E608} {35B48390-E209-4858-876D-22C1B6C98A07} = {12BCCD59-0094-404A-9EA9-B2799EA6E608} + {C6137E15-091A-4221-AB3D-218AD6BBE96B} = {12BCCD59-0094-404A-9EA9-B2799EA6E608} {2F05FD3A-8A3E-4560-95E5-70C428D176FC} = {12BCCD59-0094-404A-9EA9-B2799EA6E608} EndGlobalSection EndGlobal