From 3ff1e90fb90dae4ebd5081b2a71c42b9b8d9c153 Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Mon, 21 Dec 2020 15:53:44 +0000 Subject: [PATCH] Fix incorrect usings generator + cleanup. --- MapTo/Diagnostics.cs | 21 +++++++++++ MapTo/Extensions/RoslynExtensions.cs | 4 +-- MapTo/MapToGenerator.cs | 42 +++++++++------------- MapTo/MapToSyntaxReceiver.cs | 4 +-- MapTo/Models/MapModel.cs | 38 ++++++++++++++++---- MapTo/SourceBuilder.cs | 13 +++---- MapToTests/Tests.cs | 14 ++++---- TestConsoleApp/Program.cs | 2 ++ TestConsoleApp/ViewModels/UserViewModel.cs | 10 ++++++ 9 files changed, 99 insertions(+), 49 deletions(-) create mode 100644 MapTo/Diagnostics.cs create mode 100644 TestConsoleApp/ViewModels/UserViewModel.cs diff --git a/MapTo/Diagnostics.cs b/MapTo/Diagnostics.cs new file mode 100644 index 0000000..1161a84 --- /dev/null +++ b/MapTo/Diagnostics.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; + +namespace MapTo +{ + internal static class Diagnostics + { + private const string UsageCategory = "Usage"; + + internal static Diagnostic SymbolNotFound(Location location, string syntaxName) => + Diagnostic.Create(CreateDescriptor("MT0001", "Symbol not found.", $"Unable to find any symbols for {syntaxName}"), location); + + internal static Diagnostic MapFromAttributeNotFound(Location location) => + Diagnostic.Create(CreateDescriptor("MT0002", "Attribute Not Available", $"Unable to find {SourceBuilder.MapFromAttributeName} type."), location); + + internal static Diagnostic ClassMappingsGenerated(Location location, string typeName) => + Diagnostic.Create(CreateDescriptor("MT1001", "Mapped Type", $"Generated mappings for {typeName}", DiagnosticSeverity.Info), location); + + private static DiagnosticDescriptor CreateDescriptor(string id, string title, string message, DiagnosticSeverity severity = DiagnosticSeverity.Error) => + new(id, title, message, UsageCategory, severity, true); + } +} \ No newline at end of file diff --git a/MapTo/Extensions/RoslynExtensions.cs b/MapTo/Extensions/RoslynExtensions.cs index 6328b16..57f01cf 100644 --- a/MapTo/Extensions/RoslynExtensions.cs +++ b/MapTo/Extensions/RoslynExtensions.cs @@ -28,9 +28,9 @@ namespace MapTo.Extensions return type.GetAllMembers().OfType(); } - public static CompilationUnitSyntax? GetCompilationUnit(this SyntaxNode syntaxNode) + public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) { - return syntaxNode.Ancestors().OfType().FirstOrDefault(); + return syntaxNode.Ancestors().OfType().Single(); } public static string GetClassName(this ClassDeclarationSyntax classSyntax) diff --git a/MapTo/MapToGenerator.cs b/MapTo/MapToGenerator.cs index 56a24c9..e9ee010 100644 --- a/MapTo/MapToGenerator.cs +++ b/MapTo/MapToGenerator.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using MapTo.Extensions; using MapTo.Models; using Microsoft.CodeAnalysis; @@ -25,61 +26,50 @@ namespace MapTo return; } - foreach (var (classDeclarationSyntax, attributeSyntax) in receiver.CandidateClasses) + foreach (var classDeclarationSyntax in receiver.CandidateClasses) { - var model = GetModel(context.Compilation, classDeclarationSyntax); + var (model, diagnostic) = GetModel(context.Compilation, classDeclarationSyntax); if (model is null) { - // TODO: Emit diagnostic info. + context.ReportDiagnostic(diagnostic!); continue; } var (source, hintName) = SourceBuilder.GenerateSource(model); context.AddSource(hintName, source); + context.ReportDiagnostic(Diagnostics.ClassMappingsGenerated(classDeclarationSyntax.GetLocation(), model.ClassName)); } } - private static MapModel? GetModel(Compilation compilation, ClassDeclarationSyntax classSyntax) + private static (MapModel? model, Diagnostic? diagnostic) GetModel(Compilation compilation, ClassDeclarationSyntax classSyntax) { var root = classSyntax.GetCompilationUnit(); - if (root is null) - { - return null; - } - var classSemanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree); if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol)) { - return null; + return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); } - var destinationTypeSymbol = GetDestinationTypeSymbol(classSyntax, classSemanticModel); - if (destinationTypeSymbol is null) + var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel); + if (sourceTypeSymbol is null) { - return null; + return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); } - - return new MapModel( - ns: root.GetNamespace(), - classModifiers: classSyntax.Modifiers, - className: classSyntax.GetClassName(), - properties: classSymbol.GetAllMembersOfType(), - sourceNamespace: destinationTypeSymbol.ContainingNamespace.Name, - sourceClassName: destinationTypeSymbol.Name, - sourceTypeProperties: destinationTypeSymbol.GetAllMembersOfType()); + + return (MapModel.Create(root, classSyntax, classSymbol, sourceTypeSymbol), default); } - private static ITypeSymbol? GetDestinationTypeSymbol(ClassDeclarationSyntax classSyntax, SemanticModel model) + private static ITypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classSyntax, SemanticModel model) { - var destinationTypeExpressionSyntax = classSyntax + var sourceTypeExpressionSyntax = classSyntax .GetAttribute(SourceBuilder.MapFromAttributeName) ?.DescendantNodes() .OfType() .SingleOrDefault(); - return destinationTypeExpressionSyntax is not null ? model.GetTypeInfo(destinationTypeExpressionSyntax.Type).Type : null; + return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type : null; } } } \ No newline at end of file diff --git a/MapTo/MapToSyntaxReceiver.cs b/MapTo/MapToSyntaxReceiver.cs index a150953..a59fb65 100644 --- a/MapTo/MapToSyntaxReceiver.cs +++ b/MapTo/MapToSyntaxReceiver.cs @@ -7,7 +7,7 @@ namespace MapTo { internal class MapToSyntaxReceiver : ISyntaxReceiver { - public List<(ClassDeclarationSyntax classDeclarationSyntax, AttributeSyntax attributeSyntax)> CandidateClasses { get; } = new(); + public List CandidateClasses { get; } = new(); /// public void OnVisitSyntaxNode(SyntaxNode syntaxNode) @@ -31,7 +31,7 @@ namespace MapTo if (attributeSyntax is not null) { - CandidateClasses.Add((classDeclaration, attributeSyntax)); + CandidateClasses.Add(classDeclaration); } } } diff --git a/MapTo/Models/MapModel.cs b/MapTo/Models/MapModel.cs index fde1a70..410e429 100644 --- a/MapTo/Models/MapModel.cs +++ b/MapTo/Models/MapModel.cs @@ -1,11 +1,21 @@ using System.Collections.Generic; +using MapTo.Extensions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MapTo.Models { public class MapModel { - public MapModel(string? ns, SyntaxTokenList classModifiers, string className, IEnumerable properties, string sourceNamespace, string sourceClassName, IEnumerable sourceTypeProperties) + private MapModel( + string? ns, + SyntaxTokenList classModifiers, + string className, + IEnumerable properties, + string sourceNamespace, + string sourceClassName, + string sourceClassFullName, + IEnumerable sourceTypeProperties) { Namespace = ns; ClassModifiers = classModifiers; @@ -13,6 +23,7 @@ namespace MapTo.Models Properties = properties; SourceNamespace = sourceNamespace; SourceClassName = sourceClassName; + SourceClassFullName = sourceClassFullName; SourceTypeProperties = sourceTypeProperties; } @@ -20,14 +31,29 @@ namespace MapTo.Models public SyntaxTokenList ClassModifiers { get; } - public string ClassName { get; } - + public string ClassName { get; } + public IEnumerable Properties { get; } - + public string SourceNamespace { get; } - + public string SourceClassName { get; } - + + public string SourceClassFullName { get; } + public IEnumerable SourceTypeProperties { get; } + + internal static MapModel Create(CompilationUnitSyntax root, ClassDeclarationSyntax classSyntax, INamedTypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol) + { + return new( + root.GetNamespace(), + classSyntax.Modifiers, + classSyntax.GetClassName(), + classSymbol.GetAllMembersOfType(), + sourceTypeSymbol.ContainingNamespace.ToString(), + sourceTypeSymbol.Name, + sourceTypeSymbol.ToString(), + sourceTypeSymbol.GetAllMembersOfType()); + } } } \ No newline at end of file diff --git a/MapTo/SourceBuilder.cs b/MapTo/SourceBuilder.cs index 3469f56..1b9634c 100644 --- a/MapTo/SourceBuilder.cs +++ b/MapTo/SourceBuilder.cs @@ -68,7 +68,7 @@ namespace MapTo .AppendLine() .AppendLine() .PadLeft(Indent1) - .AppendFormat("{0} static class {1}Extensions", model.ClassModifiers.FirstOrDefault().ToFullString().Trim(), model.SourceClassName) + .AppendFormat("{0} static partial class {1}Extensions", model.ClassModifiers.FirstOrDefault().ToFullString().Trim(), model.SourceClassName) .AppendOpeningBracket(Indent1) // Extension class body @@ -87,7 +87,8 @@ namespace MapTo { builder.AppendLine("using System;"); - if (!string.IsNullOrWhiteSpace(model.SourceNamespace) && model.Namespace != model.SourceNamespace) + // NB: If class names are the same, we're going to use fully qualified names instead. + if (model.Namespace != model.SourceNamespace) { builder.AppendFormat("using {0};", model.SourceNamespace).AppendLine(); } @@ -101,7 +102,7 @@ namespace MapTo builder .PadLeft(Indent2) - .AppendFormat("public {0}({1} {2})", model.ClassName, model.SourceClassName, sourceClassParameterName) + .AppendFormat("public {0}({1} {2})", model.ClassName, model.SourceClassFullName, sourceClassParameterName) .AppendOpeningBracket(Indent2) .PadLeft(Indent3) .AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName) @@ -130,7 +131,7 @@ namespace MapTo return builder .AppendLine() .PadLeft(Indent2) - .AppendFormat("public static {0} From({1} {2})", model.ClassName, model.SourceClassName, sourceClassParameterName) + .AppendFormat("public static {0} From({1} {2})", model.ClassName, model.SourceClassFullName, sourceClassParameterName) .AppendOpeningBracket(Indent2) .PadLeft(Indent3) .AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName) @@ -143,10 +144,10 @@ namespace MapTo return builder .PadLeft(Indent2) - .AppendFormat("public static {0} To{0}(this {1} {2})", model.ClassName, model.SourceClassName, sourceClassParameterName) + .AppendFormat("public static {0} To{0}(this {1} {2})", model.ClassName, model.SourceClassFullName, sourceClassParameterName) .AppendOpeningBracket(Indent2) .PadLeft(Indent3) - .AppendFormat("return {0} == null ? null : new {1}({0})", sourceClassParameterName, model.ClassName) + .AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName) .AppendClosingBracket(Indent2); } diff --git a/MapToTests/Tests.cs b/MapToTests/Tests.cs index d9d0a26..ed82fda 100644 --- a/MapToTests/Tests.cs +++ b/MapToTests/Tests.cs @@ -93,7 +93,7 @@ namespace Test { public partial class Foo { - public Foo(Baz baz) + public Foo(Test.Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); } @@ -136,7 +136,7 @@ namespace Test { public partial class Foo { - public Foo(Baz baz) + public Foo(Test.Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); } @@ -160,7 +160,7 @@ namespace Test const string expectedResult = @" public partial class Foo { - public Foo(Baz baz) + public Foo(Test.Models.Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); Prop1 = baz.Prop1; @@ -185,7 +185,7 @@ namespace Test var source = GetSourceText(); const string expectedResult = @" - public static Foo From(Baz baz) + public static Foo From(Test.Models.Baz baz) { return baz == null ? null : new Foo(baz); } @@ -230,9 +230,9 @@ using Bazaar; const string expectedResult = @" public static class BazExtensions { - public static Foo ToFoo(this Baz baz) + public static Foo ToFoo(this Test.Models.Baz baz) { - return baz == null ? null : new Foo(baz) + return baz == null ? null : new Foo(baz); } } "; @@ -246,7 +246,7 @@ using Bazaar; compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); } - private static string GetSourceText(bool includeAttributeNamespace = false, string sourceClassNamespace = "Test") + private static string GetSourceText(bool includeAttributeNamespace = false, string sourceClassNamespace = "Test.Models") { var builder = new StringBuilder(); builder.AppendLine($@" diff --git a/TestConsoleApp/Program.cs b/TestConsoleApp/Program.cs index 6b0b98a..2542e32 100644 --- a/TestConsoleApp/Program.cs +++ b/TestConsoleApp/Program.cs @@ -8,6 +8,8 @@ namespace TestConsoleApp static void Main(string[] args) { var userViewModel = User.From(new Data.Models.User()); + var userViewModel2 = UserViewModel.From(new Data.Models.User()); + } } } \ No newline at end of file diff --git a/TestConsoleApp/ViewModels/UserViewModel.cs b/TestConsoleApp/ViewModels/UserViewModel.cs new file mode 100644 index 0000000..39e9c31 --- /dev/null +++ b/TestConsoleApp/ViewModels/UserViewModel.cs @@ -0,0 +1,10 @@ +using MapTo; + +namespace TestConsoleApp.ViewModels +{ + [MapFrom(typeof(Data.Models.User))] + public partial class UserViewModel + { + + } +} \ No newline at end of file