Skip to content

Commit 0cf2166

Browse files
committed
Identity using SyntaxFactory
1 parent 4eb3163 commit 0cf2166

24 files changed

Lines changed: 754 additions & 440 deletions

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# Snap.Hutao.SourceGeneration
2-
Source Code generator for Snap.Hutao
2+
Source Code Generator for Snap.Hutao
3+
4+
# Development Guideline
5+
6+
Use https://roslynquoter.azurewebsites.net/ to get SyntaxTree
7+
8+
1. Every `IncrementalValue(s)Provider<T>`'s step result should be an `IEquatable<T>` to make it really becomes incremental.
9+
2. So the intermediate models should be a `record (class/struct)` if possible
10+
3. Intermediate array/enumerable should be a `ImmutableArray<T>` if possible, the pipeline use IA internally and has special check for it.

src/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration/AttributeGenerator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationC
6767

6868
CompilationUnitSyntax coreAnnotation = CompilationUnit()
6969
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("Snap.Hutao.Core.Annotation")
70-
.WithLeadingTrivia(NullableEnableList)
70+
.WithLeadingTrivia(NullableEnableTriviaList)
7171
.WithMembers(List<MemberDeclarationSyntax>(
7272
[
7373
ClassDeclaration(identifierOfCommandAttribute)
@@ -173,7 +173,7 @@ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationC
173173

174174
CompilationUnitSyntax coreDependencyInjectionAnnotationHttpClient = CompilationUnit()
175175
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient")
176-
.WithLeadingTrivia(NullableEnableList)
176+
.WithLeadingTrivia(NullableEnableTriviaList)
177177
.WithMembers(List<MemberDeclarationSyntax>(
178178
[
179179
ClassDeclaration(identifierOfHttpClientAttribute)
@@ -270,7 +270,7 @@ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationC
270270

271271
CompilationUnitSyntax coreDependencyInjectionAnnotation = CompilationUnit()
272272
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("Snap.Hutao.Core.DependencyInjection.Annotation")
273-
.WithLeadingTrivia(NullableEnableList)
273+
.WithLeadingTrivia(NullableEnableTriviaList)
274274
.WithMembers(List<MemberDeclarationSyntax>(
275275
[
276276
ClassDeclaration(identifierOfServiceAttribute)
@@ -315,7 +315,7 @@ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationC
315315

316316
CompilationUnitSyntax resourceLocalization = CompilationUnit()
317317
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("Snap.Hutao.Resource.Localization")
318-
.WithLeadingTrivia(NullableEnableList)
318+
.WithLeadingTrivia(NullableEnableTriviaList)
319319
.WithMembers(List<MemberDeclarationSyntax>(
320320
[
321321
ClassDeclaration(identifierOfExtendedEnumAttribute)
@@ -343,7 +343,7 @@ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationC
343343

344344
CompilationUnitSyntax interceptsLocation = CompilationUnit()
345345
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("System.Runtime.CompilerServices")
346-
.WithLeadingTrivia(NullableEnableList)
346+
.WithLeadingTrivia(NullableEnableTriviaList)
347347
.WithMembers(SingletonList<MemberDeclarationSyntax>(
348348
ClassDeclaration(identifierOfInterceptsLocationAttribute)
349349
.WithAttributeLists(SingletonList(SystemAttributeUsageList(AttributeTargetsMethod, allowMultiple: true)))

src/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration/Automation/ApiEndpointsGenerator.cs

Lines changed: 131 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
using Microsoft.CodeAnalysis;
55
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Snap.Hutao.SourceGeneration.Model;
67
using System;
78
using System.Collections.Generic;
89
using System.Collections.Immutable;
910
using System.IO;
1011
using System.Linq;
1112
using System.Text;
1213
using System.Text.Json;
14+
using System.Threading;
1315
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1416
using static Snap.Hutao.SourceGeneration.Primitive.FastSyntaxFactory;
1517
using static Snap.Hutao.SourceGeneration.Primitive.SyntaxKeywords;
@@ -20,11 +22,14 @@ namespace Snap.Hutao.SourceGeneration.Automation;
2022
internal sealed class ApiEndpointsGenerator : IIncrementalGenerator
2123
{
2224
private const string FileName = "Endpoints.csv";
23-
private static readonly ExpressionSyntax ThrowNotSupportedException = ThrowExpression(ObjectCreationExpression(IdentifierName("NotSupportedException")).WithArgumentList());
25+
private static readonly ExpressionSyntax ThrowNotSupportedException = ThrowExpression(ObjectCreationExpression(IdentifierName("NotSupportedException")).WithEmptyArgumentList());
2426

2527
public void Initialize(IncrementalGeneratorInitializationContext context)
2628
{
27-
IncrementalValueProvider<ImmutableArray<AdditionalText>> provider = context.AdditionalTextsProvider.Where(Match).Collect();
29+
IncrementalValuesProvider<EndpointsMetadataContext> provider = context.AdditionalTextsProvider
30+
.Where(Match)
31+
.Select(EndpointsMetadataContext.Create)
32+
.Where(static context => !context.Endpoints.IsEmpty);
2833
context.RegisterImplementationSourceOutput(provider, GenerateWrapper);
2934
}
3035

@@ -34,99 +39,54 @@ private static bool Match(AdditionalText text)
3439
return Path.GetFileName(text.Path).EndsWith(FileName, StringComparison.OrdinalIgnoreCase);
3540
}
3641

37-
private static void GenerateWrapper(SourceProductionContext production, ImmutableArray<AdditionalText> texts)
42+
private static void GenerateWrapper(SourceProductionContext production, EndpointsMetadataContext context)
3843
{
3944
try
4045
{
41-
GenerateAll(production, texts);
46+
Generate(production, context);
4247
}
4348
catch (Exception ex)
4449
{
4550
production.AddSource($"Error-{Guid.NewGuid().ToString()}.g.cs", ex.ToString());
4651
}
4752
}
4853

49-
private static void GenerateAll(SourceProductionContext production, ImmutableArray<AdditionalText> texts)
54+
private static void Generate(SourceProductionContext production, EndpointsMetadataContext context)
5055
{
51-
foreach (AdditionalText csvFile in texts)
52-
{
53-
string fileName = Path.GetFileNameWithoutExtension(csvFile.Path);
54-
55-
EndpointsExtraInfo? extraInfo = default;
56-
ImmutableArray<EndpointsMetadata>.Builder endpointsBuilder = ImmutableArray.CreateBuilder<EndpointsMetadata>();
57-
using (StringReader reader = new(csvFile.GetText(production.CancellationToken)!.ToString()))
58-
{
59-
while (reader.ReadLine() is { Length: > 0 } line)
60-
{
61-
if (line is "Name,CN,OS")
62-
{
63-
continue;
64-
}
65-
66-
if (line.StartsWith("Extra:", StringComparison.OrdinalIgnoreCase))
67-
{
68-
extraInfo = JsonSerializer.Deserialize<EndpointsExtraInfo>(line[6..]);
69-
continue;
70-
}
71-
72-
IReadOnlyList<string> columns = ParseCsvLine(line);
73-
string? methodDeclarationString = columns.ElementAtOrDefault(0);
74-
string? chinese = columns.ElementAtOrDefault(1);
75-
string? oversea = columns.ElementAtOrDefault(2);
76-
EndpointsMetadata metadata = new()
77-
{
78-
MethodDeclaration = string.IsNullOrEmpty(methodDeclarationString) ? default : ParseMemberDeclaration(methodDeclarationString),
79-
Chinese = chinese,
80-
ChineseExpression = string.IsNullOrEmpty(chinese) ? ThrowNotSupportedException : ParseExpression($"$\"{chinese}\""),
81-
Oversea = oversea,
82-
OverseaExpression = string.IsNullOrEmpty(oversea) ? ThrowNotSupportedException : ParseExpression($"$\"{oversea}\""),
83-
};
84-
85-
endpointsBuilder.Add(metadata);
86-
}
87-
}
88-
89-
if (endpointsBuilder.Count <= 0)
90-
{
91-
return;
92-
}
93-
94-
ImmutableArray<EndpointsMetadata> endpoints = endpointsBuilder.ToImmutable();
95-
96-
string interfaceName = $"I{fileName}";
97-
IdentifierNameSyntax interfaceIdentifier = IdentifierName(interfaceName);
98-
string chineseImplName = $"{fileName}ImplementationForChinese";
99-
string overseaImplName = $"{fileName}ImplementationForOversea";
100-
101-
CompilationUnitSyntax compilation = CompilationUnit()
102-
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration(extraInfo?.Namespace ?? "Snap.Hutao.Web")
103-
.WithLeadingTrivia(NullableEnableList)
104-
.WithMembers(
105-
List<MemberDeclarationSyntax>(
106-
[
107-
InterfaceDeclaration(interfaceName)
108-
.WithModifiers(InternalPartialTokenList)
109-
.WithMembers(List(GenerateInterfaceMethods(endpoints))),
110-
ClassDeclaration(chineseImplName)
111-
.WithModifiers(InternalAbstractTokenList)
112-
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(interfaceIdentifier))))
113-
.WithMembers(List(GenerateClassMethods(endpoints, true))),
114-
ClassDeclaration(overseaImplName)
115-
.WithModifiers(InternalAbstractTokenList)
116-
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(interfaceIdentifier))))
117-
.WithMembers(List(GenerateClassMethods(endpoints, false)))
118-
]))))
119-
.NormalizeWhitespace();
120-
121-
production.AddSource($"{fileName}.g.cs", compilation.ToFullString());
122-
}
56+
string interfaceName = $"I{context.Name}";
57+
string chineseImplName = $"{context.Name}ImplementationForChinese";
58+
string overseaImplName = $"{context.Name}ImplementationForOversea";
59+
60+
IdentifierNameSyntax interfaceIdentifier = IdentifierName(interfaceName);
61+
62+
CompilationUnitSyntax compilation = CompilationUnit()
63+
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration(context.ExtraInfo?.Namespace ?? "Snap.Hutao.Web")
64+
.WithLeadingTrivia(NullableEnableTriviaList)
65+
.WithMembers(
66+
List<MemberDeclarationSyntax>(
67+
[
68+
InterfaceDeclaration(interfaceName)
69+
.WithModifiers(InternalPartialTokenList)
70+
.WithMembers(List(GenerateInterfaceMethods(context.Endpoints))),
71+
ClassDeclaration(chineseImplName)
72+
.WithModifiers(InternalAbstractTokenList)
73+
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(interfaceIdentifier))))
74+
.WithMembers(List(GenerateClassMethods(context.Endpoints, true))),
75+
ClassDeclaration(overseaImplName)
76+
.WithModifiers(InternalAbstractTokenList)
77+
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(interfaceIdentifier))))
78+
.WithMembers(List(GenerateClassMethods(context.Endpoints, false)))
79+
]))))
80+
.NormalizeWhitespace();
81+
82+
production.AddSource($"{context.Name}.g.cs", compilation.ToFullString());
12383
}
12484

12585
private static IEnumerable<MemberDeclarationSyntax> GenerateInterfaceMethods(ImmutableArray<EndpointsMetadata> metadataArray)
12686
{
12787
foreach (EndpointsMetadata metadata in metadataArray)
12888
{
129-
if (metadata.MethodDeclaration is not MethodDeclarationSyntax methodDeclaration)
89+
if (metadata.GetMethodDeclaration() is not MethodDeclarationSyntax methodDeclaration)
13090
{
13191
continue;
13292
}
@@ -149,14 +109,14 @@ private static IEnumerable<MemberDeclarationSyntax> GenerateClassMethods(Immutab
149109
{
150110
foreach (EndpointsMetadata metadata in metadataArray)
151111
{
152-
if (metadata.MethodDeclaration is not MethodDeclarationSyntax methodDeclaration)
112+
if (metadata.GetMethodDeclaration() is not MethodDeclarationSyntax methodDeclaration)
153113
{
154114
continue;
155115
}
156116

157117
yield return methodDeclaration
158118
.WithModifiers(PublicTokenList)
159-
.WithExpressionBody(ArrowExpressionClause(isChinese ? metadata.ChineseExpression : metadata.OverseaExpression))
119+
.WithExpressionBody(ArrowExpressionClause(isChinese ? metadata.GetChineseExpression() : metadata.GetOverseaExpression()))
160120
.WithSemicolonToken(SemicolonToken);
161121
}
162122
}
@@ -202,20 +162,105 @@ private static IReadOnlyList<string> ParseCsvLine(string line)
202162
return fields;
203163
}
204164

205-
private sealed class EndpointsMetadata
165+
private sealed record EndpointsMetadataContext
206166
{
207-
public required MemberDeclarationSyntax? MethodDeclaration { get; init; }
167+
public required string Name { get; init; }
208168

209-
public required string? Chinese { get; init; }
169+
public required EndpointsExtraInfo? ExtraInfo { get; init; }
170+
171+
public required EquatableArray<EndpointsMetadata> Endpoints { get; init; }
172+
173+
public static EndpointsMetadataContext Create(AdditionalText text, CancellationToken token)
174+
{
175+
string fileName = Path.GetFileNameWithoutExtension(text.Path);
210176

211-
public required ExpressionSyntax ChineseExpression { get; init; }
177+
EndpointsExtraInfo? extraInfo = default;
178+
ImmutableArray<EndpointsMetadata>.Builder endpointsBuilder = ImmutableArray.CreateBuilder<EndpointsMetadata>();
179+
using (StringReader reader = new(text.GetText(token)!.ToString()))
180+
{
181+
while (reader.ReadLine() is { Length: > 0 } line)
182+
{
183+
if (line is "Name,CN,OS")
184+
{
185+
continue;
186+
}
187+
188+
if (line.StartsWith("Extra:", StringComparison.OrdinalIgnoreCase))
189+
{
190+
extraInfo = JsonSerializer.Deserialize<EndpointsExtraInfo>(line[6..]);
191+
continue;
192+
}
193+
194+
IReadOnlyList<string> columns = ParseCsvLine(line);
195+
EndpointsMetadata metadata = new()
196+
{
197+
MethodString = columns.ElementAtOrDefault(0),
198+
Chinese = columns.ElementAtOrDefault(1),
199+
Oversea = columns.ElementAtOrDefault(2),
200+
};
201+
202+
endpointsBuilder.Add(metadata);
203+
}
204+
}
205+
206+
return new()
207+
{
208+
Name = fileName,
209+
ExtraInfo = extraInfo,
210+
Endpoints = endpointsBuilder.ToImmutable(),
211+
};
212+
}
213+
}
214+
215+
private sealed class EndpointsMetadata : IEquatable<EndpointsMetadata>
216+
{
217+
public required string? MethodString { get; init; }
218+
219+
public required string? Chinese { get; init; }
212220

213221
public required string? Oversea { get; init; }
214222

215-
public required ExpressionSyntax OverseaExpression { get; init; }
223+
public MemberDeclarationSyntax? GetMethodDeclaration()
224+
{
225+
return string.IsNullOrEmpty(MethodString) ? default : ParseMemberDeclaration(MethodString!);
226+
}
227+
228+
public ExpressionSyntax GetChineseExpression()
229+
{
230+
return string.IsNullOrEmpty(Chinese) ? ThrowNotSupportedException : ParseExpression($"$\"{Chinese}\"");
231+
}
232+
233+
public ExpressionSyntax GetOverseaExpression()
234+
{
235+
return string.IsNullOrEmpty(Oversea) ? ThrowNotSupportedException : ParseExpression($"$\"{Oversea}\"");
236+
}
237+
238+
public bool Equals(EndpointsMetadata? other)
239+
{
240+
if (other is null)
241+
{
242+
return false;
243+
}
244+
if (ReferenceEquals(this, other))
245+
{
246+
return true;
247+
}
248+
249+
return MethodString == other.MethodString && Chinese == other.Chinese && Oversea == other.Oversea;
250+
}
251+
252+
public override bool Equals(object? obj)
253+
{
254+
return ReferenceEquals(this, obj) || obj is EndpointsMetadata other && Equals(other);
255+
}
256+
257+
public override int GetHashCode()
258+
{
259+
return HashCode.Combine(MethodString, Chinese, Oversea);
260+
}
216261
}
217262

218-
private sealed class EndpointsExtraInfo
263+
private sealed record EndpointsExtraInfo
219264
{
220265
public string? Namespace { get; init; }
221266
}

src/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration/Automation/ConstructorGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private static void Generate(SourceProductionContext production, GeneratorAttrib
6161

6262
CompilationUnitSyntax syntax = CompilationUnit()
6363
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration(context.TargetSymbol.ContainingNamespace)
64-
.WithLeadingTrivia(NullableEnableList)
64+
.WithLeadingTrivia(NullableEnableTriviaList)
6565
.WithMembers(SingletonList<MemberDeclarationSyntax>(
6666
PartialTypeDeclaration(classSymbol)
6767
.WithMembers(List<MemberDeclarationSyntax>(

src/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private static void Generate(IncrementalGeneratorPostInitializationContext conte
4242

4343
CompilationUnitSyntax syntax = CompilationUnit()
4444
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration("Snap.Hutao.Web.Hoyolab")
45-
.WithLeadingTrivia(NullableEnableList)
45+
.WithLeadingTrivia(NullableEnableTriviaList)
4646
.WithMembers(SingletonList<MemberDeclarationSyntax>(
4747
ClassDeclaration("SaltConstants")
4848
.WithModifiers(InternalStaticTokenList)
@@ -66,7 +66,7 @@ private static FieldDeclarationSyntax ConstStringFieldDeclaration(string fieldNa
6666
.WithVariables(SingletonSeparatedList(
6767
VariableDeclarator(fieldName)
6868
.WithInitializer(EqualsValueClause(StringLiteralExpression(fieldValue))))))
69-
.WithModifiers(PublicConstList);
69+
.WithModifiers(PublicConstTokenList);
7070
}
7171

7272
private sealed class Response<[MeansImplicitUse] T>

src/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration/Automation/UnsafePropertyBackingFieldAccessorGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ private static void GenerateForContainingType(SourceProductionContext production
7474

7575
CompilationUnitSyntax syntax = CompilationUnit()
7676
.WithMembers(SingletonList<MemberDeclarationSyntax>(FileScopedNamespaceDeclaration(containingTypeSymbol.ContainingNamespace)
77-
.WithLeadingTrivia(NullableEnableList)
77+
.WithLeadingTrivia(NullableEnableTriviaList)
7878
.WithMembers(SingletonList<MemberDeclarationSyntax>(
7979
PartialTypeDeclaration(containingTypeSymbol)
8080
.WithMembers(List<MemberDeclarationSyntax>(accessMethods))))))

0 commit comments

Comments
 (0)