Skip to content

Commit 4acdac9

Browse files
dmytroettDmytro Ett
andauthored
Consolidating snapshot tests (#18)
* Adding references to solution explorer * Consolidating snapshot tests * Making one test more robust * Switch namespace declarations from file scoped to block scoped for consistency reasons. * Simplifying interface naming resolver logic --------- Co-authored-by: Dmytro Ett <dmytro.ett@outlook.com>
1 parent 772c83f commit 4acdac9

File tree

36 files changed

+677
-1780
lines changed

36 files changed

+677
-1780
lines changed

AttributedDI.slnx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
</Configurations>
77
<Folder Name="/Solution Items/">
88
<File Path=".editorconfig" />
9-
<File Path=".github/copilot-instructions.md" />
10-
<File Path=".github/instructions/source-generators.instructions.md" />
9+
<File Path="AGENTS.md" />
10+
<File Path="Directory.Build.props" />
1111
<File Path=".github/workflows/pr.yml" />
1212
<File Path=".github/workflows/release.yml" />
1313
<File Path="README.md" />
@@ -26,5 +26,7 @@
2626
<Project Path="test/assets/Company.TeamName.Project.API/Company.TeamName.Project.API.csproj" />
2727
<Project Path="test/assets/CustomModuleName/CustomModuleName.csproj" />
2828
<Project Path="test/assets/GeneratedInterfacesSut/GeneratedInterfacesSut.csproj" />
29+
<File Path="test/assets/.editorconfig" />
30+
<File Path="test/assets/Directory.Build.props" />
2931
</Folder>
3032
</Solution>

src/AttributedDI.SourceGenerator/AttributedDI.SourceGenerator.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@
1515
<PackageReference Include="PolySharp" Version="1.15.0" PrivateAssets="all" />
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<None Include="AGENTS.md" />
20+
</ItemGroup>
21+
1822
</Project>

src/AttributedDI.SourceGenerator/InterfacesGeneration/GeneratedInterfaceNamingResolver.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ namespace AttributedDI.SourceGenerator.InterfacesGeneration;
55

66
internal static class GeneratedInterfaceNamingResolver
77
{
8-
internal static bool TryResolve(
9-
INamedTypeSymbol typeSymbol,
10-
AttributeData attribute,
11-
out GeneratedInterfaceNaming? naming)
8+
internal static GeneratedInterfaceNaming Resolve(INamedTypeSymbol typeSymbol, AttributeData attribute)
129
{
1310
var interfaceNameArgument = GetOptionalStringArgument(attribute, position: 0, name: "InterfaceName");
1411
var interfaceNamespaceArgument = GetOptionalStringArgument(attribute, position: 1, name: "InterfaceNamespace");
@@ -22,10 +19,9 @@ internal static bool TryResolve(
2219
? ParseInterfaceName(interfaceNameArgument!)
2320
: (Name: defaultInterfaceName, Namespace: string.Empty);
2421

25-
naming = new GeneratedInterfaceNaming(
22+
return new GeneratedInterfaceNaming(
2623
parsed.Name,
2724
NormalizeNamespace(interfaceNamespaceArgument));
28-
return true;
2925
}
3026

3127
if (!string.IsNullOrWhiteSpace(interfaceNameArgument))
@@ -35,12 +31,10 @@ internal static bool TryResolve(
3531
? defaultNamespace
3632
: NormalizeNamespace(parsed.Namespace);
3733

38-
naming = new GeneratedInterfaceNaming(parsed.Name, resolvedNamespace);
39-
return true;
34+
return new GeneratedInterfaceNaming(parsed.Name, resolvedNamespace);
4035
}
4136

42-
naming = new GeneratedInterfaceNaming(defaultInterfaceName, defaultNamespace);
43-
return true;
37+
return new GeneratedInterfaceNaming(defaultInterfaceName, defaultNamespace);
4438
}
4539

4640
private static string? GetOptionalStringArgument(AttributeData attribute, int position, string name)

src/AttributedDI.SourceGenerator/InterfacesGeneration/GeneratedInterfacesCodeEmitter.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,35 @@ private static string BuildInterfaceSource(GeneratedInterfaceInfo interfaceInfo)
3232
builder.AppendLine();
3333

3434
var interfaceNamespace = NormalizeNamespace(interfaceInfo.InterfaceNamespace);
35-
if (!string.IsNullOrWhiteSpace(interfaceNamespace))
35+
var hasNamespace = !string.IsNullOrWhiteSpace(interfaceNamespace);
36+
if (hasNamespace)
3637
{
37-
builder.Append("namespace ").Append(interfaceNamespace).AppendLine(";");
38-
builder.AppendLine();
38+
builder.Append("namespace ").Append(interfaceNamespace).AppendLine();
39+
builder.AppendLine("{");
3940
}
4041

41-
GeneratedCodeHelper.AppendGeneratedCodeAttribute(builder, 0);
42-
builder.Append(interfaceInfo.Accessibility)
42+
var indent = hasNamespace ? " " : string.Empty;
43+
GeneratedCodeHelper.AppendGeneratedCodeAttribute(builder, hasNamespace ? 1 : 0);
44+
builder.Append(indent)
45+
.Append(interfaceInfo.Accessibility)
4346
.Append(" interface ")
4447
.Append(interfaceInfo.InterfaceName)
4548
.Append(interfaceInfo.ClassTypeParameters)
4649
.AppendLine();
4750

4851
if (!string.IsNullOrWhiteSpace(interfaceInfo.TypeParameterConstraints))
4952
{
50-
builder.Append(interfaceInfo.TypeParameterConstraints).AppendLine();
53+
builder.Append(indent)
54+
.Append(interfaceInfo.TypeParameterConstraints)
55+
.AppendLine();
5156
}
5257

53-
builder.AppendLine("{");
58+
builder.Append(indent).AppendLine("{");
5459

60+
var memberIndent = indent + " ";
5561
foreach (var member in interfaceInfo.MemberSignatures)
5662
{
57-
builder.Append(" ").Append(member);
63+
builder.Append(memberIndent).Append(member);
5864
var needsSemicolon = !member.Contains("{", StringComparison.Ordinal) && !member.EndsWith(";", StringComparison.Ordinal);
5965
if (needsSemicolon)
6066
{
@@ -64,7 +70,11 @@ private static string BuildInterfaceSource(GeneratedInterfaceInfo interfaceInfo)
6470
builder.AppendLine();
6571
}
6672

67-
builder.AppendLine("}");
73+
builder.Append(indent).AppendLine("}");
74+
if (hasNamespace)
75+
{
76+
builder.AppendLine("}");
77+
}
6878

6979
return builder.ToString();
7080
}
@@ -77,17 +87,20 @@ private static string BuildPartialClassSource(GeneratedInterfaceInfo interfaceIn
7787
builder.AppendLine();
7888

7989
var classNamespace = NormalizeNamespace(interfaceInfo.ClassNamespace);
80-
if (!string.IsNullOrWhiteSpace(classNamespace))
90+
var hasNamespace = !string.IsNullOrWhiteSpace(classNamespace);
91+
if (hasNamespace)
8192
{
82-
builder.Append("namespace ").Append(classNamespace).AppendLine(";");
83-
builder.AppendLine();
93+
builder.Append("namespace ").Append(classNamespace).AppendLine();
94+
builder.AppendLine("{");
8495
}
8596

86-
GeneratedCodeHelper.AppendGeneratedCodeAttribute(builder, 0);
97+
var indent = hasNamespace ? " " : string.Empty;
98+
GeneratedCodeHelper.AppendGeneratedCodeAttribute(builder, hasNamespace ? 1 : 0);
8799
// Build the partial class declaration that implements the interface
88100
var fullyQualifiedInterfaceName = BuildFullyQualifiedName(interfaceInfo.InterfaceNamespace, interfaceInfo.InterfaceName);
89101

90-
builder.Append(interfaceInfo.Accessibility)
102+
builder.Append(indent)
103+
.Append(interfaceInfo.Accessibility)
91104
.Append(" partial class ")
92105
.Append(interfaceInfo.ClassName)
93106
.Append(interfaceInfo.ClassTypeParameters)
@@ -106,8 +119,12 @@ private static string BuildPartialClassSource(GeneratedInterfaceInfo interfaceIn
106119
}
107120

108121
builder.AppendLine();
109-
builder.AppendLine("{");
110-
builder.AppendLine("}");
122+
builder.Append(indent).AppendLine("{");
123+
builder.Append(indent).AppendLine("}");
124+
if (hasNamespace)
125+
{
126+
builder.AppendLine("}");
127+
}
111128

112129
return builder.ToString();
113130
}

src/AttributedDI.SourceGenerator/InterfacesGeneration/InterfaceGenerationPipeline.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,7 @@ internal static class InterfaceGenerationBuilder
4646
return null;
4747
}
4848

49-
if (!GeneratedInterfaceNamingResolver.TryResolve(typeSymbol, context.Attributes[0], out var naming) || naming is null)
50-
{
51-
return null;
52-
}
53-
54-
var resolvedNaming = naming;
49+
var resolvedNaming = GeneratedInterfaceNamingResolver.Resolve(typeSymbol, context.Attributes[0]);
5550
var interfaceName = StripTypeParameters(resolvedNaming.InterfaceName);
5651
var members = CollectMembers(typeSymbol, ct);
5752
var accessibility = ResolveAccessibility(typeSymbol.DeclaredAccessibility);

src/AttributedDI.SourceGenerator/ServiceModulesGeneration/RegistrationCandidatesCollector.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,7 @@ private static ImmutableArray<RegistrationCandidate> ExtractRegisterAsGeneratedI
178178
}
179179

180180
var attribute = context.Attributes[0];
181-
if (!GeneratedInterfaceNamingResolver.TryResolve(symbol, attribute, out var naming) || naming is null)
182-
{
183-
// TODO: emit diagnostic when generated interface naming cannot be resolved.
184-
return ImmutableArray<RegistrationCandidate>.Empty;
185-
}
186-
181+
var naming = GeneratedInterfaceNamingResolver.Resolve(symbol, attribute);
187182
var implementationTypeName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
188183
var isOpenGeneric = IsOpenGenericDefinition(symbol);
189184
var unboundImplementationTypeName = ResolveUnboundName(symbol, implementationTypeName, isOpenGeneric);

src/AttributedDI/AttributedDI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<IsTrimmable>true</IsTrimmable>
1010
<IsPackable>true</IsPackable>
1111
<PackageLicenseFile>LICENSE</PackageLicenseFile>
12+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1213
<PackageProjectUrl>https://github.com/dmytroett/AttributedDI</PackageProjectUrl>
1314
<RepositoryUrl>https://github.com/dmytroett/AttributedDI</RepositoryUrl>
1415
<PackageTags>dependencyinjection;di;AoP;sourcegenerator;aot</PackageTags>

test/AttributedDI.SourceGenerator.UnitTests/BasicServicesRegistrationTests.cs

Lines changed: 27 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ namespace AttributedDI.SourceGenerator.UnitTests;
33
public class BasicServicesRegistrationTests
44
{
55
[Fact]
6-
public async Task RegistersServicesWithDifferentLifetimesAndInterfaces()
6+
public async Task RegistersServicesAcrossCommonScenarios()
77
{
8-
// Tests: RegisterAsSelf with lifetimes plus interface registrations
8+
// Tests: RegisterAsSelf/implemented interfaces, multiple registrations, disposables, and nested namespaces.
99
var code = """
1010
using AttributedDI;
11+
using System;
12+
using System.Threading.Tasks;
1113
1214
namespace MyApp
1315
{
1416
public interface IMyService { }
1517
public interface IOtherService { }
18+
public interface IService { }
19+
public interface IRepository { }
1620
1721
[Transient]
1822
public class ShouldBeRegisteredAsTransient { }
@@ -31,66 +35,38 @@ public class MyServiceImpl : IMyService { }
3135
3236
[RegisterAs<IOtherService>, Singleton]
3337
public class OtherServiceImpl : IOtherService { }
34-
}
35-
""";
36-
37-
var (output, diagnostics) = new SourceGeneratorTestFixture()
38-
.WithSourceCode(code)
39-
.AddGenerator<ServiceRegistrationGenerator>()
40-
.RunAndGetOutput();
41-
42-
Assert.Empty(diagnostics);
43-
44-
await Verify(output);
45-
}
46-
47-
[Fact]
48-
public async Task RegistersAsImplementedInterfaces()
49-
{
50-
// Tests: RegisterAsImplementedInterfaces for types implementing multiple interfaces
51-
var code = """
52-
using AttributedDI;
53-
54-
namespace MyApp
55-
{
56-
public interface IService { }
57-
public interface IRepository { }
5838
5939
[RegisterAsImplementedInterfaces]
6040
public class ServiceImpl : IService { }
6141
6242
[RegisterAsImplementedInterfaces, Scoped]
6343
public class MultiImpl : IService, IRepository { }
64-
}
65-
""";
66-
67-
var (output, diagnostics) = new SourceGeneratorTestFixture()
68-
.WithSourceCode(code)
69-
.AddGenerator<ServiceRegistrationGenerator>()
70-
.RunAndGetOutput();
7144
72-
Assert.Empty(diagnostics);
45+
[RegisterAsSelf]
46+
[RegisterAs<IService>]
47+
[RegisterAs<IRepository>]
48+
[Singleton]
49+
public class MultiRegistration : IService, IRepository { }
7350
74-
await Verify(output);
75-
}
51+
[RegisterAsImplementedInterfaces]
52+
public class DisposableService : IService, IDisposable, IAsyncDisposable
53+
{
54+
public void Dispose() { }
7655
77-
[Fact]
78-
public async Task RegistersMultipleServicesFromSameType()
79-
{
80-
// Tests: Multiple registration attributes on single type
81-
var code = """
82-
using AttributedDI;
56+
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
57+
}
58+
}
8359
84-
namespace MyApp
60+
namespace MyApp.Services
8561
{
86-
public interface IService { }
87-
public interface IRepository { }
62+
[RegisterAsSelf, Singleton]
63+
public class OuterService { }
64+
}
8865
66+
namespace MyApp.Services.Internal
67+
{
8968
[RegisterAsSelf]
90-
[RegisterAs<IService>]
91-
[RegisterAs<IRepository>]
92-
[Singleton]
93-
public class MultiRegistration : IService, IRepository { }
69+
public class InnerService { }
9470
}
9571
""";
9672

@@ -130,7 +106,7 @@ public class MyService { }
130106
}
131107

132108
[Fact]
133-
public async Task HandlesEmptyAssembly()
109+
public void HandlesEmptyAssembly()
134110
{
135111
// Tests: No services to register (should generate nothing)
136112
var code = """
@@ -148,71 +124,7 @@ public class RegularClass { }
148124
.RunAndGetOutput();
149125

150126
Assert.Empty(diagnostics);
151-
152-
await Verify(output);
153-
}
154-
155-
[Fact]
156-
public async Task HandlesDisposable()
157-
{
158-
// Tests: Service implementing IDisposable and IAsyncDisposable
159-
var code = """
160-
using AttributedDI;
161-
using System;
162-
using System.Threading.Tasks;
163-
164-
namespace MyApp
165-
{
166-
public interface IService { }
167-
168-
[RegisterAsImplementedInterfaces]
169-
public class DisposableService : IService, IDisposable, IAsyncDisposable
170-
{
171-
public void Dispose() { }
172-
173-
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
174-
}
175-
}
176-
""";
177-
178-
var (output, diagnostics) = new SourceGeneratorTestFixture()
179-
.WithSourceCode(code)
180-
.AddGenerator<ServiceRegistrationGenerator>()
181-
.RunAndGetOutput();
182-
183-
Assert.Empty(diagnostics);
184-
185-
await Verify(output);
186-
}
187-
188-
[Fact]
189-
public async Task RegistersServicesInNestedNamespaces()
190-
{
191-
// Tests: Services across different nested namespaces
192-
var code = """
193-
using AttributedDI;
194-
195-
namespace MyApp.Services
196-
{
197-
[RegisterAsSelf, Singleton]
198-
public class OuterService { }
199-
}
200-
201-
namespace MyApp.Services.Internal
202-
{
203-
[RegisterAsSelf]
204-
public class InnerService { }
205-
}
206-
""";
207-
208-
var (output, diagnostics) = new SourceGeneratorTestFixture()
209-
.WithSourceCode(code)
210-
.AddGenerator<ServiceRegistrationGenerator>()
211-
.RunAndGetOutput();
212-
213-
Assert.Empty(diagnostics);
214-
215-
await Verify(output);
127+
Assert.DoesNotContain("RegularClass", output);
216128
}
217129

218130
[Fact(Skip = "Pending diagnostics for conflicting lifetime attributes on a single type.")]

0 commit comments

Comments
 (0)