From a97c9b229db77fc3e52b115e2bef05926b0c6910 Mon Sep 17 00:00:00 2001 From: avtc Date: Thu, 16 May 2024 13:35:09 +0300 Subject: [PATCH 1/4] feat: ability to inherit from GenerateAutoInterfaceAttribute. Generated public auto interfaces are inherited from IAutoInterface and IAutoInterface to allow constraint type restrictions. Attributes moved from generated code to separate project (and nuget package), to allow inherited from GenerateAutoInterfaceAttribute classes to be used in linked projects. That package must be installed independently. Generated files are marked with // to turn off warnings. Enabled #nullable for generated files. Added support for nested types, example: class A { public int A { get; } } [GeneratedAutoInterface] class B : A { } will generate: interface IB { int A { get; } } --- .../AutoInterfaceIgnoreAttribute.cs | 9 + .../GenerateAutoInterfaceAttribute.cs | 19 ++ .../GenerateGenericAutoInterfaceAttribute.cs | 20 ++ InterfaceGenerator.Contract/IAutoInterface.cs | 16 ++ .../InterfaceGenerator.Contract.csproj | 23 +++ .../GenericAutoInterfaceTests.cs | 49 +++++ .../GenericInterfaceTests.cs | 24 ++- .../InterfaceGenerator.Tests.csproj | 1 + .../NestedAttributesTests.cs | 181 ++++++++++++++++++ InterfaceGenerator.sln | 19 +- InterfaceGenerator/AutoInterfaceGenerator.cs | 76 +++++--- InterfaceGenerator/Constants.cs | 15 ++ InterfaceGenerator/InterfaceGenerator.csproj | 65 ++++--- InterfaceGenerator/SymbolExtensions.cs | 31 ++- 14 files changed, 484 insertions(+), 64 deletions(-) create mode 100644 InterfaceGenerator.Contract/AutoInterfaceIgnoreAttribute.cs create mode 100644 InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs create mode 100644 InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs create mode 100644 InterfaceGenerator.Contract/IAutoInterface.cs create mode 100644 InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj create mode 100644 InterfaceGenerator.Tests/GenericAutoInterfaceTests.cs create mode 100644 InterfaceGenerator.Tests/NestedAttributesTests.cs create mode 100644 InterfaceGenerator/Constants.cs diff --git a/InterfaceGenerator.Contract/AutoInterfaceIgnoreAttribute.cs b/InterfaceGenerator.Contract/AutoInterfaceIgnoreAttribute.cs new file mode 100644 index 0000000..8566592 --- /dev/null +++ b/InterfaceGenerator.Contract/AutoInterfaceIgnoreAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace InterfaceGenerator +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true)] + public class AutoInterfaceIgnoreAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs new file mode 100644 index 0000000..60e5d26 --- /dev/null +++ b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace InterfaceGenerator +{ + /// + /// Mark the class/struct with the attribute to generate auto interface. + /// If an implementation is public or Visibility modifier is public then auto interface will derive from IAutoInterface. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)] + public class GenerateAutoInterfaceAttribute : Attribute + { + public string? VisibilityModifier { get; set; } + public string? Name { get; set; } + + public GenerateAutoInterfaceAttribute() + { + } + } +} \ No newline at end of file diff --git a/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs b/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs new file mode 100644 index 0000000..f28f564 --- /dev/null +++ b/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace InterfaceGenerator +{ + /// + /// Mark the class/struct with the attribute to generate auto interface. + /// If an implementation is public or Visibility modifier is public then auto interface will derive from IAutoInterface. + /// TImplementer must be public. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)] + public class GenerateGenericAutoInterfaceAttribute : Attribute + { + public string? VisibilityModifier { get; set; } + public string? Name { get; set; } + + public GenerateGenericAutoInterfaceAttribute() + { + } + } +} \ No newline at end of file diff --git a/InterfaceGenerator.Contract/IAutoInterface.cs b/InterfaceGenerator.Contract/IAutoInterface.cs new file mode 100644 index 0000000..c5a81ea --- /dev/null +++ b/InterfaceGenerator.Contract/IAutoInterface.cs @@ -0,0 +1,16 @@ +namespace InterfaceGenerator +{ + /// + /// The base interface for generated public auto interfaces + /// + public interface IAutoInterface + { + } + + /// + /// The base generic interface for generated public auto interfaces, where T is an implementation type + /// + public interface IAutoInterface : IAutoInterface + { + } +} \ No newline at end of file diff --git a/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj b/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj new file mode 100644 index 0000000..e43d345 --- /dev/null +++ b/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj @@ -0,0 +1,23 @@ + + + + netstandard2 + latest + enable + 1.0.14 + 1.0.14 + false + + + + R. David + InterfaceGenerator.Contract + Contract of a source generator that creates interfaces from implementations + https://github.com/daver32/InterfaceGenerator + https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE + https://github.com/daver32/InterfaceGenerator + git + $(AssemblyName) + + + diff --git a/InterfaceGenerator.Tests/GenericAutoInterfaceTests.cs b/InterfaceGenerator.Tests/GenericAutoInterfaceTests.cs new file mode 100644 index 0000000..23ad569 --- /dev/null +++ b/InterfaceGenerator.Tests/GenericAutoInterfaceTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Reflection; +using FluentAssertions; +using Xunit; + +namespace InterfaceGenerator.Tests; + +public class GenericAutoInterfaceTests +{ + [Fact] + public void GenericParametersGeneratedCorrectly() + { + var t = typeof(IGenericAutoInterfaceTestsService<,>); + var genericArgs = t.GetGenericArguments(); + + genericArgs.Should().HaveCount(2); + genericArgs[0].Name.Should().Be("TX"); + genericArgs[1].Name.Should().Be("TY"); + + genericArgs[0].IsClass.Should().BeTrue(); + genericArgs[0] + .GenericParameterAttributes + .Should() + .HaveFlag(GenericParameterAttributes.DefaultConstructorConstraint); + + var iEquatableOfTx = typeof(IEquatable<>).MakeGenericType(genericArgs[0]); + genericArgs[0].GetGenericParameterConstraints().Should().HaveCount(1).And.Contain(iEquatableOfTx); + + genericArgs[1].IsValueType.Should().BeTrue(); + + // base + var interfaces = t.GetInterfaces(); + interfaces.Should().HaveCount(2); + interfaces[0].Name.Should().Be(typeof(IAutoInterface<>).Name); + interfaces[1].Name.Should().Be(typeof(IAutoInterface).Name); + var interfaceGenericArgs = interfaces[0].GetGenericArguments(); + interfaceGenericArgs.Should().HaveCount(1); + interfaceGenericArgs[0].Name.Should().Be(typeof(GenericAutoInterfaceTestsService<,>).Name); + } +} + +[GenerateGenericAutoInterface] +// ReSharper disable once UnusedType.Global +public class GenericAutoInterfaceTestsService : IGenericAutoInterfaceTestsService + where TX : class, IEquatable, new () + where TY : struct +{ + public string A { get; set; } +} \ No newline at end of file diff --git a/InterfaceGenerator.Tests/GenericInterfaceTests.cs b/InterfaceGenerator.Tests/GenericInterfaceTests.cs index 8642e3b..9b919f7 100644 --- a/InterfaceGenerator.Tests/GenericInterfaceTests.cs +++ b/InterfaceGenerator.Tests/GenericInterfaceTests.cs @@ -10,7 +10,8 @@ public class GenericInterfaceTests [Fact] public void GenericParametersGeneratedCorrectly() { - var genericArgs = typeof(IGenericInterfaceTestsService<,>).GetGenericArguments(); + var t = typeof(IGenericInterfaceTestsService<,>); + var genericArgs = t.GetGenericArguments(); genericArgs.Should().HaveCount(2); genericArgs[0].Name.Should().Be("TX"); @@ -26,6 +27,19 @@ public void GenericParametersGeneratedCorrectly() genericArgs[0].GetGenericParameterConstraints().Should().HaveCount(1).And.Contain(iEquatableOfTx); genericArgs[1].IsValueType.Should().BeTrue(); + + // does not implement IAutoInterface because not public + t.GetInterfaces().Should().HaveCount(0); + } + + [Fact] + public void ImplementsIAutoInterface() + { + var t = typeof(IGenericInterfaceTestsService2<,>); + + var interfaces = t.GetInterfaces(); + interfaces.Should().HaveCount(1); + interfaces[0].Should().Be(typeof(IAutoInterface)); } } @@ -35,4 +49,12 @@ internal class GenericInterfaceTestsService : IGenericInterfaceTestsServ where TX : class, IEquatable, new() where TY : struct { +} + +[GenerateAutoInterface] +// ReSharper disable once UnusedType.Global +public class GenericInterfaceTestsService2 : IGenericInterfaceTestsService2 + where TX : class, IEquatable, new() + where TY : struct +{ } \ No newline at end of file diff --git a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj index db3e2fe..b08d26c 100644 --- a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj +++ b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/InterfaceGenerator.Tests/NestedAttributesTests.cs b/InterfaceGenerator.Tests/NestedAttributesTests.cs new file mode 100644 index 0000000..cf89d89 --- /dev/null +++ b/InterfaceGenerator.Tests/NestedAttributesTests.cs @@ -0,0 +1,181 @@ +using System.Runtime.CompilerServices; +using FluentAssertions; +using FluentAssertions.Common; +using Xunit; + +namespace InterfaceGenerator.Tests; + +public class NestedAttributesTests +{ + private readonly INestedAttributesService _sut; + + public NestedAttributesTests() + { + _sut = new NestedAttributesService(); + } + + [Fact] + public void GetSetIndexer_IsImplemented() + { + var indexer = typeof(INestedAttributesService).GetIndexerByParameterTypes(new[] { typeof(string) }); + + indexer.Should().NotBeNull(); + + indexer.GetMethod.Should().NotBeNull(); + indexer.SetMethod.Should().NotBeNull(); + + var _ = _sut[string.Empty]; + _sut[string.Empty] = 0; + } + + [Fact] + public void PublicProperty_IsImplemented() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.PublicProperty))!; + + prop.Should().NotBeNull(); + + prop.GetMethod.Should().NotBeNull(); + prop.SetMethod.Should().NotBeNull(); + + var _ = _sut.PublicProperty; + _sut.PublicProperty = string.Empty; + } + + [Fact] + public void InitProperty_IsImplemented() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.InitOnlyProperty))!; + + prop.Should().NotBeNull(); + + prop.GetMethod.Should().NotBeNull(); + prop.SetMethod.Should().NotBeNull(); + + prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); + + var _ = _sut.InitOnlyProperty; + } + + [Fact] + public void PrivateSetter_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.PropertyWithPrivateSetter))!; + + prop.Should().NotBeNull(); + + prop.GetMethod.Should().NotBeNull(); + prop.SetMethod.Should().BeNull(); + + var _ = _sut.PropertyWithPrivateSetter; + } + + [Fact] + public void PrivateGetter_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.PropertyWithPrivateGetter))!; + + prop.Should().NotBeNull(); + + prop.SetMethod.Should().NotBeNull(); + prop.GetMethod.Should().BeNull(); + + _sut.PropertyWithPrivateGetter = string.Empty; + } + + [Fact] + public void ProtectedSetter_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.PropertyWithProtectedSetter))!; + + prop.Should().NotBeNull(); + + prop.GetMethod.Should().NotBeNull(); + prop.SetMethod.Should().BeNull(); + + var _ = _sut.PropertyWithProtectedSetter; + } + + [Fact] + public void ProtectedGetter_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(INestedAttributesService.PropertyWithProtectedGetter))!; + + prop.Should().NotBeNull(); + + prop.SetMethod.Should().NotBeNull(); + prop.GetMethod.Should().BeNull(); + + _sut.PropertyWithProtectedGetter = string.Empty; + } + + [Fact] + public void IgnoredProperty_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(NestedAttributesService.IgnoredProperty)); + + prop.Should().BeNull(); + } + + [Fact] + public void StaticProperty_IsOmitted() + { + var prop = typeof(INestedAttributesService) + .GetProperty(nameof(NestedAttributesService.StaticProperty)); + + prop.Should().BeNull(); + } + + [Fact] + public void ImplementsIAutoInterface() + { + var interfaces = typeof(INestedAttributesService).GetInterfaces(); + interfaces.Should().HaveCount(1); + interfaces[0].Name.Should().Be(typeof(IAutoInterface).Name); + } +} + +public class NestedGenerateAttribute : GenerateAutoInterfaceAttribute +{ +} + +public class NestedIgnoreAttribute : AutoInterfaceIgnoreAttribute +{ +} + +// ReSharper disable UnusedMember.Local, ValueParameterNotUsed +[NestedGenerate] +public class NestedAttributesService : INestedAttributesService +{ + public int this[string x] + { + get => 0; + set + { + } + } + + public string PublicProperty { get; set; } + + public string InitOnlyProperty { get; init; } + + public string PropertyWithPrivateSetter { get; private set; } + + public string PropertyWithPrivateGetter { private get; set; } + + public string PropertyWithProtectedSetter { get; protected set; } + + public string PropertyWithProtectedGetter { protected get; set; } + + [NestedIgnore] public string IgnoredProperty { get; set; } + + public static string StaticProperty { get; set; } +} +// ReSharper enable UnusedMember.Local, ValueParameterNotUsed \ No newline at end of file diff --git a/InterfaceGenerator.sln b/InterfaceGenerator.sln index 3cae7ca..960d67c 100644 --- a/InterfaceGenerator.sln +++ b/InterfaceGenerator.sln @@ -1,8 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator", "InterfaceGenerator\InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}" +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceGenerator", "InterfaceGenerator\InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator.Tests", "InterfaceGenerator.Tests\InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceGenerator.Tests", "InterfaceGenerator.Tests\InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceGenerator.Contract", "InterfaceGenerator.Contract\InterfaceGenerator.Contract.csproj", "{51AC3DB1-FE08-4481-8DDF-52594FC17980}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,5 +23,15 @@ Global {D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU {D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU {D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}.Release|Any CPU.Build.0 = Release|Any CPU + {51AC3DB1-FE08-4481-8DDF-52594FC17980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51AC3DB1-FE08-4481-8DDF-52594FC17980}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51AC3DB1-FE08-4481-8DDF-52594FC17980}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51AC3DB1-FE08-4481-8DDF-52594FC17980}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {797494DA-A9BE-48BB-A5A0-6D1BD66754F5} EndGlobalSection EndGlobal diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index e4ec264..b026d06 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -7,8 +7,10 @@ using System.Linq; using System.Text; using System.Threading; +using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace InterfaceGenerator @@ -17,6 +19,7 @@ namespace InterfaceGenerator public class AutoInterfaceGenerator : ISourceGenerator { private INamedTypeSymbol _generateAutoInterfaceAttribute = null!; + private INamedTypeSymbol _generateGenericAutoInterfaceAttribute = null!; private INamedTypeSymbol _ignoreAttribute = null!; public void Initialize(GeneratorInitializationContext context) @@ -47,7 +50,7 @@ public void Execute(GeneratorExecutionContext context) private static void RaiseExceptionDiagnostic(GeneratorExecutionContext context, Exception exception) { var descriptor = new DiagnosticDescriptor( - "InterfaceGenerator.CriticalError", + "IG0001", $"Exception thrown in InterfaceGenerator", $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}", "InterfaceGenerator", @@ -67,19 +70,11 @@ private void ExecuteCore(GeneratorExecutionContext context) var prevCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - GenerateAttributes(context); GenerateInterfaces(context); Thread.CurrentThread.CurrentCulture = prevCulture; } - private static void GenerateAttributes(GeneratorExecutionContext context) - { - context.AddSource( - Attributes.GenerateAutoInterfaceClassname, - SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8)); - } - private void GenerateInterfaces(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not SyntaxReceiver receiver) @@ -96,7 +91,8 @@ private void GenerateInterfaces(GeneratorExecutionContext context) foreach (var implTypeSymbol in classSymbols) { - if (!implTypeSymbol.TryGetAttribute(_generateAutoInterfaceAttribute, out var attributes)) + if (!implTypeSymbol.TryGetAttribute(_generateAutoInterfaceAttribute, out var attributes) + && !implTypeSymbol.TryGetAttribute(_generateGenericAutoInterfaceAttribute, out attributes)) { continue; } @@ -108,7 +104,7 @@ private void GenerateInterfaces(GeneratorExecutionContext context) classSymbolNames.Add(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)); - var attribute = attributes.Single(); + var attribute = attributes.Last(); var source = SourceText.From(GenerateInterfaceCode(implTypeSymbol, attribute), Encoding.UTF8); context.AddSource($"{implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)}_AutoInterface.g.cs", source); @@ -117,7 +113,7 @@ private void GenerateInterfaces(GeneratorExecutionContext context) private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) { - string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName); + string? result = attributeData.GetNamedParamValue(Constants.VisibilityModifierPropName); if (!string.IsNullOrEmpty(result)) { return result!; @@ -132,7 +128,7 @@ private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeD private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) { - return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName) ?? $"I{implTypeSymbol.Name}"; + return attributeData.GetNamedParamValue(Constants.GenerateAutoInterfaceAttribute) ?? $"I{implTypeSymbol.Name}"; } private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) @@ -145,6 +141,8 @@ private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeD var interfaceName = InferInterfaceName(implTypeSymbol, attributeData); var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData); + codeWriter.WriteLine("// "); + codeWriter.WriteLine("#nullable enable"); codeWriter.WriteLine("namespace {0}", namespaceName); codeWriter.WriteLine("{"); @@ -152,6 +150,8 @@ private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeD WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol); codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName); WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); + WriteBaseInterface(codeWriter, attributeData, implTypeSymbol); + WriteTypeParameterConstraintsIfNeeded(codeWriter, implTypeSymbol); codeWriter.WriteLine(); codeWriter.WriteLine("{"); @@ -163,6 +163,7 @@ private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeD --codeWriter.Indent; codeWriter.WriteLine("}"); + codeWriter.WriteLine("#nullable restore"); codeWriter.Flush(); stream.Seek(0, SeekOrigin.Begin); @@ -170,6 +171,22 @@ private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeD return reader.ReadToEnd(); } + private void WriteBaseInterface(IndentedTextWriter codeWriter, AttributeData attributeData, INamedTypeSymbol implTypeSymbol) + { + if (implTypeSymbol.DeclaredAccessibility != Accessibility.Public) + return; + if (attributeData.AttributeClass!.Is(_generateAutoInterfaceAttribute)) + { + codeWriter.Write($" : {Constants.AttributesNamespace}.{Constants.IAutoInterface}"); + } + else if (attributeData.AttributeClass!.Is(_generateGenericAutoInterfaceAttribute)) + { + codeWriter.Write($" : {Constants.AttributesNamespace}.{Constants.IAutoInterface}<{implTypeSymbol.Name}"); + WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); + codeWriter.Write(">"); + } + } + private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol) { if (!implTypeSymbol.IsGenericType) @@ -180,16 +197,25 @@ private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbo writer.Write("<"); writer.WriteJoin(", ", implTypeSymbol.TypeParameters.Select(x => x.Name)); writer.Write(">"); + } + + private static void WriteTypeParameterConstraintsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol) + { + if (!implTypeSymbol.IsGenericType) + { + return; + } WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters); } - private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamespaceOrTypeSymbol implTypeSymbol) + private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamedTypeSymbol implTypeSymbol) { - foreach (var member in implTypeSymbol.GetMembers()) + foreach (var member in implTypeSymbol.GetAllMembers()) { - if (member.DeclaredAccessibility != Accessibility.Public || - member.HasAttribute(_ignoreAttribute)) + if (member.DeclaredAccessibility != Accessibility.Public + || member.HasAttribute(_ignoreAttribute) + || member.ContainingType.Name == nameof(Object)) { continue; } @@ -429,10 +455,13 @@ private static void WriteTypeParameterConstraints( private void InitAttributes(Compilation compilation) { _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName( - $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}")!; - + $"{Constants.AttributesNamespace}.{Constants.GenerateAutoInterfaceAttribute}")!; + + _generateGenericAutoInterfaceAttribute = compilation.GetTypeByMetadataName( + $"{Constants.AttributesNamespace}.{Constants.GenerateGenericAutoInterfaceAttribute}")!; + _ignoreAttribute = compilation.GetTypeByMetadataName( - $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}")!; + $"{Constants.AttributesNamespace}.{Constants.AutoInterfaceIgnoreAttribute}")!; } private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver) @@ -449,12 +478,7 @@ private static INamedTypeSymbol GetTypeSymbol(Compilation compilation, SyntaxNod private static Compilation GetCompilation(GeneratorExecutionContext context) { - var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions; - - var compilation = context.Compilation.AddSyntaxTrees( - CSharpSyntaxTree.ParseText( - SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8), options)); - + var compilation = context.Compilation; return compilation; } } diff --git a/InterfaceGenerator/Constants.cs b/InterfaceGenerator/Constants.cs new file mode 100644 index 0000000..4583dd7 --- /dev/null +++ b/InterfaceGenerator/Constants.cs @@ -0,0 +1,15 @@ +namespace InterfaceGenerator +{ + + internal class Constants + { + public const string AttributesNamespace = nameof(InterfaceGenerator); + public const string GenerateAutoInterfaceAttribute = "GenerateAutoInterfaceAttribute"; + public const string GenerateGenericAutoInterfaceAttribute = "GenerateGenericAutoInterfaceAttribute"; + public const string AutoInterfaceIgnoreAttribute = "AutoInterfaceIgnoreAttribute"; + public const string IAutoInterface = "IAutoInterface"; + + public const string VisibilityModifierPropName = "VisibilityModifier"; + public const string InterfaceNamePropName = "Name"; + } +} \ No newline at end of file diff --git a/InterfaceGenerator/InterfaceGenerator.csproj b/InterfaceGenerator/InterfaceGenerator.csproj index 9da3776..5ab88ed 100644 --- a/InterfaceGenerator/InterfaceGenerator.csproj +++ b/InterfaceGenerator/InterfaceGenerator.csproj @@ -1,35 +1,38 @@ - - - netstandard2 - 9.0 - enable - 1.0.14 + + + netstandard2 + latest + enable + 1.0.14 + 1.0.14 - true - false - false - true - - - - R. David - InterfaceGenerator - A source generator that creates interfaces from implementations - https://github.com/daver32/InterfaceGenerator - https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE - https://github.com/daver32/InterfaceGenerator - git - + true + false + false + true + true + - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + R. David + InterfaceGenerator + A source generator that creates interfaces from implementations + https://github.com/daver32/InterfaceGenerator + https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE + https://github.com/daver32/InterfaceGenerator + git + $(AssemblyName) + - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/InterfaceGenerator/SymbolExtensions.cs b/InterfaceGenerator/SymbolExtensions.cs index 5f309ae..dfed2f7 100644 --- a/InterfaceGenerator/SymbolExtensions.cs +++ b/InterfaceGenerator/SymbolExtensions.cs @@ -12,15 +12,22 @@ public static bool TryGetAttribute( INamedTypeSymbol attributeType, out IEnumerable attributes) { - attributes = symbol.GetAttributes() - .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); + attributes = symbol + .GetAttributes() + .Where(a => a.AttributeClass!.GetBaseTypesAndThis().Any(i => SymbolEqualityComparer.Default.Equals(i, attributeType))); return attributes.Any(); } public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) { - return symbol.GetAttributes() - .Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)); + return symbol + .GetAttributes() + .Any(a => a.AttributeClass!.GetBaseTypesAndThis().Any(i => SymbolEqualityComparer.Default.Equals(i, attributeType))); + } + + public static bool Is(this ITypeSymbol symbol, INamedTypeSymbol baseType) + { + return symbol.GetBaseTypesAndThis().Any(i => SymbolEqualityComparer.Default.Equals(i, baseType)); } //Ref: https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn @@ -64,5 +71,21 @@ private static bool IsRootNamespace(ISymbol symbol) { return symbol is INamespaceSymbol { IsGlobalNamespace: true }; } + + // Ref: https://github.com/dotnet/roslyn/blob/0c8ac4c91d0c61869a523433792691adab34242e/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs#L114 + private static IEnumerable GetBaseTypesAndThis(this ITypeSymbol? type) + { + var current = type; + while (current != null) + { + yield return current; + current = current.BaseType; + } + } + + public static IEnumerable GetAllMembers(this ITypeSymbol type) + { + return type.GetBaseTypesAndThis().SelectMany(x => x.GetMembers()); + } } } \ No newline at end of file From 694e7fec6abc713c59e366ddab375052e3df3390 Mon Sep 17 00:00:00 2001 From: avtc Date: Thu, 16 May 2024 20:02:58 +0300 Subject: [PATCH 2/4] feat: include Contracts into the same nuget as InterfaceGenerator --- .../InterfaceGenerator.Contract.csproj | 14 ------ .../InterfaceGenerator.Tests.csproj | 37 ++++++++++------ InterfaceGenerator/Attributes.cs | 43 ------------------- InterfaceGenerator/AutoInterfaceGenerator.cs | 14 +++--- InterfaceGenerator/Constants.cs | 15 ------- InterfaceGenerator/InterfaceGenerator.csproj | 13 +++++- 6 files changed, 42 insertions(+), 94 deletions(-) delete mode 100644 InterfaceGenerator/Attributes.cs delete mode 100644 InterfaceGenerator/Constants.cs diff --git a/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj b/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj index e43d345..9d4ce57 100644 --- a/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj +++ b/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj @@ -4,20 +4,6 @@ netstandard2 latest enable - 1.0.14 - 1.0.14 - false - - - - R. David - InterfaceGenerator.Contract - Contract of a source generator that creates interfaces from implementations - https://github.com/daver32/InterfaceGenerator - https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE - https://github.com/daver32/InterfaceGenerator - git - $(AssemblyName) diff --git a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj index b08d26c..4db54ca 100644 --- a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj +++ b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj @@ -1,21 +1,30 @@ - - net6.0 + + net6.0 - false - + false + - - - - - - + + + + + + - - - - + + + + + + + + + diff --git a/InterfaceGenerator/Attributes.cs b/InterfaceGenerator/Attributes.cs deleted file mode 100644 index 754b8ec..0000000 --- a/InterfaceGenerator/Attributes.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace InterfaceGenerator -{ - - internal class Attributes - { - public const string AttributesNamespace = nameof(InterfaceGenerator); - - public const string GenerateAutoInterfaceClassname = "GenerateAutoInterfaceAttribute"; - public const string AutoInterfaceIgnoreAttributeClassname = "AutoInterfaceIgnoreAttribute"; - - public const string VisibilityModifierPropName = "VisibilityModifier"; - public const string InterfaceNamePropName = "Name"; - - public static readonly string AttributesSourceCode = $@" - -using System; -using System.Diagnostics; - -#nullable enable - -namespace {AttributesNamespace} -{{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] - [Conditional(""CodeGeneration"")] - internal sealed class {GenerateAutoInterfaceClassname} : Attribute - {{ - public string? {VisibilityModifierPropName} {{ get; init; }} - public string? {InterfaceNamePropName} {{ get; init; }} - - public {GenerateAutoInterfaceClassname}() - {{ - }} - }} - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false)] - [Conditional(""CodeGeneration"")] - internal sealed class {AutoInterfaceIgnoreAttributeClassname} : Attribute - {{ - }} -}} -"; - } -} \ No newline at end of file diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index b026d06..e12c90d 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -113,7 +113,7 @@ private void GenerateInterfaces(GeneratorExecutionContext context) private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) { - string? result = attributeData.GetNamedParamValue(Constants.VisibilityModifierPropName); + string? result = attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.VisibilityModifier)); if (!string.IsNullOrEmpty(result)) { return result!; @@ -128,7 +128,7 @@ private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeD private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) { - return attributeData.GetNamedParamValue(Constants.GenerateAutoInterfaceAttribute) ?? $"I{implTypeSymbol.Name}"; + return attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.Name)) ?? $"I{implTypeSymbol.Name}"; } private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) @@ -177,11 +177,11 @@ private void WriteBaseInterface(IndentedTextWriter codeWriter, AttributeData att return; if (attributeData.AttributeClass!.Is(_generateAutoInterfaceAttribute)) { - codeWriter.Write($" : {Constants.AttributesNamespace}.{Constants.IAutoInterface}"); + codeWriter.Write($" : {typeof(IAutoInterface).Namespace}.{nameof(IAutoInterface)}"); } else if (attributeData.AttributeClass!.Is(_generateGenericAutoInterfaceAttribute)) { - codeWriter.Write($" : {Constants.AttributesNamespace}.{Constants.IAutoInterface}<{implTypeSymbol.Name}"); + codeWriter.Write($" : {typeof(IAutoInterface).Namespace}.{nameof(IAutoInterface)}<{implTypeSymbol.Name}"); WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); codeWriter.Write(">"); } @@ -455,13 +455,13 @@ private static void WriteTypeParameterConstraints( private void InitAttributes(Compilation compilation) { _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName( - $"{Constants.AttributesNamespace}.{Constants.GenerateAutoInterfaceAttribute}")!; + $"{typeof(GenerateAutoInterfaceAttribute).Namespace}.{nameof(GenerateAutoInterfaceAttribute)}")!; _generateGenericAutoInterfaceAttribute = compilation.GetTypeByMetadataName( - $"{Constants.AttributesNamespace}.{Constants.GenerateGenericAutoInterfaceAttribute}")!; + $"{typeof(GenerateGenericAutoInterfaceAttribute).Namespace}.{nameof(GenerateGenericAutoInterfaceAttribute)}")!; _ignoreAttribute = compilation.GetTypeByMetadataName( - $"{Constants.AttributesNamespace}.{Constants.AutoInterfaceIgnoreAttribute}")!; + $"{typeof(AutoInterfaceIgnoreAttribute).Namespace}.{nameof(AutoInterfaceIgnoreAttribute)}")!; } private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver) diff --git a/InterfaceGenerator/Constants.cs b/InterfaceGenerator/Constants.cs deleted file mode 100644 index 4583dd7..0000000 --- a/InterfaceGenerator/Constants.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace InterfaceGenerator -{ - - internal class Constants - { - public const string AttributesNamespace = nameof(InterfaceGenerator); - public const string GenerateAutoInterfaceAttribute = "GenerateAutoInterfaceAttribute"; - public const string GenerateGenericAutoInterfaceAttribute = "GenerateGenericAutoInterfaceAttribute"; - public const string AutoInterfaceIgnoreAttribute = "AutoInterfaceIgnoreAttribute"; - public const string IAutoInterface = "IAutoInterface"; - - public const string VisibilityModifierPropName = "VisibilityModifier"; - public const string InterfaceNamePropName = "Name"; - } -} \ No newline at end of file diff --git a/InterfaceGenerator/InterfaceGenerator.csproj b/InterfaceGenerator/InterfaceGenerator.csproj index 5ab88ed..ca4b1d8 100644 --- a/InterfaceGenerator/InterfaceGenerator.csproj +++ b/InterfaceGenerator/InterfaceGenerator.csproj @@ -6,7 +6,6 @@ 1.0.14 1.0.14 - true false false true @@ -32,7 +31,19 @@ + + + + + + + + + + + + From 48c4530db281f323898bb65f2b55b6989a482a34 Mon Sep 17 00:00:00 2001 From: avtc Date: Wed, 29 May 2024 11:46:06 +0300 Subject: [PATCH 3/4] feat: interface name template --- .../GenerateAutoInterfaceAttribute.cs | 4 ++++ .../GenerateGenericAutoInterfaceAttribute.cs | 4 ++++ .../InterfaceNameTests.cs | 23 +++++++++++++++++++ InterfaceGenerator/AutoInterfaceGenerator.cs | 4 +++- 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 InterfaceGenerator.Tests/InterfaceNameTests.cs diff --git a/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs index 60e5d26..0682713 100644 --- a/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs +++ b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs @@ -11,6 +11,10 @@ public class GenerateAutoInterfaceAttribute : Attribute { public string? VisibilityModifier { get; set; } public string? Name { get; set; } + /// + /// Default is "I{Name}" + /// + public string NameTemplate { get; set; } = "I{Name}"; public GenerateAutoInterfaceAttribute() { diff --git a/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs b/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs index f28f564..d9fb3c6 100644 --- a/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs +++ b/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs @@ -12,6 +12,10 @@ public class GenerateGenericAutoInterfaceAttribute : Attribute { public string? VisibilityModifier { get; set; } public string? Name { get; set; } + /// + /// Default is "I{Name}" + /// + public string NameTemplate { get; set; } = "I{Name}"; public GenerateGenericAutoInterfaceAttribute() { diff --git a/InterfaceGenerator.Tests/InterfaceNameTests.cs b/InterfaceGenerator.Tests/InterfaceNameTests.cs new file mode 100644 index 0000000..e2962c4 --- /dev/null +++ b/InterfaceGenerator.Tests/InterfaceNameTests.cs @@ -0,0 +1,23 @@ +namespace InterfaceGenerator.Tests; + +[GenerateAutoInterface(Name = "ICustomNameInterface")] +internal class CustomName1 : ICustomNameInterface +{ + +} + +[GenerateAutoInterface(NameTemplate = "IPrefix{Name}Suffix")] +internal class CustomName2 : IPrefixCustomName2Suffix +{ + +} + +[NestedCustomName(NameTemplate = "INested{Name}")] +internal class CustomName3 : INestedCustomName3 +{ + +} + +public class NestedCustomNameAttribute : GenerateAutoInterfaceAttribute +{ +} \ No newline at end of file diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index e12c90d..633f3f3 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -128,7 +128,9 @@ private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeD private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) { - return attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.Name)) ?? $"I{implTypeSymbol.Name}"; + return attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.Name)) + ?? attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.NameTemplate))?.Replace("{Name}", implTypeSymbol.Name) + ?? $"I{implTypeSymbol.Name}"; } private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) From d043e0a5ea2d2817806005055b384a089ebc6ba6 Mon Sep 17 00:00:00 2001 From: avtc Date: Thu, 30 May 2024 13:09:23 +0300 Subject: [PATCH 4/4] feat: override interface name template for nested attribute --- .../AutoInterfaceNameTemplateAttribute.cs | 23 ++++++++++++++ .../InterfaceNameTests.cs | 15 ++++++++-- InterfaceGenerator/AttributeDataExtensions.cs | 30 ++++++++++++++++--- InterfaceGenerator/AutoInterfaceGenerator.cs | 16 +++++++--- 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 InterfaceGenerator.Contract/AutoInterfaceNameTemplateAttribute.cs diff --git a/InterfaceGenerator.Contract/AutoInterfaceNameTemplateAttribute.cs b/InterfaceGenerator.Contract/AutoInterfaceNameTemplateAttribute.cs new file mode 100644 index 0000000..625b243 --- /dev/null +++ b/InterfaceGenerator.Contract/AutoInterfaceNameTemplateAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace InterfaceGenerator +{ + /// + /// Mark the attribute derived from GenerateAutoInterfaceAttribute or from GenerateGenericAutoInterfaceAttribute + /// with the attribute to override interface name template. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class AutoInterfaceNameTemplateAttribute : Attribute + { + /// Default is "I{Name}" + public AutoInterfaceNameTemplateAttribute(string NameTemplate) + { + this.NameTemplate = NameTemplate; + } + + /// + /// Default is "I{Name}" + /// + public string NameTemplate { get; set; } = "I{Name}"; + } +} \ No newline at end of file diff --git a/InterfaceGenerator.Tests/InterfaceNameTests.cs b/InterfaceGenerator.Tests/InterfaceNameTests.cs index e2962c4..c8f50f1 100644 --- a/InterfaceGenerator.Tests/InterfaceNameTests.cs +++ b/InterfaceGenerator.Tests/InterfaceNameTests.cs @@ -12,12 +12,23 @@ internal class CustomName2 : IPrefixCustomName2Suffix } -[NestedCustomName(NameTemplate = "INested{Name}")] +[NestedCustomName3(NameTemplate = "INested{Name}")] internal class CustomName3 : INestedCustomName3 { } -public class NestedCustomNameAttribute : GenerateAutoInterfaceAttribute +public class NestedCustomName3Attribute : GenerateAutoInterfaceAttribute +{ +} + +[NestedCustomName4] +public class CustomName4 : INestedCustomName4 +{ + +} + +[AutoInterfaceNameTemplate("INested{Name}")] +public class NestedCustomName4Attribute : GenerateGenericAutoInterfaceAttribute { } \ No newline at end of file diff --git a/InterfaceGenerator/AttributeDataExtensions.cs b/InterfaceGenerator/AttributeDataExtensions.cs index 084842a..7aa481e 100644 --- a/InterfaceGenerator/AttributeDataExtensions.cs +++ b/InterfaceGenerator/AttributeDataExtensions.cs @@ -1,14 +1,36 @@ -using System.Linq; using Microsoft.CodeAnalysis; namespace InterfaceGenerator { internal static class AttributeDataExtensions { - public static string? GetNamedParamValue(this AttributeData attributeData, string paramName) + public static TValue? GetParamValue(this AttributeData attributeData, string paramName) { - var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName); - return pair.Value.Value?.ToString(); + // Check constructor arguments + var constructor = attributeData.AttributeConstructor; + if (constructor != null) + { + var parameters = constructor.Parameters; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].Name == paramName) + { + var argument = attributeData.ConstructorArguments[i]; + return (TValue?)argument.Value; + } + } + } + + // Check named arguments + foreach (var arg in attributeData.NamedArguments) + { + if (arg.Key == paramName) + { + return (TValue?)arg.Value.Value; + } + } + + return default; } } } \ No newline at end of file diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index 633f3f3..5ef725f 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -21,6 +21,7 @@ public class AutoInterfaceGenerator : ISourceGenerator private INamedTypeSymbol _generateAutoInterfaceAttribute = null!; private INamedTypeSymbol _generateGenericAutoInterfaceAttribute = null!; private INamedTypeSymbol _ignoreAttribute = null!; + private INamedTypeSymbol _nameTemplateAttribute = null!; public void Initialize(GeneratorInitializationContext context) { @@ -113,7 +114,7 @@ private void GenerateInterfaces(GeneratorExecutionContext context) private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) { - string? result = attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.VisibilityModifier)); + string? result = attributeData.GetParamValue(nameof(GenerateAutoInterfaceAttribute.VisibilityModifier)); if (!string.IsNullOrEmpty(result)) { return result!; @@ -126,10 +127,14 @@ private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeD }; } - private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) + private string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) { - return attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.Name)) - ?? attributeData.GetNamedParamValue(nameof(GenerateAutoInterfaceAttribute.NameTemplate))?.Replace("{Name}", implTypeSymbol.Name) + return attributeData.GetParamValue(nameof(GenerateAutoInterfaceAttribute.Name)) + ?? attributeData.GetParamValue(nameof(GenerateAutoInterfaceAttribute.NameTemplate))?.Replace("{Name}", implTypeSymbol.Name) + ?? (attributeData.AttributeClass?.TryGetAttribute(_nameTemplateAttribute, out var nameTemplateAttributes) == true + ? nameTemplateAttributes.First().GetParamValue( + nameof(AutoInterfaceNameTemplateAttribute.NameTemplate))!.Replace("{Name}", implTypeSymbol.Name) + : null) ?? $"I{implTypeSymbol.Name}"; } @@ -464,6 +469,9 @@ private void InitAttributes(Compilation compilation) _ignoreAttribute = compilation.GetTypeByMetadataName( $"{typeof(AutoInterfaceIgnoreAttribute).Namespace}.{nameof(AutoInterfaceIgnoreAttribute)}")!; + + _nameTemplateAttribute = compilation.GetTypeByMetadataName( + $"{typeof(AutoInterfaceNameTemplateAttribute).Namespace}.{nameof(AutoInterfaceNameTemplateAttribute)}")!; } private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver)