Skip to content

Commit 1e049d3

Browse files
authored
[Milky] Refactor (#908)
1 parent 5a12dec commit 1e049d3

244 files changed

Lines changed: 5492 additions & 5658 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Lagrange.Core.slnx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
<Project Path="Lagrange.Proto/Lagrange.Proto.csproj" />
2222
</Folder>
2323
<Folder Name="/Protocols.Milky/">
24-
<Project Path="Lagrange.Milky.Implementation.Api.Generator/Lagrange.Milky.Implementation.Api.Generator.csproj" />
2524
<Project Path="Lagrange.Milky/Lagrange.Milky.csproj" />
25+
<Project Path="Lagrange.Milky.Generator/Lagrange.Milky.Generator.csproj" />
26+
<!-- <Project Path="Lagrange.Milky.Implementation.Api.Generator/Lagrange.Milky.Implementation.Api.Generator.csproj" /> -->
2627
</Folder>
2728
<Folder Name="/Solution Items/">
2829
<File Path="Proto.md" />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Lagrange.Milky.Generator.Api;
4+
5+
public class ApiDiagnosticDescriptors
6+
{
7+
public static readonly DiagnosticDescriptor MustImplementInterfaceError = new(
8+
id: "LMA001",
9+
title: "ApiHandler attribute usage error",
10+
messageFormat: "Type '{0}' marked with ApiHandlerAttribute must implement IApiHandler<TRequest, TResult> or INoRequestApiHandler<TResult> or INoResultApiHandler<TRequest>",
11+
category: "Lagrange.Milky.Api",
12+
DiagnosticSeverity.Error,
13+
isEnabledByDefault: true
14+
);
15+
16+
public static readonly DiagnosticDescriptor DuplicateApiNameError = new(
17+
id: "LMA002",
18+
title: "Duplicate api name",
19+
messageFormat: "Type '{0}' cannot be used as a handler with name '{1}' because this name is already associated with another handler",
20+
category: "Lagrange.Milky.Api",
21+
DiagnosticSeverity.Error,
22+
isEnabledByDefault: true
23+
);
24+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading;
7+
using Lagrange.Milky.Generator.Extensions;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
11+
namespace Lagrange.Milky.Generator.Api;
12+
13+
[Generator(LanguageNames.CSharp)]
14+
public class ApiExtensionGenerator : IIncrementalGenerator
15+
{
16+
private const string ApiHandlerAttributeTypeName = "Lagrange.Milky.Api.Attributes.ApiHandlerAttribute";
17+
private const string IApiHandlerTypeName = "Lagrange.Milky.Api.Handlers.IApiHandler`2";
18+
private const string INoRequestApiHandlerTypeName = "Lagrange.Milky.Api.Handlers.INoRequestApiHandler`1";
19+
private const string INoResultApiHandlerTypeName = "Lagrange.Milky.Api.Handlers.INoResultApiHandler`1";
20+
21+
public void Initialize(IncrementalGeneratorInitializationContext context)
22+
{
23+
var handlerInfos = context.SyntaxProvider.ForAttributeWithMetadataName(
24+
ApiHandlerAttributeTypeName,
25+
predicate: static (node, _) => node is ClassDeclarationSyntax,
26+
transform: GetHandleInfo
27+
).Collect();
28+
29+
context.RegisterSourceOutput(
30+
handlerInfos,
31+
static (context, handlerInfos) =>
32+
{
33+
var validHandlerInfos = FilterHandlerInfoAndReportDiagnostics(context, handlerInfos);
34+
35+
string generatedCode = GenerateSourceCode(validHandlerInfos);
36+
context.AddSource("Lagrange.Milky.Api.Extensions.EventExtension.g.cs", generatedCode);
37+
}
38+
);
39+
}
40+
41+
private (string, string, int, bool, Location?) GetHandleInfo(GeneratorAttributeSyntaxContext context, CancellationToken token)
42+
{
43+
var compilation = context.SemanticModel.Compilation;
44+
var handlerSymbol = (INamedTypeSymbol)context.TargetSymbol;
45+
46+
string handlerTypeName = handlerSymbol.ToDisplayString();
47+
48+
var attributeData = context.Attributes.First();
49+
string name = (string)attributeData.ConstructorArguments
50+
.First()
51+
.Value!;
52+
int lifetime = (int?)attributeData
53+
.NamedArguments
54+
.FirstOrDefault(a => a.Key == "Lifetime")
55+
.Value
56+
.Value
57+
?? 0;
58+
59+
var handlerInterfaceSymbol = compilation.GetTypeByMetadataName(IApiHandlerTypeName);
60+
var noRequestHandlerInterfaceSymbol = compilation.GetTypeByMetadataName(INoRequestApiHandlerTypeName);
61+
var noResultHandlerInterfaceSymbol = compilation.GetTypeByMetadataName(INoResultApiHandlerTypeName);
62+
bool isImpl = handlerSymbol.AllInterfaces
63+
.Any(s => s.OriginalDefinition.DefaultEquals(handlerInterfaceSymbol)
64+
|| s.OriginalDefinition.DefaultEquals(noRequestHandlerInterfaceSymbol)
65+
|| s.OriginalDefinition.DefaultEquals(noResultHandlerInterfaceSymbol));
66+
67+
var location = handlerSymbol.Locations.FirstOrDefault();
68+
69+
return (handlerTypeName, name, lifetime, isImpl, location);
70+
}
71+
72+
private static IEnumerable<(string, string, int)> FilterHandlerInfoAndReportDiagnostics(SourceProductionContext context, ImmutableArray<(string, string, int, bool, Location?)> handlerInfos)
73+
{
74+
var apiNames = new HashSet<string>(StringComparer.Ordinal);
75+
var result = new List<(string, string, int)>();
76+
77+
foreach ((string handlerTypeName, string apiName, int lifetime, bool isImpl, var location) in handlerInfos)
78+
{
79+
if (!isImpl)
80+
{
81+
context.ReportDiagnostic(Diagnostic.Create(
82+
ApiDiagnosticDescriptors.MustImplementInterfaceError,
83+
location,
84+
handlerTypeName
85+
));
86+
continue;
87+
}
88+
89+
if (!apiNames.Add(apiName))
90+
{
91+
context.ReportDiagnostic(Diagnostic.Create(
92+
ApiDiagnosticDescriptors.DuplicateApiNameError,
93+
location,
94+
handlerTypeName,
95+
apiName
96+
));
97+
continue;
98+
}
99+
100+
result.Add((handlerTypeName, apiName, lifetime));
101+
}
102+
103+
return result;
104+
}
105+
106+
private static string GenerateSourceCode(IEnumerable<(string, string, int)> handlerInfos)
107+
{
108+
var builder = new StringBuilder();
109+
110+
builder.AppendLine("// <auto-generated/>");
111+
builder.AppendLine();
112+
builder.AppendLine("namespace Lagrange.Milky.Api.Extensions;");
113+
builder.AppendLine();
114+
builder.AppendLine("public static partial class ApiExtension");
115+
builder.AppendLine("{");
116+
builder.AppendLine(" public static partial Microsoft.Extensions.DependencyInjection.IServiceCollection AddApiHandlers(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)");
117+
builder.AppendLine(" {");
118+
foreach (var (handlerName, apiName, lifetime) in handlerInfos)
119+
{
120+
builder.AppendLine($" services.Add(new Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof(Lagrange.Milky.Api.Handlers.IApiHandler), \"{apiName}\", typeof({handlerName}), (Microsoft.Extensions.DependencyInjection.ServiceLifetime){lifetime}));");
121+
}
122+
builder.AppendLine(" return services;");
123+
builder.AppendLine(" }");
124+
builder.AppendLine("}");
125+
126+
return builder.ToString();
127+
}
128+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Lagrange.Milky.Generator.Events;
4+
5+
public class EventDiagnosticDescriptors
6+
{
7+
public static readonly DiagnosticDescriptor MustImplementInterfaceError = new(
8+
id: "LME001",
9+
title: "EventSerializer attribute usage error",
10+
messageFormat: "Type '{0}' marked with EventSerializerAttribute must implement IEventSerializer<TEvent, TData>",
11+
category: "Lagrange.Milky.Events",
12+
DiagnosticSeverity.Error,
13+
isEnabledByDefault: true
14+
);
15+
16+
public static readonly DiagnosticDescriptor DuplicateEventSerializerError = new(
17+
id: "LME002",
18+
title: "Duplicate event serializer",
19+
messageFormat: "Type '{1}' (priority {2}) cannot be used as a serializer for event '{0}' because this event is already associated with another serializer",
20+
category: "Lagrange.Milky.Events",
21+
DiagnosticSeverity.Error,
22+
isEnabledByDefault: true
23+
);
24+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading;
7+
using Lagrange.Milky.Generator.Extensions;
8+
using Lagrange.Milky.Generator.Utilities;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
12+
namespace Lagrange.Milky.Generator.Events;
13+
14+
[Generator(LanguageNames.CSharp)]
15+
public class EventExtensionGenerator : IIncrementalGenerator
16+
{
17+
private const string EventConverterAttributeTypeName = "Lagrange.Milky.Events.Attributes.EventConverterAttribute";
18+
private const string IEventConverterTypeName = "Lagrange.Milky.Events.Converters.IEventConverter`2";
19+
20+
public void Initialize(IncrementalGeneratorInitializationContext context)
21+
{
22+
var converterInfos = context.SyntaxProvider.ForAttributeWithMetadataName(
23+
EventConverterAttributeTypeName,
24+
predicate: static (node, _) => node is ClassDeclarationSyntax,
25+
transform: GetConverterInfo
26+
).Collect();
27+
28+
context.RegisterSourceOutput(
29+
converterInfos,
30+
static (context, converterInfos) =>
31+
{
32+
var validConverterInfos = FilterConverterInfoAndReportDiagnostics(context, converterInfos);
33+
34+
string generatedCode = GenerateSourceCode(validConverterInfos);
35+
context.AddSource("Lagrange.Milky.Events.Extensions.EventExtension.g.cs", generatedCode);
36+
}
37+
);
38+
}
39+
40+
private (string, string?, long, int, Location?) GetConverterInfo(GeneratorAttributeSyntaxContext context, CancellationToken ct)
41+
{
42+
var compilation = context.SemanticModel.Compilation;
43+
var converterSymbol = (INamedTypeSymbol)context.TargetSymbol;
44+
45+
string converterTypeName = converterSymbol.ToDisplayString();
46+
47+
var namedArguments = context.Attributes.First().NamedArguments;
48+
long priority = (long?)namedArguments
49+
.FirstOrDefault(a => a.Key == "Priority")
50+
.Value
51+
.Value
52+
?? 0;
53+
int lifetime = (int?)namedArguments
54+
.FirstOrDefault(a => a.Key == "Lifetime")
55+
.Value
56+
.Value
57+
?? 0;
58+
59+
var converterInterfaceSymbol = compilation.GetTypeByMetadataName(IEventConverterTypeName);
60+
string? eventTypeName = converterSymbol.AllInterfaces
61+
.FirstOrDefault(s => s.OriginalDefinition.DefaultEquals(converterInterfaceSymbol))?
62+
.TypeArguments
63+
.FirstOrDefault()?
64+
.ToDisplayString();
65+
66+
var location = converterSymbol.Locations.FirstOrDefault();
67+
68+
return (converterTypeName, eventTypeName, priority, lifetime, location);
69+
}
70+
71+
private static IEnumerable<(string, string, long, int)> FilterConverterInfoAndReportDiagnostics(SourceProductionContext context, ImmutableArray<(string, string?, long, int, Location?)> converterInfos)
72+
{
73+
var eventTypeNames = new HashSet<(string, long)>(StringLongTupleComparer.Default);
74+
var result = new List<(string, string, long, int)>();
75+
76+
foreach ((string? converterTypeName, string? eventTypeName, long priority, int lifetime, var location) in converterInfos)
77+
{
78+
if (eventTypeName is null)
79+
{
80+
context.ReportDiagnostic(Diagnostic.Create(
81+
EventDiagnosticDescriptors.MustImplementInterfaceError,
82+
location,
83+
converterTypeName
84+
));
85+
continue;
86+
}
87+
88+
if (!eventTypeNames.Add((eventTypeName, priority)))
89+
{
90+
context.ReportDiagnostic(Diagnostic.Create(
91+
EventDiagnosticDescriptors.DuplicateEventSerializerError,
92+
location,
93+
eventTypeName,
94+
converterTypeName,
95+
priority
96+
));
97+
continue;
98+
}
99+
100+
result.Add((converterTypeName, eventTypeName, priority, lifetime));
101+
}
102+
103+
return result;
104+
}
105+
106+
private static string GenerateSourceCode(IEnumerable<(string, string, long Priority, int)> converterInfos)
107+
{
108+
converterInfos = [.. converterInfos.OrderBy(i => i.Priority)];
109+
110+
var builder = new StringBuilder();
111+
112+
builder.AppendLine("// <auto-generated/>");
113+
builder.AppendLine();
114+
builder.AppendLine("namespace Lagrange.Milky.Events.Extensions;");
115+
builder.AppendLine();
116+
builder.AppendLine("public static partial class EventExtension");
117+
builder.AppendLine("{");
118+
builder.AppendLine(" public static partial void RegisterConvertibleEvents(this Lagrange.Core.BotContext lagrange, Lagrange.Milky.Events.IGenericEventHandler handler)");
119+
builder.AppendLine(" {");
120+
foreach ((_, string? eventTypeName, _, _) in converterInfos)
121+
{
122+
builder.AppendLine($" lagrange.EventInvoker.RegisterEvent<{eventTypeName}>(handler.OnEvent);");
123+
}
124+
builder.AppendLine(" }");
125+
builder.AppendLine();
126+
builder.AppendLine(" public static partial void UnregisterConvertibleEvents(this Lagrange.Core.BotContext lagrange, Lagrange.Milky.Events.IGenericEventHandler handler)");
127+
builder.AppendLine(" {");
128+
foreach ((_, string? eventTypeName, _, _) in converterInfos)
129+
{
130+
builder.AppendLine($" lagrange.EventInvoker.UnregisterEvent<{eventTypeName}>(handler.OnEvent);");
131+
}
132+
builder.AppendLine(" }");
133+
builder.AppendLine();
134+
builder.AppendLine(" public static partial Microsoft.Extensions.DependencyInjection.IServiceCollection AddEventConverters(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)");
135+
builder.AppendLine(" {");
136+
foreach (var (converterName, eventName, _, lifetime) in converterInfos)
137+
{
138+
builder.AppendLine($" services.Add(new Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof(Lagrange.Milky.Events.Converters.IEventConverter<{eventName}>), typeof({converterName}), (Microsoft.Extensions.DependencyInjection.ServiceLifetime){lifetime}));");
139+
}
140+
builder.AppendLine(" return services;");
141+
builder.AppendLine(" }");
142+
builder.AppendLine("}");
143+
144+
return builder.ToString();
145+
}
146+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Lagrange.Milky.Generator.Extensions;
4+
5+
public static class SymbolExtension
6+
{
7+
public static bool DefaultEquals(this ISymbol left, ISymbol? right)
8+
{
9+
return left.Equals(right, SymbolEqualityComparer.Default);
10+
}
11+
}

Lagrange.Milky.Implementation.Api.Generator/GlobalSuppressions.cs renamed to Lagrange.Milky.Generator/GlobalSuppressions.cs

File renamed without changes.

Lagrange.Milky.Implementation.Api.Generator/Lagrange.Milky.Implementation.Api.Generator.csproj renamed to Lagrange.Milky.Generator/Lagrange.Milky.Generator.csproj

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<Nullable>enable</Nullable>
4-
<LangVersion>12.0</LangVersion>
4+
<LangVersion>14.0</LangVersion>
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
7+
8+
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
79
</PropertyGroup>
10+
<Target Name="GetDependencyTargetPaths">
11+
<ItemGroup>
12+
<TargetPathWithTargetPlatformMoniker Include="$(PkgMicrosoft_Bcl_HashCode)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
13+
</ItemGroup>
14+
</Target>
815
<ItemGroup>
9-
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
16+
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" GeneratePathProperty="true" PrivateAssets="all" ReferenceOutputAssembly="true" />
17+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0">
1018
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1119
<PrivateAssets>all</PrivateAssets>
1220
</PackageReference>
13-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" />
1422
</ItemGroup>
1523
</Project>

0 commit comments

Comments
 (0)