Use fully qualified names to reduce namespace clashes. (#68)
* Use fully qualified names to reduce namespace clashes. * Small code style fixes. * Make properties in ProxyData immutable. * Remove clutter by joining TrimEnd() to previous line. * Introduce Extension method to retrieve ITypeSymbol FullyQualifiedDisplayString * Fixed some code issues. * Fixed method call in BaseGenerator * Refactor metadata name
This commit is contained in:
@@ -23,14 +23,6 @@ internal static class NamedTypeSymbolExtensions
|
||||
return types;
|
||||
}
|
||||
|
||||
public static string GetFileName(this INamedTypeSymbol namedTypeSymbol)
|
||||
{
|
||||
var typeName = namedTypeSymbol.GetFullType();
|
||||
return !(typeName.Contains('<') && typeName.Contains('>')) ?
|
||||
typeName :
|
||||
$"{typeName.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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using ProxyInterfaceSourceGenerator.Enums;
|
||||
using ProxyInterfaceSourceGenerator.FileGenerators;
|
||||
|
||||
namespace ProxyInterfaceSourceGenerator.Extensions;
|
||||
|
||||
@@ -20,7 +21,8 @@ internal static class PropertySymbolExtensions
|
||||
var get = getIsPublic ? "get; " : string.Empty;
|
||||
var set = setIsPublic ? "set; " : string.Empty;
|
||||
|
||||
var type = !string.IsNullOrEmpty(overrideType) ? overrideType : $"{property.Type}";
|
||||
var type = !string.IsNullOrEmpty(overrideType) ? overrideType
|
||||
: BaseGenerator.FixType(property.Type.ToFullyQualifiedDisplayString(), property.NullableAnnotation);
|
||||
|
||||
return (type!, property.GetSanitizedName(), $"{{ {get}{set}}}");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using System.Text;
|
||||
|
||||
namespace ProxyInterfaceSourceGenerator.Extensions;
|
||||
|
||||
@@ -12,13 +13,6 @@ internal static class SymbolExtensions
|
||||
"System.Runtime.CompilerServices.AsyncStateMachineAttribute"
|
||||
};
|
||||
|
||||
public static string GetAttributesPrefix(this ISymbol symbol)
|
||||
{
|
||||
var attributes = symbol.GetAttributesAsList();
|
||||
|
||||
return attributes.Any() ? $"{string.Join(" ", attributes)} " : string.Empty;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<string> GetAttributesAsList(this ISymbol symbol)
|
||||
{
|
||||
return symbol
|
||||
@@ -28,12 +22,56 @@ internal static class SymbolExtensions
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static bool IsPublic(this ISymbol? symbol) =>
|
||||
symbol is { DeclaredAccessibility: Accessibility.Public };
|
||||
public static string GetAttributesPrefix(this ISymbol symbol)
|
||||
{
|
||||
var attributes = symbol.GetAttributesAsList();
|
||||
|
||||
return attributes.Any() ? $"{string.Join(" ", attributes)} " : string.Empty;
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn
|
||||
public static string GetFullMetadataName(this ISymbol s)
|
||||
{
|
||||
if (s == null || IsRootNamespace(s))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(s.MetadataName);
|
||||
var last = s;
|
||||
|
||||
s = s.ContainingSymbol;
|
||||
|
||||
while (!IsRootNamespace(s))
|
||||
{
|
||||
if (s is ITypeSymbol && last is ITypeSymbol)
|
||||
{
|
||||
sb.Insert(0, '+');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Insert(0, '.');
|
||||
}
|
||||
|
||||
sb.Insert(0, s.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||
s = s.ContainingSymbol;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetSanitizedName(this ISymbol symbol) =>
|
||||
symbol.IsKeywordOrReserved() ? $"@{symbol.Name}" : symbol.Name;
|
||||
|
||||
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;
|
||||
public static bool IsPublic(this ISymbol? symbol) =>
|
||||
symbol is { DeclaredAccessibility: Accessibility.Public };
|
||||
|
||||
private static bool IsRootNamespace(ISymbol symbol)
|
||||
{
|
||||
INamespaceSymbol s = null;
|
||||
return ((s = symbol as INamespaceSymbol) != null) && s.IsGlobalNamespace;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ internal static class SyntaxNodeExtensions
|
||||
// We have a namespace. Use that as the type
|
||||
nameSpace = namespaceParent.Name.ToString();
|
||||
|
||||
// Keep moving "out" of the namespace declarations until we
|
||||
// Keep moving "out" of the namespace declarations until we
|
||||
// run out of nested namespace declarations
|
||||
while (true)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,11 @@ internal static class TypeSymbolExtensions
|
||||
public static bool IsString(this ITypeSymbol ts) =>
|
||||
ts.ToString().ToLowerInvariant() == "string" || ts.ToString().ToLowerInvariant() == "string?";
|
||||
|
||||
public static string ToFullyQualifiedDisplayString(this ITypeSymbol property)
|
||||
{
|
||||
return property.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
}
|
||||
|
||||
internal static bool IsClass(this ITypeSymbol ts) =>
|
||||
ts.IsReferenceType && ts.TypeKind == TypeKind.Class;
|
||||
}
|
||||
@@ -11,8 +11,6 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators;
|
||||
|
||||
internal abstract class BaseGenerator
|
||||
{
|
||||
private const string Star = "*";
|
||||
|
||||
protected readonly Context Context;
|
||||
protected readonly bool SupportsNullable;
|
||||
|
||||
@@ -34,25 +32,8 @@ internal abstract class BaseGenerator
|
||||
|
||||
protected bool TryFindProxyDataByTypeName(string type, [NotNullWhen(true)] out ProxyData? proxyData)
|
||||
{
|
||||
proxyData = Context.Candidates.Values.FirstOrDefault(x => x.FullRawTypeName == type);
|
||||
if (proxyData != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var ci in Context.Candidates.Values)
|
||||
{
|
||||
foreach (var u in ci.Usings)
|
||||
{
|
||||
if ($"{u}.{ci.FullRawTypeName}" == type)
|
||||
{
|
||||
proxyData = ci;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
proxyData = Context.Candidates.Values.FirstOrDefault(x => x.FullQualifiedTypeName == type);
|
||||
return proxyData != null;
|
||||
}
|
||||
|
||||
protected string GetWhereStatementFromMethod(IMethodSymbol method)
|
||||
@@ -128,7 +109,7 @@ internal abstract class BaseGenerator
|
||||
constraints.Add("new()");
|
||||
}
|
||||
|
||||
if (constraints.Any())
|
||||
if (constraints.Count > 0)
|
||||
{
|
||||
constraint = new(typeParameterSymbol.Name, constraints);
|
||||
return true;
|
||||
@@ -138,11 +119,21 @@ internal abstract class BaseGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
internal readonly SymbolDisplayFormat NullableDisplayFormat = new SymbolDisplayFormat(
|
||||
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
|
||||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
||||
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
|
||||
miscellaneousOptions:
|
||||
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
|
||||
SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
|
||||
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
|
||||
|
||||
protected string GetReplacedTypeAsString(ITypeSymbol typeSymbol, out bool isReplaced)
|
||||
{
|
||||
isReplaced = false;
|
||||
|
||||
var typeSymbolAsString = typeSymbol.ToString();
|
||||
var typeSymbolAsString = typeSymbol.ToFullyQualifiedDisplayString();
|
||||
var nullableTypeSymbolAsString = typeSymbol.ToDisplayString(NullableFlowState.None, NullableDisplayFormat);
|
||||
|
||||
if (TryFindProxyDataByTypeName(typeSymbolAsString, out var existing))
|
||||
{
|
||||
@@ -152,7 +143,7 @@ internal abstract class BaseGenerator
|
||||
}
|
||||
|
||||
isReplaced = true;
|
||||
return FixType(existing.FullInterfaceName);
|
||||
return FixType(existing.FullInterfaceName, typeSymbol.NullableAnnotation);
|
||||
}
|
||||
|
||||
ITypeSymbol[] typeArguments;
|
||||
@@ -166,13 +157,13 @@ internal abstract class BaseGenerator
|
||||
}
|
||||
else
|
||||
{
|
||||
return FixType(typeSymbolAsString);
|
||||
return FixType(typeSymbolAsString, typeSymbol.NullableAnnotation);
|
||||
}
|
||||
|
||||
var propertyTypeAsStringToBeModified = typeSymbolAsString;
|
||||
var propertyTypeAsStringToBeModified = nullableTypeSymbolAsString;
|
||||
foreach (var typeArgument in typeArguments)
|
||||
{
|
||||
var typeArgumentAsString = typeArgument.ToString();
|
||||
var typeArgumentAsString = typeArgument.ToFullyQualifiedDisplayString();
|
||||
|
||||
if (TryFindProxyDataByTypeName(typeArgumentAsString, out var existingTypeArgument))
|
||||
{
|
||||
@@ -187,15 +178,21 @@ internal abstract class BaseGenerator
|
||||
}
|
||||
}
|
||||
|
||||
return FixType(propertyTypeAsStringToBeModified);
|
||||
return FixType(propertyTypeAsStringToBeModified, typeSymbol.NullableAnnotation);
|
||||
}
|
||||
|
||||
protected bool TryGetNamedTypeSymbolByFullName(TypeKind kind, string name, IEnumerable<string> usings, [NotNullWhen(true)] out ClassSymbol? classSymbol)
|
||||
{
|
||||
classSymbol = default;
|
||||
const string globalPrefix = "global::";
|
||||
if (name.StartsWith(globalPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
name = name.Substring(globalPrefix.Length);
|
||||
}
|
||||
|
||||
// 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 && symbol.TypeKind == kind)
|
||||
{
|
||||
classSymbol = new ClassSymbol(symbol, symbol.GetBaseTypes(), symbol.AllInterfaces.ToList());
|
||||
@@ -223,7 +220,14 @@ internal abstract class BaseGenerator
|
||||
string? type = null;
|
||||
if (includeType)
|
||||
{
|
||||
type = parameterSymbol.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(parameterSymbol, out _) : parameterSymbol.Type.ToString();
|
||||
if (parameterSymbol.GetTypeEnum() == TypeEnum.Complex)
|
||||
{
|
||||
type = GetParameterType(parameterSymbol, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = FixType(parameterSymbol.Type.ToFullyQualifiedDisplayString(), parameterSymbol.NullableAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
methodParameters.Add(MethodParameterBuilder.Build(parameterSymbol, type));
|
||||
@@ -237,36 +241,22 @@ internal abstract class BaseGenerator
|
||||
var extendsProxyClasses = new List<ProxyData>();
|
||||
foreach (var baseType in targetClassSymbol.BaseTypes)
|
||||
{
|
||||
var candidate = Context.Candidates.Values.FirstOrDefault(ci => ci.FullRawTypeName == baseType.ToString());
|
||||
var candidate = Context.Candidates.Values.FirstOrDefault(ci => ci.FullQualifiedTypeName == baseType.ToFullyQualifiedDisplayString());
|
||||
if (candidate is not null)
|
||||
{
|
||||
extendsProxyClasses.Add(candidate);
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to find with usings
|
||||
foreach (var @using in proxyData.Usings)
|
||||
{
|
||||
candidate = Context.Candidates.Values.FirstOrDefault(ci => $"{@using}.{ci.FullRawTypeName}" == baseType.ToString());
|
||||
if (candidate is not null)
|
||||
{
|
||||
// Update the FullRawTypeName
|
||||
candidate.FullRawTypeName = $"{@using}.{candidate.FullRawTypeName}";
|
||||
|
||||
extendsProxyClasses.Add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return extendsProxyClasses;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issue 54
|
||||
/// double[*,*] --> double[,]
|
||||
/// </summary>
|
||||
protected static string FixType(string type)
|
||||
internal static string FixType(string type, NullableAnnotation nullableAnnotation)
|
||||
{
|
||||
return type.Replace(Star, string.Empty);
|
||||
if (nullableAnnotation == NullableAnnotation.Annotated && !type.EndsWith("?", StringComparison.Ordinal))
|
||||
{
|
||||
return $"{type}?";
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullTypeName, pd.Usings, out var targetClassSymbol))
|
||||
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullMetadataTypeName, pd.Usings, out var targetClassSymbol))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
|
||||
var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints(targetClassSymbol.Symbol, pd.ShortInterfaceName);
|
||||
|
||||
fileData = new FileData(
|
||||
$"{sourceInterfaceSymbol.Symbol.GetFileName()}.g.cs",
|
||||
$"{sourceInterfaceSymbol.Symbol.GetFullMetadataName()}.g.cs",
|
||||
CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd)
|
||||
);
|
||||
|
||||
@@ -60,10 +60,13 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
|
||||
{
|
||||
var extendsProxyClasses = GetExtendsProxyData(proxyData, classSymbol);
|
||||
ImplementedInterfaces = classSymbol.Symbol.ResolveImplementedInterfaces(proxyData.ProxyBaseClasses);
|
||||
var implementedInterfacesNames = ImplementedInterfaces.Select(i => i.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat));
|
||||
var implementedInterfacesNames = ImplementedInterfaces.Select(i => i.ToFullyQualifiedDisplayString());
|
||||
var implements = implementedInterfacesNames.Any() ? $" : {string.Join(", ", implementedInterfacesNames)}" : string.Empty;
|
||||
var @new = extendsProxyClasses.Any() ? "new " : string.Empty;
|
||||
var (namespaceStart, namespaceEnd) = NamespaceBuilder.Build(ns);
|
||||
var events = GenerateEvents(classSymbol, proxyData.ProxyBaseClasses);
|
||||
var properties = GenerateProperties(classSymbol, proxyData.ProxyBaseClasses);
|
||||
var methods = GenerateMethods(classSymbol, proxyData.ProxyBaseClasses).TrimEnd();
|
||||
|
||||
return $@"//----------------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
@@ -80,13 +83,11 @@ using System;
|
||||
{namespaceStart}
|
||||
public partial interface {interfaceName}{implements}
|
||||
{{
|
||||
{@new}{classSymbol.Symbol} _Instance {{ get; }}
|
||||
{@new}{classSymbol} _Instance {{ get; }}
|
||||
|
||||
{GenerateProperties(classSymbol, proxyData.ProxyBaseClasses)}
|
||||
|
||||
{GenerateMethods(classSymbol, proxyData.ProxyBaseClasses)}
|
||||
|
||||
{GenerateEvents(classSymbol, proxyData.ProxyBaseClasses)}
|
||||
{events +
|
||||
properties +
|
||||
methods}
|
||||
}}
|
||||
{namespaceEnd}
|
||||
{SupportsNullable.IIf("#nullable restore")}";
|
||||
@@ -137,7 +138,6 @@ using System;
|
||||
str.AppendLine($" {getterSetter.Value.PropertyType} {propertyName} {getterSetter.Value.GetSet}");
|
||||
str.AppendLine();
|
||||
}
|
||||
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ internal partial class ProxyClassesGenerator
|
||||
foreach (var replacedType in Context.ReplacedTypes)
|
||||
{
|
||||
TryFindProxyDataByTypeName(replacedType.Key, out var fullTypeName);
|
||||
var classNameProxy = $"{fullTypeName!.Namespace}.{fullTypeName.ShortTypeName}Proxy";
|
||||
var classNameProxy = $"{fullTypeName!.NamespaceDot}{fullTypeName.ShortMetadataName}Proxy";
|
||||
|
||||
var instance = $"instance{(replacedType.Key + replacedType.Value).GetDeterministicHashCodeAsString()}";
|
||||
var proxy = $"proxy{(replacedType.Value + replacedType.Key).GetDeterministicHashCodeAsString()}";
|
||||
|
||||
@@ -12,7 +12,7 @@ internal partial class ProxyClassesGenerator
|
||||
foreach (var replacedType in Context.ReplacedTypes)
|
||||
{
|
||||
TryFindProxyDataByTypeName(replacedType.Key, out var fullTypeName);
|
||||
var classNameProxy = $"{fullTypeName!.Namespace}.{fullTypeName.ShortTypeName}Proxy";
|
||||
var classNameProxy = $"global::{fullTypeName!.NamespaceDot}{fullTypeName!.ShortMetadataName}Proxy";
|
||||
|
||||
var instance = $"instance{(replacedType.Key + replacedType.Value).GetDeterministicHashCodeAsString()}";
|
||||
var proxy = $"proxy{(replacedType.Value + replacedType.Key).GetDeterministicHashCodeAsString()}";
|
||||
|
||||
@@ -32,19 +32,19 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
|
||||
{
|
||||
fileData = default;
|
||||
|
||||
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullTypeName, pd.Usings, out var targetClassSymbol))
|
||||
if (!TryGetNamedTypeSymbolByFullName(TypeKind.Class, pd.FullMetadataTypeName, pd.Usings, out var targetClassSymbol))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints(targetClassSymbol.Symbol, pd.ShortInterfaceName);
|
||||
var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints(targetClassSymbol.Symbol, pd.FullInterfaceName);
|
||||
var className = targetClassSymbol.Symbol.ResolveProxyClassName();
|
||||
var constructorName = $"{targetClassSymbol.Symbol.Name}Proxy";
|
||||
|
||||
var extendsProxyClasses = GetExtendsProxyData(pd, targetClassSymbol);
|
||||
|
||||
fileData = new FileData(
|
||||
$"{targetClassSymbol.Symbol.GetFileName()}Proxy.g.cs",
|
||||
$"{targetClassSymbol.Symbol.GetFullMetadataName()}Proxy.g.cs",
|
||||
CreateProxyClassCode(pd, targetClassSymbol, extendsProxyClasses, interfaceName, className, constructorName)
|
||||
);
|
||||
|
||||
@@ -68,11 +68,11 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
|
||||
|
||||
if (firstExtends is not null)
|
||||
{
|
||||
extends = $"{firstExtends.Namespace}.{firstExtends.ShortTypeName}Proxy, ";
|
||||
extends = $"global::{firstExtends.NamespaceDot}{firstExtends.ShortMetadataName}Proxy, ";
|
||||
@base = " : base(instance)";
|
||||
@new = "new ";
|
||||
instanceBaseDefinition = $"public {firstExtends.FullRawTypeName} _Instance{firstExtends.FullRawTypeName.GetLastPart()} {{ get; }}";
|
||||
instanceBaseSetter = $"_Instance{firstExtends.FullRawTypeName.GetLastPart()} = instance;";
|
||||
instanceBaseDefinition = $"public {firstExtends.FullQualifiedTypeName} _Instance{firstExtends.FullQualifiedTypeName.GetLastPart()} {{ get; }}";
|
||||
instanceBaseSetter = $"_Instance{firstExtends.FullQualifiedTypeName.GetLastPart()} = instance;";
|
||||
}
|
||||
|
||||
var @abstract = string.Empty; // targetClassSymbol.Symbol.IsAbstract ? "abstract " : string.Empty;
|
||||
@@ -106,17 +106,12 @@ using System;
|
||||
{namespaceStart}
|
||||
{accessibility} {@abstract}partial class {className} : {extends}{interfaceName}
|
||||
{{
|
||||
public {@new}{targetClassSymbol.Symbol} _Instance {{ get; }}
|
||||
public {@new}{targetClassSymbol} _Instance {{ get; }}
|
||||
{instanceBaseDefinition}
|
||||
|
||||
{properties}
|
||||
|
||||
{methods}
|
||||
|
||||
{events}
|
||||
|
||||
{operators}
|
||||
|
||||
{events +
|
||||
properties +
|
||||
methods +
|
||||
operators}
|
||||
public {constructorName}({targetClassSymbol} instance){@base}
|
||||
{{
|
||||
_Instance = instance;
|
||||
@@ -244,7 +239,7 @@ using System;
|
||||
|
||||
foreach (var ps in method.Parameters.Where(p => !p.IsRef()))
|
||||
{
|
||||
var type = FixType(ps.Type.ToString());
|
||||
var type = FixType(ps.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), ps.Type.NullableAnnotation);
|
||||
string normalOrMap = $" = {ps.GetSanitizedName()}";
|
||||
if (ps.RefKind == RefKind.Out)
|
||||
{
|
||||
@@ -265,7 +260,7 @@ using System;
|
||||
var methodName = method.GetMethodNameWithOptionalTypeParameters();
|
||||
var alternateReturnVariableName = $"result_{methodName.GetDeterministicHashCodeAsString()}";
|
||||
|
||||
string instance = !method.IsStatic ? "_Instance" : $"{targetClassSymbol.Symbol}";
|
||||
string instance = method.IsStatic ? targetClassSymbol.Symbol.ToFullyQualifiedDisplayString() : "_Instance";
|
||||
|
||||
if (returnTypeAsString == "void")
|
||||
{
|
||||
|
||||
@@ -6,6 +6,6 @@ internal record ClassSymbol(INamedTypeSymbol Symbol, List<INamedTypeSymbol> Base
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Symbol.ToString();
|
||||
return Symbol.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,46 @@ namespace ProxyInterfaceSourceGenerator.Models;
|
||||
|
||||
internal class ProxyData
|
||||
{
|
||||
public string Namespace { get; init; }
|
||||
public ProxyData(string @namespace,
|
||||
string namespaceDot,
|
||||
string shortInterfaceName,
|
||||
string fullInterfaceName,
|
||||
string fullQualifiedTypeName,
|
||||
string shortMetadataTypeName,
|
||||
string fullMetadataTypeName,
|
||||
List<string> usings,
|
||||
bool proxyBaseClasses,
|
||||
ProxyClassAccessibility accessibility)
|
||||
{
|
||||
Namespace = @namespace ?? throw new ArgumentNullException(nameof(@namespace));
|
||||
NamespaceDot = namespaceDot ?? throw new ArgumentNullException(nameof(namespaceDot));
|
||||
ShortInterfaceName = shortInterfaceName ?? throw new ArgumentNullException(nameof(shortInterfaceName));
|
||||
FullInterfaceName = fullInterfaceName ?? throw new ArgumentNullException(nameof(fullInterfaceName));
|
||||
FullQualifiedTypeName = fullQualifiedTypeName ?? throw new ArgumentNullException(nameof(fullQualifiedTypeName));
|
||||
ShortMetadataName = shortMetadataTypeName ?? throw new ArgumentNullException(nameof(shortMetadataTypeName));
|
||||
FullMetadataTypeName = fullMetadataTypeName ?? throw new ArgumentNullException(nameof(fullMetadataTypeName));
|
||||
Usings = usings ?? throw new ArgumentNullException(nameof(usings));
|
||||
ProxyBaseClasses = proxyBaseClasses;
|
||||
Accessibility = accessibility;
|
||||
}
|
||||
|
||||
public string ShortInterfaceName { get; init; }
|
||||
public string Namespace { get; }
|
||||
|
||||
public string FullInterfaceName { get; init; }
|
||||
public string NamespaceDot { get; }
|
||||
|
||||
public string FullRawTypeName { get; set; }
|
||||
public string ShortInterfaceName { get; }
|
||||
|
||||
public string ShortTypeName { get; init; }
|
||||
public string FullInterfaceName { get; }
|
||||
|
||||
public string FullTypeName { get; init; }
|
||||
public string FullQualifiedTypeName { get; }
|
||||
|
||||
public List<string> Usings { get; init; }
|
||||
public string ShortMetadataName { get; }
|
||||
|
||||
public bool ProxyBaseClasses { get; init; }
|
||||
public string FullMetadataTypeName { get; }
|
||||
|
||||
public ProxyClassAccessibility Accessibility { get; init; }
|
||||
public List<string> Usings { get; }
|
||||
|
||||
public bool ProxyBaseClasses { get; }
|
||||
|
||||
public ProxyClassAccessibility Accessibility { get; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ProxyInterfaceConsumer": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "..\\..\\src-examples\\ProxyInterfaceConsumer\\ProxyInterfaceConsumer.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal
|
||||
#endif
|
||||
class ProxyInterfaceCodeGenerator : ISourceGenerator
|
||||
{
|
||||
private readonly ExtraFilesGenerator _proxyAttributeGenerator = new ();
|
||||
private readonly ExtraFilesGenerator _proxyAttributeGenerator = new();
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
@@ -26,7 +26,6 @@ class ProxyInterfaceCodeGenerator : ISourceGenerator
|
||||
System.Diagnostics.Debugger.Launch();
|
||||
}
|
||||
#endif
|
||||
|
||||
context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver());
|
||||
}
|
||||
|
||||
@@ -39,7 +38,7 @@ class ProxyInterfaceCodeGenerator : ISourceGenerator
|
||||
throw new NotSupportedException("Only C# is supported.");
|
||||
}
|
||||
|
||||
if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver)
|
||||
if (context.SyntaxContextReceiver is not ProxySyntaxReceiver receiver)
|
||||
{
|
||||
throw new NotSupportedException($"Only {nameof(ProxySyntaxReceiver)} is supported.");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Configurations>Debug;Release;DebugAttach</Configurations>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
@@ -41,11 +43,11 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Nullable" Version="1.3.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using ProxyInterfaceSourceGenerator.Extensions;
|
||||
using ProxyInterfaceSourceGenerator.Types;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver;
|
||||
|
||||
internal static class AttributeArgumentListParser
|
||||
{
|
||||
public static ProxyInterfaceGeneratorAttributeArguments ParseAttributeArguments(AttributeArgumentListSyntax? argumentList)
|
||||
public static ProxyInterfaceGeneratorAttributeArguments ParseAttributeArguments(AttributeArgumentListSyntax? argumentList, SemanticModel semanticModel)
|
||||
{
|
||||
if (argumentList is null || argumentList.Arguments.Count is < 1 or > 3)
|
||||
{
|
||||
@@ -15,9 +18,9 @@ internal static class AttributeArgumentListParser
|
||||
}
|
||||
|
||||
ProxyInterfaceGeneratorAttributeArguments result;
|
||||
if (TryParseAsType(argumentList.Arguments[0].Expression, out var rawTypeValue))
|
||||
if (TryParseAsType(argumentList.Arguments[0].Expression, semanticModel, out var fullyQualifiedDisplayString, out var metadataName))
|
||||
{
|
||||
result = new ProxyInterfaceGeneratorAttributeArguments(rawTypeValue);
|
||||
result = new ProxyInterfaceGeneratorAttributeArguments(fullyQualifiedDisplayString, metadataName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -54,13 +57,18 @@ internal static class AttributeArgumentListParser
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryParseAsType(ExpressionSyntax expressionSyntax, [NotNullWhen(true)] out string? rawTypeName)
|
||||
private static bool TryParseAsType(ExpressionSyntax expressionSyntax, SemanticModel semanticModel, [NotNullWhen(true)] out string? fullyQualifiedDisplayString, [NotNullWhen(true)] out string? metadataName)
|
||||
{
|
||||
rawTypeName = null;
|
||||
fullyQualifiedDisplayString = null;
|
||||
metadataName = null;
|
||||
|
||||
if (expressionSyntax is TypeOfExpressionSyntax typeOfExpressionSyntax)
|
||||
{
|
||||
rawTypeName = typeOfExpressionSyntax.Type.ToString();
|
||||
var typeInfo = semanticModel.GetTypeInfo(typeOfExpressionSyntax.Type);
|
||||
var typeSymbol = typeInfo.Type!;
|
||||
metadataName = typeSymbol.GetFullMetadataName();
|
||||
fullyQualifiedDisplayString = typeSymbol.ToFullyQualifiedDisplayString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,26 +6,33 @@ using ProxyInterfaceSourceGenerator.Models;
|
||||
|
||||
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver;
|
||||
|
||||
internal class ProxySyntaxReceiver : ISyntaxReceiver
|
||||
internal class ProxySyntaxReceiver : ISyntaxContextReceiver
|
||||
{
|
||||
private static readonly string[] Modifiers = { "public", "partial" };
|
||||
private const string GlobalPrefix = "global::";
|
||||
private static readonly string[] GenerateProxyAttributes = { "ProxyInterfaceGenerator.Proxy", "Proxy" };
|
||||
|
||||
private static readonly string[] Modifiers = { "public", "partial" };
|
||||
public IDictionary<InterfaceDeclarationSyntax, ProxyData> CandidateInterfaces { get; } = new Dictionary<InterfaceDeclarationSyntax, ProxyData>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
|
||||
{
|
||||
if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data))
|
||||
var syntaxNode = context.Node;
|
||||
var semanticModel = context.SemanticModel;
|
||||
|
||||
if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data, semanticModel!))
|
||||
{
|
||||
CandidateInterfaces.Add(interfaceDeclarationSyntax, data);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data)
|
||||
private static string CreateFullInterfaceName(string ns, BaseTypeDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
return !string.IsNullOrEmpty(ns) ? $"{ns}.{classDeclarationSyntax.Identifier}" : classDeclarationSyntax.Identifier.ToString();
|
||||
}
|
||||
private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data, SemanticModel semanticModel)
|
||||
{
|
||||
data = null;
|
||||
|
||||
if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Count() != 0)
|
||||
if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Any())
|
||||
{
|
||||
// InterfaceDeclarationSyntax should be "public" and "partial"
|
||||
return false;
|
||||
@@ -38,7 +45,6 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var usings = new List<string>();
|
||||
|
||||
string ns = interfaceDeclarationSyntax.GetNamespace();
|
||||
@@ -55,35 +61,25 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
|
||||
}
|
||||
}
|
||||
|
||||
var fluentBuilderAttributeArguments = AttributeArgumentListParser.ParseAttributeArguments(attributeList.Attributes.FirstOrDefault()?.ArgumentList);
|
||||
var fluentBuilderAttributeArguments = AttributeArgumentListParser.ParseAttributeArguments(attributeList.Attributes.FirstOrDefault()?.ArgumentList, semanticModel);
|
||||
|
||||
var rawTypeNameAsString = fluentBuilderAttributeArguments.RawTypeName;
|
||||
|
||||
data = new ProxyData
|
||||
{
|
||||
Namespace = ns,
|
||||
ShortInterfaceName = interfaceDeclarationSyntax.Identifier.ToString(),
|
||||
FullInterfaceName = CreateFullInterfaceName(ns, interfaceDeclarationSyntax), // $"{ns}.{interfaceDeclarationSyntax.Identifier}",
|
||||
FullRawTypeName = rawTypeNameAsString,
|
||||
ShortTypeName = ConvertTypeName(rawTypeNameAsString).Split('.').Last(),
|
||||
FullTypeName = ConvertTypeName(rawTypeNameAsString),
|
||||
Usings = usings,
|
||||
ProxyBaseClasses = fluentBuilderAttributeArguments.ProxyBaseClasses,
|
||||
Accessibility = fluentBuilderAttributeArguments.Accessibility
|
||||
};
|
||||
var metadataName = fluentBuilderAttributeArguments.MetadataName;
|
||||
var globalNamespace = string.IsNullOrEmpty(ns) ? string.Empty : $"{GlobalPrefix}{ns}";
|
||||
var namespaceDot = string.IsNullOrEmpty(ns) ? string.Empty : $"{ns}.";
|
||||
|
||||
data = new ProxyData(
|
||||
@namespace: ns,
|
||||
namespaceDot: namespaceDot,
|
||||
shortInterfaceName: interfaceDeclarationSyntax.Identifier.ToString(),
|
||||
fullInterfaceName: CreateFullInterfaceName(globalNamespace, interfaceDeclarationSyntax), // $"{ns}.{interfaceDeclarationSyntax.Identifier}",
|
||||
fullQualifiedTypeName: fluentBuilderAttributeArguments.FullyQualifiedDisplayString,
|
||||
fullMetadataTypeName: metadataName,
|
||||
shortMetadataTypeName: metadataName.Split('.').Last(),
|
||||
usings: usings,
|
||||
proxyBaseClasses: fluentBuilderAttributeArguments.ProxyBaseClasses,
|
||||
accessibility: fluentBuilderAttributeArguments.Accessibility
|
||||
);
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
private static string CreateFullInterfaceName(string ns, BaseTypeDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
return !string.IsNullOrEmpty(ns) ? $"{ns}.{classDeclarationSyntax.Identifier}" : classDeclarationSyntax.Identifier.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ProxyInterfaceSourceGenerator.Types;
|
||||
|
||||
internal record ProxyInterfaceGeneratorAttributeArguments(string RawTypeName)
|
||||
internal record ProxyInterfaceGeneratorAttributeArguments(string FullyQualifiedDisplayString, string MetadataName)
|
||||
{
|
||||
public bool ProxyBaseClasses { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user