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:
David
2024-04-28 10:25:50 +02:00
committed by GitHub
parent 68864378d0
commit 39d85588e6
56 changed files with 1123 additions and 1146 deletions
@@ -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();
}
@@ -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; }