Skip to content

Commit d4d915f

Browse files
dmytroettDmytro Ett
andauthored
Enabling AddAttributedDI method generation opt-in/out (#20)
* Adjust agent behavior slightly * WIP * Added unit tests * Integration tests * removing the features I want to delay * Adjusted tests accordingly --------- Co-authored-by: Dmytro Ett <dmytro.ett@outlook.com>
1 parent b12f0d7 commit d4d915f

28 files changed

+592
-87
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This project is about building a library to simplify dependency registration in
99
- When uncertain about APIs, best practices, or modern implementation patterns, consult Microsoft documentation.
1010
- When usage is unclear, search GitHub source code for referenced open source projects.
1111
- Prefer the configured MCP servers (GitHub, Microsoft Docs) as your first reference sources.
12+
- For guidance on APIs, best practices, packaging/publishing, testing patterns, or other non-trivial design decisions, proactively consult Microsoft Docs or GitHub MCP before answering, unless the answer is fully local to this repo.
1213

1314
## Code Quality Guidelines
1415

AttributedDI.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
</Folder>
1919
<Folder Name="/test/">
2020
<Project Path="test/AttributedDI.IntegrationTests/AttributedDI.IntegrationTests.csproj" />
21+
<Project Path="test/AttributedDI.GenerateExtensionsOptIn.IntegrationTests/AttributedDI.GenerateExtensionsOptIn.IntegrationTests.csproj" />
22+
<Project Path="test/AttributedDI.GenerateExtensionsOptOut.IntegrationTests/AttributedDI.GenerateExtensionsOptOut.IntegrationTests.csproj" />
2123
<Project Path="test/AttributedDI.SourceGenerator.UnitTests/AttributedDI.SourceGenerator.UnitTests.csproj" />
2224
</Folder>
2325
<Folder Name="/test/assets/">
@@ -26,6 +28,8 @@
2628
<Project Path="test/assets/Company.TeamName.Project.API/Company.TeamName.Project.API.csproj" />
2729
<Project Path="test/assets/CustomModuleName/CustomModuleName.csproj" />
2830
<Project Path="test/assets/GeneratedInterfacesSut/GeneratedInterfacesSut.csproj" />
31+
<Project Path="test/assets/GenerateExtensions.OptIn/GenerateExtensions.OptIn.csproj" />
32+
<Project Path="test/assets/GenerateExtensions.OptOut/GenerateExtensions.OptOut.csproj" />
2933
<File Path="test/assets/.editorconfig" />
3034
<File Path="test/assets/Directory.Build.props" />
3135
</Folder>

src/AttributedDI.SourceGenerator/ServiceCollectionExtensionsGeneration/AttributedDiServiceCollectionExtensionsEmitter.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,6 @@ public static void EmitExtensionMethods(
7272
builder.AppendLine(" }");
7373
builder.AppendLine(" }");
7474
builder.AppendLine();
75-
builder.AppendLine(" if (opts.EnableDeferred)");
76-
builder.AppendLine(" {");
77-
builder.AppendLine(" // TODO: Lazy<> support but without allocations");
78-
builder.AppendLine(" }");
79-
builder.AppendLine();
80-
builder.AppendLine(" if (opts.EnableOwned)");
81-
builder.AppendLine(" {");
82-
builder.AppendLine(" // TODO: enable Owned<> support");
83-
builder.AppendLine(" }");
84-
builder.AppendLine();
85-
builder.AppendLine(" if (opts.EnableFactory)");
86-
builder.AppendLine(" {");
87-
builder.AppendLine(" // TODO: add Func<> support.");
88-
builder.AppendLine(" }");
89-
builder.AppendLine();
9075
builder.AppendLine(" return services;");
9176
builder.AppendLine(" }");
9277
builder.AppendLine(" }");
Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,51 @@
11
using AttributedDI.SourceGenerator.ServiceModulesGeneration;
22
using Microsoft.CodeAnalysis;
33
using System.Collections.Immutable;
4-
using System.Threading;
54

65
namespace AttributedDI.SourceGenerator.ServiceCollectionExtensionsGeneration;
76

87
internal static class AttributedDiServiceCollectionExtensionsPipeline
98
{
9+
private const string GenerateExtensionsPropertyName = "GenerateAttributedDIExtensions";
10+
1011
public static IncrementalValueProvider<AttributedDiServiceCollectionExtensionsInfo> Collect(
1112
IncrementalGeneratorInitializationContext context, IncrementalValueProvider<ServiceModuleToGenerate> moduleToGenerate)
1213
{
13-
var isEntryPoint = context.CompilationProvider
14-
.Select(static (compilation, _) => compilation.Options.OutputKind != OutputKind.DynamicallyLinkedLibrary);
14+
var generateExtensionsOverride = context.AnalyzerConfigOptionsProvider
15+
.Select(static (provider, _) =>
16+
{
17+
if (provider.GlobalOptions.TryGetValue($"build_property.{GenerateExtensionsPropertyName}", out var value)
18+
&& bool.TryParse(value, out var parsed))
19+
{
20+
return (bool?)parsed;
21+
}
22+
23+
return null;
24+
});
25+
26+
var shouldGenerateExtensions = context.CompilationProvider
27+
.Select(static (compilation, _) => compilation.Options.OutputKind)
28+
.Combine(generateExtensionsOverride)
29+
.Select(static (data, _) =>
30+
{
31+
var (outputKind, overrideValue) = data;
32+
33+
return overrideValue ?? outputKind != OutputKind.DynamicallyLinkedLibrary;
34+
});
1535

1636
var generatedModulesFromReferences = context.CompilationProvider
17-
.Select(static (compilation, token) => CollectFromCompilation(compilation, token));
37+
.Combine(shouldGenerateExtensions)
38+
.Select(static (data, token) =>
39+
{
40+
var (compilation, shouldGenerate) = data;
41+
42+
if (!shouldGenerate)
43+
{
44+
return ImmutableArray<GeneratedModuleRegistrationInfo>.Empty;
45+
}
46+
47+
return GeneratedModuleReferenceCollector.CollectGeneratedModulesFromReferences(compilation, token);
48+
});
1849

1950
var modulesToInclude = moduleToGenerate
2051
.Select(static (module, _) =>
@@ -30,14 +61,14 @@ public static IncrementalValueProvider<AttributedDiServiceCollectionExtensionsIn
3061
return (GeneratedModuleRegistrationInfo?)new GeneratedModuleRegistrationInfo(fullyQualifiedName);
3162
});
3263

33-
return isEntryPoint
64+
return shouldGenerateExtensions
3465
.Combine(generatedModulesFromReferences)
3566
.Combine(modulesToInclude)
3667
.Select(static (data, _) =>
3768
{
38-
var ((isEntryPoint, modulesFromReferences), currentModuleToInclude) = data;
69+
var ((shouldGenerateExtensions, modulesFromReferences), currentModuleToInclude) = data;
3970

40-
if (!isEntryPoint)
71+
if (!shouldGenerateExtensions)
4172
{
4273
return new AttributedDiServiceCollectionExtensionsInfo(
4374
false,
@@ -49,24 +80,15 @@ public static IncrementalValueProvider<AttributedDiServiceCollectionExtensionsIn
4980
return new AttributedDiServiceCollectionExtensionsInfo(true, modulesFromReferences);
5081
}
5182

52-
return new AttributedDiServiceCollectionExtensionsInfo(true, modulesFromReferences.Add(currentModuleToInclude));
83+
return new AttributedDiServiceCollectionExtensionsInfo(
84+
true,
85+
modulesFromReferences.Add(currentModuleToInclude));
5386
});
5487
}
55-
56-
private static ImmutableArray<GeneratedModuleRegistrationInfo> CollectFromCompilation(Compilation compilation, CancellationToken token)
57-
{
58-
// do not run the collection if current project is DLL.
59-
if (compilation.Options.OutputKind == OutputKind.DynamicallyLinkedLibrary)
60-
{
61-
return ImmutableArray<GeneratedModuleRegistrationInfo>.Empty;
62-
}
63-
64-
return GeneratedModuleReferenceCollector.CollectGeneratedModulesFromReferences(compilation, token);
65-
}
6688
}
6789

6890
internal sealed record GeneratedModuleRegistrationInfo(string FullyQualifiedTypeName);
6991

7092
internal sealed record AttributedDiServiceCollectionExtensionsInfo(
71-
bool IsEntryPoint,
93+
bool ShouldGenerateExtensions,
7294
ImmutableArray<GeneratedModuleRegistrationInfo> ModuleTypes);

src/AttributedDI.SourceGenerator/ServiceRegistrationGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5252

5353
context.RegisterSourceOutput(addAttributedDiExtensions, static (spc, info) =>
5454
{
55-
if (!info.IsEntryPoint)
55+
if (!info.ShouldGenerateExtensions)
5656
{
5757
return;
5858
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Project>
2+
<ItemGroup>
3+
<CompilerVisibleProperty Include="GenerateAttributedDIExtensions" />
4+
</ItemGroup>
5+
</Project>

src/AttributedDI/AttributedDI.csproj

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
<IsAotCompatible>true</IsAotCompatible>
99
<IsTrimmable>true</IsTrimmable>
1010
<IsPackable>true</IsPackable>
11-
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1211
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1312
<PackageProjectUrl>https://github.com/dmytroett/AttributedDI</PackageProjectUrl>
1413
<RepositoryUrl>https://github.com/dmytroett/AttributedDI</RepositoryUrl>
@@ -38,15 +37,18 @@
3837
Visible="false"/>
3938
</ItemGroup>
4039

40+
<ItemGroup>
41+
<None Include="AttributedDI.SourceGenProperties.props"
42+
Pack="true"
43+
PackagePath="buildTransitive/"
44+
Visible="false"/>
45+
</ItemGroup>
46+
4147
<ItemGroup>
4248
<None Include="..\..\icon.png">
4349
<Pack>True</Pack>
4450
<PackagePath>\</PackagePath>
4551
</None>
46-
<None Include="..\..\LICENSE">
47-
<Pack>True</Pack>
48-
<PackagePath>\</PackagePath>
49-
</None>
5052
</ItemGroup>
5153

5254
</Project>

src/AttributedDI/AttributedDiOptions.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ public sealed class AttributedDiOptions
1212
{
1313
private readonly HashSet<IServiceModule> modules = new(ModuleTypeComparer.Instance);
1414

15-
/// <summary>
16-
/// Gets or sets whether deferred service support is enabled.
17-
/// </summary>
18-
public bool EnableDeferred { get; set; } = true;
19-
20-
/// <summary>
21-
/// Gets or sets whether owned service support is enabled.
22-
/// </summary>
23-
public bool EnableOwned { get; set; } = true;
24-
25-
/// <summary>
26-
/// Gets or sets whether factory delegate support is enabled.
27-
/// </summary>
28-
public bool EnableFactory { get; set; } = true;
15+
// /// <summary>
16+
// /// Gets or sets whether deferred service support is enabled.
17+
// /// </summary>
18+
// public bool EnableDeferred { get; set; } = true;
19+
20+
// /// <summary>
21+
// /// Gets or sets whether owned service support is enabled.
22+
// /// </summary>
23+
// public bool EnableOwned { get; set; } = true;
24+
25+
// /// <summary>
26+
// /// Gets or sets whether factory delegate support is enabled.
27+
// /// </summary>
28+
// public bool EnableFactory { get; set; } = true;
2929

3030
/// <summary>
3131
/// Gets or sets whether generated service modules should be registered.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<OutputType>Exe</OutputType>
7+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<Using Include="Xunit" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
21+
<PackageReference Include="xunit.v3" Version="3.2.1" />
22+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\assets\GenerateExtensions.OptIn\GenerateExtensions.OptIn.csproj" />
27+
</ItemGroup>
28+
29+
</Project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using GenerateExtensions.OptIn;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace AttributedDI.GenerateExtensionsOptIn.IntegrationTests;
5+
6+
public class GenerateExtensionsOptInTests
7+
{
8+
[Fact]
9+
public void AddAttributedDiRegistersServicesFromLibrary()
10+
{
11+
ServiceCollection services = new();
12+
13+
services.AddAttributedDi();
14+
using var provider = services.BuildServiceProvider();
15+
16+
var first = provider.GetRequiredService<OptInService>();
17+
var second = provider.GetRequiredService<OptInService>();
18+
19+
Assert.NotSame(first, second);
20+
}
21+
}

0 commit comments

Comments
 (0)