Skip to content

Commit 65edd9f

Browse files
Add handling for default parameters, generics, in/out/ref and other cases when generating interfaces
1 parent e9d44c2 commit 65edd9f

4 files changed

Lines changed: 200 additions & 3 deletions

File tree

src/AutoRegisterInject.IntegrationTests/AutoRegisterInject.IntegrationTest.Project1/Class1.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Threading.Tasks;
34
using Microsoft.Extensions.DependencyInjection;
45

@@ -339,4 +340,23 @@ public class AutomaticInterfaceRegistrationTest : IAutomaticInterfaceRegistratio
339340
{
340341

341342
}
343+
344+
[AutoInterface, RegisterScoped]
345+
public class AutomaticInterfaceRegistrationWithParametersTest : IAutomaticInterfaceRegistrationWithParametersTest
346+
{
347+
public void One(bool isDefault = true)
348+
{
349+
350+
}
351+
352+
public void Two<T>(T defaulted)
353+
{
354+
355+
}
356+
357+
public void Three(params string[] args)
358+
{
359+
360+
}
361+
}
342362
}

src/AutoRegisterInject.Tests/GenerationTests.AutoInterface.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,71 @@ await RunGenerator(
197197
EXPECTED_REGISTRATION,
198198
("AutoRegisterInject.IFoo.g.cs", EXPECTED_INTERFACE));
199199
}
200+
201+
[Fact]
202+
public async Task ShouldGenerateInterfaceWithMethodModifiersDefaultsAndGenerics()
203+
{
204+
const string INPUT = @"#nullable enable
205+
[AutoInterface]
206+
[RegisterTransient]
207+
public class Foo : IFoo
208+
{
209+
public void Log(string message = ""hello"", int count = 3) { }
210+
public void AddTags(params string[] tags) { }
211+
public T? GetValue<T>(T? fallback = default) where T : class => fallback;
212+
}";
213+
214+
const string EXPECTED_REGISTRATION = @"// <auto-generated>
215+
// Automatically generated by AutoRegisterInject.
216+
// Changes made to this file may be lost and may cause undesirable behaviour.
217+
// </auto-generated>
218+
using Microsoft.Extensions.DependencyInjection;
219+
using Microsoft.Extensions.DependencyInjection.Extensions;
220+
221+
/// <summary>
222+
/// AutoRegisterInject service collection extensions
223+
/// </summary>
224+
public static class AutoRegisterInjectServiceCollectionExtension
225+
{
226+
/// <summary>
227+
/// Adds all types registered with AutoRegisterInject attributes to the given
228+
/// service collection from the named assembly
229+
/// </summary>
230+
/// <param name=""serviceCollection"">Service collection to register types with</param>
231+
/// <returns>Service collection with registered types</returns>
232+
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AutoRegisterFromTestProject(this Microsoft.Extensions.DependencyInjection.IServiceCollection serviceCollection)
233+
{
234+
return AutoRegister(serviceCollection);
235+
}
236+
237+
/// <summary>
238+
/// Adds all types registered with AutoRegisterInject attributes to the given
239+
/// service collection
240+
/// </summary>
241+
/// <param name=""serviceCollection"">Service collection to register types with</param>
242+
/// <returns>Service collection with registered types</returns>
243+
internal static Microsoft.Extensions.DependencyInjection.IServiceCollection AutoRegister(this Microsoft.Extensions.DependencyInjection.IServiceCollection serviceCollection)
244+
{
245+
serviceCollection.AddTransient<IFoo, Foo>();
246+
return serviceCollection;
247+
}
248+
}";
249+
250+
const string EXPECTED_INTERFACE = @"// <auto-generated>
251+
// Automatically generated by AutoRegisterInject.
252+
// Changes made to this file may be lost and may cause undesirable behaviour.
253+
// </auto-generated>
254+
#nullable enable
255+
public interface IFoo
256+
{
257+
void Log(string message = ""hello"", int count = 3);
258+
void AddTags(params string[] tags);
259+
T? GetValue<T>(T? fallback = default) where T : class;
260+
}";
261+
262+
await RunGenerator(
263+
INPUT,
264+
EXPECTED_REGISTRATION,
265+
("AutoRegisterInject.IFoo.g.cs", EXPECTED_INTERFACE));
266+
}
200267
}

src/AutoRegisterInject/AutoRegisterInject.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<RepositoryUrl>https://github.com/patrickklaeren/AutoRegisterInject</RepositoryUrl>
1919
<RepositoryType>git</RepositoryType>
2020
<PackageTags>source generator;dependency injection;dependencies;registration;extensions;ioc</PackageTags>
21-
<Version>2.0.1</Version>
21+
<Version>2.0.2</Version>
2222
</PropertyGroup>
2323

2424
<ItemGroup>

src/AutoRegisterInject/Generator.cs

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Immutable;
77
using System;
88
using System.Collections.Generic;
9+
using System.Globalization;
910

1011
namespace AutoRegisterInject;
1112

@@ -271,9 +272,118 @@ private static string GetInterfaceMemberSource(ISymbol member)
271272
private static string GetMethodSource(IMethodSymbol method)
272273
{
273274
var returnType = method.ReturnType.ToDisplayString();
274-
var parameters = string.Join(", ", method.Parameters.Select(static parameter => $"{parameter.Type.ToDisplayString()} {parameter.Name}"));
275+
var typeParameters = method.TypeParameters.Length == 0
276+
? string.Empty
277+
: $"<{string.Join(", ", method.TypeParameters.Select(static typeParameter => typeParameter.Name))}>";
278+
var parameters = string.Join(", ", method.Parameters.Select(GetParameterSource));
279+
var constraints = GetTypeParameterConstraintsSource(method.TypeParameters);
275280

276-
return $" {returnType} {method.Name}({parameters});";
281+
return $" {returnType} {method.Name}{typeParameters}({parameters}){constraints};";
282+
}
283+
284+
private static string GetParameterSource(IParameterSymbol parameter)
285+
{
286+
var modifiers = GetParameterModifiersSource(parameter);
287+
var defaultValue = GetParameterDefaultValueSource(parameter);
288+
289+
return $"{modifiers}{parameter.Type.ToDisplayString()} {parameter.Name}{defaultValue}";
290+
}
291+
292+
private static string GetParameterModifiersSource(IParameterSymbol parameter)
293+
{
294+
var paramsModifier = parameter.IsParams ? "params " : string.Empty;
295+
var refModifier = parameter.RefKind switch
296+
{
297+
RefKind.Ref => "ref ",
298+
RefKind.Out => "out ",
299+
RefKind.In => "in ",
300+
_ => string.Empty,
301+
};
302+
303+
return paramsModifier + refModifier;
304+
}
305+
306+
private static string GetParameterDefaultValueSource(IParameterSymbol parameter)
307+
{
308+
var syntax = parameter.DeclaringSyntaxReferences
309+
.Select(static reference => reference.GetSyntax())
310+
.OfType<ParameterSyntax>()
311+
.FirstOrDefault();
312+
313+
if (syntax?.Default is not null)
314+
{
315+
return $" = {syntax.Default.Value}";
316+
}
317+
318+
if (!parameter.HasExplicitDefaultValue)
319+
{
320+
return string.Empty;
321+
}
322+
323+
return parameter.ExplicitDefaultValue is null
324+
? " = null"
325+
: $" = {FormatDefaultValue(parameter.ExplicitDefaultValue)}";
326+
}
327+
328+
private static string FormatDefaultValue(object value)
329+
{
330+
return value switch
331+
{
332+
bool boolValue => boolValue ? "true" : "false",
333+
char charValue => $"'{charValue}'",
334+
string stringValue => $"\"{stringValue.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"",
335+
float floatValue => $"{floatValue.ToString("R", CultureInfo.InvariantCulture)}F",
336+
double doubleValue => $"{doubleValue.ToString("R", CultureInfo.InvariantCulture)}D",
337+
decimal decimalValue => $"{decimalValue.ToString(CultureInfo.InvariantCulture)}M",
338+
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
339+
_ => value.ToString(),
340+
};
341+
}
342+
343+
private static string GetTypeParameterConstraintsSource(ImmutableArray<ITypeParameterSymbol> typeParameters)
344+
{
345+
var constraints = typeParameters
346+
.Select(GetTypeParameterConstraintSource)
347+
.Where(static constraint => !string.IsNullOrWhiteSpace(constraint))
348+
.ToArray();
349+
350+
return constraints.Length == 0
351+
? string.Empty
352+
: " " + string.Join(" ", constraints);
353+
}
354+
355+
private static string GetTypeParameterConstraintSource(ITypeParameterSymbol typeParameter)
356+
{
357+
var constraints = new List<string>();
358+
359+
if (typeParameter.HasUnmanagedTypeConstraint)
360+
{
361+
constraints.Add("unmanaged");
362+
}
363+
else if (typeParameter.HasValueTypeConstraint)
364+
{
365+
constraints.Add("struct");
366+
}
367+
else if (typeParameter.HasReferenceTypeConstraint)
368+
{
369+
constraints.Add("class");
370+
}
371+
372+
if (typeParameter.HasNotNullConstraint)
373+
{
374+
constraints.Add("notnull");
375+
}
376+
377+
constraints.AddRange(typeParameter.ConstraintTypes.Select(static type => type.ToDisplayString()));
378+
379+
if (typeParameter.HasConstructorConstraint)
380+
{
381+
constraints.Add("new()");
382+
}
383+
384+
return constraints.Count == 0
385+
? string.Empty
386+
: $"where {typeParameter.Name} : {string.Join(", ", constraints)}";
277387
}
278388

279389
private static string GetPropertySource(IPropertySymbol property)

0 commit comments

Comments
 (0)