Initial work to add records support.
This commit is contained in:
parent
98c899e078
commit
91f5e9bcf5
|
@ -28,11 +28,11 @@ namespace MapTo.Extensions
|
|||
|
||||
public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().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<NamespaceDeclarationSyntax>()
|
||||
.FirstOrDefault()
|
||||
?.Name
|
||||
|
|
|
@ -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<ClassDeclarationSyntax> candidateClasses, SourceGenerationOptions options)
|
||||
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateClasses, SourceGenerationOptions options)
|
||||
{
|
||||
foreach (var classSyntax in candidateClasses)
|
||||
{
|
||||
|
|
|
@ -8,12 +8,12 @@ namespace MapTo
|
|||
{
|
||||
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
|
||||
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace MapTo
|
|||
|
||||
if (attributeSyntax is not null)
|
||||
{
|
||||
CandidateClasses.Add(classDeclaration);
|
||||
CandidateTypes.Add(typeDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace MapTo
|
|||
{
|
||||
internal class MappingContext
|
||||
{
|
||||
private readonly ClassDeclarationSyntax _classSyntax;
|
||||
private readonly TypeDeclarationSyntax _typeSyntax;
|
||||
private readonly Compilation _compilation;
|
||||
private readonly List<Diagnostic> _diagnostics;
|
||||
private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol;
|
||||
|
@ -23,12 +23,12 @@ namespace MapTo
|
|||
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
||||
private readonly List<string> _usings;
|
||||
|
||||
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
||||
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
{
|
||||
_diagnostics = new List<Diagnostic>();
|
||||
_usings = new List<string> { "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<ConstructorDeclarationSyntax>()
|
||||
.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<MappedProperty> GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol, bool isClassInheritFromMappedBaseClass)
|
||||
private ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isClassInheritFromMappedBaseClass)
|
||||
{
|
||||
var mappedProperties = new List<MappedProperty>();
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
var classProperties = classSymbol.GetAllMembers(!isClassInheritFromMappedBaseClass)
|
||||
var classProperties = typeSymbol.GetAllMembers(!isClassInheritFromMappedBaseClass)
|
||||
.OfType<IPropertySymbol>()
|
||||
.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)
|
||||
{
|
||||
|
|
|
@ -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<MappedProperty> MappedProperties,
|
||||
bool HasMappedBaseClass,
|
||||
ImmutableArray<string> Usings,
|
||||
|
|
|
@ -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("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.ClassName}\"/> class")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.ClassName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceClassName}\"/> to use as source.</param>")
|
||||
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.ClassName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceTypeIdentifierName}\"/> to use as source.</param>")
|
||||
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue