Skip to content

Commit 3ecff3d

Browse files
Refactor: Enhance InterfaceBuilder to utilize Lines, streamline nullable handling, and integrate FileHeader for improved interface generation
1 parent cb8ad8a commit 3ecff3d

8 files changed

Lines changed: 135 additions & 132 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Pure.DI.Core.Code.Parts;
2+
3+
class FileHeader(IInformation information) : IFileHeader
4+
{
5+
public void Add(Compilation compilation, Lines code)
6+
{
7+
code.AppendComments("<auto-generated/>", $"by {information.Description}");
8+
if (compilation.Options.NullableContextOptions != NullableContextOptions.Disable)
9+
{
10+
code.AppendLine("#nullable enable annotations");
11+
}
12+
}
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Pure.DI.Core.Code.Parts;
2+
3+
interface IFileHeader
4+
{
5+
void Add(Compilation compilation, Lines code);
6+
}

src/Pure.DI.Core/Generator.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Pure.DI;
1616
using static Tag;
1717
using static Unit;
1818
using Metadata = Core.Metadata;
19+
using Interfaces = InterfaceGeneration;
1920

2021
// @formatter:off
2122
public sealed partial class Generator
@@ -51,9 +52,11 @@ private void Setup() => DI.Setup()
5152

5253
// Interface generator
5354
.PerBlock<
54-
InterfaceGeneration.InterfaceGenerator,
55-
InterfaceGeneration.RoslynSymbols,
56-
InterfaceGeneration.InterfaceBuilder>()
55+
Interfaces.InterfaceGenerator,
56+
Interfaces.RoslynSymbols,
57+
Interfaces.InterfaceBuilder>()
58+
59+
.Transient<Interfaces.InterfaceCodeBuilder>()
5760

5861
// Transient
5962
.Transient(_ => GetType().Assembly)
@@ -82,7 +85,7 @@ private void Setup() => DI.Setup()
8285
NodeTools, LocalFunctions, ExceptionHandler, WildcardMatcher, InjectionSiteFactory, Semantic, Attributes, Compilations, GraphWalker<TT, TT1>,
8386
LifetimeAnalyzer, InstanceDpProvider, Injections, NameFormatter, BindingsFactory, NodesFactory, LocationProvider,
8487
CycleTools, LifetimeProvider, VarDeclarationTools, ContractTagComparer,
85-
CodeNameProvider, DependencyNodePrioritizer>()
88+
CodeNameProvider, DependencyNodePrioritizer, FileHeader>()
8689
.PerBlock<LifetimesValidatorVisitor, CyclicDependencyValidatorVisitor, RootArgsVisitor, RootStatisticsVisitor>()
8790
.PerBlock<GraphOverrider>(Overrider)
8891
.PerBlock<GraphCleaner>(Cleaner)

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

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

3+
using System.Collections.Immutable;
34
using System.Linq;
45
using Microsoft.CodeAnalysis;
56
using Microsoft.CodeAnalysis.CSharp.Syntax;
67

78
sealed class GeneratedInterfaceDetails(
9+
SemanticModel semanticModel,
810
AttributeData? generationAttribute,
911
ITypeSymbol typeSymbol,
1012
ClassDeclarationSyntax classSyntax)
1113
{
14+
public SemanticModel SemanticModel { get; } = semanticModel;
15+
1216
public string NamespaceName { get; } = PrepareNamespaceValue(generationAttribute, typeSymbol.ContainingNamespace.ToDisplayString());
1317

1418
public string InterfaceName { get; } = PrepareValue(
@@ -23,6 +27,16 @@ sealed class GeneratedInterfaceDetails(
2327
? "internal"
2428
: "public";
2529

30+
public string ClassDocumentation { get; set; } = string.Empty;
31+
32+
public string GenericType { get; set; } = string.Empty;
33+
34+
public ImmutableArray<PropertyInfo> PropertyInfos { get; set; } = ImmutableArray<PropertyInfo>.Empty;
35+
36+
public ImmutableArray<MethodInfo> MethodInfos { get; set; } = ImmutableArray<MethodInfo>.Empty;
37+
38+
public ImmutableArray<EventInfo> Events { get; set; } = ImmutableArray<EventInfo>.Empty;
39+
2640
private static string PrepareNamespaceValue(AttributeData? generationAttribute, string defaultValue) =>
2741
PrepareValue(generationAttribute, Names.NamespaceParameterName, defaultValue);
2842

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
interface IInterfaceBuilder
44
{
5-
string BuildInterfaceFor(
5+
Lines BuildInterfaceFor(
66
SemanticModel semanticModel,
77
ITypeSymbol typeSymbol,
88
ClassDeclarationSyntax classSyntax);

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

Lines changed: 75 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// ReSharper disable LoopCanBeConvertedToQuery
12
namespace Pure.DI.InterfaceGeneration;
23

34
using System;
45
using System.Collections.Generic;
6+
using System.Collections.Immutable;
57
using System.Linq;
68
using System.Text;
79
using Microsoft.CodeAnalysis;
@@ -10,7 +12,9 @@ namespace Pure.DI.InterfaceGeneration;
1012

1113
sealed class InterfaceBuilder(
1214
IRoslynSymbols roslynSymbols,
13-
ITypes types): IInterfaceBuilder
15+
ITypes types,
16+
Func<IBuilder<GeneratedInterfaceDetails, Lines>> interfaceCodeBuilderFactory)
17+
: IInterfaceBuilder
1418
{
1519
private static readonly SymbolDisplayFormat FullyQualifiedDisplayFormat = new(
1620
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
@@ -28,68 +32,74 @@ sealed class InterfaceBuilder(
2832
globalNamespaceStyle: FullyQualifiedDisplayFormat.GlobalNamespaceStyle,
2933
miscellaneousOptions: FullyQualifiedDisplayFormat.MiscellaneousOptions);
3034

31-
public string BuildInterfaceFor(SemanticModel semanticModel, ITypeSymbol typeSymbol, ClassDeclarationSyntax classSyntax)
35+
public Lines BuildInterfaceFor(SemanticModel semanticModel, ITypeSymbol typeSymbol, ClassDeclarationSyntax classSyntax)
3236
{
3337
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
3438
{
35-
return string.Empty;
39+
return new Lines();
3640
}
3741

3842
var generationAttribute = typeSymbol.GetAttributes().FirstOrDefault(x =>
3943
x.AttributeClass != null && x.AttributeClass.Name.Contains(Names.GenerateInterfaceAttributeName, StringComparison.Ordinal));
4044

41-
var symbolDetails = new GeneratedInterfaceDetails(generationAttribute, typeSymbol, classSyntax);
42-
var interfaceGenerator = new InterfaceCodeBuilder(symbolDetails.NamespaceName, symbolDetails.InterfaceName, symbolDetails.AccessLevel);
43-
44-
interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
45-
interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));
45+
var symbolDetails = new GeneratedInterfaceDetails(semanticModel, generationAttribute, typeSymbol, classSyntax)
46+
{
47+
ClassDocumentation = GetDocumentationForClass(classSyntax),
48+
GenericType = GetGeneric(classSyntax, namedTypeSymbol)
49+
};
4650

4751
var members = roslynSymbols.GetAllMembers(typeSymbol)
4852
.Where(x => x.DeclaredAccessibility == Accessibility.Public)
4953
.Where(x => !x.IsStatic)
5054
.Where(x => !HasIgnoreAttribute(x))
5155
.ToList();
5256

53-
AddPropertiesToInterface(members, interfaceGenerator);
54-
AddMethodsToInterface(semanticModel, members, interfaceGenerator);
55-
AddEventsToInterface(members, interfaceGenerator);
57+
var hasNullable = false;
58+
symbolDetails.PropertyInfos = GetProperties(members, ref hasNullable);
59+
symbolDetails.MethodInfos = GetMethods(semanticModel, members, ref hasNullable);
60+
symbolDetails.Events = GetEvents(members, ref hasNullable);
5661

57-
return interfaceGenerator.Build();
62+
return interfaceCodeBuilderFactory().Build(symbolDetails);
5863
}
5964

60-
private void AddMethodsToInterface(SemanticModel semanticModel, List<ISymbol> members, InterfaceCodeBuilder codeGenerator)
65+
private ImmutableArray<MethodInfo> GetMethods(SemanticModel semanticModel, List<ISymbol> members, ref bool hasNullable)
6166
{
62-
members.Where(x => x.Kind == SymbolKind.Method)
63-
.OfType<IMethodSymbol>()
64-
.Where(x => x.MethodKind is MethodKind.Ordinary)
65-
.Where(x => !types.TypeEquals(x.ContainingType, semanticModel.Compilation.GetSpecialType(SpecialType.System_Object)))
66-
.Where(x => !HasIgnoreAttribute(x))
67-
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
68-
.Select(g => g.First())
69-
.ToList()
70-
.ForEach(method => AddMethod(codeGenerator, method));
67+
var methods = new List<MethodInfo>();
68+
foreach (var method in members.Where(x => x.Kind == SymbolKind.Method)
69+
.OfType<IMethodSymbol>()
70+
.Where(x => x.MethodKind is MethodKind.Ordinary)
71+
.Where(x => !types.TypeEquals(x.ContainingType, semanticModel.Compilation.GetSpecialType(SpecialType.System_Object)))
72+
.Where(x => !HasIgnoreAttribute(x))
73+
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
74+
.Select(g => g.First()))
75+
{
76+
methods.Add(GetMethodInfo(method, ref hasNullable));
77+
}
78+
79+
return methods.ToImmutableArray();
7180
}
7281

73-
private void AddMethod(InterfaceCodeBuilder codeGenerator, IMethodSymbol method)
82+
private MethodInfo GetMethodInfo(IMethodSymbol method, ref bool hasNullable)
7483
{
75-
ActivateNullableIfNeeded(codeGenerator, method);
84+
ActivateNullableIfNeeded(ref hasNullable, method);
7685

7786
var paramResult = new HashSet<string>();
78-
foreach (var parameter in method.Parameters.Select(p => GetParameterDisplayString(p, codeGenerator.HasNullable)))
87+
foreach (var methodParameter in method.Parameters)
7988
{
89+
var parameter = GetParameterDisplayString(methodParameter, hasNullable);
8090
paramResult.Add(parameter);
8191
}
8292

8393
var typedArgs = method.TypeParameters
8494
.Select(arg => (arg.ToDisplayString(FullyQualifiedDisplayFormat), roslynSymbols.GetWhereStatement(arg, FullyQualifiedDisplayFormat)))
8595
.ToList();
8696

87-
codeGenerator.AddMethodToInterface(
97+
return new MethodInfo(
8898
method.Name,
8999
GetMethodReturnType(method),
90100
InheritDoc(method),
91-
paramResult,
92-
typedArgs);
101+
paramResult.ToImmutableArray(),
102+
typedArgs.ToImmutableArray());
93103
}
94104

95105
private static string GetMethodReturnType(IMethodSymbol method)
@@ -98,19 +108,19 @@ private static string GetMethodReturnType(IMethodSymbol method)
98108
return prefix + method.ReturnType.ToDisplayString(FullyQualifiedDisplayFormat);
99109
}
100110

101-
private static void ActivateNullableIfNeeded(InterfaceCodeBuilder codeGenerator, ITypeSymbol typeSymbol)
111+
private static void ActivateNullableIfNeeded(ref bool hasNullable, ITypeSymbol typeSymbol)
102112
{
103113
if (IsNullable(typeSymbol))
104114
{
105-
codeGenerator.HasNullable = true;
115+
hasNullable = true;
106116
}
107117
}
108118

109-
private static void ActivateNullableIfNeeded(InterfaceCodeBuilder codeGenerator, IMethodSymbol method)
119+
private static void ActivateNullableIfNeeded(ref bool hasNullable, IMethodSymbol method)
110120
{
111121
if (method.Parameters.Any(x => IsNullable(x.Type)) || IsNullable(method.ReturnType))
112122
{
113-
codeGenerator.HasNullable = true;
123+
hasNullable = true;
114124
}
115125
}
116126

@@ -172,40 +182,42 @@ private static string GetParameterDisplayString(IParameterSymbol param, bool nul
172182
return typeSb.Append(restSb).ToString();
173183
}
174184

175-
private static void AddEventsToInterface(List<ISymbol> members, InterfaceCodeBuilder codeGenerator)
185+
private static ImmutableArray<EventInfo> GetEvents(List<ISymbol> members, ref bool hasNullable)
176186
{
177-
members.Where(x => x.Kind == SymbolKind.Event)
178-
.OfType<IEventSymbol>()
179-
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
180-
.Select(g => g.First())
181-
.ToList()
182-
.ForEach(evt =>
183-
{
184-
ActivateNullableIfNeeded(codeGenerator, evt.Type);
185-
codeGenerator.AddEventToInterface(evt.Name, evt.Type.ToDisplayString(FullyQualifiedDisplayFormat), InheritDoc(evt));
186-
});
187+
var events = new List<EventInfo>();
188+
foreach (var evt in members.Where(x => x.Kind == SymbolKind.Event)
189+
.OfType<IEventSymbol>()
190+
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
191+
.Select(g => g.First()))
192+
{
193+
ActivateNullableIfNeeded(ref hasNullable, evt.Type);
194+
events.Add(new EventInfo(evt.Name, evt.Type.ToDisplayString(FullyQualifiedDisplayFormat), InheritDoc(evt)));
195+
}
196+
197+
return events.ToImmutableArray();
187198
}
188199

189-
private static void AddPropertiesToInterface(List<ISymbol> members, InterfaceCodeBuilder interfaceGenerator)
200+
private static ImmutableArray<PropertyInfo> GetProperties(List<ISymbol> members, ref bool hasNullable)
190201
{
191-
members.Where(x => x.Kind == SymbolKind.Property)
192-
.OfType<IPropertySymbol>()
193-
.Where(x => !x.IsIndexer)
194-
.GroupBy(x => x.Name)
195-
.Select(g => g.First())
196-
.ToList()
197-
.ForEach(prop =>
198-
{
199-
ActivateNullableIfNeeded(interfaceGenerator, prop.Type);
200-
201-
interfaceGenerator.AddPropertyToInterface(
202-
prop.Name,
203-
prop.Type.ToDisplayString(FullyQualifiedDisplayFormat),
204-
prop.GetMethod?.DeclaredAccessibility == Accessibility.Public,
205-
GetSetKind(prop.SetMethod),
206-
prop.ReturnsByRef,
207-
InheritDoc(prop));
208-
});
202+
var properties = new List<PropertyInfo>();
203+
foreach (var prop in members.Where(x => x.Kind == SymbolKind.Property)
204+
.OfType<IPropertySymbol>()
205+
.Where(x => !x.IsIndexer)
206+
.GroupBy(x => x.Name)
207+
.Select(g => g.First()))
208+
{
209+
ActivateNullableIfNeeded(ref hasNullable, prop.Type);
210+
211+
properties.Add(new PropertyInfo(
212+
prop.Name,
213+
prop.Type.ToDisplayString(FullyQualifiedDisplayFormat),
214+
prop.GetMethod?.DeclaredAccessibility == Accessibility.Public,
215+
GetSetKind(prop.SetMethod),
216+
prop.ReturnsByRef,
217+
InheritDoc(prop)));
218+
}
219+
220+
return properties.ToImmutableArray();
209221
}
210222

211223
private static PropertySetKind GetSetKind(IMethodSymbol? setMethodSymbol) =>
@@ -251,4 +263,4 @@ private string GetGeneric(TypeDeclarationSyntax classSyntax, INamedTypeSymbol ty
251263

252264
private static string InheritDoc(ISymbol source) =>
253265
$"/// <inheritdoc cref=\"{source.ToDisplayString().Replace("<", "{").Replace(">", "}").Replace("params ", string.Empty)}\" />";
254-
}
266+
}

0 commit comments

Comments
 (0)