From f9664e056476a13e5f3b6fdc9750ccea03b71af0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 1 Feb 2022 18:49:01 +0100 Subject: [PATCH] ProxyBaseClasses (#27) --- ...ceSourceGenerator Solution.sln.DotSettings | 2 + README.md | 13 +- .../ProxyInterfaceConsumer/IAddress.cs | 18 +- .../ProxyInterfaceConsumer/IPersonT.cs | 4 +- .../ProxyInterfaceConsumer/Program.cs | 18 +- .../ProxyInterfaceConsumer.csproj | 7 + .../Compatibility/NullableAttributes.cs | 27 +- src/ProxyInterfaceSourceGenerator/Context.cs | 16 +- .../ContextData.cs | 13 +- .../Enums/TypeEnum.cs | 13 +- .../Extensions/MethodSymbolExtensions.cs | 18 +- .../Extensions/NamedTypeSymbolExtensions.cs | 101 +++--- .../Extensions/ParameterSymbolExtensions.cs | 45 ++- .../Extensions/PropertySymbolExtensions.cs | 47 ++- .../Extensions/SymbolExtensions.cs | 17 +- .../Extensions/SyntaxNodeUtils.cs | 47 ++- .../TypeParameterSymbolExtensions.cs | 61 ++-- .../Extensions/TypeSymbolExtensions.cs | 31 +- .../FileGenerators/BaseGenerator.cs | 138 ++++---- .../FileGenerators/FileData.cs | 9 +- .../FileGenerators/IFileGenerator.cs | 9 +- .../FileGenerators/IFilesGenerator.cs | 9 +- .../PartialInterfacesGenerator.cs | 151 ++++---- .../FileGenerators/ProxyAttributeGenerator.cs | 23 +- .../FileGenerators/ProxyClassesGenerator.cs | 322 +++++++++--------- .../Model/ClassSymbol.cs | 11 + .../ProxyInterfaceCodeGenerator.cs | 123 ++++--- .../ProxyInterfaceSourceGenerator.csproj | 5 +- .../SyntaxReceiver/ProxyData.cs | 17 +- .../SyntaxReceiver/ProxySyntaxReceiver.cs | 154 +++++---- .../Utils/MemberHelper.cs | 134 ++++---- ...ourceGeneratorTests.Source.HumanProxy.g.cs | 38 +++ ...aceSourceGeneratorTests.Source.IHuman.g.cs | 26 ++ ...eSourceGeneratorTests.Source.IPerson.g.cs} | 2 +- ...eGeneratorTests.Source.IPersonExtends.g.cs | 56 +++ ...eratorTests.Source.PersonExtendsProxy.g.cs | 117 +++++++ ...rceGeneratorTests.Source.PersonProxy.g.cs} | 64 ++-- .../ProxyInterfaceSourceGeneratorTest.cs | 106 +++++- .../ProxyInterfaceSourceGeneratorTests.csproj | 36 +- .../Source/Human.cs | 12 + .../Source/IHuman.cs | 6 + .../Source/IPerson.cs | 2 +- .../Source/IPersonExtends.cs | 6 + .../Source/Person.cs | 2 +- .../Source/PersonExtends.cs | 65 ++++ 45 files changed, 1305 insertions(+), 836 deletions(-) create mode 100644 ProxyInterfaceSourceGenerator Solution.sln.DotSettings create mode 100644 src/ProxyInterfaceSourceGenerator/Model/ClassSymbol.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.HumanProxy.g.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IHuman.g.cs rename tests/ProxyInterfaceSourceGeneratorTests/Destination/{ProxyInterfaceSourceGeneratorTests.DTO.IPerson.g.cs => ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs} (95%) create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPersonExtends.g.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonExtendsProxy.g.cs rename tests/ProxyInterfaceSourceGeneratorTests/Destination/{ProxyInterfaceSourceGeneratorTests.DTO.PersonProxy.g.cs => ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs} (55%) create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Source/Human.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Source/IHuman.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Source/IPersonExtends.cs create mode 100644 tests/ProxyInterfaceSourceGeneratorTests/Source/PersonExtends.cs diff --git a/ProxyInterfaceSourceGenerator Solution.sln.DotSettings b/ProxyInterfaceSourceGenerator Solution.sln.DotSettings new file mode 100644 index 0000000..db9ce64 --- /dev/null +++ b/ProxyInterfaceSourceGenerator Solution.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/README.md b/README.md index c8f2244..02afc5b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,17 @@ public partial interface IPerson } ``` +#### ProxyBaseClasses +In case also want to proxy the properties/methods/events from the base class(es), use this: + +``` c# +[ProxyInterfaceGenerator.Proxy(typeof(ProxyInterfaceConsumer.Person), true)] // 👈 Provide `true` as second parameter. +public partial interface IPerson +{ +} +``` + + ### When the code is compiled, this source generator creates the following #### :one: An additional partial interface @@ -80,4 +91,4 @@ public class PersonProxy : IPerson IPerson p = new PersonProxy(new Person()); p.Name = "test"; p.HelloWorld("stef"); -``` +``` \ No newline at end of file diff --git a/src-examples/ProxyInterfaceConsumer/IAddress.cs b/src-examples/ProxyInterfaceConsumer/IAddress.cs index 70b8fcb..1366e7f 100644 --- a/src-examples/ProxyInterfaceConsumer/IAddress.cs +++ b/src-examples/ProxyInterfaceConsumer/IAddress.cs @@ -1,11 +1,9 @@ -using DifferentNamespace; -using System; -using System.Linq; - -namespace ProxyInterfaceConsumer -{ - [ProxyInterfaceGenerator.Proxy(typeof(Address))] - public partial interface IAddress - { - } +using DifferentNamespace; + +namespace ProxyInterfaceConsumer +{ + [ProxyInterfaceGenerator.Proxy(typeof(Address))] + public partial interface IAddress + { + } } \ No newline at end of file diff --git a/src-examples/ProxyInterfaceConsumer/IPersonT.cs b/src-examples/ProxyInterfaceConsumer/IPersonT.cs index 1327039..3153816 100644 --- a/src-examples/ProxyInterfaceConsumer/IPersonT.cs +++ b/src-examples/ProxyInterfaceConsumer/IPersonT.cs @@ -1,7 +1,7 @@ -namespace ProxyInterfaceConsumer +namespace ProxyInterfaceConsumer { [ProxyInterfaceGenerator.Proxy(typeof(ProxyInterfaceConsumer.PersonT<>))] - public partial interface IPersonT where T : struct + public partial interface IPersonT // where T : struct { } } \ No newline at end of file diff --git a/src-examples/ProxyInterfaceConsumer/Program.cs b/src-examples/ProxyInterfaceConsumer/Program.cs index 2692a00..b10cbb1 100644 --- a/src-examples/ProxyInterfaceConsumer/Program.cs +++ b/src-examples/ProxyInterfaceConsumer/Program.cs @@ -15,16 +15,16 @@ namespace ProxyInterfaceConsumer public static void Main() { - IPersonT pT = new PersonTProxy(new PersonT()); - pT.TVal = 1; - Console.WriteLine(JsonSerializer.Serialize(pT, JsonSerializerOptions)); - Console.WriteLine(new string('-', 80)); + //IPersonT pT = new PersonTProxy(new PersonT()); + //pT.TVal = 1; + //Console.WriteLine(JsonSerializer.Serialize(pT, JsonSerializerOptions)); + //Console.WriteLine(new string('-', 80)); - IPersonTT pTT = new PersonTTProxy(new PersonTT()); - pTT.TVal1 = 42; - pTT.TVal2 = new Program(); - Console.WriteLine(JsonSerializer.Serialize(pTT, JsonSerializerOptions)); - Console.WriteLine(new string('-', 80)); + //IPersonTT pTT = new PersonTTProxy(new PersonTT()); + //pTT.TVal1 = 42; + //pTT.TVal2 = new Program(); + //Console.WriteLine(JsonSerializer.Serialize(pTT, JsonSerializerOptions)); + //Console.WriteLine(new string('-', 80)); var ap = new AddressProxy(new Address { HouseNumber = 42 }); ap.HouseNumber = -1; diff --git a/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj index 7718314..43d1dba 100644 --- a/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj +++ b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj @@ -7,6 +7,13 @@ enable + + + + + + + diff --git a/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs b/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs index 82dfeb2..5aceb18 100644 --- a/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs +++ b/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs @@ -1,18 +1,17 @@ -// https://stackoverflow.com/questions/61573959/how-to-resolve-error-notnullwhen-attribute-is-inaccessible-due-to-its-protectio +// https://stackoverflow.com/questions/61573959/how-to-resolve-error-notnullwhen-attribute-is-inaccessible-due-to-its-protectio -namespace System.Diagnostics.CodeAnalysis +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class NotNullWhenAttribute : Attribute { - /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class NotNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - /// Gets the return value condition. - public bool ReturnValue { get; } - } + /// Gets the return value condition. + public bool ReturnValue { get; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Context.cs b/src/ProxyInterfaceSourceGenerator/Context.cs index 65c3914..e065eac 100644 --- a/src/ProxyInterfaceSourceGenerator/Context.cs +++ b/src/ProxyInterfaceSourceGenerator/Context.cs @@ -1,18 +1,16 @@ -using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ProxyInterfaceSourceGenerator.SyntaxReceiver; -namespace ProxyInterfaceSourceGenerator +namespace ProxyInterfaceSourceGenerator; + +internal record Context { - internal record Context - { - public GeneratorExecutionContext GeneratorExecutionContext { get; init; } + public GeneratorExecutionContext GeneratorExecutionContext { get; init; } - // public List GeneratedData { get; } = new List(); + // public List GeneratedData { get; } = new List(); - public IDictionary CandidateInterfaces { get; init; } = default!; + public IDictionary CandidateInterfaces { get; init; } = default!; - public Dictionary ReplacedTypes { get; } = new Dictionary(); - } + public Dictionary ReplacedTypes { get; } = new Dictionary(); } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ContextData.cs b/src/ProxyInterfaceSourceGenerator/ContextData.cs index 301062f..85fc823 100644 --- a/src/ProxyInterfaceSourceGenerator/ContextData.cs +++ b/src/ProxyInterfaceSourceGenerator/ContextData.cs @@ -1,13 +1,12 @@ using ProxyInterfaceSourceGenerator.FileGenerators; -namespace ProxyInterfaceSourceGenerator +namespace ProxyInterfaceSourceGenerator; + +internal record ContextData { - internal record ContextData - { - public string? InterfaceName { get; init; } + public string? InterfaceName { get; init; } - public string? ClassName { get; init; } + public string? ClassName { get; init; } - public FileData FileData { get; init; } = default!; - } + public FileData FileData { get; init; } = default!; } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Enums/TypeEnum.cs b/src/ProxyInterfaceSourceGenerator/Enums/TypeEnum.cs index 3ca55a9..14ae99f 100644 --- a/src/ProxyInterfaceSourceGenerator/Enums/TypeEnum.cs +++ b/src/ProxyInterfaceSourceGenerator/Enums/TypeEnum.cs @@ -1,11 +1,10 @@ -namespace ProxyInterfaceSourceGenerator.Enums +namespace ProxyInterfaceSourceGenerator.Enums; + +internal enum TypeEnum { - internal enum TypeEnum - { - ValueTypeOrString, + ValueTypeOrString, - Interface, + Interface, - Complex - } + Complex } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/MethodSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/MethodSymbolExtensions.cs index 8ed0307..96826f2 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/MethodSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/MethodSymbolExtensions.cs @@ -1,14 +1,12 @@ -using System.Linq; using Microsoft.CodeAnalysis; -namespace ProxyInterfaceSourceGenerator.Extensions -{ - internal static class MethodSymbolExtensions - { - public static string GetMethodNameWithOptionalTypeParameters(this IMethodSymbol method) => - !method.IsGenericMethod ? method.Name : $"{method.Name}<{string.Join(", ", method.TypeParameters.Select(tp => tp.Name))}>"; +namespace ProxyInterfaceSourceGenerator.Extensions; - public static string GetWhereStatement(this IMethodSymbol method) => - !method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereStatement())); - } +internal static class MethodSymbolExtensions +{ + public static string GetMethodNameWithOptionalTypeParameters(this IMethodSymbol method) => + !method.IsGenericMethod ? method.Name : $"{method.Name}<{string.Join(", ", method.TypeParameters.Select(tp => tp.Name))}>"; + + public static string GetWhereStatement(this IMethodSymbol method) => + !method.IsGenericMethod ? string.Empty : string.Join("", method.TypeParameters.Select(tp => tp.GetWhereStatement())); } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/NamedTypeSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/NamedTypeSymbolExtensions.cs index 54369b7..db1dfa7 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/NamedTypeSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/NamedTypeSymbolExtensions.cs @@ -1,57 +1,74 @@ -using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class NamedTypeSymbolExtensions { - internal static class NamedTypeSymbolExtensions + public static List GetBaseTypes(this INamedTypeSymbol? type) { - public static string GetFileName(this INamedTypeSymbol namedTypeSymbol) + var types = new List(); + + bool me = true; + while (type != null && type.SpecialType != SpecialType.System_Object) { - var typeName = namedTypeSymbol.GetFullType(); - return !(typeName.Contains('<') && typeName.Contains('>')) ? - typeName : - $"{typeName.Replace('.', '_').Replace('<', '_').Replace('>', '_').Replace(", ", "-")}_{typeName.Count(c => c == ',') + 1}"; - } - - public static string GetFullType(this INamedTypeSymbol namedTypeSymbol) - { - // https://www.codeproject.com/Articles/861548/Roslyn-Code-Analysis-in-Easy-Samples-Part - //var str = new StringBuilder(namedTypeSymbol.Name); - - //if (namedTypeSymbol.TypeArguments.Count() > 0) - //{ - // str.AppendFormat("<{0}>", string.Join(", ", namedTypeSymbol.TypeArguments.OfType().Select(typeArg => typeArg.GetFullType()))); - //} - - return namedTypeSymbol.OriginalDefinition.ToString();// str.ToString(); - } - - public static string ResolveInterfaceNameWithOptionalTypeConstraints(this INamedTypeSymbol namedTypeSymbol, string interfaceName) - { - if (!namedTypeSymbol.IsGenericType) + if (!me) { - return interfaceName; + types.Add(type); } - var str = new StringBuilder($"{interfaceName}<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>"); - - foreach (var typeParameterSymbol in namedTypeSymbol.TypeArguments.OfType()) - { - str.Append(typeParameterSymbol.GetWhereStatement()); - } - - return str.ToString(); + type = type.BaseType; + me = false; } - /// - /// See https://stackoverflow.com/questions/24157101/roslyns-gettypebymetadataname-and-generic-types - /// - public static string ResolveProxyClassName(this INamedTypeSymbol namedTypeSymbol) + return types; + } + + public static string GetFileName(this INamedTypeSymbol namedTypeSymbol) + { + var typeName = namedTypeSymbol.GetFullType(); + return !(typeName.Contains('<') && typeName.Contains('>')) ? + typeName : + $"{typeName.Replace('.', '_').Replace('<', '_').Replace('>', '_').Replace(", ", "-")}_{typeName.Count(c => c == ',') + 1}"; + } + + public static string GetFullType(this INamedTypeSymbol namedTypeSymbol) + { + // https://www.codeproject.com/Articles/861548/Roslyn-Code-Analysis-in-Easy-Samples-Part + //var str = new StringBuilder(namedTypeSymbol.Name); + + //if (namedTypeSymbol.TypeArguments.Count() > 0) + //{ + // str.AppendFormat("<{0}>", string.Join(", ", namedTypeSymbol.TypeArguments.OfType().Select(typeArg => typeArg.GetFullType()))); + //} + + return namedTypeSymbol.OriginalDefinition.ToString();// str.ToString(); + } + + public static string ResolveInterfaceNameWithOptionalTypeConstraints(this INamedTypeSymbol namedTypeSymbol, string interfaceName) + { + if (!namedTypeSymbol.IsGenericType) { - return !namedTypeSymbol.IsGenericType ? - $"{namedTypeSymbol.Name}Proxy" : - $"{namedTypeSymbol.Name}Proxy<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>"; + return interfaceName; } + + var str = new StringBuilder($"{interfaceName}<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>"); + + foreach (var typeParameterSymbol in namedTypeSymbol.TypeArguments.OfType()) + { + str.Append(typeParameterSymbol.GetWhereStatement()); + } + + return str.ToString(); + } + + /// + /// See https://stackoverflow.com/questions/24157101/roslyns-gettypebymetadataname-and-generic-types + /// + public static string ResolveProxyClassName(this INamedTypeSymbol namedTypeSymbol) + { + return !namedTypeSymbol.IsGenericType ? + $"{namedTypeSymbol.Name}Proxy" : + $"{namedTypeSymbol.Name}Proxy<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>"; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs index 620fc44..7e4094f 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs @@ -1,35 +1,34 @@ using Microsoft.CodeAnalysis; using ProxyInterfaceSourceGenerator.Enums; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class ParameterSymbolExtensions { - internal static class ParameterSymbolExtensions + public static string GetRefPrefix(this IParameterSymbol ps) { - public static string GetRefPrefix(this IParameterSymbol ps) + switch (ps.RefKind) { - switch (ps.RefKind) - { - case RefKind.In: - return "in "; + case RefKind.In: + return "in "; - case RefKind.Out: - return "out "; + case RefKind.Out: + return "out "; - case RefKind.Ref: - return "ref "; + case RefKind.Ref: + return "ref "; - default: - return string.Empty; - } + default: + return string.Empty; } - - public static string GetParamsPrefix(this IParameterSymbol ps) => - ps.IsParams ? "params " : string.Empty; - - public static string GetDefaultValue(this IParameterSymbol ps) => - ps.HasExplicitDefaultValue ? $" = {ps.ExplicitDefaultValue}" : string.Empty; - - public static TypeEnum GetTypeEnum(this IParameterSymbol p) => - p.Type.GetTypeEnum(); } + + public static string GetParamsPrefix(this IParameterSymbol ps) => + ps.IsParams ? "params " : string.Empty; + + public static string GetDefaultValue(this IParameterSymbol ps) => + ps.HasExplicitDefaultValue ? $" = {ps.ExplicitDefaultValue}" : string.Empty; + + public static TypeEnum GetTypeEnum(this IParameterSymbol p) => + p.Type.GetTypeEnum(); } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/PropertySymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/PropertySymbolExtensions.cs index 7a3ee6c..1fb24c9 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/PropertySymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/PropertySymbolExtensions.cs @@ -1,37 +1,36 @@ using Microsoft.CodeAnalysis; using ProxyInterfaceSourceGenerator.Enums; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class PropertySymbolExtensions { - internal static class PropertySymbolExtensions + public static TypeEnum GetTypeEnum(this IPropertySymbol p) => + p.Type.GetTypeEnum(); + + public static string ToPropertyText(this IPropertySymbol property, string? overrideType = null) { - public static TypeEnum GetTypeEnum(this IPropertySymbol p) => - p.Type.GetTypeEnum(); + var get = property.GetMethod != null ? "get; " : string.Empty; + var set = property.SetMethod != null ? "set; " : string.Empty; - public static string ToPropertyText(this IPropertySymbol property, string? overrideType = null) - { - var get = property.GetMethod != null ? "get; " : string.Empty; - var set = property.SetMethod != null ? "set; " : string.Empty; + var type = !string.IsNullOrEmpty(overrideType) ? overrideType : $"{property.Type}"; - var type = !string.IsNullOrEmpty(overrideType) ? overrideType : $"{property.Type}"; + return $"{type} {property.GetSanitizedName()} {{ {get}{set}}}"; + } - return $"{type} {property.GetSanitizedName()} {{ {get}{set}}}"; - } + public static string ToPropertyTextForClass(this IPropertySymbol property) + { + var get = property.GetMethod != null ? $"get => _Instance.{property.GetSanitizedName()}; " : string.Empty; + var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = value; " : string.Empty; - public static string ToPropertyTextForClass(this IPropertySymbol property) - { - var get = property.GetMethod != null ? $"get => _Instance.{property.GetSanitizedName()}; " : string.Empty; - var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = value; " : string.Empty; + return $"{property.Type} {property.GetSanitizedName()} {{ {get}{set}}}"; + } - return $"{property.Type} {property.GetSanitizedName()} {{ {get}{set}}}"; - } + public static string ToPropertyTextForClass(this IPropertySymbol property, string overrideType) + { + var get = property.GetMethod != null ? $"get => _mapper.Map<{overrideType}>(_Instance.{property.GetSanitizedName()}); " : string.Empty; + var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = _mapper.Map<{property.Type}>(value); " : string.Empty; - public static string ToPropertyTextForClass(this IPropertySymbol property, string overrideType) - { - var get = property.GetMethod != null ? $"get => _mapper.Map<{overrideType}>(_Instance.{property.GetSanitizedName()}); " : string.Empty; - var set = property.SetMethod != null ? $"set => _Instance.{property.GetSanitizedName()} = _mapper.Map<{property.Type}>(value); " : string.Empty; - - return $"{overrideType} {property.GetSanitizedName()} {{ {get}{set}}}"; - } + return $"{overrideType} {property.GetSanitizedName()} {{ {get}{set}}}"; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs index 04699ef..f7b68ce 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs @@ -1,14 +1,13 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace ProxyInterfaceSourceGenerator.Extensions -{ - internal static class SymbolExtensions - { - public static bool IsKeywordOrReserved(this ISymbol symbol) => - SyntaxFacts.GetKeywordKind(symbol.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(symbol.Name) != SyntaxKind.None; +namespace ProxyInterfaceSourceGenerator.Extensions; - public static string GetSanitizedName(this ISymbol symbol) => - symbol.IsKeywordOrReserved() ? $"@{symbol.Name}" : symbol.Name; - } +internal static class SymbolExtensions +{ + public static bool IsKeywordOrReserved(this ISymbol symbol) => + SyntaxFacts.GetKeywordKind(symbol.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(symbol.Name) != SyntaxKind.None; + + public static string GetSanitizedName(this ISymbol symbol) => + symbol.IsKeywordOrReserved() ? $"@{symbol.Name}" : symbol.Name; } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeUtils.cs b/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeUtils.cs index bb15b92..4aed807 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeUtils.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeUtils.cs @@ -1,41 +1,40 @@ using Microsoft.CodeAnalysis; using System.Diagnostics.CodeAnalysis; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class SyntaxNodeUtils { - internal static class SyntaxNodeUtils + // https://stackoverflow.com/questions/20458457/getting-class-fullname-including-namespace-from-roslyn-classdeclarationsyntax + public static bool TryGetParentSyntax(this SyntaxNode? syntaxNode, [NotNullWhen(true)] out T? result) where T : SyntaxNode { - // https://stackoverflow.com/questions/20458457/getting-class-fullname-including-namespace-from-roslyn-classdeclarationsyntax - public static bool TryGetParentSyntax(this SyntaxNode? syntaxNode, [NotNullWhen(true)] out T? result) where T : SyntaxNode + result = null; + + if (syntaxNode is null) { - result = null; + return false; + } + + try + { + syntaxNode = syntaxNode.Parent; if (syntaxNode is null) { return false; } - try + if (syntaxNode.GetType() == typeof(T)) { - syntaxNode = syntaxNode.Parent; - - if (syntaxNode is null) - { - return false; - } - - if (syntaxNode.GetType() == typeof(T)) - { - result = (T)syntaxNode; - return true; - } - - return TryGetParentSyntax(syntaxNode, out result); - } - catch - { - return false; + result = (T)syntaxNode; + return true; } + + return TryGetParentSyntax(syntaxNode, out result); + } + catch + { + return false; } } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/TypeParameterSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/TypeParameterSymbolExtensions.cs index 154f83c..4ea595b 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/TypeParameterSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/TypeParameterSymbolExtensions.cs @@ -1,40 +1,37 @@ -using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class TypeParameterSymbolExtensions { - internal static class TypeParameterSymbolExtensions + /// + /// https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2 + /// + public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSymbol) { - /// - /// https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2 - /// - public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSymbol) + var constraints = new List(); + if (typeParameterSymbol.HasReferenceTypeConstraint) { - var constraints = new List(); - if (typeParameterSymbol.HasReferenceTypeConstraint) - { - constraints.Add("class"); - } - - if (typeParameterSymbol.HasValueTypeConstraint) - { - constraints.Add("struct"); - } - - if (typeParameterSymbol.HasConstructorConstraint) - { - constraints.Add("new()"); - } - - constraints.AddRange(typeParameterSymbol.ConstraintTypes.OfType().Select(contstraintType => contstraintType.GetFullType())); - - if (!constraints.Any()) - { - return string.Empty; - } - - return $" where {typeParameterSymbol.Name} : {string.Join(", ", constraints)}"; + constraints.Add("class"); } + + if (typeParameterSymbol.HasValueTypeConstraint) + { + constraints.Add("struct"); + } + + if (typeParameterSymbol.HasConstructorConstraint) + { + constraints.Add("new()"); + } + + constraints.AddRange(typeParameterSymbol.ConstraintTypes.OfType().Select(contstraintType => contstraintType.GetFullType())); + + if (!constraints.Any()) + { + return string.Empty; + } + + return $" where {typeParameterSymbol.Name} : {string.Join(", ", constraints)}"; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/TypeSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/TypeSymbolExtensions.cs index 672cf3c..95761bb 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/TypeSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/TypeSymbolExtensions.cs @@ -1,28 +1,27 @@ using Microsoft.CodeAnalysis; using ProxyInterfaceSourceGenerator.Enums; -namespace ProxyInterfaceSourceGenerator.Extensions +namespace ProxyInterfaceSourceGenerator.Extensions; + +internal static class TypeSymbolExtensions { - internal static class TypeSymbolExtensions + public static TypeEnum GetTypeEnum(this ITypeSymbol ts) { - public static TypeEnum GetTypeEnum(this ITypeSymbol ts) + if (ts.IsValueType || ts.IsString()) { - if (ts.IsValueType || ts.IsString()) - { - return TypeEnum.ValueTypeOrString; - } - - if (ts.TypeKind == TypeKind.Interface) - { - return TypeEnum.Interface; - } - - return TypeEnum.Complex; + return TypeEnum.ValueTypeOrString; } - public static bool IsString(this ITypeSymbol ts) + if (ts.TypeKind == TypeKind.Interface) { - return ts.ToString() == "string" || ts.ToString() == "string?"; + return TypeEnum.Interface; } + + return TypeEnum.Complex; + } + + public static bool IsString(this ITypeSymbol ts) + { + return ts.ToString() == "string" || ts.ToString() == "string?"; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs index 5203738..619be08 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs @@ -1,97 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.CodeAnalysis; +using ProxyInterfaceSourceGenerator.Extensions; +using ProxyInterfaceSourceGenerator.Model; -namespace ProxyInterfaceSourceGenerator.FileGenerators +namespace ProxyInterfaceSourceGenerator.FileGenerators; + +internal abstract class BaseGenerator { - internal abstract class BaseGenerator + protected readonly Context Context; + protected readonly bool SupportsNullable; + + protected BaseGenerator(Context context, bool supportsNullable) { - protected readonly Context _context; - protected readonly bool _supportsNullable; + Context = context; + SupportsNullable = supportsNullable; + } - public BaseGenerator(Context context, bool supportsNullable) + protected string GetPropertyType(IPropertySymbol property, out bool isReplaced) + { + return GetReplacedType(property.Type, out isReplaced); + } + + protected string GetParameterType(IParameterSymbol property, out bool isReplaced) + { + return GetReplacedType(property.Type, out isReplaced); + } + + protected string GetReplacedType(ITypeSymbol typeSymbol, out bool isReplaced) + { + isReplaced = false; + + var typeSymbolAsString = typeSymbol.ToString(); + + var existing = Context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeSymbolAsString); + if (existing is not null) { - _context = context; - _supportsNullable = supportsNullable; - } - - protected string GetPropertyType(IPropertySymbol property, out bool isReplaced) - { - return GetReplacedType(property.Type, out isReplaced); - } - - protected string GetParameterType(IParameterSymbol property, out bool isReplaced) - { - return GetReplacedType(property.Type, out isReplaced); - } - - protected string GetReplacedType(ITypeSymbol typeSymbol, out bool isReplaced) - { - isReplaced = false; - - var typeSymbolAsString = typeSymbol.ToString(); - - var existing = _context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeSymbolAsString); - if (existing is not null) + if (!Context.ReplacedTypes.ContainsKey(typeSymbolAsString)) { - if (!_context.ReplacedTypes.ContainsKey(typeSymbolAsString)) - { - _context.ReplacedTypes.Add(typeSymbolAsString, existing.InterfaceName); - } - - isReplaced = true; - return existing.InterfaceName; + Context.ReplacedTypes.Add(typeSymbolAsString, existing.InterfaceName); } - if (typeSymbol is INamedTypeSymbol namedTypedSymbol) + isReplaced = true; + return existing.InterfaceName; + } + + if (typeSymbol is INamedTypeSymbol namedTypedSymbol) + { + var propertyTypeAsStringToBeModified = typeSymbolAsString; + foreach (var typeArgument in namedTypedSymbol.TypeArguments) { - var propertyTypeAsStringToBeModified = typeSymbolAsString; - foreach (var typeArgument in namedTypedSymbol.TypeArguments) + var typeArgumentAsString = typeArgument.ToString(); + var exist = Context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeArgumentAsString); + if (exist is not null) { - var typeArgumentAsString = typeArgument.ToString(); - var exist = _context.CandidateInterfaces.Values.FirstOrDefault(x => x.RawTypeName == typeArgumentAsString); - if (exist is not null) + isReplaced = true; + + if (!Context.ReplacedTypes.ContainsKey(typeArgumentAsString)) { - isReplaced = true; - - if (!_context.ReplacedTypes.ContainsKey(typeArgumentAsString)) - { - _context.ReplacedTypes.Add(typeArgumentAsString, exist.InterfaceName); - } - - propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.InterfaceName); + Context.ReplacedTypes.Add(typeArgumentAsString, exist.InterfaceName); } - } - return propertyTypeAsStringToBeModified; + propertyTypeAsStringToBeModified = propertyTypeAsStringToBeModified.Replace(typeArgumentAsString, exist.InterfaceName); + } } - return typeSymbolAsString; + return propertyTypeAsStringToBeModified; } - protected INamedTypeSymbol GetNamedTypeSymbolByFullName(string name, IEnumerable? usings = null) + return typeSymbolAsString; + } + + protected ClassSymbol GetNamedTypeSymbolByFullName(string name, IEnumerable? usings = null) + { + // The GetTypeByMetadataName method returns null if no type matches the full name or if 2 or more types (in different assemblies) match the full name. + var symbol = Context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName(name); + if (symbol is not null) { - // The GetTypeByMetadataName method returns null if no type matches the full name or if 2 or more types (in different assemblies) match the full name. - var symbol = _context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName(name); - if (symbol is not null) - { - return symbol; - } + return new ClassSymbol(symbol, symbol.GetBaseTypes()); + } - if (usings is not null) + if (usings is not null) + { + foreach (var @using in usings) { - foreach (var @using in usings) + symbol = Context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName($"{@using}.{name}"); + if (symbol is not null) { - symbol = _context.GeneratorExecutionContext.Compilation.GetTypeByMetadataName($"{@using}.{name}"); - if (symbol is not null) - { - return symbol; - } + return new ClassSymbol(symbol, symbol.GetBaseTypes()); } } - - throw new Exception($"The type '{name}' is not found."); } + + throw new Exception($"The type '{name}' is not found."); } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/FileData.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/FileData.cs index 8d85676..b86ee44 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/FileData.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/FileData.cs @@ -1,6 +1,3 @@ -namespace ProxyInterfaceSourceGenerator.FileGenerators -{ - internal record FileData(string FileName, string Text) - { - } -} \ No newline at end of file +namespace ProxyInterfaceSourceGenerator.FileGenerators; + +internal record FileData(string FileName, string Text); \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs index 6c1b8dc..2a15607 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs @@ -1,7 +1,6 @@ -namespace ProxyInterfaceSourceGenerator.FileGenerators +namespace ProxyInterfaceSourceGenerator.FileGenerators; + +internal interface IFileGenerator { - internal interface IFileGenerator - { - FileData GenerateFile(); - } + FileData GenerateFile(); } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs index 1219da8..eb4b764 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace ProxyInterfaceSourceGenerator.FileGenerators; -namespace ProxyInterfaceSourceGenerator.FileGenerators +internal interface IFilesGenerator { - internal interface IFilesGenerator - { - IEnumerable GenerateFiles(); - } + IEnumerable GenerateFiles(); } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs index 87aa153..988ceee 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs @@ -1,47 +1,47 @@ -using System.Collections.Generic; -using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ProxyInterfaceSourceGenerator.Enums; using ProxyInterfaceSourceGenerator.Extensions; +using ProxyInterfaceSourceGenerator.Model; using ProxyInterfaceSourceGenerator.SyntaxReceiver; using ProxyInterfaceSourceGenerator.Utils; -namespace ProxyInterfaceSourceGenerator.FileGenerators +namespace ProxyInterfaceSourceGenerator.FileGenerators; + +internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator { - internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator + public PartialInterfacesGenerator(Context context, bool supportsNullable) : + base(context, supportsNullable) { - public PartialInterfacesGenerator(Context context, bool supportsNullable) : - base(context, supportsNullable) + } + + public IEnumerable GenerateFiles() + { + foreach (var ci in Context.CandidateInterfaces) { + yield return GenerateFile(ci.Key, ci.Value); } + } - public IEnumerable GenerateFiles() - { - foreach (var ci in _context.CandidateInterfaces) - { - yield return GenerateFile(ci.Key, ci.Value); - } - } + private FileData GenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd) + { + var sourceInterfaceSymbol = GetNamedTypeSymbolByFullName(ci.Identifier.ToString(), pd.Usings); + var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings); + var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName); - private FileData GenerateFile(InterfaceDeclarationSyntax ci, ProxyData pd) - { - var sourceInterfaceSymbol = GetNamedTypeSymbolByFullName(ci.Identifier.ToString(), pd.Usings); - var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings); - var interfaceName = targetClassSymbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName); + var file = new FileData( + $"{sourceInterfaceSymbol.Symbol.GetFileName()}.g.cs", + CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd.ProxyBaseClasses) + ); - var file = new FileData( - $"{sourceInterfaceSymbol.GetFileName()}.g.cs", - CreatePartialInterfaceCode(pd.Namespace, targetClassSymbol, interfaceName, pd.ProxyAll) - ); + return file; + } - // _context.GeneratedData.Add(new() { InterfaceName = interfaceName, ClassName = null, FileData = file }); - - return file; - } - - private string CreatePartialInterfaceCode(string ns, INamedTypeSymbol targetClassSymbol, string interfaceName, bool proxyAll) => $@"//---------------------------------------------------------------------------------------- + private string CreatePartialInterfaceCode( + string ns, + ClassSymbol classSymbol, + string interfaceName, + bool proxyBaseClasses) => $@"//---------------------------------------------------------------------------------------- // // This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator. // @@ -50,74 +50,73 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators // //---------------------------------------------------------------------------------------- -{(_supportsNullable ? "#nullable enable" : string.Empty)} +{(SupportsNullable ? "#nullable enable" : string.Empty)} using System; namespace {ns} {{ public partial interface {interfaceName} {{ -{GenerateProperties(targetClassSymbol, proxyAll)} +{GenerateProperties(classSymbol, proxyBaseClasses)} -{GenerateMethods(targetClassSymbol)} +{GenerateMethods(classSymbol, proxyBaseClasses)} -{GenerateEvents(targetClassSymbol)} +{GenerateEvents(classSymbol, proxyBaseClasses)} }} }} -{(_supportsNullable ? "#nullable disable" : string.Empty)}"; +{(SupportsNullable ? "#nullable disable" : string.Empty)}"; - private string GenerateProperties(INamedTypeSymbol targetClassSymbol, bool proxyAll) + private string GenerateProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + + foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses)) { - var str = new StringBuilder(); - - foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol)) + var type = GetPropertyType(property, out var isReplaced); + if (isReplaced) { - var type = GetPropertyType(property, out var isReplaced); - if (isReplaced) - { - str.AppendLine($" {property.ToPropertyText(type)}"); - } - else - { - str.AppendLine($" {property.ToPropertyText()}"); - } - str.AppendLine(); + str.AppendLine($" {property.ToPropertyText(type)}"); } - - return str.ToString(); + else + { + str.AppendLine($" {property.ToPropertyText()}"); + } + str.AppendLine(); } - private string GenerateMethods(INamedTypeSymbol targetClassSymbol) + return str.ToString(); + } + + private string GenerateMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses)) { - var str = new StringBuilder(); - foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol)) + var methodParameters = new List(); + foreach (var ps in method.Parameters) { - var methodParameters = new List(); - foreach (var ps in method.Parameters) - { - var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); - methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); - } - - str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()};"); - str.AppendLine(); - } - - return str.ToString(); - } - - private string GenerateEvents(INamedTypeSymbol targetClassSymbol) - { - var str = new StringBuilder(); - foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol)) - { - var ps = @event.First().Parameters.First(); var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); - str.AppendLine($" event {type} {@event.Key.GetSanitizedName()};"); - str.AppendLine(); + methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); } - return str.ToString(); + str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()};"); + str.AppendLine(); } + + return str.ToString(); + } + + private string GenerateEvents(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses)) + { + var ps = @event.First().Parameters.First(); + var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); + str.AppendLine($" event {type} {@event.Key.GetSanitizedName()};"); + str.AppendLine(); + } + + return str.ToString(); } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs index ca91969..4accc18 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs @@ -1,12 +1,12 @@ -namespace ProxyInterfaceSourceGenerator.FileGenerators -{ - internal class ProxyAttributeGenerator : IFileGenerator - { - private const string ClassName = "ProxyAttribute"; +namespace ProxyInterfaceSourceGenerator.FileGenerators; - public FileData GenerateFile() - { - return new FileData($"ProxyInterfaceGenerator.{ClassName}.g.cs", $@"//---------------------------------------------------------------------------------------- +internal class ProxyAttributeGenerator : IFileGenerator +{ + private const string ClassName = "ProxyAttribute"; + + public FileData GenerateFile() + { + return new FileData($"ProxyInterfaceGenerator.{ClassName}.g.cs", $@"//---------------------------------------------------------------------------------------- // // This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator. // @@ -23,15 +23,14 @@ namespace ProxyInterfaceGenerator public class {ClassName} : Attribute {{ public Type Type {{ get; }} - public bool ProxyAll {{ get; }} + public bool ProxyBaseClasses {{ get; }} - public {ClassName}(Type type, bool proxyAll = false) + public {ClassName}(Type type, bool proxyBaseClasses = false) {{ Type = type; - ProxyAll = proxyAll; + ProxyBaseClasses = proxyBaseClasses; }} }} }}"); - } } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs index c694c0e..9dde281 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs @@ -1,52 +1,49 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using ProxyInterfaceSourceGenerator.Enums; using ProxyInterfaceSourceGenerator.Extensions; +using ProxyInterfaceSourceGenerator.Model; using ProxyInterfaceSourceGenerator.SyntaxReceiver; using ProxyInterfaceSourceGenerator.Utils; -namespace ProxyInterfaceSourceGenerator.FileGenerators +namespace ProxyInterfaceSourceGenerator.FileGenerators; + +internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator { - internal class ProxyClassesGenerator : BaseGenerator, IFilesGenerator + public ProxyClassesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable) { - public ProxyClassesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable) + } + + public IEnumerable GenerateFiles() + { + foreach (var ci in Context.CandidateInterfaces) { + yield return GenerateFile(ci.Value); } + } - public IEnumerable GenerateFiles() - { - foreach (var ci in _context.CandidateInterfaces) - { - yield return GenerateFile(ci.Value); - } - } + private FileData GenerateFile(ProxyData pd) + { + var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings); + var interfaceName = targetClassSymbol.Symbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName); + var className = targetClassSymbol.Symbol.ResolveProxyClassName(); + var constructorName = $"{targetClassSymbol.Symbol.Name}Proxy"; - private FileData GenerateFile(ProxyData pd) - { - var targetClassSymbol = GetNamedTypeSymbolByFullName(pd.TypeName, pd.Usings); - var interfaceName = targetClassSymbol.ResolveInterfaceNameWithOptionalTypeConstraints(pd.InterfaceName); - var className = targetClassSymbol.ResolveProxyClassName(); - var constructorName = $"{targetClassSymbol.Name}Proxy"; + var file = new FileData( + $"{targetClassSymbol.Symbol.GetFileName()}Proxy.g.cs", + CreateProxyClassCode(pd.Namespace, targetClassSymbol, pd.ProxyBaseClasses, interfaceName, className, constructorName) + ); - var file = new FileData( - $"{targetClassSymbol.GetFileName()}Proxy.g.cs", - CreateProxyClassCode(pd.Namespace, targetClassSymbol, interfaceName, className, constructorName) - ); + return file; + } - // _context.GeneratedData.Add(new() { InterfaceName = interfaceName, ClassName = pd.ClassName, FileData = file }); - - return file; - } - - private string CreateProxyClassCode( - string ns, - INamedTypeSymbol targetClassSymbol, - string interfaceName, - string className, - string constructorName) => $@"//---------------------------------------------------------------------------------------- + private string CreateProxyClassCode( + string ns, + ClassSymbol targetClassSymbol, + bool proxyBaseClasses, + string interfaceName, + string className, + string constructorName) => $@"//---------------------------------------------------------------------------------------- // // This code was generated by https://github.com/StefH/ProxyInterfaceSourceGenerator. // @@ -55,7 +52,7 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators // //---------------------------------------------------------------------------------------- -{(_supportsNullable ? "#nullable enable" : string.Empty)} +{(SupportsNullable ? "#nullable enable" : string.Empty)} using System; using AutoMapper; @@ -63,13 +60,13 @@ namespace {ns} {{ public class {className} : {interfaceName} {{ - public {targetClassSymbol} _Instance {{ get; }} + public {targetClassSymbol.Symbol} _Instance {{ get; }} -{GeneratePublicProperties(targetClassSymbol, false)} +{GeneratePublicProperties(targetClassSymbol, proxyBaseClasses)} -{GeneratePublicMethods(targetClassSymbol)} +{GeneratePublicMethods(targetClassSymbol, proxyBaseClasses)} -{GenerateEvents(targetClassSymbol)} +{GenerateEvents(targetClassSymbol, proxyBaseClasses)} public {constructorName}({targetClassSymbol} instance) {{ @@ -81,165 +78,164 @@ namespace {ns} {GeneratePrivateAutoMapper()} }} }} -{(_supportsNullable ? "#nullable disable" : string.Empty)}"; - private string GeneratePrivateAutoMapper() +{(SupportsNullable ? "#nullable disable" : string.Empty)}"; + private string GeneratePrivateAutoMapper() + { + return Context.ReplacedTypes.Count == 0 ? string.Empty : " private readonly IMapper _mapper;"; + } + + private string GenerateMapperConfigurationForAutoMapper() + { + if (Context.ReplacedTypes.Count == 0) { - return _context.ReplacedTypes.Count == 0 ? string.Empty : " private readonly IMapper _mapper;"; + return string.Empty; } - private string GenerateMapperConfigurationForAutoMapper() + var str = new StringBuilder(); + + str.AppendLine(" _mapper = new MapperConfiguration(cfg =>"); + str.AppendLine(" {"); + foreach (var replacedType in Context.ReplacedTypes) { - if (_context.ReplacedTypes.Count == 0) + str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>();"); + str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>();"); + } + str.AppendLine(" }).CreateMapper();"); + + return str.ToString(); + } + + private string GeneratePublicProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + + foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses)) + { + var type = GetPropertyType(property, out var isReplaced); + if (isReplaced) { - return string.Empty; + str.AppendLine($" public {property.ToPropertyTextForClass(type)}"); } - - var str = new StringBuilder(); - - str.AppendLine(" _mapper = new MapperConfiguration(cfg =>"); - str.AppendLine(" {"); - foreach (var replacedType in _context.ReplacedTypes) + else { - str.AppendLine($" cfg.CreateMap<{replacedType.Key}, {replacedType.Value}>();"); - str.AppendLine($" cfg.CreateMap<{replacedType.Value}, {replacedType.Key}>();"); + str.AppendLine($" public {property.ToPropertyTextForClass()}"); } - str.AppendLine(" }).CreateMapper();"); - - return str.ToString(); + str.AppendLine(); } - private string GeneratePublicProperties(INamedTypeSymbol targetClassSymbol, bool proxyAll) - { - var str = new StringBuilder(); + return str.ToString(); + } - foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol)) + private string GeneratePublicMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses)) + { + var methodParameters = new List(); + var invokeParameters = new List(); + + foreach (var ps in method.Parameters) { - var type = GetPropertyType(property, out var isReplaced); - if (isReplaced) + var type = GetParameterType(ps, out _); + + methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); + invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_"); + } + + string returnTypeAsString = GetReplacedType(method.ReturnType, out var returnIsReplaced); + + str.AppendLine($" public {returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()}"); + str.AppendLine(" {"); + foreach (var ps in method.Parameters) + { + string normalOrMap = $" = {ps.GetSanitizedName()}"; + if (ps.RefKind == RefKind.Out) { - str.AppendLine($" public {property.ToPropertyTextForClass(type)}"); + normalOrMap = string.Empty; } else { - str.AppendLine($" public {property.ToPropertyTextForClass()}"); + var type = GetParameterType(ps, out var isReplaced); + if (isReplaced) + { + normalOrMap = $" = _mapper.Map<{ps.Type}>({ps.GetSanitizedName()})"; + } } - str.AppendLine(); + + str.AppendLine($" {ps.Type} {ps.GetSanitizedName()}_{normalOrMap};"); } - return str.ToString(); - } - - private string GeneratePublicMethods(INamedTypeSymbol targetClassSymbol) - { - var str = new StringBuilder(); - foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol)) - { - var methodParameters = new List(); - var invokeParameters = new List(); - - foreach (var ps in method.Parameters) - { - var type = GetParameterType(ps, out _); - - methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); - invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_"); - } - - string returnTypeAsString = GetReplacedType(method.ReturnType, out var returnIsReplaced); - - str.AppendLine($" public {returnTypeAsString} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){method.GetWhereStatement()}"); - str.AppendLine(" {"); - foreach (var ps in method.Parameters) - { - string normalOrMap = $" = {ps.GetSanitizedName()}"; - if (ps.RefKind == RefKind.Out) - { - normalOrMap = string.Empty; - } - else - { - var type = GetParameterType(ps, out var isReplaced); - if (isReplaced) - { - normalOrMap = $" = _mapper.Map<{ps.Type}>({ps.GetSanitizedName()})"; - } - } - - str.AppendLine($" {ps.Type} {ps.GetSanitizedName()}_{normalOrMap};"); - } - #pragma warning disable RS1024 // Compare symbols correctly - int hash = method.ReturnType.GetHashCode(); + int hash = method.ReturnType.GetHashCode(); #pragma warning restore RS1024 // Compare symbols correctly - var alternateReturnVariableName = $"result_{Math.Abs(hash)}"; + var alternateReturnVariableName = $"result_{Math.Abs(hash)}"; - if (returnTypeAsString == "void") + if (returnTypeAsString == "void") + { + str.AppendLine($" _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});"); + } + else + { + str.AppendLine($" var {alternateReturnVariableName} = _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});"); + } + + foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out)) + { + string normalOrMap = $" = {ps.GetSanitizedName()}_"; + if (ps.GetTypeEnum() == TypeEnum.Complex) { - str.AppendLine($" _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});"); + var type = GetParameterType(ps, out var isReplaced); + if (isReplaced) + { + normalOrMap = $" = _mapper.Map<{type}>({ps.GetSanitizedName()}_)"; + } + } + + str.AppendLine($" {ps.GetSanitizedName()}{normalOrMap};"); + } + + if (returnTypeAsString != "void") + { + if (returnIsReplaced) + { + str.AppendLine($" return _mapper.Map<{returnTypeAsString}>({alternateReturnVariableName});"); } else { - str.AppendLine($" var {alternateReturnVariableName} = _Instance.{method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", invokeParameters)});"); + str.AppendLine($" return {alternateReturnVariableName};"); } - - foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out)) - { - string normalOrMap = $" = {ps.GetSanitizedName()}_"; - if (ps.GetTypeEnum() == TypeEnum.Complex) - { - var type = GetParameterType(ps, out var isReplaced); - if (isReplaced) - { - normalOrMap = $" = _mapper.Map<{type}>({ps.GetSanitizedName()}_)"; - } - } - - str.AppendLine($" {ps.GetSanitizedName()}{normalOrMap};"); - } - - if (returnTypeAsString != "void") - { - if (returnIsReplaced) - { - str.AppendLine($" return _mapper.Map<{returnTypeAsString}>({alternateReturnVariableName});"); - } - else - { - str.AppendLine($" return {alternateReturnVariableName};"); - } - } - - str.AppendLine(" }"); - str.AppendLine(); } - return str.ToString(); + str.AppendLine(" }"); + str.AppendLine(); } - private string GenerateEvents(INamedTypeSymbol targetClassSymbol) + return str.ToString(); + } + + private string GenerateEvents(ClassSymbol targetClassSymbol, bool proxyBaseClasses) + { + var str = new StringBuilder(); + foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses)) { - var str = new StringBuilder(); - foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol)) + var name = @event.Key.GetSanitizedName(); + var ps = @event.First().Parameters.First(); + var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); + str.Append($" public event {type} {name} {{"); + + if (@event.Any(e => e.MethodKind == MethodKind.EventAdd)) { - var name = @event.Key.GetSanitizedName(); - var ps = @event.First().Parameters.First(); - var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); - str.Append($" public event {type} {name} {{"); - - if (@event.Any(e => e.MethodKind == MethodKind.EventAdd)) - { - str.Append($" add {{ _Instance.{name} += value; }}"); - } - if (@event.Any(e => e.MethodKind == MethodKind.EventRemove)) - { - str.Append($" remove {{ _Instance.{name} -= value; }}"); - } - - str.AppendLine(" }"); - str.AppendLine(); + str.Append($" add {{ _Instance.{name} += value; }}"); + } + if (@event.Any(e => e.MethodKind == MethodKind.EventRemove)) + { + str.Append($" remove {{ _Instance.{name} -= value; }}"); } - return str.ToString(); + str.AppendLine(" }"); + str.AppendLine(); } + + return str.ToString(); } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Model/ClassSymbol.cs b/src/ProxyInterfaceSourceGenerator/Model/ClassSymbol.cs new file mode 100644 index 0000000..06cfee2 --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/Model/ClassSymbol.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis; + +namespace ProxyInterfaceSourceGenerator.Model; + +internal record ClassSymbol(INamedTypeSymbol Symbol, List BaseTypes) +{ + public override string ToString() + { + return Symbol.ToString(); + } +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs index 39b2800..2b3070a 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -6,83 +5,95 @@ using Microsoft.CodeAnalysis.Text; using ProxyInterfaceSourceGenerator.FileGenerators; using ProxyInterfaceSourceGenerator.SyntaxReceiver; -namespace ProxyInterfaceSourceGenerator +namespace ProxyInterfaceSourceGenerator; + +[Generator] +internal class ProxyInterfaceCodeGenerator : ISourceGenerator { - [Generator] - internal class ProxyInterfaceCodeGenerator : ISourceGenerator + private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new ProxyAttributeGenerator(); + + public void Initialize(GeneratorInitializationContext context) { - private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new ProxyAttributeGenerator(); + //if (!System.Diagnostics.Debugger.IsAttached) + //{ + // System.Diagnostics.Debugger.Launch(); + //} - public void Initialize(GeneratorInitializationContext context) + context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.ParseOptions is not CSharpParseOptions csharpParseOptions) { - //if (!System.Diagnostics.Debugger.IsAttached) - //{ - // System.Diagnostics.Debugger.Launch(); - //} - - context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver()); + throw new NotSupportedException("Only C# is supported."); } - public void Execute(GeneratorExecutionContext context) + if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver) { - if (context.ParseOptions is not CSharpParseOptions csharpParseOptions) - { - throw new NotSupportedException("Only C# is supported."); - } + return; + } - if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver) - { - return; - } - - // https://github.com/reactiveui/refit/blob/main/InterfaceStubGenerator.Core/InterfaceStubGenerator.cs - var supportsNullable = csharpParseOptions.LanguageVersion >= LanguageVersion.CSharp8; + // https://github.com/reactiveui/refit/blob/main/InterfaceStubGenerator.Core/InterfaceStubGenerator.cs + var supportsNullable = csharpParseOptions.LanguageVersion >= LanguageVersion.CSharp8; + try + { GenerateProxyAttribute(context, receiver); GeneratePartialInterfaces(context, receiver, supportsNullable); GenerateProxyClasses(context, receiver, supportsNullable); } - - private void GenerateProxyAttribute(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver) + catch (Exception exception) { - var context = new Context - { - GeneratorExecutionContext = ctx, - CandidateInterfaces = receiver.CandidateInterfaces - }; - - var attributeData = _proxyAttributeGenerator.GenerateFile(); - context.GeneratorExecutionContext.AddSource(attributeData.FileName, SourceText.From(attributeData.Text, Encoding.UTF8)); + GenerateError(context, exception); } + } - private static void GeneratePartialInterfaces(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable) + private void GenerateError(GeneratorExecutionContext context, Exception exception) + { + var message = $"/*\r\n{nameof(ProxyInterfaceCodeGenerator)}\r\n\r\n{exception}\r\n*/"; + context.AddSource("Error.g", SourceText.From(message, Encoding.UTF8)); + } + + private void GenerateProxyAttribute(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver) + { + var context = new Context { - var context = new Context - { - GeneratorExecutionContext = ctx, - CandidateInterfaces = receiver.CandidateInterfaces - }; + GeneratorExecutionContext = ctx, + CandidateInterfaces = receiver.CandidateInterfaces + }; - var partialInterfacesGenerator = new PartialInterfacesGenerator(context, supportsNullable); - foreach (var data in partialInterfacesGenerator.GenerateFiles()) - { - context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8)); - } + var attributeData = _proxyAttributeGenerator.GenerateFile(); + context.GeneratorExecutionContext.AddSource(attributeData.FileName, SourceText.From(attributeData.Text, Encoding.UTF8)); + } + + private static void GeneratePartialInterfaces(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable) + { + var context = new Context + { + GeneratorExecutionContext = ctx, + CandidateInterfaces = receiver.CandidateInterfaces + }; + + var partialInterfacesGenerator = new PartialInterfacesGenerator(context, supportsNullable); + foreach (var data in partialInterfacesGenerator.GenerateFiles()) + { + context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8)); } + } - private static void GenerateProxyClasses(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable) + private static void GenerateProxyClasses(GeneratorExecutionContext ctx, ProxySyntaxReceiver receiver, bool supportsNullable) + { + var context = new Context { - var context = new Context - { - GeneratorExecutionContext = ctx, - CandidateInterfaces = receiver.CandidateInterfaces - }; + GeneratorExecutionContext = ctx, + CandidateInterfaces = receiver.CandidateInterfaces + }; - var proxyClassesGenerator = new ProxyClassesGenerator(context, supportsNullable); - foreach (var data in proxyClassesGenerator.GenerateFiles()) - { - context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8)); - } + var proxyClassesGenerator = new ProxyClassesGenerator(context, supportsNullable); + foreach (var data in proxyClassesGenerator.GenerateFiles()) + { + context.GeneratorExecutionContext.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8)); } } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj index e827ecf..5a5b9e4 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj @@ -4,7 +4,7 @@ 0.0.11 netstandard2.0 {12344228-91F4-4502-9595-39584E5ABB34} - 9 + 10 enable Stef Heyenrath @@ -24,6 +24,7 @@ true $(BaseIntermediateOutputPath)Generated true + enable @@ -31,7 +32,7 @@ - + diff --git a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxyData.cs b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxyData.cs index 0965348..bd7a443 100644 --- a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxyData.cs +++ b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxyData.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +namespace ProxyInterfaceSourceGenerator.SyntaxReceiver; -namespace ProxyInterfaceSourceGenerator.SyntaxReceiver -{ - internal record ProxyData(string Namespace, string InterfaceName, string RawTypeName, string TypeName, List Usings, bool ProxyAll) - { - } -} \ No newline at end of file +internal record ProxyData +( + string Namespace, + string InterfaceName, + string RawTypeName, + string TypeName, + List Usings, + bool ProxyBaseClasses +); \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs index 81f9d13..3adf2d2 100644 --- a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs +++ b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs @@ -1,85 +1,91 @@ -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using ProxyInterfaceSourceGenerator.Extensions; -namespace ProxyInterfaceSourceGenerator.SyntaxReceiver +namespace ProxyInterfaceSourceGenerator.SyntaxReceiver; + +internal class ProxySyntaxReceiver : ISyntaxReceiver { - internal class ProxySyntaxReceiver : ISyntaxReceiver + private static readonly string[] Modifiers = { "public", "partial" }; + + public IDictionary CandidateInterfaces { get; } = new Dictionary(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - private static readonly string[] Modifiers = new[] { "public", "partial" }; - - public IDictionary CandidateInterfaces { get; } = new Dictionary(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data)) { - if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out var data)) - { - CandidateInterfaces.Add(interfaceDeclarationSyntax, data); - } - } - - private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data) - { - data = null; - - if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Count() != 0) - { - // InterfaceDeclarationSyntax should be "public" and "partial" - return false; - } - - var attributeLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => a.Name.ToString().Equals("ProxyInterfaceGenerator.Proxy"))); - if (attributeLists is null) - { - return false; - } - - var argumentList = attributeLists.Attributes.FirstOrDefault()?.ArgumentList; - if (argumentList is null) - { - return false; - } - - var usings = new List(); - - string ns = string.Empty; - if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out NamespaceDeclarationSyntax? namespaceDeclarationSyntax)) - { - ns = namespaceDeclarationSyntax.Name.ToString(); - usings.Add(ns); - } - - if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out CompilationUnitSyntax? cc)) - { - foreach (var @using in cc.Usings) - { - usings.Add(@using.Name.ToString()); - } - } - - string rawTypeName = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type.ToString(); - - data = new - ( - ns, - interfaceDeclarationSyntax.Identifier.ToString(), - rawTypeName, - ConvertTypeName(rawTypeName), - usings, - false //bool.Parse(argumentList.Arguments[1].Expression.GetText().ToString()) - ); - - return true; - } - - private static string ConvertTypeName(string typeName) - { - return !(typeName.Contains('<') && typeName.Contains('>')) ? - typeName : - $"{typeName.Replace("<", string.Empty).Replace(">", string.Empty).Replace(",", string.Empty).Trim()}`{typeName.Count(c => c == ',') + 1}"; + CandidateInterfaces.Add(interfaceDeclarationSyntax, data); } } + + private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, [NotNullWhen(true)] out ProxyData? data) + { + data = null; + + if (interfaceDeclarationSyntax.Modifiers.Select(m => m.ToString()).Except(Modifiers).Count() != 0) + { + // InterfaceDeclarationSyntax should be "public" and "partial" + return false; + } + + var attributeLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => a.Name.ToString().Equals("ProxyInterfaceGenerator.Proxy"))); + if (attributeLists is null) + { + return false; + } + + var argumentList = attributeLists.Attributes.FirstOrDefault()?.ArgumentList; + if (argumentList is null) + { + return false; + } + + var usings = new List(); + + string ns = string.Empty; + if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out NamespaceDeclarationSyntax? namespaceDeclarationSyntax)) + { + ns = namespaceDeclarationSyntax.Name.ToString(); + usings.Add(ns); + } + + if (SyntaxNodeUtils.TryGetParentSyntax(interfaceDeclarationSyntax, out CompilationUnitSyntax? cc)) + { + foreach (var @using in cc.Usings) + { + usings.Add(@using.Name.ToString()); + } + } + + string rawTypeName = ((TypeOfExpressionSyntax)argumentList.Arguments[0].Expression).Type.ToString(); + bool proxyAllClasses; + try + { + proxyAllClasses = bool.Parse(((LiteralExpressionSyntax)argumentList.Arguments[1].Expression).ToString()); + } + catch + { + proxyAllClasses = false; + } + + data = new + ( + ns, + interfaceDeclarationSyntax.Identifier.ToString(), + rawTypeName, + ConvertTypeName(rawTypeName), + usings, + proxyAllClasses + ); + + return true; + } + + private static string ConvertTypeName(string typeName) + { + return !(typeName.Contains('<') && typeName.Contains('>')) ? + typeName : + $"{typeName.Replace("<", string.Empty).Replace(">", string.Empty).Replace(",", string.Empty).Trim()}`{typeName.Count(c => c == ',') + 1}"; + } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs index e49efec..486b2f6 100644 --- a/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs +++ b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs @@ -1,87 +1,105 @@ using Microsoft.CodeAnalysis; -using System; -using System.Collections.Generic; -using System.Linq; +using ProxyInterfaceSourceGenerator.Model; -namespace ProxyInterfaceSourceGenerator.Utils +namespace ProxyInterfaceSourceGenerator.Utils; + +internal static class MemberHelper { - internal static class MemberHelper + private static readonly string[] ExcludedMethods = { "ToString", "GetHashCode" }; + + public static IEnumerable GetPublicProperties( + ClassSymbol classSymbol, + bool proxyBaseClasses, + params Func[] filters) { - private static string[] _excludedMethods = new string[] { "ToString", "GetHashCode" }; - - public static IEnumerable GetPublicProperties(INamedTypeSymbol classSymbol, params Func[] filters) + var allFilters = new List>(filters) { - var allFilters = new List>(filters); - allFilters.Add(p => p.Kind == SymbolKind.Property); + p => p.Kind == SymbolKind.Property + }; - return GetPublicMembers(classSymbol, allFilters.ToArray()); + return GetPublicMembers(classSymbol, proxyBaseClasses, allFilters.ToArray()); + } + + public static IEnumerable GetPublicMethods( + ClassSymbol classSymbol, + bool proxyBaseClasses, + Func? filter = null) + { + if (filter is null) + { + filter = _ => true; } - public static IEnumerable GetPublicMethods(INamedTypeSymbol classSymbol, Func? filter = null) - { - if (filter is null) - { - filter = _ => true; - } + return GetPublicMembers(classSymbol, + proxyBaseClasses, + m => m.Kind == SymbolKind.Method, + m => m.MethodKind == MethodKind.Ordinary, + m => !ExcludedMethods.Contains(m.Name), + filter); + } - return GetPublicMembers(classSymbol, - m => m.Kind == SymbolKind.Method, - m => m.MethodKind == MethodKind.Ordinary, - m => !_excludedMethods.Contains(m.Name), - filter); + public static IEnumerable> GetPublicEvents( + ClassSymbol classSymbol, + bool proxyBaseClasses, + Func? filter = null) + { + if (filter is null) + { + filter = _ => true; } - public static IEnumerable> GetPublicEvents(INamedTypeSymbol classSymbol, Func? filter = null) - { - if (filter is null) - { - filter = _ => true; - } - #pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. #pragma warning disable RS1024 // Compare symbols correctly - return GetPublicMembers(classSymbol, + return GetPublicMembers(classSymbol, + proxyBaseClasses, m => m.MethodKind == MethodKind.EventAdd || m.MethodKind == MethodKind.EventRemove/* || m.MethodKind == MethodKind.EventRaise*/, filter) - .GroupBy(e => e.AssociatedSymbol); + .GroupBy(e => e.AssociatedSymbol); #pragma warning restore RS1024 // Compare symbols correctly #pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. + } + + // TODO : do we need also to check for "SanitizedName()" here? + private static IEnumerable GetPublicMembers( + ClassSymbol classSymbol, + bool proxyBaseClasses, + params Func[] filters) where T : ISymbol + { + var membersQuery = classSymbol.Symbol.GetMembers().OfType() + .Where(m => m.DeclaredAccessibility == Accessibility.Public); + + foreach (var filter in filters) + { + membersQuery = membersQuery.Where(filter); } - // TODO : do we need also to check for "SanitizedName()" here? - private static IEnumerable GetPublicMembers(INamedTypeSymbol classSymbol, params Func[] filters) where T : ISymbol + var ownMembers = membersQuery.ToList(); + var ownPropertyNames = ownMembers.Select(x => x.Name); + + if (!proxyBaseClasses) { - var membersQuery = classSymbol.GetMembers().OfType() - .Where(m => m.DeclaredAccessibility == Accessibility.Public); + return ownMembers; + } + + var allMembers = ownMembers; + var baseType = classSymbol.Symbol.BaseType; + + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) + { + var baseMembers = baseType.GetMembers().OfType() + .Where(m => m.DeclaredAccessibility == Accessibility.Public) + .Where(x => !ownPropertyNames.Contains(x.Name)); foreach (var filter in filters) { - membersQuery = membersQuery.Where(filter); + baseMembers = baseMembers.Where(filter); } - var members = membersQuery.ToList(); + allMembers.AddRange(baseMembers); - var propertyNames = membersQuery.Select(x => x.Name); - - var baseType = classSymbol.BaseType; - - while (baseType != null) - { - var baseMembers = baseType.GetMembers().OfType() - .Where(m => m.DeclaredAccessibility == Accessibility.Public) - .Where(x => !propertyNames.Contains(x.Name)); - - foreach (var filter in filters) - { - baseMembers = baseMembers.Where(filter); - } - - members.AddRange(baseMembers); - - baseType = baseType.BaseType; - } - - return membersQuery; + baseType = baseType.BaseType; } + + return allMembers; } } \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.HumanProxy.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.HumanProxy.g.cs new file mode 100644 index 0000000..3beaf96 --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.HumanProxy.g.cs @@ -0,0 +1,38 @@ +//---------------------------------------------------------------------------------------- +// +// 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; +using AutoMapper; + +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public class HumanProxy : IHuman + { + public ProxyInterfaceSourceGeneratorTests.Source.Human _Instance { get; } + + public bool IsAlive { get => _Instance.IsAlive; set => _Instance.IsAlive = value; } + + + + + + + + public HumanProxy(ProxyInterfaceSourceGeneratorTests.Source.Human instance) + { + _Instance = instance; + + + } + + + } +} +#nullable disable \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IHuman.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IHuman.g.cs new file mode 100644 index 0000000..5c712c3 --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IHuman.g.cs @@ -0,0 +1,26 @@ +//---------------------------------------------------------------------------------------- +// +// 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 IHuman + { + bool IsAlive { get; set; } + + + + + + + } +} +#nullable disable \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.IPerson.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs similarity index 95% rename from tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.IPerson.g.cs rename to tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs index 69bdafe..bf16cf5 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.IPerson.g.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs @@ -10,7 +10,7 @@ #nullable enable using System; -namespace ProxyInterfaceSourceGeneratorTests.DTO +namespace ProxyInterfaceSourceGeneratorTests.Source { public partial interface IPerson { diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPersonExtends.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPersonExtends.g.cs new file mode 100644 index 0000000..523ae49 --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPersonExtends.g.cs @@ -0,0 +1,56 @@ +//---------------------------------------------------------------------------------------- +// +// 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 IPersonExtends + { + string Name { get; set; } + + string? StringNullable { get; set; } + + long? NullableLong { get; } + + object @object { get; set; } + + bool IsAlive { get; set; } + + bool X { get; set; } + + + + void Void(); + + string HelloWorld(string name); + + void WithParams(params string[] values); + + string Add(string s, string @string); + + int DefaultValue(int x = 100); + + void In_Out_Ref1(in int a, out int b, ref int c); + + bool Generic2(int x, T1 t1, T2 t2) where T1 : struct where T2 : class, new(); + + System.Threading.Tasks.Task Method1Async(); + + System.Threading.Tasks.Task Method2Async(); + + System.Threading.Tasks.Task Method3Async(); + + + + + } +} +#nullable disable \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonExtendsProxy.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonExtendsProxy.g.cs new file mode 100644 index 0000000..8bcce3f --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonExtendsProxy.g.cs @@ -0,0 +1,117 @@ +//---------------------------------------------------------------------------------------- +// +// 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; +using AutoMapper; + +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public class PersonExtendsProxy : IPersonExtends + { + public ProxyInterfaceSourceGeneratorTests.Source.PersonExtends _Instance { get; } + + public string Name { get => _Instance.Name; set => _Instance.Name = value; } + + public string? StringNullable { get => _Instance.StringNullable; set => _Instance.StringNullable = value; } + + public long? NullableLong { get => _Instance.NullableLong; } + + public object @object { get => _Instance.@object; set => _Instance.@object = value; } + + public bool IsAlive { get => _Instance.IsAlive; set => _Instance.IsAlive = value; } + + public bool X { get => _Instance.X; set => _Instance.X = value; } + + + + public void Void() + { + _Instance.Void(); + } + + public string HelloWorld(string name) + { + string name_ = name; + var result_58477331 = _Instance.HelloWorld(name_); + return result_58477331; + } + + public void WithParams(params string[] values) + { + string[] values_ = values; + _Instance.WithParams(values_); + } + + public string Add(string s, string @string) + { + string s_ = s; + string @string_ = @string; + var result_58477331 = _Instance.Add(s_, @string_); + return result_58477331; + } + + public int DefaultValue(int x = 100) + { + int x_ = x; + var result_42930144 = _Instance.DefaultValue(x_); + return result_42930144; + } + + public void In_Out_Ref1(in int a, out int b, ref int c) + { + int a_ = a; + int b_; + int c_ = c; + _Instance.In_Out_Ref1(in a_, out b_, ref c_); + b = b_; + } + + public bool Generic2(int x, T1 t1, T2 t2) where T1 : struct where T2 : class, new() + { + int x_ = x; + T1 t1_ = t1; + T2 t2_ = t2; + var result_38995950 = _Instance.Generic2(x_, t1_, t2_); + return result_38995950; + } + + public System.Threading.Tasks.Task Method1Async() + { + var result_51708797 = _Instance.Method1Async(); + return result_51708797; + } + + public System.Threading.Tasks.Task Method2Async() + { + var result_1620952573 = _Instance.Method2Async(); + return result_1620952573; + } + + public System.Threading.Tasks.Task Method3Async() + { + var result_1636499760 = _Instance.Method3Async(); + return result_1636499760; + } + + + + + + public PersonExtendsProxy(ProxyInterfaceSourceGeneratorTests.Source.PersonExtends instance) + { + _Instance = instance; + + + } + + + } +} +#nullable disable \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.PersonProxy.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs similarity index 55% rename from tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.PersonProxy.g.cs rename to tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs index 1fa560a..8025ed0 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.DTO.PersonProxy.g.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs @@ -11,11 +11,11 @@ using System; using AutoMapper; -namespace ProxyInterfaceSourceGeneratorTests.DTO +namespace ProxyInterfaceSourceGeneratorTests.Source { public class PersonProxy : IPerson { - public ProxyInterfaceSourceGeneratorTests.DTO.Person _Instance { get; } + public ProxyInterfaceSourceGeneratorTests.Source.Person _Instance { get; } public string Name { get => _Instance.Name; set => _Instance.Name = value; } @@ -29,78 +29,78 @@ namespace ProxyInterfaceSourceGeneratorTests.DTO public void Void() { - _Instance.Void(); + _Instance.Void(); } public string HelloWorld(string name) { - string name_ = name; - var result_15289640 = _Instance.HelloWorld(name_); - return result_15289640; + string name_ = name; + var result_56365455 = _Instance.HelloWorld(name_); + return result_56365455; } public void WithParams(params string[] values) { - string[] values_ = values; - _Instance.WithParams(values_); + string[] values_ = values; + _Instance.WithParams(values_); } public string Add(string s, string @string) { - string s_ = s; - string @string_ = @string; - var result_15289640 = _Instance.Add(s_, @string_); - return result_15289640; + string s_ = s; + string @string_ = @string; + var result_56365455 = _Instance.Add(s_, @string_); + return result_56365455; } public int DefaultValue(int x = 100) { - int x_ = x; - var result_54302544 = _Instance.DefaultValue(x_); - return result_54302544; + int x_ = x; + var result_39875940 = _Instance.DefaultValue(x_); + return result_39875940; } public void In_Out_Ref1(in int a, out int b, ref int c) { - int a_ = a; - int b_; - int c_ = c; - _Instance.In_Out_Ref1(in a_, out b_, ref c_); - b = b_; + int a_ = a; + int b_; + int c_ = c; + _Instance.In_Out_Ref1(in a_, out b_, ref c_); + b = b_; } public bool Generic2(int x, T1 t1, T2 t2) where T1 : struct where T2 : class, new() { - int x_ = x; - T1 t1_ = t1; - T2 t2_ = t2; - var result_40004473 = _Instance.Generic2(x_, t1_, t2_); - return result_40004473; + int x_ = x; + T1 t1_ = t1; + T2 t2_ = t2; + var result_41799290 = _Instance.Generic2(x_, t1_, t2_); + return result_41799290; } public System.Threading.Tasks.Task Method1Async() { - var result_50153955 = _Instance.Method1Async(); - return result_50153955; + var result_32599313 = _Instance.Method1Async(); + return result_32599313; } public System.Threading.Tasks.Task Method2Async() { - var result_1151242754 = _Instance.Method2Async(); - return result_1151242754; + var result_1620495907 = _Instance.Method2Async(); + return result_1620495907; } public System.Threading.Tasks.Task Method3Async() { - var result_1190255658 = _Instance.Method3Async(); - return result_1190255658; + var result_1604006392 = _Instance.Method3Async(); + return result_1604006392; } - public PersonProxy(ProxyInterfaceSourceGeneratorTests.DTO.Person instance) + public PersonProxy(ProxyInterfaceSourceGeneratorTests.Source.Person instance) { _Instance = instance; diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs index 40b74bd..c291b0b 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTest.cs @@ -1,14 +1,18 @@ +using System.Collections.Generic; using System.IO; +using AnyOfTypes; using CSharp.SourceGenerators.Extensions; using CSharp.SourceGenerators.Extensions.Models; using FluentAssertions; using ProxyInterfaceSourceGenerator; using Xunit; -namespace FluentBuilderGeneratorTests +namespace ProxyInterfaceSourceGeneratorTests { public class ProxyInterfaceSourceGeneratorTest { + private bool Write = true; + private readonly ProxyInterfaceCodeGenerator _sut; public ProxyInterfaceSourceGeneratorTest() @@ -21,10 +25,10 @@ namespace FluentBuilderGeneratorTests { // Arrange var attributeFilename = "ProxyInterfaceGenerator.ProxyAttribute.g.cs"; - var interfaceFilename = "ProxyInterfaceSourceGeneratorTests.DTO.IPerson.g.cs"; - var proxyClassFilename = "ProxyInterfaceSourceGeneratorTests.DTO.PersonProxy.g.cs"; + var interfaceFilename = "ProxyInterfaceSourceGeneratorTests.Source.IPersonExtends.g.cs"; + var proxyClassFilename = "ProxyInterfaceSourceGeneratorTests.Source.PersonExtendsProxy.g.cs"; - var path = "./Source/IPerson.cs"; + var path = "./Source/IPersonExtends.cs"; var sourceFile = new SourceFile { Path = path, @@ -32,7 +36,7 @@ namespace FluentBuilderGeneratorTests AttributeToAddToInterface = new ExtraAttribute { Name = "ProxyInterfaceGenerator.Proxy", - ArgumentList = "typeof(ProxyInterfaceSourceGeneratorTests.DTO.Person)" + ArgumentList = new [] { "typeof(ProxyInterfaceSourceGeneratorTests.Source.PersonExtends)", "true" } } }; @@ -43,16 +47,16 @@ namespace FluentBuilderGeneratorTests result.Valid.Should().BeTrue(); result.Files.Should().HaveCount(3); - // Assert interface - var @attribute = result.Files[0].SyntaxTree; - @attribute.FilePath.Should().EndWith(attributeFilename); + // Assert attribute + var attribute = result.Files[0].SyntaxTree; + attribute.FilePath.Should().EndWith(attributeFilename); // Assert interface var @interface = result.Files[1].SyntaxTree; @interface.FilePath.Should().EndWith(interfaceFilename); var interfaceCode = @interface.ToString(); - File.WriteAllText($"../../../Destination/{interfaceFilename}", interfaceCode); + if (Write) File.WriteAllText($"../../../Destination/{interfaceFilename}", interfaceCode); interfaceCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{interfaceFilename}")); // Assert Proxy @@ -60,8 +64,90 @@ namespace FluentBuilderGeneratorTests proxyClass.FilePath.Should().EndWith(proxyClassFilename); var proxyCode = proxyClass.ToString(); - File.WriteAllText($"../../../Destination/{proxyClassFilename}", proxyCode); + if (Write) File.WriteAllText($"../../../Destination/{proxyClassFilename}", proxyCode); proxyCode.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{proxyClassFilename}")); } + + [Fact] + public void GenerateFiles_ForTwoClasses_Should_GenerateCorrectFiles() + { + // Arrange + var attributeFilename = "ProxyInterfaceGenerator.ProxyAttribute.g.cs"; + var interfaceHumanFilename = "ProxyInterfaceSourceGeneratorTests.Source.IHuman.g.cs"; + var proxyClassHumanFilename = "ProxyInterfaceSourceGeneratorTests.Source.HumanProxy.g.cs"; + var interfacePersonFilename = "ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs"; + var proxyClassPersonFilename = "ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs"; + + var pathPerson = "./Source/IPerson.cs"; + var sourceFilePerson = new SourceFile + { + Path = pathPerson, + Text = File.ReadAllText(pathPerson), + AttributeToAddToInterface = new ExtraAttribute + { + Name = "ProxyInterfaceGenerator.Proxy", + ArgumentList = "typeof(ProxyInterfaceSourceGeneratorTests.Source.Person)" + } + }; + + var pathHuman = "./Source/IHuman.cs"; + var sourceFileHuman = new SourceFile + { + Path = pathHuman, + Text = File.ReadAllText(pathHuman), + AttributeToAddToInterface = new ExtraAttribute + { + Name = "ProxyInterfaceGenerator.Proxy", + ArgumentList = "typeof(ProxyInterfaceSourceGeneratorTests.Source.Human)" + } + }; + + // Act + var result = _sut.Execute(new[] { sourceFileHuman, sourceFilePerson }); + + // Assert + result.Valid.Should().BeTrue(); + result.Files.Should().HaveCount(5); + + // Assert attribute + var attribute = result.Files[0].SyntaxTree; + attribute.FilePath.Should().EndWith(attributeFilename); + + + // Assert interface Human + var interfaceHuman = result.Files[1].SyntaxTree; + interfaceHuman.FilePath.Should().EndWith(interfaceHumanFilename); + + var interfaceCodeHuman = interfaceHuman.ToString(); + if (Write) File.WriteAllText($"../../../Destination/{interfaceHumanFilename}", interfaceCodeHuman); + interfaceCodeHuman.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{interfaceHumanFilename}")); + + + // Assert interface Person + var interfacePerson = result.Files[2].SyntaxTree; + interfacePerson.FilePath.Should().EndWith(interfacePersonFilename); + + var interfaceCodePerson = interfacePerson.ToString(); + if (Write) File.WriteAllText($"../../../Destination/{interfacePersonFilename}", interfaceCodePerson); + interfaceCodePerson.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{interfacePersonFilename}")); + + + // Assert Proxy Human + var proxyClassHuman = result.Files[3].SyntaxTree; + proxyClassHuman.FilePath.Should().EndWith(proxyClassHumanFilename); + + var proxyCodeHuman = proxyClassHuman.ToString(); + if (Write) File.WriteAllText($"../../../Destination/{proxyClassHumanFilename}", proxyCodeHuman); + proxyCodeHuman.Should().NotBeNullOrEmpty().And.Be(File.ReadAllText($"../../../Destination/{proxyClassHumanFilename}")); + + + // Assert Proxy Person + var proxyClassPerson = result.Files[4].SyntaxTree; + proxyClassPerson.FilePath.Should().EndWith(proxyClassPersonFilename); + + var proxyCode = proxyClassPerson.ToString(); + 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 975143a..aa629d4 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj @@ -8,11 +8,6 @@ false - - - - - @@ -32,22 +27,29 @@ - + - - PreserveNewest - - - PreserveNewest - - - - - - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/Human.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/Human.cs new file mode 100644 index 0000000..05eeaae --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/Human.cs @@ -0,0 +1,12 @@ +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public class Human : Animal + { + public bool IsAlive { get; set; } + } + + public class Animal + { + public bool X { get; set; } + } +} \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/IHuman.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/IHuman.cs new file mode 100644 index 0000000..22ba8b1 --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/IHuman.cs @@ -0,0 +1,6 @@ +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public partial interface IHuman + { + } +} \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs index 45e7781..7ca6065 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs @@ -1,4 +1,4 @@ -namespace ProxyInterfaceSourceGeneratorTests.DTO +namespace ProxyInterfaceSourceGeneratorTests.Source { public partial interface IPerson { diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/IPersonExtends.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPersonExtends.cs new file mode 100644 index 0000000..8d37a42 --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPersonExtends.cs @@ -0,0 +1,6 @@ +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public partial interface IPersonExtends + { + } +} \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs index bf38d53..66d430d 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace ProxyInterfaceSourceGeneratorTests.DTO +namespace ProxyInterfaceSourceGeneratorTests.Source { public class Person { diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/PersonExtends.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/PersonExtends.cs new file mode 100644 index 0000000..712898a --- /dev/null +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/PersonExtends.cs @@ -0,0 +1,65 @@ +using System.Threading.Tasks; + +namespace ProxyInterfaceSourceGeneratorTests.Source +{ + public class PersonExtends : Human + { + public string Name { get; set; } + + public string? StringNullable { get; set; } + + public long? NullableLong { get; } + + public object @object { get; set; } + + public void Void() + { + } + + public string HelloWorld(string name) + { + return $"Hello {name} !"; + } + + public void WithParams(params string[] values) + { + } + + public string Add(string s, string @string) + { + return s + @string; + } + + public int DefaultValue(int x = 100) + { + return x + 1; + } + + public void In_Out_Ref1(in int a, out int b, ref int c) + { + b = 1; + } + + public bool Generic2(int x, T1 t1, T2 t2) + where T1 : struct + where T2 : class, new() + { + return true; + } + + public Task Method1Async() + { + return Task.CompletedTask; + } + + public Task Method2Async() + { + return Task.FromResult(1); + } + + public Task Method3Async() + { + return Task.FromResult((string?)""); + } + } +} \ No newline at end of file