33
44using Microsoft . CodeAnalysis ;
55using Microsoft . CodeAnalysis . CSharp . Syntax ;
6+ using Snap . Hutao . SourceGeneration . Model ;
67using System ;
78using System . Collections . Generic ;
89using System . Collections . Immutable ;
910using System . IO ;
1011using System . Linq ;
1112using System . Text ;
1213using System . Text . Json ;
14+ using System . Threading ;
1315using static Microsoft . CodeAnalysis . CSharp . SyntaxFactory ;
1416using static Snap . Hutao . SourceGeneration . Primitive . FastSyntaxFactory ;
1517using static Snap . Hutao . SourceGeneration . Primitive . SyntaxKeywords ;
@@ -20,11 +22,14 @@ namespace Snap.Hutao.SourceGeneration.Automation;
2022internal 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 }
0 commit comments