From 23e9f808324da117ff31f3bfd6dea14b33a0f1a0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 7 May 2022 09:33:15 +0200 Subject: [PATCH] Add support for 'file-scoped' namespaces (#32) * 008 * n * da --- ProxyInterfaceSourceGenerator Solution.sln | 7 +++ .../ProxyInterfaceConsumer.csproj | 2 +- .../Extensions/SyntaxNodeExtensions.cs | 51 +++++++++++++++++++ .../Models/ProxyData.cs | 22 ++++---- .../ProxyInterfaceCodeGenerator.cs | 10 ++-- .../ProxyInterfaceSourceGenerator.csproj | 13 +++-- .../SyntaxReceiver/ProxySyntaxReceiver.cs | 34 +++++++------ .../ProxyInterfaceSourceGeneratorTests.csproj | 10 ++-- .../Source/IPerson.cs | 8 +-- .../Source/MyStruct.cs | 9 ++-- 10 files changed, 116 insertions(+), 50 deletions(-) diff --git a/ProxyInterfaceSourceGenerator Solution.sln b/ProxyInterfaceSourceGenerator Solution.sln index efb4084..b307734 100644 --- a/ProxyInterfaceSourceGenerator Solution.sln +++ b/ProxyInterfaceSourceGenerator Solution.sln @@ -31,23 +31,30 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + DebugAttach|Any CPU = DebugAttach|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {12344228-91F4-4502-9595-39584E5ABB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {12344228-91F4-4502-9595-39584E5ABB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12344228-91F4-4502-9595-39584E5ABB34}.DebugAttach|Any CPU.ActiveCfg = DebugAttach|Any CPU + {12344228-91F4-4502-9595-39584E5ABB34}.DebugAttach|Any CPU.Build.0 = DebugAttach|Any CPU {12344228-91F4-4502-9595-39584E5ABB34}.Release|Any CPU.ActiveCfg = Release|Any CPU {12344228-91F4-4502-9595-39584E5ABB34}.Release|Any CPU.Build.0 = Release|Any CPU {7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.DebugAttach|Any CPU.ActiveCfg = Debug|Any CPU {7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Release|Any CPU.Build.0 = Release|Any CPU {6BEBFEB9-635F-44A2-949C-15DDDF0B7740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6BEBFEB9-635F-44A2-949C-15DDDF0B7740}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BEBFEB9-635F-44A2-949C-15DDDF0B7740}.DebugAttach|Any CPU.ActiveCfg = Debug|Any CPU {6BEBFEB9-635F-44A2-949C-15DDDF0B7740}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BEBFEB9-635F-44A2-949C-15DDDF0B7740}.Release|Any CPU.Build.0 = Release|Any CPU {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.DebugAttach|Any CPU.ActiveCfg = DebugAttach|Any CPU + {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.DebugAttach|Any CPU.Build.0 = DebugAttach|Any CPU {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1BDB9046-D6D1-4FB4-AAB5-F24E33EEAE0A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection diff --git a/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj index 0941af8..9fa564f 100644 --- a/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj +++ b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeExtensions.cs index 16b0a1a..07749ce 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/SyntaxNodeExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics.CodeAnalysis; namespace ProxyInterfaceSourceGenerator.Extensions; @@ -37,4 +38,54 @@ internal static class SyntaxNodeExtensions return false; } } + + /// + /// determine the namespace the class/enum/struct is declared in, if any + /// https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/ + /// + /// + /// NameSpace + public static string GetNamespace(this SyntaxNode syntaxNode) + { + // If we don't have a namespace at all we'll return an empty string + // This accounts for the "default namespace" case + string nameSpace = string.Empty; + + // Get the containing syntax node for the type declaration + // (could be a nested type, for example) + SyntaxNode? potentialNamespaceParent = syntaxNode.Parent; + + // Keep moving "out" of nested classes etc until we get to a namespace + // or until we run out of parents + while (potentialNamespaceParent != null && + potentialNamespaceParent is not NamespaceDeclarationSyntax + && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) + { + potentialNamespaceParent = potentialNamespaceParent.Parent; + } + + // Build up the final namespace by looping until we no longer have a namespace declaration + if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) + { + // We have a namespace. Use that as the type + nameSpace = namespaceParent.Name.ToString(); + + // Keep moving "out" of the namespace declarations until we + // run out of nested namespace declarations + while (true) + { + if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) + { + break; + } + + // Add the outer namespace as a prefix to the final namespace + nameSpace = $"{namespaceParent.Name}.{nameSpace}"; + namespaceParent = parent; + } + } + + // return the final namespace + return nameSpace; + } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs index 1340fa4..d5c481b 100644 --- a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs +++ b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs @@ -1,13 +1,13 @@ namespace ProxyInterfaceSourceGenerator.Models; -internal record ProxyData -( - string Namespace, - string ShortInterfaceName, - string FullInterfaceName, - string FullRawTypeName, - string ShortTypeName, - string FullTypeName, - List Usings, - bool ProxyBaseClasses -); \ No newline at end of file +internal class ProxyData +{ + public string Namespace { get; init; } + public string ShortInterfaceName { get; init; } + public string FullInterfaceName { get; init; } + public string FullRawTypeName { get; init; } + public string ShortTypeName { get; init; } + public string FullTypeName { get; init; } + public List Usings { get; init; } + public bool ProxyBaseClasses { get; init; } +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs index 5b148bc..cc402ea 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs @@ -15,10 +15,12 @@ internal class ProxyInterfaceCodeGenerator : ISourceGenerator public void Initialize(GeneratorInitializationContext context) { - //if (!System.Diagnostics.Debugger.IsAttached) - //{ - // System.Diagnostics.Debugger.Launch(); - //} +#if DEBUGATTACH + if (!System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Launch(); + } +#endif context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver()); } diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj index f63ef22..927bb21 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj @@ -25,6 +25,7 @@ $(BaseIntermediateOutputPath)Generated true enable + Debug;Release;DebugAttach @@ -37,12 +38,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + @@ -57,8 +58,10 @@ - - + + + + diff --git a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs index 293e3ec..f19294a 100644 --- a/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs +++ b/src/ProxyInterfaceSourceGenerator/SyntaxReceiver/ProxySyntaxReceiver.cs @@ -46,10 +46,9 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver var usings = new List(); - string ns = string.Empty; - if (interfaceDeclarationSyntax.TryGetParentSyntax(out NamespaceDeclarationSyntax? namespaceDeclarationSyntax)) + string ns = interfaceDeclarationSyntax.GetNamespace(); + if (!string.IsNullOrEmpty(ns)) { - ns = namespaceDeclarationSyntax.Name.ToString(); usings.Add(ns); } @@ -73,18 +72,18 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver { proxyAllClasses = false; } - - data = new - ( - ns, - interfaceDeclarationSyntax.Identifier.ToString(), - $"{ns}.{interfaceDeclarationSyntax.Identifier}", - rawTypeName, - ConvertTypeName(rawTypeName).Split('.').Last(), // ShortTypeName - ConvertTypeName(rawTypeName), // FullTypeName - usings, - proxyAllClasses - ); + + data = new ProxyData + { + Namespace = ns, + ShortInterfaceName = interfaceDeclarationSyntax.Identifier.ToString(), + FullInterfaceName = CreateFullBuilderClassName(ns, interfaceDeclarationSyntax), // $"{ns}.{interfaceDeclarationSyntax.Identifier}", + FullRawTypeName = rawTypeName, + ShortTypeName = ConvertTypeName(rawTypeName).Split('.').Last(), + FullTypeName = ConvertTypeName(rawTypeName), + Usings = usings, + ProxyBaseClasses = proxyAllClasses + }; return true; } @@ -95,4 +94,9 @@ internal class ProxySyntaxReceiver : ISyntaxReceiver typeName : $"{typeName.Replace("<", string.Empty).Replace(">", string.Empty).Replace(",", string.Empty).Trim()}`{typeName.Count(c => c == ',') + 1}"; } + + private static string CreateFullBuilderClassName(string ns, BaseTypeDeclarationSyntax classDeclarationSyntax) + { + return !string.IsNullOrEmpty(ns) ? $"{ns}.{classDeclarationSyntax.Identifier}" : classDeclarationSyntax.Identifier.ToString(); + } } \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj index 8f54307..eab8098 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj @@ -3,16 +3,16 @@ net6.0 false - 9 + 10.0 enable - false + Debug;Release;DebugAttach - - + + @@ -30,7 +30,7 @@ - + diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs index 9d9c6e3..e0a3d37 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/IPerson.cs @@ -1,6 +1,6 @@ -namespace ProxyInterfaceSourceGeneratorTests.Source +// file-scoped namespace ! +namespace ProxyInterfaceSourceGeneratorTests.Source; + +public partial interface IPerson : IHuman { - public partial interface IPerson : IHuman - { - } } \ No newline at end of file diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/MyStruct.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/MyStruct.cs index e2b579e..62a10de 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Source/MyStruct.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/MyStruct.cs @@ -1,7 +1,6 @@ -namespace ProxyInterfaceSourceGeneratorTests.Source +namespace ProxyInterfaceSourceGeneratorTests.Source; + +public struct MyStruct { - public struct MyStruct - { - public int Id { get; set; } - } + public int Id { get; set; } } \ No newline at end of file