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,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Model;
using ProxyInterfaceSourceGenerator.Models;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
@@ -36,11 +37,11 @@ internal abstract class BaseGenerator
{
if (!Context.ReplacedTypes.ContainsKey(typeSymbolAsString))
{
Context.ReplacedTypes.Add(typeSymbolAsString, existing.InterfaceName);
Context.ReplacedTypes.Add(typeSymbolAsString, existing.FullInterfaceName);
}
isReplaced = true;
return existing.InterfaceName;
return existing.FullInterfaceName;
}
if (typeSymbol is INamedTypeSymbol namedTypedSymbol)
@@ -56,10 +57,10 @@ internal abstract class BaseGenerator
if (!Context.ReplacedTypes.ContainsKey(typeArgumentAsString))
{
Context.ReplacedTypes.Add(typeArgumentAsString, exist.InterfaceName);
Context.ReplacedTypes.Add(typeArgumentAsString, exist.FullInterfaceName);
}
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.InterfaceName);
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.FullInterfaceName);
}
}
@@ -69,27 +70,28 @@ internal abstract class BaseGenerator
return typeSymbolAsString;
}
protected ClassSymbol GetNamedTypeSymbolByFullName(string name, IEnumerable<string>? usings = null)
protected bool TryGetNamedTypeSymbolByFullName(TypeKind kind, string name, IEnumerable<string> usings, [NotNullWhen(true)] out ClassSymbol? classSymbol)
{
classSymbol = default;
// The GetTypeByMetadataName method returns null if no type matches the full name or if 2 or more types (in different assemblies) match the full name.
var symbol = Context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName(name);
if (symbol is not null)
if (symbol is not null && symbol.TypeKind == kind)
{
return new ClassSymbol(symbol, symbol.GetBaseTypes(), symbol.AllInterfaces.ToList());
classSymbol = new ClassSymbol(symbol, symbol.GetBaseTypes(), symbol.AllInterfaces.ToList());
return true;
}
if (usings is not null)
foreach (var @using in usings)
{
foreach (var @using in usings)
symbol = Context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName($"{@using}.{name}");
if (symbol is not null && symbol.TypeKind == kind)
{
symbol = Context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName($"{@using}.{name}");
if (symbol is not null)
{
return new ClassSymbol(symbol, symbol.GetBaseTypes(), symbol.AllInterfaces.ToList());
}
classSymbol = new ClassSymbol(symbol, symbol.GetBaseTypes(), symbol.AllInterfaces.ToList());
return true;
}
}
throw new Exception($"The type '{name}' is not found.");
return false;
}
}
@@ -1,3 +0,0 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal record FileData(string FileName, string Text);
@@ -1,3 +1,5 @@
using ProxyInterfaceSourceGenerator.Models;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal interface IFileGenerator
@@ -1,3 +1,5 @@
using ProxyInterfaceSourceGenerator.Models;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal interface IFilesGenerator
@@ -1,9 +1,10 @@
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;
@@ -19,22 +20,35 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
{
foreach (var ci in Context.CandidateInterfaces)
{
yield return GenerateFile(ci.Key, ci.Value);
if (TryGenerateFile(ci.Key, ci.Value, out var file))
{
yield return file;
}
}
}
private FileData GenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd)
private bool TryGenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd, [NotNullWhen(true)] out FileData? fileData)
{
var sourceInterfaceSymbol = GetNamedTypeSymbolByFullName(ci.Identifier.ToString(), pd.Usings);
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
fileData = default;
var file = new FileData(
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Interface, ci.Identifier.ToString(), pd.Usings, out var sourceInterfaceSymbol))
{
return false;
}
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.TypeName, pd.Usings, out var targetClassSymbol))
{
return false;
}
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.ShortInterfaceName);
fileData = new FileData(
$"{sourceInterfaceSymbol.Symbol.GetFileName()}.g.cs",
CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd.ProxyBaseClasses)
);
return file;
return true;
}
private string CreatePartialInterfaceCode(
@@ -1,3 +1,5 @@
using ProxyInterfaceSourceGenerator.Models;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal class ProxyAttributeGenerator : IFileGenerator
@@ -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))