ProxyBaseClasses (#27)

This commit is contained in:
Stef Heyenrath
2022-02-01 18:49:01 +01:00
committed by GitHub
parent 649ed89bb6
commit f9664e0564
45 changed files with 1305 additions and 836 deletions
@@ -1,18 +1,17 @@
// https://stackoverflow.com/questions/61573959/how-to-resolve-error-notnullwhen-attribute-is-inaccessible-due-to-its-protectio
// https://stackoverflow.com/questions/61573959/how-to-resolve-error-notnullwhen-attribute-is-inaccessible-due-to-its-protectio
namespace System.Diagnostics.CodeAnalysis
namespace System.Diagnostics.CodeAnalysis;
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
+7 -9
View File
@@ -1,18 +1,16 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ProxyInterfaceSourceGenerator.SyntaxReceiver;
namespace ProxyInterfaceSourceGenerator
namespace ProxyInterfaceSourceGenerator;
internal record Context
{
internal record Context
{
public GeneratorExecutionContext GeneratorExecutionContext { get; init; }
public GeneratorExecutionContext GeneratorExecutionContext { get; init; }
// public List<ContextData> GeneratedData { get; } = new List<ContextData>();
// public List<ContextData> GeneratedData { get; } = new List<ContextData>();
public IDictionary<InterfaceDeclarationSyntax, ProxyData> CandidateInterfaces { get; init; } = default!;
public IDictionary<InterfaceDeclarationSyntax, ProxyData> CandidateInterfaces { get; init; } = default!;
public Dictionary<string, string> ReplacedTypes { get; } = new Dictionary<string, string>();
}
public Dictionary<string, string> ReplacedTypes { get; } = new Dictionary<string, string>();
}
@@ -1,13 +1,12 @@
using ProxyInterfaceSourceGenerator.FileGenerators;
namespace ProxyInterfaceSourceGenerator
namespace ProxyInterfaceSourceGenerator;
internal record ContextData
{
internal record ContextData
{
public string? InterfaceName { get; init; }
public string? InterfaceName { get; init; }
public string? ClassName { get; init; }
public string? ClassName { get; init; }
public FileData FileData { get; init; } = default!;
}
public FileData FileData { get; init; } = default!;
}
@@ -1,11 +1,10 @@
namespace ProxyInterfaceSourceGenerator.Enums
namespace ProxyInterfaceSourceGenerator.Enums;
internal enum TypeEnum
{
internal enum TypeEnum
{
ValueTypeOrString,
ValueTypeOrString,
Interface,
Interface,
Complex
}
Complex
}
@@ -1,14 +1,12 @@
using System.Linq;
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions
{
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))}>";
namespace ProxyInterfaceSourceGenerator.Extensions;
public static string GetWhereStatement(this IMethodSymbol method) =>
!method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereStatement()));
}
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()));
}
@@ -1,57 +1,74 @@
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class NamedTypeSymbolExtensions
{
internal static class NamedTypeSymbolExtensions
public static List<INamedTypeSymbol> GetBaseTypes(this INamedTypeSymbol? type)
{
public static string GetFileName(this INamedTypeSymbol namedTypeSymbol)
var types = new List<INamedTypeSymbol>();
bool me = true;
while (type != null && type.SpecialType != SpecialType.System_Object)
{
var typeName = namedTypeSymbol.GetFullType();
return !(typeName.Contains('<') && typeName.Contains('>')) ?
typeName :
$"{typeName.Replace('.', '_').Replace('<', '_').Replace('>', '_').Replace(", ", "-")}_{typeName.Count(c => c == ',') + 1}";
}
public static string GetFullType(this INamedTypeSymbol namedTypeSymbol)
{
// https://www.codeproject.com/Articles/861548/Roslyn-Code-Analysis-in-Easy-Samples-Part
//var str = new StringBuilder(namedTypeSymbol.Name);
//if (namedTypeSymbol.TypeArguments.Count() > 0)
//{
// str.AppendFormat("<{0}>", string.Join(", ", namedTypeSymbol.TypeArguments.OfType<INamedTypeSymbol>().Select(typeArg => typeArg.GetFullType())));
//}
return namedTypeSymbol.OriginalDefinition.ToString();// str.ToString();
}
public static string ResolveInterfaceNameWithOptionalTypeConstraints(this INamedTypeSymbol namedTypeSymbol, string interfaceName)
{
if (!namedTypeSymbol.IsGenericType)
if (!me)
{
return interfaceName;
types.Add(type);
}
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();
type = type.BaseType;
me = false;
}
/// <summary>
/// See https://stackoverflow.com/questions/24157101/roslyns-gettypebymetadataname-and-generic-types
/// </summary>
public static string ResolveProxyClassName(this INamedTypeSymbol namedTypeSymbol)
return types;
}
public static string GetFileName(this INamedTypeSymbol namedTypeSymbol)
{
var typeName = namedTypeSymbol.GetFullType();
return !(typeName.Contains('<') && typeName.Contains('>')) ?
typeName :
$"{typeName.Replace('.', '_').Replace('<', '_').Replace('>', '_').Replace(", ", "-")}_{typeName.Count(c => c == ',') + 1}";
}
public static string GetFullType(this INamedTypeSymbol namedTypeSymbol)
{
// https://www.codeproject.com/Articles/861548/Roslyn-Code-Analysis-in-Easy-Samples-Part
//var str = new StringBuilder(namedTypeSymbol.Name);
//if (namedTypeSymbol.TypeArguments.Count() > 0)
//{
// str.AppendFormat("<{0}>", string.Join(", ", namedTypeSymbol.TypeArguments.OfType<INamedTypeSymbol>().Select(typeArg => typeArg.GetFullType())));
//}
return namedTypeSymbol.OriginalDefinition.ToString();// str.ToString();
}
public static string ResolveInterfaceNameWithOptionalTypeConstraints(this INamedTypeSymbol namedTypeSymbol, string interfaceName)
{
if (!namedTypeSymbol.IsGenericType)
{
return !namedTypeSymbol.IsGenericType ?
$"{namedTypeSymbol.Name}Proxy" :
$"{namedTypeSymbol.Name}Proxy<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>";
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
/// </summary>
public static string ResolveProxyClassName(this INamedTypeSymbol namedTypeSymbol)
{
return !namedTypeSymbol.IsGenericType ?
$"{namedTypeSymbol.Name}Proxy" :
$"{namedTypeSymbol.Name}Proxy<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>";
}
}
@@ -1,35 +1,34 @@
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Enums;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class ParameterSymbolExtensions
{
internal static class ParameterSymbolExtensions
public static string GetRefPrefix(this IParameterSymbol ps)
{
public static string GetRefPrefix(this IParameterSymbol ps)
switch (ps.RefKind)
{
switch (ps.RefKind)
{
case RefKind.In:
return "in ";
case RefKind.In:
return "in ";
case RefKind.Out:
return "out ";
case RefKind.Out:
return "out ";
case RefKind.Ref:
return "ref ";
case RefKind.Ref:
return "ref ";
default:
return string.Empty;
}
default:
return string.Empty;
}
public static string GetParamsPrefix(this IParameterSymbol ps) =>
ps.IsParams ? "params " : string.Empty;
public static string GetDefaultValue(this IParameterSymbol ps) =>
ps.HasExplicitDefaultValue ? $" = {ps.ExplicitDefaultValue}" : string.Empty;
public static TypeEnum GetTypeEnum(this IParameterSymbol p) =>
p.Type.GetTypeEnum();
}
public static string GetParamsPrefix(this IParameterSymbol ps) =>
ps.IsParams ? "params " : string.Empty;
public static string GetDefaultValue(this IParameterSymbol ps) =>
ps.HasExplicitDefaultValue ? $" = {ps.ExplicitDefaultValue}" : string.Empty;
public static TypeEnum GetTypeEnum(this IParameterSymbol p) =>
p.Type.GetTypeEnum();
}
@@ -1,37 +1,36 @@
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Enums;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class PropertySymbolExtensions
{
internal static class PropertySymbolExtensions
public static TypeEnum GetTypeEnum(this IPropertySymbol p) =>
p.Type.GetTypeEnum();
public static string ToPropertyText(this IPropertySymbol property, string? overrideType = null)
{
public static TypeEnum GetTypeEnum(this IPropertySymbol p) =>
p.Type.GetTypeEnum();
var get = property.GetMethod != null ? "get; " : string.Empty;
var set = property.SetMethod != null ? "set; " : string.Empty;
public static string ToPropertyText(this IPropertySymbol property, string? overrideType = null)
{
var get = property.GetMethod != null ? "get; " : string.Empty;
var set = property.SetMethod != null ? "set; " : string.Empty;
var type = !string.IsNullOrEmpty(overrideType) ? overrideType : $"{property.Type}";
var type = !string.IsNullOrEmpty(overrideType) ? overrideType : $"{property.Type}";
return $"{type} {property.GetSanitizedName()} {{ {get}{set}}}";
}
return $"{type} {property.GetSanitizedName()} {{ {get}{set}}}";
}
public static string ToPropertyTextForClass(this IPropertySymbol property)
{
var get = property.GetMethod != null ? $"get => _Instance.{property.GetSanitizedName()}; " : string.Empty;
var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = value; " : string.Empty;
public static string ToPropertyTextForClass(this IPropertySymbol property)
{
var get = property.GetMethod != null ? $"get => _Instance.{property.GetSanitizedName()}; " : string.Empty;
var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = value; " : string.Empty;
return $"{property.Type} {property.GetSanitizedName()} {{ {get}{set}}}";
}
return $"{property.Type} {property.GetSanitizedName()} {{ {get}{set}}}";
}
public static string ToPropertyTextForClass(this IPropertySymbol property, string overrideType)
{
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;
public static string ToPropertyTextForClass(this IPropertySymbol property, string overrideType)
{
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 $"{overrideType} {property.GetSanitizedName()} {{ {get}{set}}}";
}
}
@@ -1,14 +1,13 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace ProxyInterfaceSourceGenerator.Extensions
{
internal static class SymbolExtensions
{
public static bool IsKeywordOrReserved(this ISymbol symbol) =>
SyntaxFacts.GetKeywordKind(symbol.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(symbol.Name) != SyntaxKind.None;
namespace ProxyInterfaceSourceGenerator.Extensions;
public static string GetSanitizedName(this ISymbol symbol) =>
symbol.IsKeywordOrReserved() ? $"@{symbol.Name}" : symbol.Name;
}
internal static class SymbolExtensions
{
public static bool IsKeywordOrReserved(this ISymbol symbol) =>
SyntaxFacts.GetKeywordKind(symbol.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(symbol.Name) != SyntaxKind.None;
public static string GetSanitizedName(this ISymbol symbol) =>
symbol.IsKeywordOrReserved() ? $"@{symbol.Name}" : symbol.Name;
}
@@ -1,41 +1,40 @@
using Microsoft.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class SyntaxNodeUtils
{
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
{
// 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)
{
result = null;
return false;
}
try
{
syntaxNode = syntaxNode.Parent;
if (syntaxNode is null)
{
return false;
}
try
if (syntaxNode.GetType() == typeof(T))
{
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;
result = (T)syntaxNode;
return true;
}
return TryGetParentSyntax(syntaxNode, out result);
}
catch
{
return false;
}
}
}
@@ -1,40 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class TypeParameterSymbolExtensions
{
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)
{
/// <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)
{
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)}";
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,28 +1,27 @@
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Enums;
namespace ProxyInterfaceSourceGenerator.Extensions
namespace ProxyInterfaceSourceGenerator.Extensions;
internal static class TypeSymbolExtensions
{
internal static class TypeSymbolExtensions
public static TypeEnum GetTypeEnum(this ITypeSymbol ts)
{
public static TypeEnum GetTypeEnum(this ITypeSymbol ts)
if (ts.IsValueType || ts.IsString())
{
if (ts.IsValueType || ts.IsString())
{
return TypeEnum.ValueTypeOrString;
}
if (ts.TypeKind == TypeKind.Interface)
{
return TypeEnum.Interface;
}
return TypeEnum.Complex;
return TypeEnum.ValueTypeOrString;
}
public static bool IsString(this ITypeSymbol ts)
if (ts.TypeKind == TypeKind.Interface)
{
return ts.ToString() == "string" || ts.ToString() == "string?";
return TypeEnum.Interface;
}
return TypeEnum.Complex;
}
public static bool IsString(this ITypeSymbol ts)
{
return ts.ToString() == "string" || ts.ToString() == "string?";
}
}
@@ -1,97 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Model;
namespace ProxyInterfaceSourceGenerator.FileGenerators
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal abstract class BaseGenerator
{
internal abstract class BaseGenerator
protected readonly Context Context;
protected readonly bool SupportsNullable;
protected BaseGenerator(Context context, bool supportsNullable)
{
protected readonly Context _context;
protected readonly bool _supportsNullable;
Context = context;
SupportsNullable = supportsNullable;
}
public BaseGenerator(Context context, bool supportsNullable)
protected string GetPropertyType(IPropertySymbol property, out bool isReplaced)
{
return GetReplacedType(property.Type, out isReplaced);
}
protected string GetParameterType(IParameterSymbol property, out bool isReplaced)
{
return GetReplacedType(property.Type, out isReplaced);
}
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)
{
_context = context;
_supportsNullable = supportsNullable;
}
protected string GetPropertyType(IPropertySymbol property, out bool isReplaced)
{
return GetReplacedType(property.Type, out isReplaced);
}
protected string GetParameterType(IParameterSymbol property, out bool isReplaced)
{
return GetReplacedType(property.Type, out isReplaced);
}
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 (!Context.ReplacedTypes.ContainsKey(typeSymbolAsString))
{
if (!_context.ReplacedTypes.ContainsKey(typeSymbolAsString))
{
_context.ReplacedTypes.Add(typeSymbolAsString, existing.InterfaceName);
}
isReplaced = true;
return existing.InterfaceName;
Context.ReplacedTypes.Add(typeSymbolAsString, existing.InterfaceName);
}
if (typeSymbol is INamedTypeSymbol namedTypedSymbol)
isReplaced = true;
return existing.InterfaceName;
}
if (typeSymbol is INamedTypeSymbol namedTypedSymbol)
{
var propertyTypeAsStringToBeModified = typeSymbolAsString;
foreach (var typeArgument in namedTypedSymbol.TypeArguments)
{
var propertyTypeAsStringToBeModified = typeSymbolAsString;
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)
{
var typeArgumentAsString = typeArgument.ToString();
var exist = _context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeArgumentAsString);
if (exist is not null)
isReplaced = true;
if (!Context.ReplacedTypes.ContainsKey(typeArgumentAsString))
{
isReplaced = true;
if (!_context.ReplacedTypes.ContainsKey(typeArgumentAsString))
{
_context.ReplacedTypes.Add(typeArgumentAsString, exist.InterfaceName);
}
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.InterfaceName);
Context.ReplacedTypes.Add(typeArgumentAsString, exist.InterfaceName);
}
}
return propertyTypeAsStringToBeModified;
propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.InterfaceName);
}
}
return typeSymbolAsString;
return propertyTypeAsStringToBeModified;
}
protected INamedTypeSymbol GetNamedTypeSymbolByFullName(string name, IEnumerable<string>? usings = null)
return typeSymbolAsString;
}
protected ClassSymbol GetNamedTypeSymbolByFullName(string name, IEnumerable<string>? usings = null)
{
// 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)
{
// 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)
{
return symbol;
}
return new ClassSymbol(symbol, symbol.GetBaseTypes());
}
if (usings is not null)
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 = _context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName($"{@using}.{name}");
if (symbol is not null)
{
return symbol;
}
return new ClassSymbol(symbol, symbol.GetBaseTypes());
}
}
throw new Exception($"The type '{name}' is not found.");
}
throw new Exception($"The type '{name}' is not found.");
}
}
@@ -1,6 +1,3 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal record FileData(string FileName, string Text)
{
}
}
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal record FileData(string FileName, string Text);
@@ -1,7 +1,6 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal interface IFileGenerator
{
internal interface IFileGenerator
{
FileData GenerateFile();
}
FileData GenerateFile();
}
@@ -1,9 +1,6 @@
using System.Collections.Generic;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
namespace ProxyInterfaceSourceGenerator.FileGenerators
internal interface IFilesGenerator
{
internal interface IFilesGenerator
{
IEnumerable<FileData> GenerateFiles();
}
IEnumerable<FileData> GenerateFiles();
}
@@ -1,47 +1,47 @@
using System.Collections.Generic;
using System.Linq;
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.Utils;
namespace ProxyInterfaceSourceGenerator.FileGenerators
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
{
internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
public PartialInterfacesGenerator(Context context, bool supportsNullable) :
base(context, supportsNullable)
{
public PartialInterfacesGenerator(Context context, bool supportsNullable) :
base(context, supportsNullable)
}
public IEnumerable<FileData> GenerateFiles()
{
foreach (var ci in Context.CandidateInterfaces)
{
yield return GenerateFile(ci.Key, ci.Value);
}
}
public IEnumerable<FileData> GenerateFiles()
{
foreach (var ci in _context.CandidateInterfaces)
{
yield return GenerateFile(ci.Key, ci.Value);
}
}
private FileData GenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd)
{
var sourceInterfaceSymbol = GetNamedTypeSymbolByFullName(ci.Identifier.ToString(), pd.Usings);
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
private FileData GenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd)
{
var sourceInterfaceSymbol = GetNamedTypeSymbolByFullName(ci.Identifier.ToString(), pd.Usings);
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
var file = new FileData(
$"{sourceInterfaceSymbol.Symbol.GetFileName()}.g.cs",
CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd.ProxyBaseClasses)
);
var file = new FileData(
$"{sourceInterfaceSymbol.GetFileName()}.g.cs",
CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd.ProxyAll)
);
return file;
}
// _context.GeneratedData.Add(new() { InterfaceName = interfaceName, ClassName = null, FileData = file });
return file;
}
private string CreatePartialInterfaceCode(string ns, INamedTypeSymbol targetClassSymbol, string interfaceName, bool proxyAll) => $@"//----------------------------------------------------------------------------------------
private string CreatePartialInterfaceCode(
string ns,
ClassSymbol classSymbol,
string interfaceName,
bool proxyBaseClasses) => $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
@@ -50,74 +50,73 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators
// </auto-generated>
//----------------------------------------------------------------------------------------
{(_supportsNullable ? "#nullable enable" : string.Empty)}
{(SupportsNullable ? "#nullable enable" : string.Empty)}
using System;
namespace {ns}
{{
public partial interface {interfaceName}
{{
{GenerateProperties(targetClassSymbol, proxyAll)}
{GenerateProperties(classSymbol, proxyBaseClasses)}
{GenerateMethods(targetClassSymbol)}
{GenerateMethods(classSymbol, proxyBaseClasses)}
{GenerateEvents(targetClassSymbol)}
{GenerateEvents(classSymbol, proxyBaseClasses)}
}}
}}
{(_supportsNullable ? "#nullable disable" : string.Empty)}";
{(SupportsNullable ? "#nullable disable" : string.Empty)}";
private string GenerateProperties(INamedTypeSymbol targetClassSymbol, bool proxyAll)
private string GenerateProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses))
{
var str = new StringBuilder();
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol))
var type = GetPropertyType(property, out var isReplaced);
if (isReplaced)
{
var type = GetPropertyType(property, out var isReplaced);
if (isReplaced)
{
str.AppendLine($" {property.ToPropertyText(type)}");
}
else
{
str.AppendLine($" {property.ToPropertyText()}");
}
str.AppendLine();
str.AppendLine($" {property.ToPropertyText(type)}");
}
return str.ToString();
else
{
str.AppendLine($" {property.ToPropertyText()}");
}
str.AppendLine();
}
private string GenerateMethods(INamedTypeSymbol targetClassSymbol)
return str.ToString();
}
private string GenerateMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses))
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol))
var methodParameters = new List<string>();
foreach (var ps in method.Parameters)
{
var methodParameters = new List<string>();
foreach (var ps in method.Parameters)
{
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}");
}
str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()};");
str.AppendLine();
}
return str.ToString();
}
private string GenerateEvents(INamedTypeSymbol targetClassSymbol)
{
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol))
{
var ps = @event.First().Parameters.First();
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
str.AppendLine($" event {type} {@event.Key.GetSanitizedName()};");
str.AppendLine();
methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}");
}
return str.ToString();
str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()};");
str.AppendLine();
}
return str.ToString();
}
private string GenerateEvents(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses))
{
var ps = @event.First().Parameters.First();
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
str.AppendLine($" event {type} {@event.Key.GetSanitizedName()};");
str.AppendLine();
}
return str.ToString();
}
}
@@ -1,12 +1,12 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal class ProxyAttributeGenerator : IFileGenerator
{
private const string ClassName = "ProxyAttribute";
namespace ProxyInterfaceSourceGenerator.FileGenerators;
public FileData GenerateFile()
{
return new FileData($"ProxyInterfaceGenerator.{ClassName}.g.cs", $@"//----------------------------------------------------------------------------------------
internal class ProxyAttributeGenerator : IFileGenerator
{
private const string ClassName = "ProxyAttribute";
public FileData GenerateFile()
{
return new FileData($"ProxyInterfaceGenerator.{ClassName}.g.cs", $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
@@ -23,15 +23,14 @@ namespace ProxyInterfaceGenerator
public class {ClassName} : Attribute
{{
public Type Type {{ get; }}
public bool ProxyAll {{ get; }}
public bool ProxyBaseClasses {{ get; }}
public {ClassName}(Type type, bool proxyAll = false)
public {ClassName}(Type type, bool proxyBaseClasses = false)
{{
Type = type;
ProxyAll = proxyAll;
ProxyBaseClasses = proxyBaseClasses;
}}
}}
}}");
}
}
}
@@ -1,52 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using ProxyInterfaceSourceGenerator.Enums;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Model;
using ProxyInterfaceSourceGenerator.SyntaxReceiver;
using ProxyInterfaceSourceGenerator.Utils;
namespace ProxyInterfaceSourceGenerator.FileGenerators
namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
{
internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
public ProxyClassesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable)
{
public ProxyClassesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable)
}
public IEnumerable<FileData> GenerateFiles()
{
foreach (var ci in Context.CandidateInterfaces)
{
yield return GenerateFile(ci.Value);
}
}
public IEnumerable<FileData> GenerateFiles()
{
foreach (var ci in _context.CandidateInterfaces)
{
yield return GenerateFile(ci.Value);
}
}
private FileData GenerateFile(ProxyData pd)
{
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
var className = targetClassSymbol.Symbol.ResolveProxyClassName();
var constructorName = $"{targetClassSymbol.Symbol.Name}Proxy";
private FileData GenerateFile(ProxyData pd)
{
var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings);
var interfaceName = targetClassSymbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName);
var className = targetClassSymbol.ResolveProxyClassName();
var constructorName = $"{targetClassSymbol.Name}Proxy";
var file = new FileData(
$"{targetClassSymbol.Symbol.GetFileName()}Proxy.g.cs",
CreateProxyClassCode(pd.Namespace, targetClassSymbol, pd.ProxyBaseClasses, interfaceName, className, constructorName)
);
var file = new FileData(
$"{targetClassSymbol.GetFileName()}Proxy.g.cs",
CreateProxyClassCode(pd.Namespace, targetClassSymbol, interfaceName, className, constructorName)
);
return file;
}
// _context.GeneratedData.Add(new() { InterfaceName = interfaceName, ClassName = pd.ClassName, FileData = file });
return file;
}
private string CreateProxyClassCode(
string ns,
INamedTypeSymbol targetClassSymbol,
string interfaceName,
string className,
string constructorName) => $@"//----------------------------------------------------------------------------------------
private string CreateProxyClassCode(
string ns,
ClassSymbol targetClassSymbol,
bool proxyBaseClasses,
string interfaceName,
string className,
string constructorName) => $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
@@ -55,7 +52,7 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators
// </auto-generated>
//----------------------------------------------------------------------------------------
{(_supportsNullable ? "#nullable enable" : string.Empty)}
{(SupportsNullable ? "#nullable enable" : string.Empty)}
using System;
using AutoMapper;
@@ -63,13 +60,13 @@ namespace {ns}
{{
public class {className} : {interfaceName}
{{
public {targetClassSymbol} _Instance {{ get; }}
public {targetClassSymbol.Symbol} _Instance {{ get; }}
{GeneratePublicProperties(targetClassSymbol, false)}
{GeneratePublicProperties(targetClassSymbol, proxyBaseClasses)}
{GeneratePublicMethods(targetClassSymbol)}
{GeneratePublicMethods(targetClassSymbol, proxyBaseClasses)}
{GenerateEvents(targetClassSymbol)}
{GenerateEvents(targetClassSymbol, proxyBaseClasses)}
public {constructorName}({targetClassSymbol} instance)
{{
@@ -81,165 +78,164 @@ namespace {ns}
{GeneratePrivateAutoMapper()}
}}
}}
{(_supportsNullable ? "#nullable disable" : string.Empty)}";
private string GeneratePrivateAutoMapper()
{(SupportsNullable ? "#nullable disable" : string.Empty)}";
private string GeneratePrivateAutoMapper()
{
return Context.ReplacedTypes.Count == 0 ? string.Empty : " private readonly IMapper _mapper;";
}
private string GenerateMapperConfigurationForAutoMapper()
{
if (Context.ReplacedTypes.Count == 0)
{
return _context.ReplacedTypes.Count == 0 ? string.Empty : " private readonly IMapper _mapper;";
return string.Empty;
}
private string GenerateMapperConfigurationForAutoMapper()
var str = new StringBuilder();
str.AppendLine(" _mapper = new MapperConfiguration(cfg =>");
str.AppendLine(" {");
foreach (var replacedType in Context.ReplacedTypes)
{
if (_context.ReplacedTypes.Count == 0)
str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>();");
str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>();");
}
str.AppendLine(" }).CreateMapper();");
return str.ToString();
}
private string GeneratePublicProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses))
{
var type = GetPropertyType(property, out var isReplaced);
if (isReplaced)
{
return string.Empty;
str.AppendLine($" public {property.ToPropertyTextForClass(type)}");
}
var str = new StringBuilder();
str.AppendLine(" _mapper = new MapperConfiguration(cfg =>");
str.AppendLine(" {");
foreach (var replacedType in _context.ReplacedTypes)
else
{
str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>();");
str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>();");
str.AppendLine($" public {property.ToPropertyTextForClass()}");
}
str.AppendLine(" }).CreateMapper();");
return str.ToString();
str.AppendLine();
}
private string GeneratePublicProperties(INamedTypeSymbol targetClassSymbol, bool proxyAll)
{
var str = new StringBuilder();
return str.ToString();
}
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol))
private string GeneratePublicMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses))
{
var methodParameters = new List<string>();
var invokeParameters = new List<string>();
foreach (var ps in method.Parameters)
{
var type = GetPropertyType(property, out var isReplaced);
if (isReplaced)
var type = GetParameterType(ps, out _);
methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}");
invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_");
}
string returnTypeAsString = GetReplacedType(method.ReturnType, out var returnIsReplaced);
str.AppendLine($" public {returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()}");
str.AppendLine(" {");
foreach (var ps in method.Parameters)
{
string normalOrMap = $" = {ps.GetSanitizedName()}";
if (ps.RefKind == RefKind.Out)
{
str.AppendLine($" public {property.ToPropertyTextForClass(type)}");
normalOrMap = string.Empty;
}
else
{
str.AppendLine($" public {property.ToPropertyTextForClass()}");
var type = GetParameterType(ps, out var isReplaced);
if (isReplaced)
{
normalOrMap = $" = _mapper.Map<{ps.Type}>({ps.GetSanitizedName()})";
}
}
str.AppendLine();
str.AppendLine($" {ps.Type} {ps.GetSanitizedName()}_{normalOrMap};");
}
return str.ToString();
}
private string GeneratePublicMethods(INamedTypeSymbol targetClassSymbol)
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol))
{
var methodParameters = new List<string>();
var invokeParameters = new List<string>();
foreach (var ps in method.Parameters)
{
var type = GetParameterType(ps, out _);
methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}");
invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_");
}
string returnTypeAsString = GetReplacedType(method.ReturnType, out var returnIsReplaced);
str.AppendLine($" public {returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()}");
str.AppendLine(" {");
foreach (var ps in method.Parameters)
{
string normalOrMap = $" = {ps.GetSanitizedName()}";
if (ps.RefKind == RefKind.Out)
{
normalOrMap = string.Empty;
}
else
{
var type = GetParameterType(ps, out var isReplaced);
if (isReplaced)
{
normalOrMap = $" = _mapper.Map<{ps.Type}>({ps.GetSanitizedName()})";
}
}
str.AppendLine($" {ps.Type} {ps.GetSanitizedName()}_{normalOrMap};");
}
#pragma warning disable RS1024 // Compare symbols correctly
int hash = method.ReturnType.GetHashCode();
int hash = method.ReturnType.GetHashCode();
#pragma warning restore RS1024 // Compare symbols correctly
var alternateReturnVariableName = $"result_{Math.Abs(hash)}";
var alternateReturnVariableName = $"result_{Math.Abs(hash)}";
if (returnTypeAsString == "void")
if (returnTypeAsString == "void")
{
str.AppendLine($" _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
}
else
{
str.AppendLine($" var {alternateReturnVariableName} = _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
}
foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out))
{
string normalOrMap = $" = {ps.GetSanitizedName()}_";
if (ps.GetTypeEnum() == TypeEnum.Complex)
{
str.AppendLine($" _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
var type = GetParameterType(ps, out var isReplaced);
if (isReplaced)
{
normalOrMap = $" = _mapper.Map<{type}>({ps.GetSanitizedName()}_)";
}
}
str.AppendLine($" {ps.GetSanitizedName()}{normalOrMap};");
}
if (returnTypeAsString != "void")
{
if (returnIsReplaced)
{
str.AppendLine($" return _mapper.Map<{returnTypeAsString}>({alternateReturnVariableName});");
}
else
{
str.AppendLine($" var {alternateReturnVariableName} = _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});");
str.AppendLine($" return {alternateReturnVariableName};");
}
foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out))
{
string normalOrMap = $" = {ps.GetSanitizedName()}_";
if (ps.GetTypeEnum() == TypeEnum.Complex)
{
var type = GetParameterType(ps, out var isReplaced);
if (isReplaced)
{
normalOrMap = $" = _mapper.Map<{type}>({ps.GetSanitizedName()}_)";
}
}
str.AppendLine($" {ps.GetSanitizedName()}{normalOrMap};");
}
if (returnTypeAsString != "void")
{
if (returnIsReplaced)
{
str.AppendLine($" return _mapper.Map<{returnTypeAsString}>({alternateReturnVariableName});");
}
else
{
str.AppendLine($" return {alternateReturnVariableName};");
}
}
str.AppendLine(" }");
str.AppendLine();
}
return str.ToString();
str.AppendLine(" }");
str.AppendLine();
}
private string GenerateEvents(INamedTypeSymbol targetClassSymbol)
return str.ToString();
}
private string GenerateEvents(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses))
{
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol))
var name = @event.Key.GetSanitizedName();
var ps = @event.First().Parameters.First();
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
str.Append($" public event {type} {name} {{");
if (@event.Any(e => e.MethodKind == MethodKind.EventAdd))
{
var name = @event.Key.GetSanitizedName();
var ps = @event.First().Parameters.First();
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
str.Append($" public event {type} {name} {{");
if (@event.Any(e => e.MethodKind == MethodKind.EventAdd))
{
str.Append($" add {{ _Instance.{name} += value; }}");
}
if (@event.Any(e => e.MethodKind == MethodKind.EventRemove))
{
str.Append($" remove {{ _Instance.{name} -= value; }}");
}
str.AppendLine(" }");
str.AppendLine();
str.Append($" add {{ _Instance.{name} += value; }}");
}
if (@event.Any(e => e.MethodKind == MethodKind.EventRemove))
{
str.Append($" remove {{ _Instance.{name} -= value; }}");
}
return str.ToString();
str.AppendLine(" }");
str.AppendLine();
}
return str.ToString();
}
}
@@ -0,0 +1,11 @@
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Model;
internal record ClassSymbol(INamedTypeSymbol Symbol, List<INamedTypeSymbol> BaseTypes)
{
public override string ToString()
{
return Symbol.ToString();
}
}
@@ -1,4 +1,3 @@
using System;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -6,83 +5,95 @@ using Microsoft.CodeAnalysis.Text;
using ProxyInterfaceSourceGenerator.FileGenerators;
using ProxyInterfaceSourceGenerator.SyntaxReceiver;
namespace ProxyInterfaceSourceGenerator
namespace ProxyInterfaceSourceGenerator;
[Generator]
internal class ProxyInterfaceCodeGenerator : ISourceGenerator
{
[Generator]
internal class ProxyInterfaceCodeGenerator : ISourceGenerator
private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new ProxyAttributeGenerator();
public void Initialize(GeneratorInitializationContext context)
{
private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new ProxyAttributeGenerator();
//if (!System.Diagnostics.Debugger.IsAttached)
//{
// System.Diagnostics.Debugger.Launch();
//}
public void Initialize(GeneratorInitializationContext context)
context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
if (context.ParseOptions is not CSharpParseOptions csharpParseOptions)
{
//if (!System.Diagnostics.Debugger.IsAttached)
//{
// System.Diagnostics.Debugger.Launch();
//}
context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver());
throw new NotSupportedException("Only C# is supported.");
}
public void Execute(GeneratorExecutionContext context)
if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver)
{
if (context.ParseOptions is not CSharpParseOptions csharpParseOptions)
{
throw new NotSupportedException("Only C# is supported.");
}
return;
}
if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver)
{
return;
}
// https://github.com/reactiveui/refit/blob/main/InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
var supportsNullable = csharpParseOptions.LanguageVersion >= LanguageVersion.CSharp8;
// https://github.com/reactiveui/refit/blob/main/InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
var supportsNullable = csharpParseOptions.LanguageVersion >= LanguageVersion.CSharp8;
try
{
GenerateProxyAttribute(context, receiver);
GeneratePartialInterfaces(context, receiver, supportsNullable);
GenerateProxyClasses(context, receiver, supportsNullable);
}
private void GenerateProxyAttribute(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver)
catch (Exception exception)
{
var context = new Context
{
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
var attributeData = _proxyAttributeGenerator.GenerateFile();
context.GeneratorExecutionContext.AddSource(attributeData.FileName, SourceText.From(attributeData.Text, Encoding.UTF8));
GenerateError(context, exception);
}
}
private static void GeneratePartialInterfaces(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable)
private void GenerateError(GeneratorExecutionContext context, Exception exception)
{
var message = $"/*\r\n{nameof(ProxyInterfaceCodeGenerator)}\r\n\r\n{exception}\r\n*/";
context.AddSource("Error.g", SourceText.From(message, Encoding.UTF8));
}
private void GenerateProxyAttribute(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver)
{
var context = new Context
{
var context = new Context
{
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
var partialInterfacesGenerator = new PartialInterfacesGenerator(context, supportsNullable);
foreach (var data in partialInterfacesGenerator.GenerateFiles())
{
context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8));
}
var attributeData = _proxyAttributeGenerator.GenerateFile();
context.GeneratorExecutionContext.AddSource(attributeData.FileName, SourceText.From(attributeData.Text, Encoding.UTF8));
}
private static void GeneratePartialInterfaces(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable)
{
var context = new Context
{
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
var partialInterfacesGenerator = new PartialInterfacesGenerator(context, supportsNullable);
foreach (var data in partialInterfacesGenerator.GenerateFiles())
{
context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8));
}
}
private static void GenerateProxyClasses(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable)
private static void GenerateProxyClasses(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable)
{
var context = new Context
{
var context = new Context
{
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
GeneratorExecutionContext = ctx,
CandidateInterfaces = receiver.CandidateInterfaces
};
var proxyClassesGenerator = new ProxyClassesGenerator(context, supportsNullable);
foreach (var data in proxyClassesGenerator.GenerateFiles())
{
context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8));
}
var proxyClassesGenerator = new ProxyClassesGenerator(context, supportsNullable);
foreach (var data in proxyClassesGenerator.GenerateFiles())
{
context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8));
}
}
}
@@ -4,7 +4,7 @@
<Version>0.0.11</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<ProjectGuid>{12344228-91F4-4502-9595-39584E5ABB34}</ProjectGuid>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<Authors>Stef Heyenrath</Authors>
<Description></Description>
@@ -24,6 +24,7 @@
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
<DevelopmentDependency>true</DevelopmentDependency>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -31,7 +32,7 @@
</PropertyGroup>
<ItemGroup>
<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>
<None Include="../../PackageReadme.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
@@ -1,8 +1,11 @@
using System.Collections.Generic;
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver;
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver
{
internal record ProxyData(string Namespace, string InterfaceName, string RawTypeName, string TypeName, List<string> Usings, bool ProxyAll)
{
}
}
internal record ProxyData
(
string Namespace,
string InterfaceName,
string RawTypeName,
string TypeName,
List<string> Usings,
bool ProxyBaseClasses
);
@@ -1,85 +1,91 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ProxyInterfaceSourceGenerator.Extensions;
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver;
internal class ProxySyntaxReceiver : ISyntaxReceiver
{
internal class ProxySyntaxReceiver : ISyntaxReceiver
private static readonly string[] Modifiers = { "public", "partial" };
public IDictionary<InterfaceDeclarationSyntax, ProxyData> CandidateInterfaces { get; } = new Dictionary<InterfaceDeclarationSyntax, ProxyData>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
private static readonly string[] Modifiers = new[] { "public", "partial" };
public IDictionary<InterfaceDeclarationSyntax, ProxyData> CandidateInterfaces { get; } = new Dictionary<InterfaceDeclarationSyntax, ProxyData>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data))
{
if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data))
{
CandidateInterfaces.Add(interfaceDeclarationSyntax, data);
}
}
private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data)
{
data = null;
if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Count() != 0)
{
// InterfaceDeclarationSyntax should be "public" and "partial"
return false;
}
var attributeLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => a.Name.ToString().Equals("ProxyInterfaceGenerator.Proxy")));
if (attributeLists is null)
{
return false;
}
var argumentList = attributeLists.Attributes.FirstOrDefault()?.ArgumentList;
if (argumentList is null)
{
return false;
}
var usings = new List<string>();
string ns = string.Empty;
if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out NamespaceDeclarationSyntax? namespaceDeclarationSyntax))
{
ns = namespaceDeclarationSyntax.Name.ToString();
usings.Add(ns);
}
if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out CompilationUnitSyntax? cc))
{
foreach (var @using in cc.Usings)
{
usings.Add(@using.Name.ToString());
}
}
string rawTypeName = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type.ToString();
data = new
(
ns,
interfaceDeclarationSyntax.Identifier.ToString(),
rawTypeName,
ConvertTypeName(rawTypeName),
usings,
false //bool.Parse(argumentList.Arguments[1].Expression.GetText().ToString())
);
return true;
}
private static string ConvertTypeName(string typeName)
{
return !(typeName.Contains('<') && typeName.Contains('>')) ?
typeName :
$"{typeName.Replace("<", string.Empty).Replace(">", string.Empty).Replace(",", string.Empty).Trim()}`{typeName.Count(c => c == ',') + 1}";
CandidateInterfaces.Add(interfaceDeclarationSyntax, data);
}
}
private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data)
{
data = null;
if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Count() != 0)
{
// InterfaceDeclarationSyntax should be "public" and "partial"
return false;
}
var attributeLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => a.Name.ToString().Equals("ProxyInterfaceGenerator.Proxy")));
if (attributeLists is null)
{
return false;
}
var argumentList = attributeLists.Attributes.FirstOrDefault()?.ArgumentList;
if (argumentList is null)
{
return false;
}
var usings = new List<string>();
string ns = string.Empty;
if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out NamespaceDeclarationSyntax? namespaceDeclarationSyntax))
{
ns = namespaceDeclarationSyntax.Name.ToString();
usings.Add(ns);
}
if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out CompilationUnitSyntax? cc))
{
foreach (var @using in cc.Usings)
{
usings.Add(@using.Name.ToString());
}
}
string rawTypeName = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type.ToString();
bool proxyAllClasses;
try
{
proxyAllClasses = bool.Parse(((LiteralExpressionSyntax)argumentList.Arguments[1].Expression).ToString());
}
catch
{
proxyAllClasses = false;
}
data = new
(
ns,
interfaceDeclarationSyntax.Identifier.ToString(),
rawTypeName,
ConvertTypeName(rawTypeName),
usings,
proxyAllClasses
);
return true;
}
private static string ConvertTypeName(string typeName)
{
return !(typeName.Contains('<') && typeName.Contains('>')) ?
typeName :
$"{typeName.Replace("<", string.Empty).Replace(">", string.Empty).Replace(",", string.Empty).Trim()}`{typeName.Count(c => c == ',') + 1}";
}
}
@@ -1,87 +1,105 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;
using ProxyInterfaceSourceGenerator.Model;
namespace ProxyInterfaceSourceGenerator.Utils
namespace ProxyInterfaceSourceGenerator.Utils;
internal static class MemberHelper
{
internal static class MemberHelper
private static readonly string[] ExcludedMethods = { "ToString", "GetHashCode" };
public static IEnumerable<IPropertySymbol> GetPublicProperties(
ClassSymbol classSymbol,
bool proxyBaseClasses,
params Func<IPropertySymbol, bool>[] filters)
{
private static string[] _excludedMethods = new string[] { "ToString", "GetHashCode" };
public static IEnumerable<IPropertySymbol> GetPublicProperties(INamedTypeSymbol classSymbol, params Func<IPropertySymbol, bool>[] filters)
var allFilters = new List<Func<IPropertySymbol, bool>>(filters)
{
var allFilters = new List<Func<IPropertySymbol, bool>>(filters);
allFilters.Add(p => p.Kind == SymbolKind.Property);
p => p.Kind == SymbolKind.Property
};
return GetPublicMembers(classSymbol, allFilters.ToArray());
return GetPublicMembers(classSymbol, proxyBaseClasses, allFilters.ToArray());
}
public static IEnumerable<IMethodSymbol> GetPublicMethods(
ClassSymbol classSymbol,
bool proxyBaseClasses,
Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
filter = _ => true;
}
public static IEnumerable<IMethodSymbol> GetPublicMethods(INamedTypeSymbol classSymbol, Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
filter = _ => true;
}
return GetPublicMembers(classSymbol,
proxyBaseClasses,
m => m.Kind == SymbolKind.Method,
m => m.MethodKind == MethodKind.Ordinary,
m => !ExcludedMethods.Contains(m.Name),
filter);
}
return GetPublicMembers(classSymbol,
m => m.Kind == SymbolKind.Method,
m => m.MethodKind == MethodKind.Ordinary,
m => !_excludedMethods.Contains(m.Name),
filter);
public static IEnumerable<IGrouping<ISymbol, IMethodSymbol>> GetPublicEvents(
ClassSymbol classSymbol,
bool proxyBaseClasses,
Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
filter = _ => true;
}
public static IEnumerable<IGrouping<ISymbol, IMethodSymbol>> GetPublicEvents(INamedTypeSymbol classSymbol, Func<IMethodSymbol, bool>? filter = null)
{
if (filter is null)
{
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,
return GetPublicMembers(classSymbol,
proxyBaseClasses,
m => m.MethodKind == MethodKind.EventAdd || m.MethodKind == MethodKind.EventRemove/* || m.MethodKind == MethodKind.EventRaise*/,
filter)
.GroupBy(e => e.AssociatedSymbol);
.GroupBy(e => e.AssociatedSymbol);
#pragma warning restore RS1024 // Compare symbols correctly
#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type.
}
// TODO : do we need also to check for "SanitizedName()" here?
private static IEnumerable<T> GetPublicMembers<T>(
ClassSymbol classSymbol,
bool proxyBaseClasses,
params Func<T, bool>[] filters) where T : ISymbol
{
var membersQuery = classSymbol.Symbol.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public);
foreach (var filter in filters)
{
membersQuery = membersQuery.Where(filter);
}
// TODO : do we need also to check for "SanitizedName()" here?
private static IEnumerable<T> GetPublicMembers<T>(INamedTypeSymbol classSymbol, params Func<T, bool>[] filters) where T : ISymbol
var ownMembers = membersQuery.ToList();
var ownPropertyNames = ownMembers.Select(x => x.Name);
if (!proxyBaseClasses)
{
var membersQuery = classSymbol.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public);
return ownMembers;
}
var allMembers = ownMembers;
var baseType = classSymbol.Symbol.BaseType;
while (baseType != null && baseType.SpecialType != SpecialType.System_Object)
{
var baseMembers = baseType.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public)
.Where(x => !ownPropertyNames.Contains(x.Name));
foreach (var filter in filters)
{
membersQuery = membersQuery.Where(filter);
baseMembers = baseMembers.Where(filter);
}
var members = membersQuery.ToList();
allMembers.AddRange(baseMembers);
var propertyNames = membersQuery.Select(x => x.Name);
var baseType = classSymbol.BaseType;
while (baseType != null)
{
var baseMembers = baseType.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public)
.Where(x => !propertyNames.Contains(x.Name));
foreach (var filter in filters)
{
baseMembers = baseMembers.Where(filter);
}
members.AddRange(baseMembers);
baseType = baseType.BaseType;
}
return membersQuery;
baseType = baseType.BaseType;
}
return allMembers;
}
}