Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
25 changes: 10 additions & 15 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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()
Expand All @@ -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<ISymbol> members, InterfaceBuilder codeGenerator)
Expand Down
61 changes: 61 additions & 0 deletions AutomaticInterface/AutomaticInterface/GeneratedSymbolDetails.cs
Original file line number Diff line number Diff line change
@@ -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
)
{
/// <summary>
/// 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.
/// </summary>
public string NamespaceName { get; } =
PrepareValue(
generationAttribute,
AutomaticInterfaceGenerator.NamespaceParameterName,
typeSymbol.ContainingNamespace.ToDisplayString()
);

/// <summary>
/// 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.
/// </summary>
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ namespace AutomaticInterface
/// <summary>
/// Use source generator to automatically create a Interface from this class
/// </summary>
/// <param name="namespaceName">Namespace name for the generated interface. Defaults to the same namespace as the class.</param>
/// <param name="interfaceName">Interface name for the generated interface. Defaults to an interface version of the class name.</param>
[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}}} = "") { }
}
}
""",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using AutomaticInterface;

namespace AutomaticInterfaceExample;

[GenerateAutomaticInterface(interfaceName: "ISpecialInterface")]
public class DemoClassWithCustomInterfaceName : ISpecialInterface
{
/// <summary>
/// This is a test method
/// </summary>
public void Test() { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
namespace AutomaticInterfaceExample
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
public partial interface ISpecialInterface
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.MethodWithDefaultParameter(AutomaticInterfaceExample.EnumWithByteType)" />
void MethodWithDefaultParameter(global::AutomaticInterfaceExample.EnumWithByteType a = global::AutomaticInterfaceExample.EnumWithByteType.B);
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
void Test();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------

namespace CustomNamespace
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface ISpecialInterface
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
void Test();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------

namespace CustomNamespace
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface ISpecialInterface
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
void Test();

}
}
72 changes: 72 additions & 0 deletions AutomaticInterface/Tests/Misc/Misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// This is a test method
/// </summary>
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
{
/// <summary>
/// This is a test method
/// </summary>
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
{
/// <summary>
/// This is a test method
/// </summary>
public void Test() { }
}
}

""";

await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task CustomNameSpace()
{
Expand Down
6 changes: 0 additions & 6 deletions AutomaticInterface/Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,4 @@
<ItemGroup>
<ProjectReference Include="..\AutomaticInterface\AutomaticInterface.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Methods\Methods.WorksWithOptionalParameters.verified.txt">
<ParentFile>Methods</ParentFile>
</None>
</ItemGroup>
</Project>
Loading