Add support for base (proxy) class (#29)
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user