Skip to content

Commit cb8ad8a

Browse files
Refactor: Simplify interface generation by embedding sources directly and removing IInterfaceAttributeSources abstraction
1 parent b2219a7 commit cb8ad8a

10 files changed

Lines changed: 113 additions & 165 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// <auto-generated/>
2+
#if !PUREDI_API_SUPPRESSION || PUREDI_API_V2
3+
#pragma warning disable
4+
5+
#if !PUREDI_API_INTERFACE_SUPPRESSION
6+
namespace Pure.DI
7+
{
8+
/// <summary>
9+
/// Generates an interface from the attributed class.
10+
/// </summary>
11+
[global::System.AttributeUsage(global::System.AttributeTargets.Class)]
12+
[global::System.CodeDom.Compiler.GeneratedCode("Pure.DI", "")]
13+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
14+
internal sealed class GenerateInterfaceAttribute : global::System.Attribute
15+
{
16+
/// <summary>
17+
/// Generates an interface from the attributed class.
18+
/// </summary>
19+
internal GenerateInterfaceAttribute(
20+
string namespaceName = default(string),
21+
string interfaceName = default(string),
22+
bool asInternal = false) { }
23+
}
24+
25+
/// <summary>
26+
/// Ignores a class member when generating an interface.
27+
/// </summary>
28+
[global::System.AttributeUsage(global::System.AttributeTargets.Method | global::System.AttributeTargets.Property | global::System.AttributeTargets.Event)]
29+
[global::System.CodeDom.Compiler.GeneratedCode("Pure.DI", "")]
30+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
31+
internal sealed class IgnoreInterfaceAttribute : global::System.Attribute
32+
{
33+
}
34+
}
35+
36+
#endif
37+
#pragma warning restore
38+
#endif

src/Pure.DI.Core/Generator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ private void Setup() => DI.Setup()
5252
// Interface generator
5353
.PerBlock<
5454
InterfaceGeneration.InterfaceGenerator,
55-
InterfaceGeneration.InterfaceAttributeSources,
5655
InterfaceGeneration.RoslynSymbols,
5756
InterfaceGeneration.InterfaceBuilder>()
5857

src/Pure.DI.Core/IInterfaceGenerator.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
public interface IInterfaceGenerator
44
{
5-
IEnumerable<Source> Api { get; }
6-
75
bool HasGenerateInterfaceAttribute(ClassDeclarationSyntax classSyntax);
86

97
void Generate(SourceProductionContext context, ImmutableArray<GeneratorSyntaxContext> syntaxContexts);

src/Pure.DI.Core/InterfaceGeneration/IInterfaceAttributeSources.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/Pure.DI.Core/InterfaceGeneration/InterfaceAttributeSources.cs

Lines changed: 0 additions & 55 deletions
This file was deleted.

src/Pure.DI.Core/InterfaceGeneration/InterfaceGenerator.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,14 @@
11
namespace Pure.DI.InterfaceGeneration;
22

3-
using System.Collections.Generic;
43
using System.Collections.Immutable;
54
using System.Text;
65
using Microsoft.CodeAnalysis;
76
using Microsoft.CodeAnalysis.CSharp.Syntax;
87
using Microsoft.CodeAnalysis.Text;
98
using Pure.DI;
109

11-
sealed class InterfaceGenerator : IInterfaceGenerator
10+
sealed class InterfaceGenerator(IInterfaceBuilder interfaceBuilder) : IInterfaceGenerator
1211
{
13-
private readonly IInterfaceBuilder _interfaceBuilder;
14-
private readonly IInterfaceAttributeSources _attributeSources;
15-
16-
internal InterfaceGenerator(
17-
IInterfaceBuilder interfaceBuilder,
18-
IInterfaceAttributeSources attributeSources)
19-
{
20-
_interfaceBuilder = interfaceBuilder;
21-
_attributeSources = attributeSources;
22-
}
23-
24-
public IEnumerable<Source> Api =>
25-
[
26-
new($"{Names.GenerateInterfaceAttributeName}.Attribute.g.cs", SourceText.From(_attributeSources.GenerateInterfaceAttributeSource, Encoding.UTF8)),
27-
new($"{Names.IgnoreInterfaceAttributeName}.Attribute.g.cs", SourceText.From(_attributeSources.IgnoreInterfaceAttributeSource, Encoding.UTF8))
28-
];
29-
3012
public bool HasGenerateInterfaceAttribute(ClassDeclarationSyntax classSyntax) => classSyntax.AttributeLists
3113
.SelectMany(list => list.Attributes)
3214
.Any(attribute => attribute.Name.ToString().Contains(Names.GenerateInterfaceAttributeName, StringComparison.Ordinal));
@@ -50,7 +32,7 @@ public void Generate(SourceProductionContext context, ImmutableArray<GeneratorSy
5032
continue;
5133
}
5234

53-
var code = _interfaceBuilder.BuildInterfaceFor(syntaxContext.SemanticModel, typeSymbol, classSyntax);
35+
var code = interfaceBuilder.BuildInterfaceFor(syntaxContext.SemanticModel, typeSymbol, classSyntax);
5436
if (string.IsNullOrWhiteSpace(code))
5537
{
5638
continue;

src/Pure.DI.Core/Pure.DI.Core.csproj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<DependentUpon>GenericTypeArguments.g.tt</DependentUpon>
3131
</Compile>
3232
<EmbeddedResource Include="Components\GenericTypeArguments.g.cs" />
33+
<EmbeddedResource Include="Components\Interfaces.g.cs"/>
3334
<EmbeddedResource Include="Features\Default.g.cs"/>
3435
<EmbeddedResource Update="Strings.resx">
3536
<Generator>ResXFileCodeGenerator</Generator>
@@ -44,11 +45,5 @@
4445
<DependentUpon>Strings.resx</DependentUpon>
4546
</Compile>
4647
</ItemGroup>
47-
48-
<ItemGroup>
49-
<None Update="Components\Api.g.tt">
50-
<Generator>TextTemplatingFileGenerator</Generator>
51-
</None>
52-
</ItemGroup>
5348

5449
</Project>

src/Pure.DI/SourceGenerator.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2323
{
2424
initializationContext.AddSource(apiSource.HintName, apiSource.SourceText);
2525
}
26-
27-
foreach (var apiSource in interfaceGenerator.Api)
28-
{
29-
initializationContext.AddSource(apiSource.HintName, apiSource.SourceText);
30-
}
3126
});
3227

3328
var setupContexts = context.SyntaxProvider
Lines changed: 50 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
namespace Pure.DI.IntegrationTests;
22

3-
using System;
4-
using System.Linq;
5-
using InterfaceGeneration;
6-
using Microsoft.CodeAnalysis;
7-
using Microsoft.CodeAnalysis.CSharp;
8-
93
public class InterfaceGenerationTests
104
{
115
[Fact]
12-
public void ShouldGenerateAnInterfaceFromAnnotatedClass()
6+
public async Task ShouldGenerateAnInterfaceFromAnnotatedClass()
137
{
14-
var generated = GenerateInterfaceSource("""
8+
var result = await """
9+
using System;
1510
using Pure.DI;
1611
1712
namespace Demo;
1813
19-
public partial interface IService;
14+
public partial interface IService
15+
{
16+
}
2017
2118
[GenerateInterface]
2219
public partial class Service
@@ -32,26 +29,35 @@ public string GetText<T>(string? value)
3229
where T : class
3330
=> value ?? string.Empty;
3431
}
35-
""",
36-
"IService");
37-
38-
generated.ShouldContain("public partial interface IService");
39-
generated.ShouldContain("string Name { get; set; }");
40-
generated.ShouldContain("Changed");
41-
generated.ShouldContain("GetText<T>");
42-
generated.ShouldNotContain("Hidden");
32+
33+
public class Program
34+
{
35+
public static void Main() { }
36+
}
37+
""".RunAsync(new Options(LanguageVersion.CSharp10, CheckCompilationErrors: false));
38+
39+
result.Errors.Count.ShouldBe(0, result);
40+
result.Warnings.Count.ShouldBe(0, result);
41+
42+
result.GeneratedCode.ShouldContain("public partial interface IService");
43+
result.GeneratedCode.ShouldContain("string Name { get; set; }");
44+
result.GeneratedCode.ShouldContain("Changed");
45+
result.GeneratedCode.ShouldContain("GetText<T>");
46+
result.GeneratedCode.ShouldNotContain("Hidden");
4347
}
4448

4549
[Fact]
46-
public void ShouldGenerateInterfaceForGenericType()
50+
public async Task ShouldGenerateInterfaceForGenericType()
4751
{
48-
var generated = GenerateInterfaceSource("""
52+
var result = await """
4953
using System;
5054
using Pure.DI;
5155
5256
namespace Demo;
5357
54-
public partial interface IRepository<TItem>;
58+
public partial interface IRepository<TItem>
59+
{
60+
}
5561
5662
[GenerateInterface]
5763
public partial class Repository<TItem>
@@ -63,13 +69,20 @@ public partial class Repository<TItem>
6369
6470
public TItem Create() => new();
6571
}
66-
""",
67-
"IRepository");
6872
69-
generated.ShouldContain("public partial interface IRepository<TItem>");
70-
generated.ShouldContain("where TItem : class, new()");
71-
generated.ShouldContain("TItem? Current { get; set; }");
72-
generated.ShouldContain("EventHandler<TItem>");
73+
public class Program
74+
{
75+
public static void Main() { }
76+
}
77+
""".RunAsync(new Options(LanguageVersion.CSharp10, CheckCompilationErrors: false));
78+
79+
result.Errors.Count.ShouldBe(0, result);
80+
result.Warnings.Count.ShouldBe(0, result);
81+
82+
result.GeneratedCode.ShouldContain("public partial interface IRepository<TItem>");
83+
result.GeneratedCode.ShouldContain("where TItem : class, new()");
84+
result.GeneratedCode.ShouldContain("TItem? Current { get; set; }");
85+
result.GeneratedCode.ShouldContain("EventHandler<TItem>");
7386
}
7487

7588
[Fact]
@@ -80,17 +93,24 @@ public async Task ShouldUseGeneratedInterfaceWithPureDi()
8093
8194
namespace Demo;
8295
83-
public partial interface IService;
96+
public partial interface IService
97+
{
98+
}
8499
85100
[GenerateInterface]
86101
public partial class Service : IService
87102
{
88103
public string Message => "ok";
89104
}
90105
91-
public partial class Consumer(IService service)
106+
public partial class Consumer
92107
{
93-
public string Message { get; } = service.Message;
108+
public Consumer(IService service)
109+
{
110+
Message = service.Message;
111+
}
112+
113+
public string Message { get; }
94114
}
95115
96116
partial class Setup
@@ -112,49 +132,11 @@ public static void Main()
112132
}
113133
""";
114134

115-
var generator = new Generator();
116-
var interfaceGenerator = generator.InterfaceGenerator;
117-
var generatedInterface = GenerateInterfaceSource(code, "IService");
118-
var result = await interfaceGenerator.Api
119-
.Select(source => source.SourceText.ToString())
120-
.Append(code)
121-
.Append(generatedInterface)
122-
.RunAsync(new Options(LanguageVersion.CSharp12, CheckCompilationErrors: false));
135+
var result = await code.RunAsync(new Options(LanguageVersion.CSharp10, CheckCompilationErrors: false));
123136

124137
result.Errors.Count.ShouldBe(0, result);
125138
result.Warnings.Count.ShouldBe(0, result);
126139
result.Success.ShouldBeTrue(result);
127140
result.StdOut.ShouldContain("ok");
128141
}
129-
130-
private static string GenerateInterfaceSource(string code, string interfaceName)
131-
{
132-
var syntaxTree = CSharpSyntaxTree.ParseText(code);
133-
var references = AppDomain
134-
.CurrentDomain
135-
.GetAssemblies()
136-
.Where(assembly => !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location))
137-
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
138-
.Cast<MetadataReference>();
139-
140-
var compilation = CSharpCompilation.Create(
141-
"InterfaceGenerationTests",
142-
[syntaxTree],
143-
references,
144-
new(OutputKind.DynamicallyLinkedLibrary));
145-
146-
var sourceGenerator = new SourceGenerator();
147-
CSharpGeneratorDriver.Create(sourceGenerator).RunGeneratorsAndUpdateCompilation(
148-
compilation,
149-
out var outputCompilation,
150-
out var diagnostics);
151-
152-
diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ShouldBeEmpty();
153-
154-
return outputCompilation.SyntaxTrees
155-
.Select(tree => tree.ToString())
156-
.FirstOrDefault(text => text.Contains("[global::System.CodeDom.Compiler.GeneratedCode(\"Pure.DI\"" ) && text.Contains($"partial interface {interfaceName}"))
157-
?? throw new InvalidOperationException(string.Join(Environment.NewLine + "---" + Environment.NewLine,
158-
outputCompilation.SyntaxTrees.Select(tree => tree.ToString())));
159-
}
160142
}

0 commit comments

Comments
 (0)