From 91f5e9bcf57a641994e02446b8440ad20fef758b Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Tue, 29 Jun 2021 07:15:17 +0100 Subject: [PATCH] Initial work to add records support. --- src/MapTo/Extensions/RoslynExtensions.cs | 10 ++--- src/MapTo/MapToGenerator.cs | 6 +-- src/MapTo/MapToSyntaxReceiver.cs | 8 ++-- src/MapTo/MappingContext.cs | 53 ++++++++++++------------ src/MapTo/Models.cs | 9 ++-- src/MapTo/Sources/MapClassSource.cs | 34 +++++++-------- 6 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/MapTo/Extensions/RoslynExtensions.cs b/src/MapTo/Extensions/RoslynExtensions.cs index cc2b0d4..2c5a847 100644 --- a/src/MapTo/Extensions/RoslynExtensions.cs +++ b/src/MapTo/Extensions/RoslynExtensions.cs @@ -28,11 +28,11 @@ namespace MapTo.Extensions public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType().Single(); - public static string GetClassName(this ClassDeclarationSyntax classSyntax) => classSyntax.Identifier.Text; + public static string GetIdentifierName(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Identifier.Text; - public static AttributeSyntax? GetAttribute(this ClassDeclarationSyntax classSyntax, string attributeName) + public static AttributeSyntax? GetAttribute(this TypeDeclarationSyntax typeDeclarationSyntax, string attributeName) { - return classSyntax.AttributeLists + return typeDeclarationSyntax.AttributeLists .SelectMany(al => al.Attributes) .SingleOrDefault(a => (a.Name as IdentifierNameSyntax)?.Identifier.ValueText == attributeName || @@ -48,8 +48,8 @@ namespace MapTo.Extensions public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) => symbol.GetAttributes(attributeSymbol).FirstOrDefault(); - public static string? GetNamespace(this ClassDeclarationSyntax classDeclarationSyntax) => - classDeclarationSyntax.Ancestors() + public static string? GetNamespace(this TypeDeclarationSyntax typeDeclarationSyntax) => + typeDeclarationSyntax.Ancestors() .OfType() .FirstOrDefault() ?.Name diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs index 2c42e74..6a39f62 100644 --- a/src/MapTo/MapToGenerator.cs +++ b/src/MapTo/MapToGenerator.cs @@ -35,9 +35,9 @@ namespace MapTo .AddSource(ref context, MapPropertyAttributeSource.Generate(options)) .AddSource(ref context, MappingContextSource.Generate(options)); - if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any()) + if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateTypes.Any()) { - AddGeneratedMappingsClasses(context, compilation, receiver.CandidateClasses, options); + AddGeneratedMappingsClasses(context, compilation, receiver.CandidateTypes, options); } } catch (Exception ex) @@ -47,7 +47,7 @@ namespace MapTo } } - private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable candidateClasses, SourceGenerationOptions options) + private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable candidateClasses, SourceGenerationOptions options) { foreach (var classSyntax in candidateClasses) { diff --git a/src/MapTo/MapToSyntaxReceiver.cs b/src/MapTo/MapToSyntaxReceiver.cs index cb2312c..24ce03a 100644 --- a/src/MapTo/MapToSyntaxReceiver.cs +++ b/src/MapTo/MapToSyntaxReceiver.cs @@ -8,12 +8,12 @@ namespace MapTo { internal class MapToSyntaxReceiver : ISyntaxReceiver { - public List CandidateClasses { get; } = new(); + public List CandidateTypes { get; } = new(); /// public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - if (syntaxNode is not ClassDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } classDeclaration) + if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax) { return; } @@ -21,7 +21,7 @@ namespace MapTo var attributeSyntax = attributes .SelectMany(a => a.Attributes) .SingleOrDefault(a => a.Name is - IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom] + IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom] or QualifiedNameSyntax // For: [MapTo.MapFrom] { @@ -32,7 +32,7 @@ namespace MapTo if (attributeSyntax is not null) { - CandidateClasses.Add(classDeclaration); + CandidateTypes.Add(typeDeclarationSyntax); } } } diff --git a/src/MapTo/MappingContext.cs b/src/MapTo/MappingContext.cs index b65b769..ebd64ea 100644 --- a/src/MapTo/MappingContext.cs +++ b/src/MapTo/MappingContext.cs @@ -11,7 +11,7 @@ namespace MapTo { internal class MappingContext { - private readonly ClassDeclarationSyntax _classSyntax; + private readonly TypeDeclarationSyntax _typeSyntax; private readonly Compilation _compilation; private readonly List _diagnostics; private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol; @@ -23,12 +23,12 @@ namespace MapTo private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol; private readonly List _usings; - internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax) + internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) { _diagnostics = new List(); _usings = new List { "System", Constants.RootNamespace }; _sourceGenerationOptions = sourceGenerationOptions; - _classSyntax = classSyntax; + _typeSyntax = typeSyntax; _compilation = compilation; _ignorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName); @@ -49,29 +49,29 @@ namespace MapTo private void Initialize() { - var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree); - if (!(ModelExtensions.GetDeclaredSymbol(semanticModel, _classSyntax) is INamedTypeSymbol classTypeSymbol)) + var semanticModel = _compilation.GetSemanticModel(_typeSyntax.SyntaxTree); + if (ModelExtensions.GetDeclaredSymbol(semanticModel, _typeSyntax) is not INamedTypeSymbol classTypeSymbol) { - _diagnostics.Add(DiagnosticsFactory.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText)); + _diagnostics.Add(DiagnosticsFactory.TypeNotFoundError(_typeSyntax.GetLocation(), _typeSyntax.Identifier.ValueText)); return; } - var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel); + var sourceTypeSymbol = GetSourceTypeSymbol(_typeSyntax, semanticModel); if (sourceTypeSymbol is null) { - _diagnostics.Add(DiagnosticsFactory.MapFromAttributeNotFoundError(_classSyntax.GetLocation())); + _diagnostics.Add(DiagnosticsFactory.MapFromAttributeNotFoundError(_typeSyntax.GetLocation())); return; } - var className = _classSyntax.GetClassName(); - var sourceClassName = sourceTypeSymbol.Name; - var isClassInheritFromMappedBaseClass = IsClassInheritFromMappedBaseClass(semanticModel); + var typeIdentifierName = _typeSyntax.GetIdentifierName(); + var sourceTypeIdentifierName = sourceTypeSymbol.Name; + var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel); var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol); - var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass); + var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass); if (!mappedProperties.Any()) { - _diagnostics.Add(DiagnosticsFactory.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol)); + _diagnostics.Add(DiagnosticsFactory.NoMatchingPropertyFoundError(_typeSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol)); return; } @@ -80,21 +80,22 @@ namespace MapTo Model = new MappingModel( _sourceGenerationOptions, - _classSyntax.GetNamespace(), - _classSyntax.Modifiers, - className, + _typeSyntax.GetNamespace(), + _typeSyntax.Modifiers, + _typeSyntax.Keyword.Text, + typeIdentifierName, sourceTypeSymbol.ContainingNamespace.ToString(), - sourceClassName, + sourceTypeIdentifierName, sourceTypeSymbol.ToString(), mappedProperties.ToImmutableArray(), - isClassInheritFromMappedBaseClass, + isTypeInheritFromMappedBaseClass, _usings.ToImmutableArray(), shouldGenerateSecondaryConstructor); } private bool ShouldGenerateSecondaryConstructor(SemanticModel semanticModel, ISymbol sourceTypeSymbol) { - var constructorSyntax = _classSyntax.DescendantNodes() + var constructorSyntax = _typeSyntax.DescendantNodes() .OfType() .SingleOrDefault(c => c.ParameterList.Parameters.Count == 1 && @@ -116,18 +117,18 @@ namespace MapTo return false; } - private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel) + private bool IsTypeInheritFromMappedBaseClass(SemanticModel semanticModel) { - return _classSyntax.BaseList is not null && _classSyntax.BaseList.Types + return _typeSyntax.BaseList is not null && _typeSyntax.BaseList.Types .Select(t => ModelExtensions.GetTypeInfo(semanticModel, t.Type).Type) .Any(t => t?.GetAttribute(_mapFromAttributeTypeSymbol) != null); } - private ImmutableArray GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol, bool isClassInheritFromMappedBaseClass) + private ImmutableArray GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isClassInheritFromMappedBaseClass) { var mappedProperties = new List(); var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); - var classProperties = classSymbol.GetAllMembers(!isClassInheritFromMappedBaseClass) + var classProperties = typeSymbol.GetAllMembers(!isClassInheritFromMappedBaseClass) .OfType() .Where(p => !p.HasAttribute(_ignorePropertyAttributeTypeSymbol)); @@ -207,7 +208,7 @@ namespace MapTo private void AddUsingIfRequired(bool condition, string? ns) { - if (condition && ns is not null && ns != _classSyntax.GetNamespace() && !_usings.Contains(ns)) + if (condition && ns is not null && ns != _typeSyntax.GetNamespace() && !_usings.Contains(ns)) { _usings.Add(ns); } @@ -270,8 +271,8 @@ namespace MapTo : converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray(); } - private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) => - GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); + private INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) => + GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); private INamedTypeSymbol? GetSourceTypeSymbol(AttributeSyntax? attributeSyntax, SemanticModel? semanticModel = null) { diff --git a/src/MapTo/Models.cs b/src/MapTo/Models.cs index 6769e67..83fcc19 100644 --- a/src/MapTo/Models.cs +++ b/src/MapTo/Models.cs @@ -22,11 +22,12 @@ namespace MapTo internal record MappingModel ( SourceGenerationOptions Options, string? Namespace, - SyntaxTokenList ClassModifiers, - string ClassName, + SyntaxTokenList Modifiers, + string Type, + string TypeIdentifierName, string SourceNamespace, - string SourceClassName, - string SourceClassFullName, + string SourceTypeIdentifierName, + string SourceTypeFullName, ImmutableArray MappedProperties, bool HasMappedBaseClass, ImmutableArray Usings, diff --git a/src/MapTo/Sources/MapClassSource.cs b/src/MapTo/Sources/MapClassSource.cs index b76b213..86fa6a5 100644 --- a/src/MapTo/Sources/MapClassSource.cs +++ b/src/MapTo/Sources/MapClassSource.cs @@ -18,7 +18,7 @@ namespace MapTo.Sources .WriteLine() // Class declaration - .WriteLine($"partial class {model.ClassName}") + .WriteLine($"partial class {model.TypeIdentifierName}") .WriteOpeningBracket(); // Class body @@ -44,37 +44,37 @@ namespace MapTo.Sources // End namespace declaration .WriteClosingBracket(); - return new(builder.ToString(), $"{model.ClassName}.g.cs"); + return new(builder.ToString(), $"{model.TypeIdentifierName}.g.cs"); } private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model) { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); if (model.Options.GenerateXmlDocument) { builder .WriteLine("/// ") - .WriteLine($"/// Initializes a new instance of the class") + .WriteLine($"/// Initializes a new instance of the class") .WriteLine($"/// using the property values from the specified .") .WriteLine("/// ") .WriteLine($"/// {sourceClassParameterName} is null"); } return builder - .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName})") + .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceTypeIdentifierName} {sourceClassParameterName})") .WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}"); } private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model) { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); const string mappingContextParameterName = "context"; var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty; builder - .WriteLine($"private protected {model.ClassName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceClassName} {sourceClassParameterName}){baseConstructor}") + .WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceTypeIdentifierName} {sourceClassParameterName}){baseConstructor}") .WriteOpeningBracket() .WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));") .WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));") @@ -113,14 +113,14 @@ namespace MapTo.Sources private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model) { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); return builder .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") - .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceTypeIdentifierName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") .WriteOpeningBracket() - .WriteLine($"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceClassName}, {model.ClassName}>({sourceClassParameterName});") + .WriteLine($"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceTypeIdentifierName}, {model.TypeIdentifierName}>({sourceClassParameterName});") .WriteClosingBracket(); } @@ -133,17 +133,17 @@ namespace MapTo.Sources return builder .WriteLine("/// ") - .WriteLine($"/// Creates a new instance of and sets its participating properties") + .WriteLine($"/// Creates a new instance of and sets its participating properties") .WriteLine($"/// using the property values from .") .WriteLine("/// ") - .WriteLine($"/// The instance of to use as source.") - .WriteLine($"/// A new instance of -or- null if is null."); + .WriteLine($"/// The instance of to use as source.") + .WriteLine($"/// A new instance of -or- null if is null."); } private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model) { return builder - .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceClassName}To{model.ClassName}Extensions") + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions") .WriteOpeningBracket() .GenerateSourceTypeExtensionMethod(model) .WriteClosingBracket(); @@ -151,14 +151,14 @@ namespace MapTo.Sources private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model) { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); return builder .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") - .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceTypeIdentifierName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") .WriteOpeningBracket() - .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") + .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});") .WriteClosingBracket(); } }