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/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.Contract/GenerateAutoInterfaceAttribute.cs b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs
new file mode 100644
index 0000000..0682713
--- /dev/null
+++ b/InterfaceGenerator.Contract/GenerateAutoInterfaceAttribute.cs
@@ -0,0 +1,23 @@
+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; }
+ ///
+ /// Default is "I{Name}"
+ ///
+ public string NameTemplate { get; set; } = "I{Name}";
+
+ 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..d9fb3c6
--- /dev/null
+++ b/InterfaceGenerator.Contract/GenerateGenericAutoInterfaceAttribute.cs
@@ -0,0 +1,24 @@
+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; }
+ ///
+ /// Default is "I{Name}"
+ ///
+ public string NameTemplate { get; set; } = "I{Name}";
+
+ 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..9d4ce57
--- /dev/null
+++ b/InterfaceGenerator.Contract/InterfaceGenerator.Contract.csproj
@@ -0,0 +1,9 @@
+
+
+
+ netstandard2
+ latest
+ enable
+
+
+
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..4db54ca 100644
--- a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj
+++ b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj
@@ -1,20 +1,30 @@
-
- net6.0
+
+ net6.0
- false
-
+ false
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/InterfaceGenerator.Tests/InterfaceNameTests.cs b/InterfaceGenerator.Tests/InterfaceNameTests.cs
new file mode 100644
index 0000000..c8f50f1
--- /dev/null
+++ b/InterfaceGenerator.Tests/InterfaceNameTests.cs
@@ -0,0 +1,34 @@
+namespace InterfaceGenerator.Tests;
+
+[GenerateAutoInterface(Name = "ICustomNameInterface")]
+internal class CustomName1 : ICustomNameInterface
+{
+
+}
+
+[GenerateAutoInterface(NameTemplate = "IPrefix{Name}Suffix")]
+internal class CustomName2 : IPrefixCustomName2Suffix
+{
+
+}
+
+[NestedCustomName3(NameTemplate = "INested{Name}")]
+internal class CustomName3 : INestedCustomName3
+{
+
+}
+
+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.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/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/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 e4ec264..5ef725f 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,7 +19,9 @@ namespace InterfaceGenerator
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)
{
@@ -47,7 +51,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 +71,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 +92,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 +105,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 +114,7 @@ private void GenerateInterfaces(GeneratorExecutionContext context)
private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData)
{
- string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName);
+ string? result = attributeData.GetParamValue(nameof(GenerateAutoInterfaceAttribute.VisibilityModifier));
if (!string.IsNullOrEmpty(result))
{
return result!;
@@ -130,9 +127,15 @@ 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(Attributes.InterfaceNamePropName) ?? $"I{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}";
}
private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData)
@@ -145,6 +148,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 +157,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 +170,7 @@ private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeD
--codeWriter.Indent;
codeWriter.WriteLine("}");
+ codeWriter.WriteLine("#nullable restore");
codeWriter.Flush();
stream.Seek(0, SeekOrigin.Begin);
@@ -170,6 +178,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($" : {typeof(IAutoInterface).Namespace}.{nameof(IAutoInterface)}");
+ }
+ else if (attributeData.AttributeClass!.Is(_generateGenericAutoInterfaceAttribute))
+ {
+ codeWriter.Write($" : {typeof(IAutoInterface).Namespace}.{nameof(IAutoInterface)}<{implTypeSymbol.Name}");
+ WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol);
+ codeWriter.Write(">");
+ }
+ }
+
private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol)
{
if (!implTypeSymbol.IsGenericType)
@@ -180,16 +204,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 +462,16 @@ private static void WriteTypeParameterConstraints(
private void InitAttributes(Compilation compilation)
{
_generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName(
- $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}")!;
-
+ $"{typeof(GenerateAutoInterfaceAttribute).Namespace}.{nameof(GenerateAutoInterfaceAttribute)}")!;
+
+ _generateGenericAutoInterfaceAttribute = compilation.GetTypeByMetadataName(
+ $"{typeof(GenerateGenericAutoInterfaceAttribute).Namespace}.{nameof(GenerateGenericAutoInterfaceAttribute)}")!;
+
_ignoreAttribute = compilation.GetTypeByMetadataName(
- $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}")!;
+ $"{typeof(AutoInterfaceIgnoreAttribute).Namespace}.{nameof(AutoInterfaceIgnoreAttribute)}")!;
+
+ _nameTemplateAttribute = compilation.GetTypeByMetadataName(
+ $"{typeof(AutoInterfaceNameTemplateAttribute).Namespace}.{nameof(AutoInterfaceNameTemplateAttribute)}")!;
}
private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver)
@@ -449,12 +488,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/InterfaceGenerator.csproj b/InterfaceGenerator/InterfaceGenerator.csproj
index 9da3776..ca4b1d8 100644
--- a/InterfaceGenerator/InterfaceGenerator.csproj
+++ b/InterfaceGenerator/InterfaceGenerator.csproj
@@ -1,35 +1,49 @@
-
-
- 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
-
+ 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