Add support for public and internal ProxyClass (#58)

* x

* fx

* .
This commit is contained in:
Stef Heyenrath
2023-02-24 16:22:26 +01:00
committed by GitHub
parent 4c7f7cde4d
commit eadcf8585f
15 changed files with 296 additions and 36 deletions
@@ -26,13 +26,35 @@ namespace ProxyInterfaceGenerator
{{
public Type Type {{ get; }}
public bool ProxyBaseClasses {{ get; }}
public ProxyClassAccessibility Accessibility {{ get; }}
public {ClassName}(Type type, bool proxyBaseClasses = false)
public {ClassName}(Type type) : this(type, false, ProxyClassAccessibility.Public)
{{
}}
public {ClassName}(Type type, bool proxyBaseClasses) : this(type, proxyBaseClasses, ProxyClassAccessibility.Public)
{{
}}
public {ClassName}(Type type, ProxyClassAccessibility accessibility) : this(type, false, accessibility)
{{
}}
public {ClassName}(Type type, bool proxyBaseClasses, ProxyClassAccessibility accessibility)
{{
Type = type;
ProxyBaseClasses = proxyBaseClasses;
Accessibility = accessibility;
}}
}}
[Flags]
internal enum ProxyClassAccessibility
{{
Public = 0,
Internal = 1
}}
}}");
}
}
@@ -5,6 +5,7 @@ using ProxyInterfaceSourceGenerator.Builders;
using ProxyInterfaceSourceGenerator.Enums;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.Models;
using ProxyInterfaceSourceGenerator.Types;
using ProxyInterfaceSourceGenerator.Utils;
namespace ProxyInterfaceSourceGenerator.FileGenerators;
@@ -88,6 +89,8 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
var (namespaceStart, namespaceEnd) = NamespaceBuilder.Build(pd.Namespace);
var accessibility = pd.Accessibility == ProxyClassAccessibility.Internal ? "internal" : "public";
return $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
@@ -101,7 +104,7 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
using System;
{namespaceStart}
public {@abstract}partial class {className} : {extends}{interfaceName}
{accessibility} {@abstract}partial class {className} : {extends}{interfaceName}
{{
public {@new}{targetClassSymbol.Symbol} _Instance {{ get; }}
{instanceBaseDefinition}
@@ -1,3 +1,5 @@
using ProxyInterfaceSourceGenerator.Types;
namespace ProxyInterfaceSourceGenerator.Models;
internal class ProxyData
@@ -17,4 +19,6 @@ internal class ProxyData
public List<string> Usings { get; init; }
public bool ProxyBaseClasses { get; init; }
public ProxyClassAccessibility Accessibility { get; init; }
}
@@ -37,14 +37,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<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="Polyfill.NET" Version="1.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
@@ -59,10 +63,6 @@
</Compile>
</ItemGroup>
<!--<ItemGroup>
<None Remove="bin\Debug\netstandard2.0\ProxyInterfaceSourceGenerator.dll" />
</ItemGroup>-->
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
@@ -0,0 +1,82 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ProxyInterfaceSourceGenerator.Types;
namespace ProxyInterfaceSourceGenerator.SyntaxReceiver;
internal static class AttributeArgumentListParser
{
public static ProxyInterfaceGeneratorAttributeArguments ParseAttributeArguments(AttributeArgumentListSyntax? argumentList)
{
if (argumentList is null || argumentList.Arguments.Count is < 1 or > 3)
{
throw new ArgumentException("The ProxyAttribute requires 1, 2 or 3 arguments.");
}
ProxyInterfaceGeneratorAttributeArguments result;
if (TryParseAsType(argumentList.Arguments[0].Expression, out var rawTypeValue))
{
result = new ProxyInterfaceGeneratorAttributeArguments(rawTypeValue);
}
else
{
throw new ArgumentException("The first argument from the ProxyAttribute should be a Type.");
}
foreach (var argument in argumentList.Arguments.Skip(1))
{
if (TryParseAsBoolean(argument.Expression, out var proxyBaseClasses))
{
result = result with { ProxyBaseClasses = proxyBaseClasses };
continue;
}
if (TryParseAsEnum<ProxyClassAccessibility>(argument.Expression, out var accessibility))
{
result = result with { Accessibility = accessibility };
}
}
return result;
}
private static bool TryParseAsBoolean(ExpressionSyntax expressionSyntax, out bool value)
{
value = default;
if (expressionSyntax is LiteralExpressionSyntax literalExpressionSyntax)
{
value = literalExpressionSyntax.Kind() == SyntaxKind.TrueLiteralExpression;
return true;
}
return false;
}
private static bool TryParseAsType(ExpressionSyntax expressionSyntax, [NotNullWhen(true)] out string? rawTypeName)
{
rawTypeName = null;
if (expressionSyntax is TypeOfExpressionSyntax typeOfExpressionSyntax)
{
rawTypeName = typeOfExpressionSyntax.Type.ToString();
return true;
}
return false;
}
private static bool TryParseAsEnum<TEnum>(ExpressionSyntax expressionSyntax, out TEnum value)
where TEnum : struct
{
var enumAsString = expressionSyntax.ToString();
if (enumAsString.Length > typeof(TEnum).Name.Length && Enum.TryParse(expressionSyntax.ToString().Substring(typeof(TEnum).Name.Length + 1), out value))
{
return true;
}
value = default;
return false;
}
}
@@ -31,19 +31,14 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
return false;
}
var attributeLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => GenerateProxyAttributes.Contains(a.Name.ToString())));
if (attributeLists is null)
var attributeList = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => GenerateProxyAttributes.Contains(a.Name.ToString())));
if (attributeList is null)
{
// InterfaceDeclarationSyntax should have the correct attribute
return false;
}
var argumentList = attributeLists.Attributes.FirstOrDefault()?.ArgumentList;
if (argumentList is null)
{
return false;
}
var usings = new List<string>();
string ns = interfaceDeclarationSyntax.GetNamespace();
@@ -60,29 +55,21 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver
}
}
var typeSyntax = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type;
string rawTypeName = typeSyntax.ToString();
bool proxyBaseClasses;
try
{
proxyBaseClasses = bool.Parse(((LiteralExpressionSyntax)argumentList.Arguments[1].Expression).ToString());
}
catch
{
proxyBaseClasses = false;
}
var fluentBuilderAttributeArguments = AttributeArgumentListParser.ParseAttributeArguments(attributeList.Attributes.FirstOrDefault()?.ArgumentList);
var rawTypeNameAsString = fluentBuilderAttributeArguments.RawTypeName;
data = new ProxyData
{
Namespace = ns,
ShortInterfaceName = interfaceDeclarationSyntax.Identifier.ToString(),
FullInterfaceName = CreateFullInterfaceName(ns, interfaceDeclarationSyntax), // $"{ns}.{interfaceDeclarationSyntax.Identifier}",
FullRawTypeName = rawTypeName,
ShortTypeName = ConvertTypeName(rawTypeName).Split('.').Last(),
FullTypeName = ConvertTypeName(rawTypeName),
FullRawTypeName = rawTypeNameAsString,
ShortTypeName = ConvertTypeName(rawTypeNameAsString).Split('.').Last(),
FullTypeName = ConvertTypeName(rawTypeNameAsString),
Usings = usings,
ProxyBaseClasses = proxyBaseClasses
ProxyBaseClasses = fluentBuilderAttributeArguments.ProxyBaseClasses,
Accessibility = fluentBuilderAttributeArguments.Accessibility
};
return true;
@@ -0,0 +1,8 @@
namespace ProxyInterfaceSourceGenerator.Types;
internal record ProxyInterfaceGeneratorAttributeArguments(string RawTypeName)
{
public bool ProxyBaseClasses { get; set; }
public ProxyClassAccessibility Accessibility { get; set; }
}
@@ -0,0 +1,9 @@
namespace ProxyInterfaceSourceGenerator.Types;
[Flags]
internal enum ProxyClassAccessibility
{
Public = 0,
Internal = 1
}
@@ -0,0 +1,28 @@
//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------------------
#nullable enable
using System;
namespace ProxyInterfaceSourceGeneratorTests.Source
{
public partial interface ITestClassInternal
{
ProxyInterfaceSourceGeneratorTests.Source.TestClassInternal _Instance { get; }
bool Test { get; set; }
}
}
#nullable disable
@@ -0,0 +1,39 @@
//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------------------
#nullable enable
using System;
namespace ProxyInterfaceSourceGeneratorTests.Source
{
internal partial class TestClassInternalProxy : ITestClassInternal
{
public ProxyInterfaceSourceGeneratorTests.Source.TestClassInternal _Instance { get; }
public bool Test { get => _Instance.Test; set => _Instance.Test = value; }
public TestClassInternalProxy(ProxyInterfaceSourceGeneratorTests.Source.TestClassInternal instance)
{
_Instance = instance;
}
}
}
#nullable disable
@@ -20,13 +20,35 @@ namespace ProxyInterfaceGenerator
{
public Type Type { get; }
public bool ProxyBaseClasses { get; }
public ProxyClassAccessibility Accessibility { get; }
public ProxyAttribute(Type type, bool proxyBaseClasses = false)
public ProxyAttribute(Type type) : this(type, false, ProxyClassAccessibility.Public)
{
}
public ProxyAttribute(Type type, bool proxyBaseClasses) : this(type, proxyBaseClasses, ProxyClassAccessibility.Public)
{
}
public ProxyAttribute(Type type, ProxyClassAccessibility accessibility) : this(type, false, accessibility)
{
}
public ProxyAttribute(Type type, bool proxyBaseClasses, ProxyClassAccessibility accessibility)
{
Type = type;
ProxyBaseClasses = proxyBaseClasses;
Accessibility = accessibility;
}
}
[Flags]
internal enum ProxyClassAccessibility
{
Public = 0,
Internal = 1
}
}
},
{
@@ -317,6 +317,49 @@ public class ProxyInterfaceSourceGeneratorTest
proxyCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{proxyClassFilename}"));
}
[Fact]
public void GenerateFiles_ForSingleClass_AsInternal_Should_GenerateCorrectFiles()
{
// Arrange
var interfaceFilename = "ProxyInterfaceSourceGeneratorTests.Source.ITestClassInternal.g.cs";
var proxyClassFilename = "ProxyInterfaceSourceGeneratorTests.Source.TestClassInternalProxy.g.cs";
var path = "./Source/ITestClassInternal.cs";
var sourceFile = new SourceFile
{
Path = path,
Text = File.ReadAllText(path),
AttributeToAddToInterface = new ExtraAttribute
{
Name = "ProxyInterfaceGenerator.Proxy",
ArgumentList = new[] { "typeof(ProxyInterfaceSourceGeneratorTests.Source.TestClassInternal)", "ProxyClassAccessibility.Internal" }
}
};
// Act
var result = _sut.Execute(new[] { sourceFile });
// Assert
result.Valid.Should().BeTrue();
result.Files.Should().HaveCount(3);
// Assert interface
var @interface = result.Files[1].SyntaxTree;
@interface.FilePath.Should().EndWith(interfaceFilename);
var interfaceCode = @interface.ToString();
if (Write) File.WriteAllText($"../../../Destination/{interfaceFilename}", interfaceCode);
interfaceCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{interfaceFilename}"));
// Assert Proxy
var proxyClass = result.Files[2].SyntaxTree;
proxyClass.FilePath.Should().EndWith(proxyClassFilename);
var proxyCode = proxyClass.ToString();
if (Write) File.WriteAllText($"../../../Destination/{proxyClassFilename}", proxyCode);
proxyCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{proxyClassFilename}"));
}
[Fact]
public void GenerateFiles_ForTwoClasses_Should_GenerateCorrectFiles()
{
@@ -398,6 +441,4 @@ public class ProxyInterfaceSourceGeneratorTest
if (Write) File.WriteAllText($"../../../Destination/{proxyClassPersonFilename}", proxyCode);
proxyCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{proxyClassPersonFilename}"));
}
}
@@ -10,6 +10,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Verify.SourceGenerators" Version="2.0.1" />
<PackageReference Include="Verify.Xunit" Version="19.6.0" />
<PackageReference Include="Akka.Remote" Version="1.4.47" />
<PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="CultureAwareTesting.xUnit" Version="0.0.1" />
@@ -0,0 +1,6 @@
namespace ProxyInterfaceSourceGeneratorTests.Source
{
public partial interface ITestClassInternal
{
}
}
@@ -0,0 +1,7 @@
namespace ProxyInterfaceSourceGeneratorTests.Source
{
public class TestClassInternal
{
public bool Test { get; set; }
}
}