Fixed TryFindProxyDataByTypeName (#30)

* pnp

* .

* .

* okee

* ns

* .

* o

* .

* ,

* x

* t

* co

* .

* r

* CastTo
This commit is contained in:
Stef Heyenrath
2022-02-06 11:14:14 +01:00
committed by GitHub
parent 9024b5f42c
commit b995ac3912
35 changed files with 2361 additions and 157 deletions
@@ -7,6 +7,6 @@ internal static class MethodSymbolExtensions
public static string GetMethodNameWithOptionalTypeParameters(this IMethodSymbol method) =>
!method.IsGenericMethod ? method.Name : $"{method.Name}<{string.Join(", ", method.TypeParameters.Select(tp => tp.Name))}>";
public static string GetWhereStatement(this IMethodSymbol method) =>
!method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereStatement()));
//public static string GetWhereStatement(this IMethodSymbol method) =>
// !method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereConstraints()));
}
@@ -45,22 +45,7 @@ internal static class NamedTypeSymbolExtensions
return namedTypeSymbol.OriginalDefinition.ToString();// str.ToString();
}
public static string ResolveInterfaceNameWithOptionalTypeConstraints(this INamedTypeSymbol namedTypeSymbol, string interfaceName)
{
if (!namedTypeSymbol.IsGenericType)
{
return interfaceName;
}
var str = new StringBuilder($"{interfaceName}<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>");
foreach (var typeParameterSymbol in namedTypeSymbol.TypeArguments.OfType<ITypeParameterSymbol>())
{
str.Append(typeParameterSymbol.GetWhereStatement());
}
return str.ToString();
}
/// <summary>
/// See https://stackoverflow.com/questions/24157101/roslyns-gettypebymetadataname-and-generic-types
@@ -37,9 +37,19 @@ internal static class PropertySymbolExtensions
"_Instance" :
$"{targetClassSymbol.Symbol}";
string overrideOrVirtual = string.Empty;
if (property.IsOverride)
{
overrideOrVirtual = "override ";
}
else if (property.IsVirtual)
{
overrideOrVirtual = "virtual ";
}
var get = property.GetMethod != null ? $"get => _mapper.Map<{overrideType}>({instance}.{property.GetSanitizedName()}); " : string.Empty;
var set = property.SetMethod != null ? $"set => {instance}.{property.GetSanitizedName()} = _mapper.Map<{property.Type}>(value); " : string.Empty;
return $"{overrideType} {property.GetSanitizedName()} {{ {get}{set}}}";
return $"{overrideOrVirtual}{overrideType} {property.GetSanitizedName()} {{ {get}{set}}}";
}
}
@@ -1,40 +1,40 @@
using Microsoft.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class SyntaxNodeUtils
{
// https://stackoverflow.com/questions/20458457/getting-class-fullname-including-namespace-from-roslyn-classdeclarationsyntax
public static bool TryGetParentSyntax<T>(this SyntaxNode? syntaxNode, [NotNullWhen(true)] out T? result) where T : SyntaxNode
{
result = null;
if (syntaxNode is null)
{
return false;
}
try
{
syntaxNode = syntaxNode.Parent;
if (syntaxNode is null)
{
return false;
}
if (syntaxNode.GetType() == typeof(T))
{
result = (T)syntaxNode;
return true;
}
return TryGetParentSyntax(syntaxNode, out result);
}
catch
{
return false;
}
}
using Microsoft.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class SyntaxNodeExtensions
{
// https://stackoverflow.com/questions/20458457/getting-class-fullname-including-namespace-from-roslyn-classdeclarationsyntax
public static bool TryGetParentSyntax<T>(this SyntaxNode? syntaxNode, [NotNullWhen(true)] out T? result) where T : SyntaxNode
{
result = null;
if (syntaxNode is null)
{
return false;
}
try
{
syntaxNode = syntaxNode.Parent;
if (syntaxNode is null)
{
return false;
}
if (syntaxNode.GetType() == typeof(T))
{
result = (T)syntaxNode;
return true;
}
return TryGetParentSyntax(syntaxNode, out result);
}
catch
{
return false;
}
}
}
@@ -1,37 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Models;
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class TypeParameterSymbolExtensions
{
/// <summary>
/// https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2
/// </summary>
public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSymbol)
{
var constraints = new List<string>();
if (typeParameterSymbol.HasReferenceTypeConstraint)
{
constraints.Add("class");
}
if (typeParameterSymbol.HasValueTypeConstraint)
{
constraints.Add("struct");
}
if (typeParameterSymbol.HasConstructorConstraint)
{
constraints.Add("new()");
}
constraints.AddRange(typeParameterSymbol.ConstraintTypes.OfType<INamedTypeSymbol>().Select(contstraintType => contstraintType.GetFullType()));
if (!constraints.Any())
{
return string.Empty;
}
return $" where {typeParameterSymbol.Name} : {string.Join(", ", constraints)}";
}
}
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Models;
@@ -26,14 +27,118 @@ internal abstract class BaseGenerator
return GetReplacedType(property.Type, out isReplaced);
}
protected bool TryFindProxyDataByTypeName(string type, [NotNullWhen(true)] out ProxyData? proxyData)
{
proxyData = Context.CandidateInterfaces.Values.FirstOrDefault(x => x.FullRawTypeName == type);
if (proxyData != null)
{
return true;
}
foreach (var ci in Context.CandidateInterfaces.Values)
{
foreach (var u in ci.Usings)
{
if ($"{u}.{ci.FullRawTypeName}" == type)
{
proxyData = ci;
return true;
}
}
}
return false;
}
protected string GetWhereStatementFromMethod(IMethodSymbol method)
{
if (!method.IsGenericMethod)
{
return string.Empty;
}
var list = new List<string>();
foreach (var typeParameterSymbol in method.TypeParameters)
{
if (TryGetWhereConstraints(typeParameterSymbol, false, out var constraint))
{
list.Add(constraint.ToString());
}
}
return string.Concat(list);
}
protected string ResolveInterfaceNameWithOptionalTypeConstraints(INamedTypeSymbol namedTypeSymbol, string interfaceName)
{
if (!namedTypeSymbol.IsGenericType)
{
return interfaceName;
}
var str = new StringBuilder($"{interfaceName}<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>");
foreach (var typeParameterSymbol in namedTypeSymbol.TypeArguments.OfType<ITypeParameterSymbol>())
{
if (TryGetWhereConstraints(typeParameterSymbol, false, out var constraint))
{
str.Append(constraint);
}
}
return str.ToString();
}
/// <summary>
/// https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2
/// </summary>
public bool TryGetWhereConstraints(ITypeParameterSymbol typeParameterSymbol, bool replaceIt, [NotNullWhen(true)] out ConstraintInfo? constraint)
{
var constraints = new List<string>();
if (typeParameterSymbol.HasReferenceTypeConstraint)
{
constraints.Add("class");
}
if (typeParameterSymbol.HasValueTypeConstraint)
{
constraints.Add("struct");
}
if (typeParameterSymbol.HasConstructorConstraint)
{
constraints.Add("new()");
}
foreach (var namedTypeSymbol in typeParameterSymbol.ConstraintTypes.OfType<INamedTypeSymbol>())
{
if (replaceIt)
{
constraints.Add(GetReplacedType(namedTypeSymbol, out _));
}
else
{
constraints.Add(namedTypeSymbol.GetFullType());
}
}
if (!constraints.Any())
{
constraint = null;
return false;
}
constraint = new(typeParameterSymbol.Name, constraints);
return true;
}
protected string GetReplacedType(ITypeSymbol typeSymbol, out bool isReplaced)
{
isReplaced = false;
var typeSymbolAsString = typeSymbol.ToString();
var existing = Context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeSymbolAsString);
if (existing is not null)
if (TryFindProxyDataByTypeName(typeSymbolAsString, out var existing))
{
if (!Context.ReplacedTypes.ContainsKey(typeSymbolAsString))
{
@@ -50,17 +155,17 @@ internal abstract class BaseGenerator
foreach (var typeArgument in namedTypedSymbol.TypeArguments)
{
var typeArgumentAsString = typeArgument.ToString();
var exist = Context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeArgumentAsString);
if (exist is not null)
if (TryFindProxyDataByTypeName(typeArgumentAsString, out var existingTypeArgument))
{
isReplaced = true;
if (!Context.ReplacedTypes.ContainsKey(typeArgumentAsString))
{
Context.ReplacedTypes.Add(typeArgumentAsString, exist.FullInterfaceName);
Context.ReplacedTypes.Add(typeArgumentAsString, existingTypeArgument.FullInterfaceName);
}
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.FullInterfaceName);
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, existingTypeArgument.FullInterfaceName);
}
}
@@ -36,12 +36,12 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
return false;
}
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.TypeName, pd.Usings, out var targetClassSymbol))
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullTypeName, pd.Usings, out var targetClassSymbol))
{
return false;
}
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.ShortInterfaceName);
var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints(targetClassSymbol.Symbol, pd.ShortInterfaceName);
fileData = new FileData(
$"{sourceInterfaceSymbol.Symbol.GetFileName()}.g.cs",
@@ -113,7 +113,12 @@ namespace {ns}
methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}");
}
str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()};");
var whereStatement = GetWhereStatementFromMethod(method);
//public static string GetWhereStatement(this IMethodSymbol method) =>
// !method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereConstraints()));
str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement};");
str.AppendLine();
}
@@ -0,0 +1,34 @@
using System.Text;
using ProxyInterfaceSourceGenerator.Extensions;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal partial class ProxyClassesGenerator
{
private static string GeneratePrivateAutoMapper()
{
return " private readonly IMapper _mapper;";
}
private string GenerateMapperConfigurationForAutoMapper()
{
var str = new StringBuilder();
str.AppendLine(" _mapper = new MapperConfiguration(cfg =>");
str.AppendLine(" {");
foreach (var replacedType in Context.ReplacedTypes)
{
TryFindProxyDataByTypeName(replacedType.Key, out var fullTypeName);
var classNameProxy = $"{fullTypeName!.Namespace}.{fullTypeName.ShortTypeName}Proxy";
var instance = $"instance{(replacedType.Key + replacedType.Value).GetDeterministicHashCodeAsString()}";
var proxy = $"proxy{(replacedType.Value + replacedType.Key).GetDeterministicHashCodeAsString()}";
str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>().ConstructUsing({instance} => new {classNameProxy}({instance}));");
str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>().ConstructUsing({proxy} => (({classNameProxy}) {proxy})._Instance);");
}
str.AppendLine(" }).CreateMapper();");
return str.ToString();
}
}
@@ -30,20 +30,20 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
{
fileData = default;
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.TypeName, pd.Usings, out var targetClassSymbol))
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullTypeName, pd.Usings, out var targetClassSymbol))
{
return false;
}
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.ShortInterfaceName);
var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints(targetClassSymbol.Symbol, pd.ShortInterfaceName);
var className = targetClassSymbol.Symbol.ResolveProxyClassName();
var constructorName = $"{targetClassSymbol.Symbol.Name}Proxy";
var extendsProxyClasses = targetClassSymbol.BaseTypes
.Join(
Context.CandidateInterfaces.Values.Select(v => v.RawTypeName),
bt => bt.ToString(),
ci => ci, (bt, _) => bt
Context.CandidateInterfaces.Values,
namedTypeSymbol => namedTypeSymbol.ToString(),
proxyData => proxyData.FullRawTypeName, (_, proxyData) => proxyData
).ToList();
fileData = new FileData(
@@ -57,20 +57,19 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
private string CreateProxyClassCode(
ProxyData pd,
ClassSymbol targetClassSymbol,
List<INamedTypeSymbol> extendsProxyClasses,
List<ProxyData> extendsProxyClasses,
string interfaceName,
string className,
string constructorName)
{
var extendsFullNames = extendsProxyClasses.Select(e => e.ResolveFullProxyClassName()).ToList();
var extends = extendsProxyClasses.Any() ? $"{string.Join(", ", extendsFullNames)}, " : string.Empty;
var extends = extendsProxyClasses.Select(e => $"{e.Namespace}.{e.ShortTypeName}Proxy, ").FirstOrDefault() ?? string.Empty;
var @base = extendsProxyClasses.Any() ? " : base(instance)" : string.Empty;
var @new = extendsProxyClasses.Any() ? "new " : string.Empty;
var instanceBaseDefinition = extendsProxyClasses.Any() ? $"public {extendsProxyClasses.First()} _InstanceBase {{ get; }}\r\n" : string.Empty;
var instanceBaseDefinition = extendsProxyClasses.Any() ? $"public {extendsProxyClasses[0].FullRawTypeName} _InstanceBase {{ get; }}\r\n" : string.Empty;
var instanceBaseSet = extendsProxyClasses.Any() ? "_InstanceBase = instance;" : string.Empty;
var properties = GeneratePublicProperties(targetClassSymbol, pd.ProxyBaseClasses);
var methods = GeneratePublicMethods(targetClassSymbol, pd.ProxyBaseClasses);
var methods = GeneratePublicMethods(targetClassSymbol, pd.ProxyBaseClasses, extendsProxyClasses);
var events = GenerateEvents(targetClassSymbol, pd.ProxyBaseClasses);
var configurationForAutoMapper = string.Empty;
@@ -123,29 +122,6 @@ namespace {pd.Namespace}
{(SupportsNullable ? "#nullable disable" : string.Empty)}";
}
private static string GeneratePrivateAutoMapper()
{
return " private readonly IMapper _mapper;";
}
private string GenerateMapperConfigurationForAutoMapper()
{
var str = new StringBuilder();
str.AppendLine(" _mapper = new MapperConfiguration(cfg =>");
str.AppendLine(" {");
foreach (var replacedType in Context.ReplacedTypes)
{
var proxy = $"{replacedType.Key}Proxy";
str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>().ConstructUsing(instance => new {proxy}(instance));");
str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>().ConstructUsing(proxy => (({proxy}) proxy)._Instance);");
}
str.AppendLine(" }).CreateMapper();");
return str.ToString();
}
private string GeneratePublicProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
@@ -153,6 +129,7 @@ namespace {pd.Namespace}
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses))
{
var type = GetPropertyType(property, out var isReplaced);
if (isReplaced)
{
str.AppendLine($" public {property.ToPropertyTextForClass(targetClassSymbol, type)}");
@@ -167,7 +144,7 @@ namespace {pd.Namespace}
return str.ToString();
}
private string GeneratePublicMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
private string GeneratePublicMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses, List<ProxyData> extendsProxyClasses)
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses))
@@ -183,9 +160,25 @@ namespace {pd.Namespace}
invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_");
}
string overrideOrVirtual = string.Empty;
if (method.IsOverride && method.OverriddenMethod != null)
{
var baseType = method.OverriddenMethod.ContainingType.GetFullType();
if (TryGetNamedTypeSymbolByFullName(TypeKind.Class, baseType, Enumerable.Empty<string>(), out _))
{
overrideOrVirtual = "override ";
}
}
else if (method.IsVirtual)
{
overrideOrVirtual = "virtual ";
}
string returnTypeAsString = GetReplacedType(method.ReturnType, out var returnIsReplaced);
str.AppendLine($" public {returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()}");
var whereStatement = GetWhereStatementFromMethod(method);
str.AppendLine($" public {overrideOrVirtual}{returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement}");
str.AppendLine(" {");
foreach (var ps in method.Parameters)
{
@@ -0,0 +1,9 @@
namespace ProxyInterfaceSourceGenerator.Models;
internal record ConstraintInfo(string Type, List<string> Items)
{
public override string ToString()
{
return Items.Any() ? $" where {Type} : {string.Join(", ", Items)}" : string.Empty;
}
}
@@ -5,8 +5,9 @@ internal record ProxyData
string Namespace,
string ShortInterfaceName,
string FullInterfaceName,
string RawTypeName,
string TypeName,
string FullRawTypeName,
string ShortTypeName,
string FullTypeName,
List<string> Usings,
bool ProxyBaseClasses
);
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.0.14</Version>
<Version>0.0.15-preview-02</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<ProjectGuid>{12344228-91F4-4502-9595-39584E5ABB34}</ProjectGuid>
<LangVersion>10</LangVersion>
@@ -59,9 +59,9 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
}
}
var type = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type;
var typeSyntax = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type;
string rawTypeName = typeSyntax.ToString();
string rawTypeName = type.ToString();
bool proxyAllClasses;
try
{
@@ -71,14 +71,15 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
{
proxyAllClasses = false;
}
data = new
(
ns,
interfaceDeclarationSyntax.Identifier.ToString(),
$"{ns}.{interfaceDeclarationSyntax.Identifier}",
rawTypeName,
ConvertTypeName(rawTypeName),
ConvertTypeName(rawTypeName).Split('.').Last(), // ShortTypeName
ConvertTypeName(rawTypeName), // FullTypeName
usings,
proxyAllClasses
);
@@ -86,6 +87,11 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
return true;
}
private string GetFullTypeName(TypeSyntax typeSyntax, string ns)
{
return "";
}
private static string ConvertTypeName(string typeName)
{
return !(typeName.Contains('<') && typeName.Contains('>')) ?
@@ -25,10 +25,7 @@ internal static class MemberHelper
bool proxyBaseClasses,
Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
filter = _ => true;
}
filter ??= _ => true;
return GetPublicMembers(classSymbol,
proxyBaseClasses,
@@ -43,16 +40,13 @@ internal static class MemberHelper
bool proxyBaseClasses,
Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
filter = _ => true;
}
filter ??= _ => true;
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
#pragma warning disable RS1024 // Compare symbols correctly
return GetPublicMembers(classSymbol,
proxyBaseClasses,
m => m.MethodKind == MethodKind.EventAdd || m.MethodKind == MethodKind.EventRemove/* || m.MethodKind == MethodKind.EventRaise*/,
m => m.MethodKind is MethodKind.EventAdd or MethodKind.EventRemove/* || m.MethodKind == MethodKind.EventRaise*/,
filter)
.GroupBy(e => e.AssociatedSymbol);
#pragma warning restore RS1024 // Compare symbols correctly