Skip to content

Commit e43296f

Browse files
merge
2 parents 5188979 + 18c9e26 commit e43296f

11 files changed

Lines changed: 222 additions & 31 deletions

AutomaticInterface/AutomaticInterface/AutomaticInterfaceGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class AutomaticInterfaceGenerator : IIncrementalGenerator
1010
{
1111
public const string DefaultAttributeName = "GenerateAutomaticInterface";
1212
public const string IgnoreAutomaticInterfaceAttributeName = "IgnoreAutomaticInterface";
13+
public const string NamespaceParameterName = "namespaceName";
14+
public const string InterfaceParameterName = "interfaceName";
1315

1416
public void Initialize(IncrementalGeneratorInitializationContext context)
1517
{

AutomaticInterface/AutomaticInterface/Builder.cs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ is not ClassDeclarationSyntax classSyntax
5353
return string.Empty;
5454
}
5555
var generationAttribute = GetGenerationAttribute(typeSymbol);
56-
var namespaceName = GetNameSpace(typeSymbol, generationAttribute);
5756
var asInternal = GetAsInternal(generationAttribute);
58-
var interfaceName = $"I{classSyntax.GetClassName()}";
59-
60-
var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName, asInternal);
57+
var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);
58+
var interfaceGenerator = new InterfaceBuilder(
59+
symbolDetails.NamespaceName,
60+
symbolDetails.InterfaceName,
61+
asInternal
62+
);
6163

6264
interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
6365
interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));
@@ -78,14 +80,14 @@ is not ClassDeclarationSyntax classSyntax
7880
return generatedCode;
7981
}
8082

81-
private static AttributeData? GetGenerationAttribute(ISymbol typeSymbol)
82-
{
83-
return typeSymbol
84-
.GetAttributes()
85-
.FirstOrDefault(x =>
86-
x.AttributeClass != null
87-
&& x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName)
88-
);
83+
private static AttributeData? GetGenerationAttribute(ISymbol typeSymbol)
84+
{
85+
return typeSymbol
86+
.GetAttributes()
87+
.FirstOrDefault(x =>
88+
x.AttributeClass != null
89+
&& x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName)
90+
);
8991
}
9092

9193
private static string GetNameSpace(ISymbol typeSymbol, AttributeData? generationAttribute)
@@ -100,20 +102,36 @@ private static string GetNameSpace(ISymbol typeSymbol, AttributeData? generation
100102
return string.IsNullOrWhiteSpace(customNs)
101103
? typeSymbol.ContainingNamespace.ToDisplayString()
102104
: customNs!;
103-
}
104-
105+
}
106+
105107
private static bool GetAsInternal(AttributeData? generationAttribute)
106108
{
107109
if (generationAttribute == null)
108110
{
109111
return false;
110112
}
111113

112-
var asInternal = (bool?)generationAttribute.ConstructorArguments.Skip(1).FirstOrDefault().Value;
114+
var asInternal = (bool?)
115+
generationAttribute.ConstructorArguments.Skip(2).FirstOrDefault().Value;
113116

114117
return asInternal.GetValueOrDefault();
115118
}
116119

120+
private static GeneratedSymbolDetails GetSymbolDetails(
121+
ITypeSymbol typeSymbol,
122+
ClassDeclarationSyntax classSyntax
123+
)
124+
{
125+
var generationAttribute = typeSymbol
126+
.GetAttributes()
127+
.FirstOrDefault(x =>
128+
x.AttributeClass != null
129+
&& x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName)
130+
);
131+
132+
return new GeneratedSymbolDetails(generationAttribute, typeSymbol, classSyntax);
133+
}
134+
117135
private static void AddMethodsToInterface(List<ISymbol> members, InterfaceBuilder codeGenerator)
118136
{
119137
members
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System.Linq;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
5+
namespace AutomaticInterface;
6+
7+
internal sealed class GeneratedSymbolDetails(
8+
AttributeData? generationAttribute,
9+
ITypeSymbol typeSymbol,
10+
ClassDeclarationSyntax classSyntax
11+
)
12+
{
13+
/// <summary>
14+
/// Represents the namespace name associated with the generated interface or type symbol.
15+
/// This value is typically derived from the provided generation attribute or defaults
16+
/// to the containing namespace of the type symbol.
17+
/// </summary>
18+
public string NamespaceName { get; } =
19+
PrepareValue(
20+
generationAttribute,
21+
AutomaticInterfaceGenerator.NamespaceParameterName,
22+
typeSymbol.ContainingNamespace.ToDisplayString()
23+
);
24+
25+
/// <summary>
26+
/// Represents the name of the interface generated for a class. The interface name
27+
/// is derived from the class name, prefixed with 'I', unless overridden by a specific
28+
/// attribute value during generation.
29+
/// </summary>
30+
public string InterfaceName { get; } =
31+
PrepareValue(
32+
generationAttribute,
33+
AutomaticInterfaceGenerator.InterfaceParameterName,
34+
$"I{classSyntax.GetClassName()}"
35+
);
36+
37+
private static string PrepareValue(
38+
AttributeData? generationAttribute,
39+
string key,
40+
string defaultValue
41+
)
42+
{
43+
var parameterSymbol = generationAttribute?.AttributeConstructor?.Parameters.SingleOrDefault(
44+
x => x.Name == key
45+
);
46+
47+
if (parameterSymbol != null)
48+
{
49+
var index = generationAttribute!.AttributeConstructor!.Parameters.IndexOf(
50+
parameterSymbol
51+
);
52+
var result = generationAttribute.ConstructorArguments[index].Value!.ToString();
53+
if (!string.IsNullOrWhiteSpace(result))
54+
{
55+
return result;
56+
}
57+
}
58+
59+
return defaultValue;
60+
}
61+
}

AutomaticInterface/AutomaticInterface/RegisterAttributesExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ namespace AutomaticInterface
2424
/// <summary>
2525
/// Use source generator to automatically create a Interface from this class
2626
/// </summary>
27+
/// <param name="namespaceName">Namespace name for the generated interface. Defaults to the same namespace as the class.</param>
28+
/// <param name="interfaceName">Interface name for the generated interface. Defaults to an interface version of the class name.</param>
2729
[AttributeUsage(AttributeTargets.Class)]
2830
internal sealed class {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute : Attribute
2931
{
30-
internal {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute(string namespaceName = "", bool asInternal = false) { }
32+
internal {{{AutomaticInterfaceGenerator.DefaultAttributeName}}}Attribute(string {{{AutomaticInterfaceGenerator.NamespaceParameterName}}} = "", string {{{AutomaticInterfaceGenerator.InterfaceParameterName}}} = "", bool asInternal = false) { }
3133
}
3234
}
3335
""",

AutomaticInterface/AutomaticInterfaceExample/AutomaticInterfaceExample.csproj

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,4 @@
1818
<ItemGroup>
1919
<Folder Include="logs" />
2020
</ItemGroup>
21-
22-
<ItemGroup>
23-
24-
<None Remove="logs\AutomaticInterfaceGenerator_log.txt" />
25-
</ItemGroup>
26-
2721
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using AutomaticInterface;
2+
3+
namespace AutomaticInterfaceExample;
4+
5+
[GenerateAutomaticInterface(interfaceName: "ISpecialInterface")]
6+
public class DemoClassWithCustomInterfaceName : ISpecialInterface
7+
{
8+
/// <summary>
9+
/// This is a test method
10+
/// </summary>
11+
public void Test() { }
12+
}

AutomaticInterface/Tests/Enums/Enums.WorksWithEnum.received.txt renamed to AutomaticInterface/Tests/Misc/Misc.CustomInterface.verified.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
namespace AutomaticInterfaceExample
1010
{
1111
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
12-
public partial interface IDemoClass
12+
public partial interface ISpecialInterface
1313
{
14-
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.MethodWithDefaultParameter(AutomaticInterfaceExample.EnumWithByteType)" />
15-
void MethodWithDefaultParameter(global::AutomaticInterfaceExample.EnumWithByteType a = global::AutomaticInterfaceExample.EnumWithByteType.B);
14+
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
15+
void Test();
1616

1717
}
1818
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//--------------------------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by a tool.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
6+
// </auto-generated>
7+
//--------------------------------------------------------------------------------------------------
8+
9+
namespace CustomNamespace
10+
{
11+
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
12+
public partial interface ISpecialInterface
13+
{
14+
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
15+
void Test();
16+
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//--------------------------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by a tool.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
6+
// </auto-generated>
7+
//--------------------------------------------------------------------------------------------------
8+
9+
namespace CustomNamespace
10+
{
11+
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
12+
public partial interface ISpecialInterface
13+
{
14+
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClassWithCustomInterfaceName.Test()" />
15+
void Test();
16+
17+
}
18+
}

AutomaticInterface/Tests/Misc/Misc.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,78 @@ public string AMethod(DemoClass? x, string y)
254254
await Verify(Infrastructure.GenerateCode(code));
255255
}
256256

257+
[Fact]
258+
public async Task CustomInterfaceAndNamespace()
259+
{
260+
const string code = """
261+
262+
using AutomaticInterface;
263+
264+
namespace AutomaticInterfaceExample
265+
{
266+
[GenerateAutomaticInterface("CustomNamespace", "ISpecialInterface")]
267+
public class DemoClassWithCustomInterfaceName : ISpecialInterface
268+
{
269+
/// <summary>
270+
/// This is a test method
271+
/// </summary>
272+
public void Test() { }
273+
}
274+
}
275+
276+
""";
277+
278+
await Verify(Infrastructure.GenerateCode(code));
279+
}
280+
281+
[Fact]
282+
public async Task CustomInterfaceAndNamespaceParametersReversed()
283+
{
284+
const string code = """
285+
286+
using AutomaticInterface;
287+
288+
namespace AutomaticInterfaceExample
289+
{
290+
[GenerateAutomaticInterface(interfaceName: "ISpecialInterface", namespaceName: "CustomNamespace")]
291+
public class DemoClassWithCustomInterfaceName : ISpecialInterface
292+
{
293+
/// <summary>
294+
/// This is a test method
295+
/// </summary>
296+
public void Test() { }
297+
}
298+
}
299+
300+
""";
301+
302+
await Verify(Infrastructure.GenerateCode(code));
303+
}
304+
305+
[Fact]
306+
public async Task CustomInterface()
307+
{
308+
const string code = """
309+
310+
using AutomaticInterface;
311+
312+
namespace AutomaticInterfaceExample
313+
{
314+
[GenerateAutomaticInterface(interfaceName: "ISpecialInterface")]
315+
public class DemoClassWithCustomInterfaceName : ISpecialInterface
316+
{
317+
/// <summary>
318+
/// This is a test method
319+
/// </summary>
320+
public void Test() { }
321+
}
322+
}
323+
324+
""";
325+
326+
await Verify(Infrastructure.GenerateCode(code));
327+
}
328+
257329
[Fact]
258330
public async Task CustomNameSpace()
259331
{

0 commit comments

Comments
 (0)