Fix incorrect usings generator + cleanup.

This commit is contained in:
Mohammadreza Taikandi 2020-12-21 15:53:44 +00:00
parent 4734b8170a
commit 3ff1e90fb9
9 changed files with 99 additions and 49 deletions

21
MapTo/Diagnostics.cs Normal file
View File

@ -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);
}
}

View File

@ -28,9 +28,9 @@ namespace MapTo.Extensions
return type.GetAllMembers().OfType<T>();
}
public static CompilationUnitSyntax? GetCompilationUnit(this SyntaxNode syntaxNode)
public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode)
{
return syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().FirstOrDefault();
return syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().Single();
}
public static string GetClassName(this ClassDeclarationSyntax classSyntax)

View File

@ -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<IPropertySymbol>(),
sourceNamespace: destinationTypeSymbol.ContainingNamespace.Name,
sourceClassName: destinationTypeSymbol.Name,
sourceTypeProperties: destinationTypeSymbol.GetAllMembersOfType<IPropertySymbol>());
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<TypeOfExpressionSyntax>()
.SingleOrDefault();
return destinationTypeExpressionSyntax is not null ? model.GetTypeInfo(destinationTypeExpressionSyntax.Type).Type : null;
return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type : null;
}
}
}

View File

@ -7,7 +7,7 @@ namespace MapTo
{
internal class MapToSyntaxReceiver : ISyntaxReceiver
{
public List<(ClassDeclarationSyntax classDeclarationSyntax, AttributeSyntax attributeSyntax)> CandidateClasses { get; } = new();
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
/// <inheritdoc />
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
@ -31,7 +31,7 @@ namespace MapTo
if (attributeSyntax is not null)
{
CandidateClasses.Add((classDeclaration, attributeSyntax));
CandidateClasses.Add(classDeclaration);
}
}
}

View File

@ -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<IPropertySymbol> properties, string sourceNamespace, string sourceClassName, IEnumerable<IPropertySymbol> sourceTypeProperties)
private MapModel(
string? ns,
SyntaxTokenList classModifiers,
string className,
IEnumerable<IPropertySymbol> properties,
string sourceNamespace,
string sourceClassName,
string sourceClassFullName,
IEnumerable<IPropertySymbol> 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<IPropertySymbol> Properties { get; }
public string SourceNamespace { get; }
public string SourceClassName { get; }
public string SourceClassFullName { get; }
public IEnumerable<IPropertySymbol> SourceTypeProperties { get; }
internal static MapModel Create(CompilationUnitSyntax root, ClassDeclarationSyntax classSyntax, INamedTypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
{
return new(
root.GetNamespace(),
classSyntax.Modifiers,
classSyntax.GetClassName(),
classSymbol.GetAllMembersOfType<IPropertySymbol>(),
sourceTypeSymbol.ContainingNamespace.ToString(),
sourceTypeSymbol.Name,
sourceTypeSymbol.ToString(),
sourceTypeSymbol.GetAllMembersOfType<IPropertySymbol>());
}
}
}

View File

@ -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);
}

View File

@ -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($@"

View File

@ -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());
}
}
}

View File

@ -0,0 +1,10 @@
using MapTo;
namespace TestConsoleApp.ViewModels
{
[MapFrom(typeof(Data.Models.User))]
public partial class UserViewModel
{
}
}