Fix incorrect usings generator + cleanup.
This commit is contained in:
parent
4734b8170a
commit
3ff1e90fb9
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,9 +28,9 @@ namespace MapTo.Extensions
|
||||||
return type.GetAllMembers().OfType<T>();
|
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)
|
public static string GetClassName(this ClassDeclarationSyntax classSyntax)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Models;
|
using MapTo.Models;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
@ -25,61 +26,50 @@ namespace MapTo
|
||||||
return;
|
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)
|
if (model is null)
|
||||||
{
|
{
|
||||||
// TODO: Emit diagnostic info.
|
context.ReportDiagnostic(diagnostic!);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (source, hintName) = SourceBuilder.GenerateSource(model);
|
var (source, hintName) = SourceBuilder.GenerateSource(model);
|
||||||
|
|
||||||
context.AddSource(hintName, source);
|
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();
|
var root = classSyntax.GetCompilationUnit();
|
||||||
if (root is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var classSemanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree);
|
var classSemanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree);
|
||||||
|
|
||||||
if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol))
|
if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol))
|
||||||
{
|
{
|
||||||
return null;
|
return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
|
||||||
}
|
}
|
||||||
|
|
||||||
var destinationTypeSymbol = GetDestinationTypeSymbol(classSyntax, classSemanticModel);
|
var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel);
|
||||||
if (destinationTypeSymbol is null)
|
if (sourceTypeSymbol is null)
|
||||||
{
|
{
|
||||||
return null;
|
return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MapModel(
|
return (MapModel.Create(root, classSyntax, classSymbol, sourceTypeSymbol), default);
|
||||||
ns: root.GetNamespace(),
|
|
||||||
classModifiers: classSyntax.Modifiers,
|
|
||||||
className: classSyntax.GetClassName(),
|
|
||||||
properties: classSymbol.GetAllMembersOfType<IPropertySymbol>(),
|
|
||||||
sourceNamespace: destinationTypeSymbol.ContainingNamespace.Name,
|
|
||||||
sourceClassName: destinationTypeSymbol.Name,
|
|
||||||
sourceTypeProperties: destinationTypeSymbol.GetAllMembersOfType<IPropertySymbol>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
.GetAttribute(SourceBuilder.MapFromAttributeName)
|
||||||
?.DescendantNodes()
|
?.DescendantNodes()
|
||||||
.OfType<TypeOfExpressionSyntax>()
|
.OfType<TypeOfExpressionSyntax>()
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
|
|
||||||
return destinationTypeExpressionSyntax is not null ? model.GetTypeInfo(destinationTypeExpressionSyntax.Type).Type : null;
|
return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
||||||
{
|
{
|
||||||
public List<(ClassDeclarationSyntax classDeclarationSyntax, AttributeSyntax attributeSyntax)> CandidateClasses { get; } = new();
|
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||||
|
@ -31,7 +31,7 @@ namespace MapTo
|
||||||
|
|
||||||
if (attributeSyntax is not null)
|
if (attributeSyntax is not null)
|
||||||
{
|
{
|
||||||
CandidateClasses.Add((classDeclaration, attributeSyntax));
|
CandidateClasses.Add(classDeclaration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MapTo.Extensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace MapTo.Models
|
namespace MapTo.Models
|
||||||
{
|
{
|
||||||
public class MapModel
|
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;
|
Namespace = ns;
|
||||||
ClassModifiers = classModifiers;
|
ClassModifiers = classModifiers;
|
||||||
|
@ -13,6 +23,7 @@ namespace MapTo.Models
|
||||||
Properties = properties;
|
Properties = properties;
|
||||||
SourceNamespace = sourceNamespace;
|
SourceNamespace = sourceNamespace;
|
||||||
SourceClassName = sourceClassName;
|
SourceClassName = sourceClassName;
|
||||||
|
SourceClassFullName = sourceClassFullName;
|
||||||
SourceTypeProperties = sourceTypeProperties;
|
SourceTypeProperties = sourceTypeProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +39,21 @@ namespace MapTo.Models
|
||||||
|
|
||||||
public string SourceClassName { get; }
|
public string SourceClassName { get; }
|
||||||
|
|
||||||
|
public string SourceClassFullName { get; }
|
||||||
|
|
||||||
public IEnumerable<IPropertySymbol> SourceTypeProperties { 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>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -68,7 +68,7 @@ namespace MapTo
|
||||||
.AppendLine()
|
.AppendLine()
|
||||||
.AppendLine()
|
.AppendLine()
|
||||||
.PadLeft(Indent1)
|
.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)
|
.AppendOpeningBracket(Indent1)
|
||||||
|
|
||||||
// Extension class body
|
// Extension class body
|
||||||
|
@ -87,7 +87,8 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
builder.AppendLine("using System;");
|
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();
|
builder.AppendFormat("using {0};", model.SourceNamespace).AppendLine();
|
||||||
}
|
}
|
||||||
|
@ -101,7 +102,7 @@ namespace MapTo
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.PadLeft(Indent2)
|
.PadLeft(Indent2)
|
||||||
.AppendFormat("public {0}({1} {2})", model.ClassName, model.SourceClassName, sourceClassParameterName)
|
.AppendFormat("public {0}({1} {2})", model.ClassName, model.SourceClassFullName, sourceClassParameterName)
|
||||||
.AppendOpeningBracket(Indent2)
|
.AppendOpeningBracket(Indent2)
|
||||||
.PadLeft(Indent3)
|
.PadLeft(Indent3)
|
||||||
.AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName)
|
.AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName)
|
||||||
|
@ -130,7 +131,7 @@ namespace MapTo
|
||||||
return builder
|
return builder
|
||||||
.AppendLine()
|
.AppendLine()
|
||||||
.PadLeft(Indent2)
|
.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)
|
.AppendOpeningBracket(Indent2)
|
||||||
.PadLeft(Indent3)
|
.PadLeft(Indent3)
|
||||||
.AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName)
|
.AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName)
|
||||||
|
@ -143,10 +144,10 @@ namespace MapTo
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
.PadLeft(Indent2)
|
.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)
|
.AppendOpeningBracket(Indent2)
|
||||||
.PadLeft(Indent3)
|
.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);
|
.AppendClosingBracket(Indent2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace Test
|
||||||
{
|
{
|
||||||
public partial class Foo
|
public partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Test.Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ namespace Test
|
||||||
{
|
{
|
||||||
public partial class Foo
|
public partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Test.Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public partial class Foo
|
public partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Test.Models.Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
|
@ -185,7 +185,7 @@ namespace Test
|
||||||
var source = GetSourceText();
|
var source = GetSourceText();
|
||||||
|
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public static Foo From(Baz baz)
|
public static Foo From(Test.Models.Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : new Foo(baz);
|
||||||
}
|
}
|
||||||
|
@ -230,9 +230,9 @@ using Bazaar;
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public static class BazExtensions
|
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());
|
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();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine($@"
|
builder.AppendLine($@"
|
||||||
|
|
|
@ -8,6 +8,8 @@ namespace TestConsoleApp
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var userViewModel = User.From(new Data.Models.User());
|
var userViewModel = User.From(new Data.Models.User());
|
||||||
|
var userViewModel2 = UserViewModel.From(new Data.Models.User());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using MapTo;
|
||||||
|
|
||||||
|
namespace TestConsoleApp.ViewModels
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(Data.Models.User))]
|
||||||
|
public partial class UserViewModel
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue