diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs index 4470820..cd3adfa 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs @@ -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 + }} }}"); } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs index 9ecfa8b..0eb1639 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs @@ -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 $@"//---------------------------------------------------------------------------------------- // // 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} diff --git a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs index ebe7855..60d5cdc 100644 --- a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs +++ b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs @@ -1,3 +1,5 @@ +using ProxyInterfaceSourceGenerator.Types; + namespace ProxyInterfaceSourceGenerator.Models; internal class ProxyData @@ -17,4 +19,6 @@ internal class ProxyData public List Usings { get; init; } public bool ProxyBaseClasses { get; init; } + + public ProxyClassAccessibility Accessibility { get; init; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj index c88d5f1..e2b26d8 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj @@ -37,14 +37,18 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -59,10 +63,6 @@ - - diff --git a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/AttributeArgumentListParser.cs b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/AttributeArgumentListParser.cs new file mode 100644 index 0000000..95772f9 --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/AttributeArgumentListParser.cs @@ -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(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(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; + } +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs index e2c194f..0f9a131 100644 --- a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs +++ b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs @@ -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 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; diff --git a/src/ProxyInterfaceSourceGenerator/Types/FluentBuilderAttributeArguments.cs b/src/ProxyInterfaceSourceGenerator/Types/FluentBuilderAttributeArguments.cs new file mode 100644 index 0000000..0621f0c --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/Types/FluentBuilderAttributeArguments.cs @@ -0,0 +1,8 @@ +namespace ProxyInterfaceSourceGenerator.Types; + +internal record ProxyInterfaceGeneratorAttributeArguments(string RawTypeName) +{ + public bool ProxyBaseClasses { get; set; } + + public ProxyClassAccessibility Accessibility { get; set; } +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Types/ProxyInterfaceGeneratorAccessibility.cs b/src/ProxyInterfaceSourceGenerator/Types/ProxyInterfaceGeneratorAccessibility.cs new file mode 100644 index 0000000..3fb156e --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/Types/ProxyInterfaceGeneratorAccessibility.cs @@ -0,0 +1,9 @@ +namespace ProxyInterfaceSourceGenerator.Types; + +[Flags] +internal enum ProxyClassAccessibility +{ + Public = 0, + + Internal = 1 +} \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.ITestClassInternal.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.ITestClassInternal.g.cs new file mode 100644 index 0000000..7c661ad --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.ITestClassInternal.g.cs @@ -0,0 +1,28 @@ +//---------------------------------------------------------------------------------------- +// +// 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. +// +//---------------------------------------------------------------------------------------- + +#nullable enable +using System; + +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public partial interface ITestClassInternal + { + ProxyInterfaceSourceGeneratorTests.Source.TestClassInternal _Instance { get; } + + bool Test { get; set; } + + + + + + + } +} +#nullable disable \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.TestClassInternalProxy.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.TestClassInternalProxy.g.cs new file mode 100644 index 0000000..1bc56fc --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.TestClassInternalProxy.g.cs @@ -0,0 +1,39 @@ +//---------------------------------------------------------------------------------------- +// +// 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. +// +//---------------------------------------------------------------------------------------- + +#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 \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles.verified.txt b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles.verified.txt index 7c8cf35..a2cdd6a 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles.verified.txt +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles.verified.txt @@ -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 + } } }, { diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs index 174fade..9341635 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs @@ -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}")); } - - } \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj index 74f8d77..cb61422 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj @@ -10,6 +10,8 @@ + + diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/ITestClassInternal.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/ITestClassInternal.cs new file mode 100644 index 0000000..049dc9f --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/ITestClassInternal.cs @@ -0,0 +1,6 @@ +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public partial interface ITestClassInternal + { + } +} \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/TestClassInternal.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/TestClassInternal.cs new file mode 100644 index 0000000..488d96f --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/TestClassInternal.cs @@ -0,0 +1,7 @@ +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public class TestClassInternal + { + public bool Test { get; set; } + } +} \ No newline at end of file