Add support for base (proxy) class (#29)

This commit is contained in:
Stef Heyenrath
2022-02-04 11:33:26 +01:00
committed by GitHub
parent 94d322cfb3
commit d7483d6b7e
38 changed files with 400 additions and 309 deletions
@@ -1,15 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ProxyInterfaceSourceGenerator.Enums;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Model;
using ProxyInterfaceSourceGenerator.SyntaxReceiver;
using ProxyInterfaceSourceGenerator.Models;
using ProxyInterfaceSourceGenerator.Utils;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
{
public ProxyClassesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable)
{
@@ -19,32 +18,72 @@ internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
{
foreach (var ci in Context.CandidateInterfaces)
{
yield return GenerateFile(ci.Value, Context.CandidateInterfaces);
if (TryGenerateFile(ci.Value, out var file))
{
yield return file;
}
}
}
private FileData GenerateFile(ProxyData pd, IDictionary<InterfaceDeclarationSyntax, ProxyData> candidateInterfaces)
[SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1024:Compare symbols correctly", Justification = "<Pending>")]
private bool TryGenerateFile(ProxyData pd, [NotNullWhen(true)] out FileData? fileData)
{
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
fileData = default;
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.TypeName, pd.Usings, out var targetClassSymbol))
{
return false;
}
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.ShortInterfaceName);
var className = targetClassSymbol.Symbol.ResolveProxyClassName();
var constructorName = $"{targetClassSymbol.Symbol.Name}Proxy";
var file = new FileData(
var extendsProxyClasses = targetClassSymbol.BaseTypes
.Join(
Context.CandidateInterfaces.Values.Select(v => v.RawTypeName),
bt => bt.ToString(),
ci => ci, (bt, _) => bt
).ToList();
fileData = new FileData(
$"{targetClassSymbol.Symbol.GetFileName()}Proxy.g.cs",
CreateProxyClassCode(pd.Namespace, targetClassSymbol, pd.ProxyBaseClasses, interfaceName, className, constructorName)
CreateProxyClassCode(pd, targetClassSymbol, extendsProxyClasses, interfaceName, className, constructorName)
);
return file;
return true;
}
private string CreateProxyClassCode(
string ns,
ProxyData pd,
ClassSymbol targetClassSymbol,
bool proxyBaseClasses,
List<INamedTypeSymbol> extendsProxyClasses,
string interfaceName,
string className,
string constructorName) => $@"//----------------------------------------------------------------------------------------
string constructorName)
{
var extendsFullNames = extendsProxyClasses.Select(e => e.ResolveFullProxyClassName()).ToList();
var extends = extendsProxyClasses.Any() ? $"{string.Join(", ", extendsFullNames)}, " : 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 instanceBaseSet = extendsProxyClasses.Any() ? "_InstanceBase = instance;" : string.Empty;
var properties = GeneratePublicProperties(targetClassSymbol, pd.ProxyBaseClasses);
var methods = GeneratePublicMethods(targetClassSymbol, pd.ProxyBaseClasses);
var events = GenerateEvents(targetClassSymbol, pd.ProxyBaseClasses);
var configurationForAutoMapper = string.Empty;
var privateAutoMapper = string.Empty;
var usingAutoMapper = string.Empty;
if (Context.ReplacedTypes.Any())
{
configurationForAutoMapper = GenerateMapperConfigurationForAutoMapper();
privateAutoMapper = GeneratePrivateAutoMapper();
usingAutoMapper = "using AutoMapper;";
}
return $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
@@ -55,52 +94,52 @@ internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
{(SupportsNullable ? "#nullable enable" : string.Empty)}
using System;
using AutoMapper;
{usingAutoMapper}
namespace {ns}
namespace {pd.Namespace}
{{
public partial class {className} : {interfaceName}
public partial class {className} : {extends}{interfaceName}
{{
public {targetClassSymbol.Symbol} _Instance {{ get; }}
public {@new}{targetClassSymbol.Symbol} _Instance {{ get; }}
{instanceBaseDefinition}
{GeneratePublicProperties(targetClassSymbol, proxyBaseClasses)}
{properties}
{GeneratePublicMethods(targetClassSymbol, proxyBaseClasses)}
{methods}
{GenerateEvents(targetClassSymbol, proxyBaseClasses)}
{events}
public {constructorName}({targetClassSymbol} instance)
public {constructorName}({targetClassSymbol} instance){@base}
{{
_Instance = instance;
{instanceBaseSet}
{GenerateMapperConfigurationForAutoMapper()}
{configurationForAutoMapper}
}}
{GeneratePrivateAutoMapper()}
{privateAutoMapper}
}}
}}
{(SupportsNullable ? "#nullable disable" : string.Empty)}";
}
private string GeneratePrivateAutoMapper()
private static string GeneratePrivateAutoMapper()
{
return Context.ReplacedTypes.Count == 0 ? string.Empty : " private readonly IMapper _mapper;";
return " private readonly IMapper _mapper;";
}
private string GenerateMapperConfigurationForAutoMapper()
{
if (Context.ReplacedTypes.Count == 0)
{
return string.Empty;
}
var str = new StringBuilder();
str.AppendLine(" _mapper = new MapperConfiguration(cfg =>");
str.AppendLine(" {");
foreach (var replacedType in Context.ReplacedTypes)
{
str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>();");
str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>();");
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();");
@@ -167,11 +206,8 @@ namespace {ns}
str.AppendLine($" {ps.Type} {ps.GetSanitizedName()}_{normalOrMap};");
}
#pragma warning disable RS1024 // Compare symbols correctly
int hash = method.ReturnType.GetHashCode();
#pragma warning restore RS1024 // Compare symbols correctly
var alternateReturnVariableName = $"result_{Math.Abs(hash)}";
var methodName = method.GetMethodNameWithOptionalTypeParameters();
var alternateReturnVariableName = $"result_{methodName.GetDeterministicHashCodeAsString()}";
string instance = !method.IsStatic ?
"_Instance" :
@@ -179,11 +215,11 @@ namespace {ns}
if (returnTypeAsString == "void")
{
str.AppendLine($" {instance}.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
str.AppendLine($" {instance}.{methodName}({string.Join(", ", invokeParameters)});");
}
else
{
str.AppendLine($" var {alternateReturnVariableName} = {instance}.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
str.AppendLine($" var {alternateReturnVariableName} = {instance}.{methodName}({string.Join(", ", invokeParameters)});");
}
foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out))