diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs b/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs index e9a076a..aa8a5b6 100644 --- a/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs +++ b/AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs @@ -10,6 +10,8 @@ public class AutomaticInterfaceGenerator : IIncrementalGenerator { public const string DefaultAttributeName = "GenerateAutomaticInterface"; public const string IgnoreAutomaticInterfaceAttributeName = "IgnoreAutomaticInterface"; + public const string NamespaceParameterName = "namespaceName"; + public const string InterfaceParameterName = "interfaceName"; public void Initialize(IncrementalGeneratorInitializationContext context) { diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 1f028e9..a725079 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -52,11 +52,12 @@ is not ClassDeclarationSyntax classSyntax { return string.Empty; } - var namespaceName = GetNameSpace(typeSymbol); - var interfaceName = $"I{classSyntax.GetClassName()}"; - - var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName); + var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax); + var interfaceGenerator = new InterfaceBuilder( + symbolDetails.NamespaceName, + symbolDetails.InterfaceName + ); interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax)); interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol)); @@ -77,7 +78,10 @@ is not ClassDeclarationSyntax classSyntax return generatedCode; } - private static string GetNameSpace(ISymbol typeSymbol) + private static GeneratedSymbolDetails GetSymbolDetails( + ITypeSymbol typeSymbol, + ClassDeclarationSyntax classSyntax + ) { var generationAttribute = typeSymbol .GetAttributes() @@ -86,16 +90,7 @@ private static string GetNameSpace(ISymbol typeSymbol) && x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName) ); - if (generationAttribute == null) - { - return typeSymbol.ContainingNamespace.ToDisplayString(); - } - - var customNs = generationAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - - return string.IsNullOrWhiteSpace(customNs) - ? typeSymbol.ContainingNamespace.ToDisplayString() - : customNs!; + return new GeneratedSymbolDetails(generationAttribute, typeSymbol, classSyntax); } private static void AddMethodsToInterface(List members, InterfaceBuilder codeGenerator) diff --git a/AutomaticInterface/AutomaticInterface/GeneratedSymbolDetails.cs b/AutomaticInterface/AutomaticInterface/GeneratedSymbolDetails.cs new file mode 100644 index 0000000..b0c63d9 --- /dev/null +++ b/AutomaticInterface/AutomaticInterface/GeneratedSymbolDetails.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace AutomaticInterface; + +internal sealed class GeneratedSymbolDetails( + AttributeData? generationAttribute, + ITypeSymbol typeSymbol, + ClassDeclarationSyntax classSyntax +) +{ + /// + /// Represents the namespace name associated with the generated interface or type symbol. + /// This value is typically derived from the provided generation attribute or defaults + /// to the containing namespace of the type symbol. + /// + public string NamespaceName { get; } = + PrepareValue( + generationAttribute, + AutomaticInterfaceGenerator.NamespaceParameterName, + typeSymbol.ContainingNamespace.ToDisplayString() + ); + + /// + /// Represents the name of the interface generated for a class. The interface name + /// is derived from the class name, prefixed with 'I', unless overridden by a specific + /// attribute value during generation. + /// + public string InterfaceName { get; } = + PrepareValue( + generationAttribute, + AutomaticInterfaceGenerator.InterfaceParameterName, + $"I{classSyntax.GetClassName()}" + ); + + private static string PrepareValue( + AttributeData? generationAttribute, + string key, + string defaultValue + ) + { + var parameterSymbol = generationAttribute?.AttributeConstructor?.Parameters.SingleOrDefault( + x => x.Name == key + ); + + if (parameterSymbol != null) + { + var index = generationAttribute!.AttributeConstructor!.Parameters.IndexOf( + parameterSymbol + ); + var result = generationAttribute.ConstructorArguments[index].Value!.ToString(); + if (!string.IsNullOrWhiteSpace(result)) + { + return result; + } + } + + return defaultValue; + } +} diff --git a/AutomaticInterface/AutomaticInterface/RegisterAttributesExtensions.cs b/AutomaticInterface/AutomaticInterface/RegisterAttributesExtensions.cs index d18d044..a21d7e1 100644 --- a/AutomaticInterface/AutomaticInterface/RegisterAttributesExtensions.cs +++ b/AutomaticInterface/AutomaticInterface/RegisterAttributesExtensions.cs @@ -24,10 +24,12 @@ namespace AutomaticInterface /// /// Use source generator to automatically create a Interface from this class /// + /// Namespace name for the generated interface. Defaults to the same namespace as the class. + /// Interface name for the generated interface. Defaults to an interface version of the class name. [AttributeUsage(AttributeTargets.Class)] internal sealed class {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute : Attribute { - internal {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute(string namespaceName = "") { } + internal {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute(string {{{AutomaticInterfaceGenerator.NamespaceParameterName}}} = "", string {{{AutomaticInterfaceGenerator.InterfaceParameterName}}} = "") { } } } """, diff --git a/AutomaticInterface/AutomaticInterfaceExample/DemoClassWithCustomInterfaceName.cs b/AutomaticInterface/AutomaticInterfaceExample/DemoClassWithCustomInterfaceName.cs new file mode 100644 index 0000000..540e695 --- /dev/null +++ b/AutomaticInterface/AutomaticInterfaceExample/DemoClassWithCustomInterfaceName.cs @@ -0,0 +1,12 @@ +using AutomaticInterface; + +namespace AutomaticInterfaceExample; + +[GenerateAutomaticInterface(interfaceName: "ISpecialInterface")] +public class DemoClassWithCustomInterfaceName : ISpecialInterface +{ + /// + /// This is a test method + /// + public void Test() { } +} diff --git a/AutomaticInterface/Tests/Enums/Enums.WorksWithEnum.received.txt b/AutomaticInterface/Tests/Misc/Misc.CustomInterface.verified.txt similarity index 69% rename from AutomaticInterface/Tests/Enums/Enums.WorksWithEnum.received.txt rename to AutomaticInterface/Tests/Misc/Misc.CustomInterface.verified.txt index 90b8ad7..60c018a 100644 --- a/AutomaticInterface/Tests/Enums/Enums.WorksWithEnum.received.txt +++ b/AutomaticInterface/Tests/Misc/Misc.CustomInterface.verified.txt @@ -9,10 +9,10 @@ namespace AutomaticInterfaceExample { [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] - public partial interface IDemoClass + public partial interface ISpecialInterface { - /// - void MethodWithDefaultParameter(global::AutomaticInterfaceExample.EnumWithByteType a = global::AutomaticInterfaceExample.EnumWithByteType.B); + /// + void Test(); } } diff --git a/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespace.verified.txt b/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespace.verified.txt new file mode 100644 index 0000000..f7ef5ba --- /dev/null +++ b/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespace.verified.txt @@ -0,0 +1,18 @@ +//-------------------------------------------------------------------------------------------------- +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// +//-------------------------------------------------------------------------------------------------- + +namespace CustomNamespace +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] + public partial interface ISpecialInterface + { + /// + void Test(); + + } +} diff --git a/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespaceParametersReversed.verified.txt b/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespaceParametersReversed.verified.txt new file mode 100644 index 0000000..f7ef5ba --- /dev/null +++ b/AutomaticInterface/Tests/Misc/Misc.CustomInterfaceAndNamespaceParametersReversed.verified.txt @@ -0,0 +1,18 @@ +//-------------------------------------------------------------------------------------------------- +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// +//-------------------------------------------------------------------------------------------------- + +namespace CustomNamespace +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")] + public partial interface ISpecialInterface + { + /// + void Test(); + + } +} diff --git a/AutomaticInterface/Tests/Misc/Misc.cs b/AutomaticInterface/Tests/Misc/Misc.cs index c573a0b..06a4cfc 100644 --- a/AutomaticInterface/Tests/Misc/Misc.cs +++ b/AutomaticInterface/Tests/Misc/Misc.cs @@ -254,6 +254,78 @@ public string AMethod(DemoClass? x, string y) await Verify(Infrastructure.GenerateCode(code)); } + [Fact] + public async Task CustomInterfaceAndNamespace() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GenerateAutomaticInterface("CustomNamespace", "ISpecialInterface")] + public class DemoClassWithCustomInterfaceName : ISpecialInterface + { + /// + /// This is a test method + /// + public void Test() { } + } + } + + """; + + await Verify(Infrastructure.GenerateCode(code)); + } + + [Fact] + public async Task CustomInterfaceAndNamespaceParametersReversed() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GenerateAutomaticInterface(interfaceName: "ISpecialInterface", namespaceName: "CustomNamespace")] + public class DemoClassWithCustomInterfaceName : ISpecialInterface + { + /// + /// This is a test method + /// + public void Test() { } + } + } + + """; + + await Verify(Infrastructure.GenerateCode(code)); + } + + [Fact] + public async Task CustomInterface() + { + const string code = """ + + using AutomaticInterface; + + namespace AutomaticInterfaceExample + { + [GenerateAutomaticInterface(interfaceName: "ISpecialInterface")] + public class DemoClassWithCustomInterfaceName : ISpecialInterface + { + /// + /// This is a test method + /// + public void Test() { } + } + } + + """; + + await Verify(Infrastructure.GenerateCode(code)); + } + [Fact] public async Task CustomNameSpace() { diff --git a/AutomaticInterface/Tests/Tests.csproj b/AutomaticInterface/Tests/Tests.csproj index 15f695b..4c9a0bf 100644 --- a/AutomaticInterface/Tests/Tests.csproj +++ b/AutomaticInterface/Tests/Tests.csproj @@ -23,10 +23,4 @@ - - - - Methods - -