diff --git a/src/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj b/src/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj
deleted file mode 100644
index 4ad31fb..0000000
--- a/src/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- netstandard2.0
- AutoWasmApiGenerator
- latest
- 0.1.1
-
-
-
-
-
-
diff --git a/src/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs b/src/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs
deleted file mode 100644
index 780651b..0000000
--- a/src/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using Microsoft.CodeAnalysis;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace AutoWasmApiGenerator
-{
- internal class DiagnosticDefinitions
- {
- ///
- /// 继承多个接口需要指定接口标注[WebControllerAttribute]
- ///
- ///
- ///
- public static Diagnostic WAG00001(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00001",
- title: "继承多个接口需要指定接口标注[WebControllerAttribute]",
- messageFormat: "继承多个接口需要指定接口标注[WebControllerAttribute]",
- category: typeof(ControllerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 无法为该类型生成WebApi调用类,缺少接口
- ///
- ///
- ///
- public static Diagnostic WAG00002(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00002",
- title: "无法为该类型生成WebApi调用类,缺少接口",
- messageFormat: "无法为该类型生成WebApi调用类,缺少接口",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 方法参数过多
- ///
- ///
- ///
- public static Diagnostic WAG00003(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00003",
- title: "方法参数过多",
- messageFormat: "方法参数过多",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 控制器(controller)不能包含泛型
- ///
- ///
- ///
- public static Diagnostic WAG00004(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00004",
- title: "控制器(controller)不能包含泛型",
- messageFormat: "控制器(controller)不能包含泛型",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 仅支持异步方法
- ///
- ///
- ///
- public static Diagnostic WAG00005(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00005",
- title: "仅支持异步方法",
- messageFormat: "仅支持异步方法",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 路由参数
- ///
- ///
- ///
- public static Diagnostic WAG00006(Location? location, string? symbolString = null) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00006",
- title: "路由中未包含路由参数",
- messageFormat: $"路由中未包含路由参数({symbolString})",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 不能同时设置[FromBody]和[FromForm]
- ///
- ///
- ///
- public static Diagnostic WAG00007(Location? location, string? symbolString = null) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00007",
- title: "不能同时设置[FromBody]和[FromForm]",
- messageFormat: $"不能同时设置[FromBody]和[FromForm]({symbolString})",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 不能设置多个[FromBody]
- ///
- ///
- ///
- public static Diagnostic WAG00008(Location? location, string? symbolString = null) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00008",
- title: "不能设置多个[FromBody]",
- messageFormat: $"不能设置多个[FromBody]({symbolString})",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
-
- ///
- /// 暂不支持的返回值类型
- ///
- ///
- ///
- public static Diagnostic WAG00009(Location? location, string? symbolString = null) => Diagnostic.Create(new DiagnosticDescriptor(
- id: "WAG00009",
- title: "暂不支持的返回值类型",
- messageFormat: $"暂不支持的返回值类型({symbolString})",
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true), location);
- }
-}
diff --git a/src/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs b/src/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs
deleted file mode 100644
index 0af5869..0000000
--- a/src/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs
+++ /dev/null
@@ -1,429 +0,0 @@
-using Generators.Shared;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using System.Collections.Generic;
-using System.Linq;
-using static AutoWasmApiGenerator.GeneratorHelpers;
-using Generators.Shared.Builder;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection.Metadata;
-using System.Text.RegularExpressions;
-
-namespace AutoWasmApiGenerator
-{
- [Generator(LanguageNames.CSharp)]
- public class HttpServiceInvokerGenerator : IIncrementalGenerator
- {
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- //var list = context.SyntaxProvider.ForAttributeWithMetadataName(
- // ApiInvokerAssemblyAttributeFullName,
- // static (node, token) => true,
- // static (c, t) => c);
-
- context.RegisterSourceOutput(context.CompilationProvider, static (context, compilation) =>
- {
- try
- {
- if (!compilation.Assembly.HasAttribute(ApiInvokerAssemblyAttributeFullName))
- {
- return;
- }
- var all = compilation.GetAllSymbols(ApiInvokerAttributeFullName);
- foreach (var item in all)
- {
- if (!item.HasAttribute(WebControllerAttributeFullName))
- {
- continue;
- }
- if (CreateCodeFile(item, context, out var file))
- {
-#if DEBUG
- var ss = file.ToString();
-#endif
- context.AddSource(file);
- }
- }
- }
- catch (System.Exception ex)
- {
- context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
- id: "ERROR00001",
- title: "生成错误",
- messageFormat: ex.Message,
- category: typeof(HttpServiceInvokerGenerator).FullName!,
- defaultSeverity: DiagnosticSeverity.Warning,
- isEnabledByDefault: true
- ), Location.None));
- }
- });
- }
-
- private static bool CreateCodeFile(INamedTypeSymbol interfaceSymbol, SourceProductionContext context, [NotNullWhen(true)] out CodeFile? file)
- {
- var methods = interfaceSymbol.GetAllMethodWithAttribute(WebMethodAttributeFullName);
- List members = new List();
- _ = interfaceSymbol.GetAttribute(WebControllerAttributeFullName, out var controllerAttrData);
- var ns = NamespaceBuilder.Default.Namespace(interfaceSymbol.ContainingNamespace.ToDisplayString());
- var invokeClass = CreateHttpClassBuilder(interfaceSymbol);
- var scopeName = interfaceSymbol.FormatClassName();
- var route = controllerAttrData.GetNamedValue("Route") as string;
- bool needAuth = (bool)(controllerAttrData.GetNamedValue("Authorize") ?? false);
- foreach (var method in methods)
- {
- var result = BuildMethod(method, route, scopeName, needAuth, out var n);
- if (n && !needAuth)
- {
- needAuth = true;
- }
- if (result.Item2 != null)
- {
- context.ReportDiagnostic(result.Item2);
- file = null;
- return false;
- }
- members.Add(result.Item1!);
- }
-
- var fields = BuildField(needAuth);
- var constructor = BuildConstructor(interfaceSymbol, needAuth);
- members.AddRange(fields);
- members.Add(constructor);
-
- file = CodeFile.New($"{interfaceSymbol.FormatFileName()}ApiInvoker.g.cs")
- .AddUsings("using Microsoft.Extensions.DependencyInjection;")
- .AddMembers(ns.AddMembers(invokeClass.AddMembers([.. members])));
-
- return true;
- }
-
- private static (MethodBuilder?, Diagnostic?) BuildMethod((IMethodSymbol, AttributeData?) method, string? route, string scopeName, bool controllerAuth, out bool needAuth)
- {
- var methodSymbol = method.Item1;
- var methodAttribute = method.Item2;
-
- var allowsAnonymous = (bool)(methodAttribute.GetNamedValue("AllowAnonymous") ?? false);
- var authorize = (bool)(methodAttribute.GetNamedValue("Authorize") ?? false);
- needAuth = !allowsAnonymous && (authorize || controllerAuth);
-
- if (methodSymbol.HasAttribute(NotSupported))
- {
- var b = MethodBuilder.Default
- .MethodName(methodSymbol.Name)
- .Generic([.. methodSymbol.GetTypeParameters()])
- .ReturnType(methodSymbol.ReturnType.ToDisplayString())
- .AddParameter([.. methodSymbol.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")])
- .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
- .Lambda("throw new global::System.NotSupportedException()");
- return (b, null);
- }
-
- // 检查当前返回类型是否是Task或Task
- // 如果检查类型不符合要求,说明不是异步方法
- // 返回错误信息
- var returnTypeInfo = methodSymbol.ReturnType;
- var isTask = returnTypeInfo.Name == "Task";
- var isGenericTask = returnTypeInfo.OriginalDefinition.ToDisplayString() == "System.Threading.Tasks.Task";
-
- if (!isTask && !isGenericTask)
- {
- return (null, DiagnosticDefinitions.WAG00005(methodSymbol.Locations.FirstOrDefault()));
- }
-
- string webMethod;
- if (!methodAttribute.GetNamedValue("Method", out var v))
- {
- webMethod = "Post";
- }
- else
- {
- webMethod = WebMethod[(int)v!];
- }
- var methodScoped = methodSymbol.Name.Replace("Async", "");
- var customRoute = methodAttribute?.GetNamedValue("Route")?.ToString();
- string methodRoute;
- bool useRouteParam = false;
- if (string.IsNullOrEmpty(customRoute))
- {
- methodRoute = methodScoped;
- }
- else if (Regex.Match(customRoute, "{.+}").Success)
- {
- useRouteParam = true;
- methodRoute = $"{methodScoped}/{customRoute}";
- }
- else
- {
- methodRoute = customRoute!;
- }
- //var methodRoute = $"{methodAttribute?.GetNamedValue("Route") ?? methodSymbol.Name.Replace("Async", "")}";
- List statements =
- [
- // var url = "";
- // var client = clientFactory.CreateClient(nameof());
- $"var _client_gen = this.clientFactory.CreateClient(\"{scopeName}\")",
- // var request = new HttpRequestMessage();
- "var _request_gen = new global::System.Net.Http.HttpRequestMessage()",
- // request.Method = HttpMethod.
- $"_request_gen.Method = global::System.Net.Http.HttpMethod.{webMethod}",
- ];
- if (needAuth)
- {
- statements.Add("await headerHandler.SetRequestHeaderAsync(_request_gen)");
- }
-
- // 处理参数标签
- var paramInfos = methodSymbol.Parameters.Select(p =>
- {
- if (p.GetAttribute(WebMethodParameterBindingAttribute, out var ad))
- {
- ad!.GetConstructorValue(0, out var bindingType);
- var t = (int)bindingType!;
- return ((int)bindingType!, p);
- }
- if (useRouteParam && customRoute!.Contains(p.Name))
- {
- return (1, p);
- }
- return (-1, p);
- });
-
- // 0 - Query
- // 1 - Route
- // 2 - Form
- // 3 - Body
- // 4 - Header
-
- #region 检查参数配置
-
- var routerParameters = paramInfos.Where(t => t.Item1 == 1);
- foreach ((int _, IParameterSymbol p) item in routerParameters)
- {
- if (!methodRoute.Contains($"{{{item.p.Name}}}"))
- {
- return (null, DiagnosticDefinitions.WAG00006(methodSymbol.Locations.FirstOrDefault(), methodSymbol.ToDisplayString()));
- }
- }
-
- if (paramInfos.Any(t => t.Item1 == 2) && paramInfos.Any(t => t.Item1 == 3))
- {
- return (null, DiagnosticDefinitions.WAG00007(methodSymbol.Locations.FirstOrDefault(), methodSymbol.ToDisplayString()));
- }
- #endregion
-
- var url = $"""
- var _url_gen = $"api/{route ?? scopeName}/{methodRoute}"
- """;
- statements.Add(url);
-
- #region 处理Query参数
-
- var queryParameters = paramInfos.Where(t => (t.Item1 == -1 && webMethod == "Get") || t.Item1 == 0);
-
- if (queryParameters.Any())
- {
- statements.Add("var _queries_gen = new List()");
- foreach (var item in queryParameters)
- {
- var p = item.p;
- if (p.Type is INamedTypeSymbol { TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String } parameterClassType)
- {
- var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
- foreach (var prop in properties)
- {
- statements.Add($$"""_queries_gen.Add($"{nameof({{p.Name}}.{{prop.Name}})}={{{p.Name}}.{{prop.Name}}}")""");
- }
- }
- else
- {
- statements.Add($$"""_queries_gen.Add($"{nameof({{p.Name}})}={{{p.Name}}}")""");
- }
- }
- var setUrl = """
- _url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}"
- """;
- statements.Add(setUrl);
- }
-
- #endregion
-
- #region 处理Form参数
-
- var formParameters = paramInfos.Where(t => t.Item1 == 2);
-
- if (formParameters.Any())
- {
- statements.Add("var _formDatas_gen = new List>()");
- foreach (var item in formParameters)
- {
- var p = item.p;
- if (p.Type is INamedTypeSymbol { TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String } parameterClassType)
- {
- var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
- foreach (var prop in properties)
- {
- statements.Add($$"""
- _formDatas_gen.Add(new global::System.Collections.Generic.KeyValuePair(nameof({{p.Name}}.{{prop.Name}}), $"{{{p.Name}}.{{prop.Name}}}"))
- """);
- }
- }
- else
- {
- statements.Add($$"""_formDatas_gen.Add(new global::System.Collections.Generic.KeyValuePair(nameof({{p.Name}}), $"{{{p.Name}}}"))""");
- }
- }
- statements.Add("var _formContent_gen = new global::System.Net.Http.FormUrlEncodedContent(_formDatas_gen)");
- statements.Add("_formContent_gen.Headers.ContentType = new(\"application/x-www-form-urlencoded\")");
- statements.Add("""_request_gen.Content = _formContent_gen""");
-
- }
-
- #endregion
-
- #region 处理Body参数
-
- var bodyParameters = paramInfos.Where(t => (t.Item1 == -1 && webMethod != "Get") || t.Item1 == 3);
- if (bodyParameters.Count() > 1)
- {
- return (null, DiagnosticDefinitions.WAG00008(methodSymbol.Locations.FirstOrDefault(), methodSymbol.ToDisplayString()));
- }
-
- if (bodyParameters.Any())
- {
- var p = bodyParameters.First().p;
- statements.Add($"var _json_gen = global::System.Text.Json.JsonSerializer.Serialize({p.Name})");
- statements.Add("""_request_gen.Content = new global::System.Net.Http.StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json")""");
- }
-
- #endregion
-
- #region 处理Header参数
-
- var headerParameters = paramInfos.Where(t => t.Item1 == 4);
-
- if (headerParameters.Any())
- {
- foreach (var item in headerParameters)
- {
- var p = item.p;
- if (p.Type is INamedTypeSymbol { TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String } parameterClassType)
- {
- var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
- foreach (var prop in properties)
- {
- statements.Add($$"""_request_gen.Headers.Add(nameof({{p.Name}}.{{prop.Name}}), $"{{{p.Name}}.{{prop.Name}}}")""");
- }
- }
- else
- {
- statements.Add($$"""_request_gen.Headers.Add(nameof({{p.Name}}), $"{{{p.Name}}}")""");
- }
- }
- }
-
- #endregion
-
- statements.Add("_request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative)");
- var returnType = methodSymbol.ReturnType.GetGenericTypes().FirstOrDefault() ?? methodSymbol.ReturnType;
-
- if (methodSymbol.ReturnsVoid || returnType.ToDisplayString() == "System.Threading.Tasks.Task")
- {
- statements.Add("_ = await _client_gen.SendAsync(_request_gen)");
- }
- else
- {
- statements.Add("var _response_gen = await _client_gen.SendAsync(_request_gen)");
- statements.Add("_response_gen.EnsureSuccessStatusCode()");
- // 返回值是复杂类型,使用Json反序列化
- if (returnType is { TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String })
- {
- statements.Add("var _stream_gen = await _response_gen.Content.ReadAsStreamAsync()");
- //return System.Text.Json.JsonSerializer.Deserialize(jsonStream, jsonOptions);
- statements.Add($"return global::System.Text.Json.JsonSerializer.Deserialize<{returnType.ToDisplayString()}>(_stream_gen, _JSON_OPTIONS_gen);");
- }
- else
- {
- statements.Add("var _str_gen = await _response_gen.Content.ReadAsStringAsync()");
- if (returnType.SpecialType == SpecialType.System_String)
- {
- statements.Add("return _str_gen");
- }
- else if (returnType.IsValueType)
- {
- statements.Add($"{returnType.ToDisplayString()}.TryParse(_str_gen, out var val)");
- statements.Add("return val");
- }
- else
- {
- return (null, DiagnosticDefinitions.WAG00009(methodSymbol.Locations.FirstOrDefault()));
- }
- }
- }
-
- var builder = MethodBuilder.Default
- .MethodName(methodSymbol.Name)
- .Generic([.. methodSymbol.GetTypeParameters()])
- .Async()
- .ReturnType(methodSymbol.ReturnType.ToDisplayString())
- .AddParameter([.. methodSymbol.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")])
- .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
- .AddBody([.. statements]);
- return (builder, null);
- }
-
- private static IEnumerable BuildField(bool needAuth)
- {
- // private readonly JsonSerializerOptions jsonOptions;
- yield return FieldBuilder.Default.MemberType("global::System.Text.Json.JsonSerializerOptions")
- .FieldName("_JSON_OPTIONS_gen");
- // private readonly IHttpClientFactory clientFactory;
- yield return FieldBuilder.Default
- .MemberType("global::System.Net.Http.IHttpClientFactory")
- .FieldName("clientFactory");
- if (needAuth)
- {
- yield return FieldBuilder.Default
- .MemberType("global::AutoWasmApiGenerator.IHttpClientHeaderHandler")
- .FieldName("headerHandler");
- }
- }
-
- private static ConstructorBuilder BuildConstructor(INamedTypeSymbol classSymbol, bool needAuth)
- {
- List parameters = [
- "global::System.Net.Http.IHttpClientFactory factory"
- ];
- List body = ["clientFactory = factory;"];
- if (needAuth)
- {
- //parameters.Add("global::AutoWasmApiGenerator.IHttpClientHeaderHandler handler");
- parameters.Add("global::System.IServiceProvider services");
- body.Add("headerHandler = services.GetService() ?? global::AutoWasmApiGenerator.DefaultHttpClientHeaderHandler.Default");
- }
-
- return ConstructorBuilder.Default
- .MethodName($"{FormatClassName(classSymbol.MetadataName)}ApiInvoker")
- .AddParameter([.. parameters])
- .AddBody([.. body])
- .AddBody("_JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };");
- }
-
- private static ClassBuilder CreateHttpClassBuilder(INamedTypeSymbol interfaceSymbol)
- {
- IEnumerable additionalAttribute = [];
- if (interfaceSymbol.GetAttribute(ApiInvokerAttributeFullName, out var data))
- {
- //var o = data.GetAttributeValue(nameof(ApiInvokerGeneraAttribute.Attribute));
- additionalAttribute = interfaceSymbol.GetAttributeInitInfo(ApiInvokerAttributeFullName, data!);
- }
-
-
- return ClassBuilder.Default
- .ClassName($"{FormatClassName(interfaceSymbol.MetadataName)}ApiInvoker")
- .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
- .Attribute([.. additionalAttribute.Select(i => i.ToString())])
- .BaseType(interfaceSymbol.ToDisplayString());
- }
- }
-}
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Shipped.md b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000..d567f14
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Shipped.md
@@ -0,0 +1,3 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Unshipped.md b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Unshipped.md
new file mode 100644
index 0000000..f7a0679
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,17 @@
+; Unshipped analyzer release
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+### New Rules
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+WAG00001 | AutoWasmApiGenerator.ControllerGenerator | Error | 继承多个接口需要指定接口标注[WebControllerAttribute]
+WAG00002 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 无法为该类型生成WebApi调用类,缺少接口
+WAG00003 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 方法参数过多
+WAG00004 | AutoWasmApiGenerator.ControllerGenerator | Error | 控制器(controller)不能包含泛型
+WAG00005 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 仅支持异步方法
+WAG00006 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 路由中未包含路由参数({0})
+WAG00007 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 不能同时设置[FromBody]和[FromForm]({0})
+WAG00008 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 不能设置多个[FromBody]({0})
+WAG00009 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 暂不支持的返回值类型({0})
+WAG00010 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | 生成服务调用器过程中发生错误: {0}
+WAG00011 | AutoWasmApiGenerator.HttpServiceInvokerGenerator | Error | AutoWasmApiGenerator.ApiInvokerGenerateAttribute需要同时设置AutoWasmApiGenerator.WebControllerAttribute
\ No newline at end of file
diff --git a/src/AutoWasmApi.Roslyn/Attributes/NullableAttributes.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Attributes/NullableAttributes.cs
similarity index 100%
rename from src/AutoWasmApi.Roslyn/Attributes/NullableAttributes.cs
rename to src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Attributes/NullableAttributes.cs
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj
new file mode 100644
index 0000000..f8f722b
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/AutoWasmApi.Roslyn.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
diff --git a/src/AutoWasmApi.Roslyn/ControllerGenerator.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/ControllerGenerator.cs
similarity index 95%
rename from src/AutoWasmApi.Roslyn/ControllerGenerator.cs
rename to src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/ControllerGenerator.cs
index 8db296b..7e07580 100644
--- a/src/AutoWasmApi.Roslyn/ControllerGenerator.cs
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/ControllerGenerator.cs
@@ -5,6 +5,7 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
+using AutoWasmApiGenerator.Options;
using Generators.Shared;
using Generators.Shared.Builder;
using Microsoft.CodeAnalysis;
@@ -25,18 +26,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// WebControllerAttributeFullName
// , static (node, _) => true
// , static (ctx, _) => ctx);
-#if DEBUG && false
- if (!Debugger.IsAttached)
- {
- Debugger.Launch(); // This will launch the debugger when the source generator runs.
- }
-#endif
+ var globalOptions = context.AnalyzerConfigOptionsProvider.Select(GlobalOptions.Select);
- context.RegisterSourceOutput(context.CompilationProvider, static (context, compilation) =>
+ context.RegisterSourceOutput(context.CompilationProvider.Combine(globalOptions), static (context, compilationData) =>
{
//try
//{
- if (!compilation.Assembly.HasAttribute(WebControllerAssemblyAttributeFullName))
+ var compilation = compilationData.Left;
+ var globalOptions = compilationData.Right;
+ var generate = compilation.Assembly.HasAttribute(WebControllerAssemblyAttributeFullName)
+ || globalOptions.GenerateWebController;
+
+ if (!generate)
{
return;
}
@@ -118,7 +119,7 @@ private static MethodBuilder BuildMethod((IMethodSymbol, AttributeData?) data, s
{
methodRoute = methodScoped;
}
- else if(Regex.Match(customRoute, "{.+}").Success)
+ else if (Regex.Match(customRoute, "{.+}").Success)
{
methodRoute = $"{methodScoped}/{customRoute}";
}
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs
new file mode 100644
index 0000000..4a5e442
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/DiagnosticDefinitions.cs
@@ -0,0 +1,219 @@
+using Microsoft.CodeAnalysis;
+using System;
+using System.Collections.Generic;
+using System.Text;
+// ReSharper disable InconsistentNaming
+
+namespace AutoWasmApiGenerator
+{
+ internal class DiagnosticDefinitions
+ {
+ private static readonly DiagnosticDescriptor Wag00001DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00001",
+ title: "继承多个接口需要指定接口标注[WebControllerAttribute]",
+ messageFormat: "继承多个接口需要指定接口标注[WebControllerAttribute]",
+ category: typeof(ControllerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00002DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00002",
+ title: "无法为该类型生成WebApi调用类,缺少接口",
+ messageFormat: "无法为该类型生成WebApi调用类,缺少接口",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00003DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00003",
+ title: "方法参数过多",
+ messageFormat: "方法参数过多",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00004DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00004",
+ title: "控制器(controller)不能包含泛型",
+ messageFormat: "控制器(controller)不能包含泛型",
+ category: typeof(ControllerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00005DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00005",
+ title: "仅支持异步方法",
+ messageFormat: "仅支持异步方法",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00006DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00006",
+ title: "路由中未包含路由参数",
+ messageFormat: "路由中未包含路由参数({0})",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00007DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00007",
+ title: "不能同时设置[FromBody]和[FromForm]",
+ messageFormat: "不能同时设置[FromBody]和[FromForm]({0})",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00008DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00008",
+ title: "不能设置多个[FromBody]",
+ messageFormat: "不能设置多个[FromBody]({0})",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+
+ private static readonly DiagnosticDescriptor Wag00009DiagDescriptor = new DiagnosticDescriptor(
+ id: "WAG00009",
+ title: "暂不支持的返回值类型",
+ messageFormat: "暂不支持的返回值类型({0})",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Wag00010DiagDescriptor = new(
+ id: "WAG00010",
+ title: "生成错误",
+ messageFormat: "生成过程中发生错误: {0}",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true
+ );
+
+ private static readonly DiagnosticDescriptor Wag00011DiagDescriptor = new(
+ id: "WAG00011",
+ title: "属性依赖错误",
+ messageFormat: "AutoWasmApiGenerator.ApiInvokerGenerateAttribute需要同时设置AutoWasmApiGenerator.WebControllerAttribute",
+ category: typeof(HttpServiceInvokerGenerator).FullName!,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true
+ );
+
+ ///
+ /// 继承多个接口需要指定接口标注[WebControllerAttribute]
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00001(Location? location)
+ {
+ return Diagnostic.Create(Wag00001DiagDescriptor, location);
+ }
+
+ ///
+ /// 无法为该类型生成WebApi调用类,缺少接口
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00002(Location? location)
+ {
+ return Diagnostic.Create(Wag00002DiagDescriptor, location);
+ }
+
+ ///
+ /// 方法参数过多
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00003(Location? location)
+ {
+ return Diagnostic.Create(Wag00003DiagDescriptor, location);
+ }
+
+ ///
+ /// 控制器(controller)不能包含泛型
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00004(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00004DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 仅支持异步方法
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00005(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00005DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 路由中未包含路由参数
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00006(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00006DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 不能同时设置[FromBody]和[FromForm]
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00007(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00007DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 不能设置多个[FromBody]
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00008(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00008DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 暂不支持的返回值类型
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00009(Location? location, string? symbolString = null)
+ {
+ return Diagnostic.Create(Wag00009DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 生成错误
+ ///
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00010(Location? location, string symbolString)
+ {
+ return Diagnostic.Create(Wag00010DiagDescriptor, location, symbolString);
+ }
+
+ ///
+ /// 属性依赖错误
+ ///
+ ///
+ ///
+ public static Diagnostic WAG00011(Location? location)
+ {
+ return Diagnostic.Create(Wag00011DiagDescriptor, location);
+ }
+ }
+}
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Extensions/MethodCheckerExtensions.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Extensions/MethodCheckerExtensions.cs
new file mode 100644
index 0000000..8106e10
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Extensions/MethodCheckerExtensions.cs
@@ -0,0 +1,17 @@
+using Microsoft.CodeAnalysis;
+
+namespace AutoWasmApiGenerator.Extensions;
+
+internal static class MethodCheckerExtensions
+{
+ public static bool HasTryParseMethod(this ITypeSymbol returnType)
+ {
+ foreach (var method in returnType.GetMembers("TryParse").OfType())
+ {
+ var match = method is { Parameters.Length: 2, ReturnType.Name: "Boolean" };
+ if (match) return true;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/AutoWasmApi.Roslyn/GeneratorHelpers.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/GeneratorHelpers.cs
similarity index 96%
rename from src/AutoWasmApi.Roslyn/GeneratorHelpers.cs
rename to src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/GeneratorHelpers.cs
index cc829a5..54d4fcb 100644
--- a/src/AutoWasmApi.Roslyn/GeneratorHelpers.cs
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/GeneratorHelpers.cs
@@ -8,7 +8,7 @@ public static class GeneratorHelpers
{
public const string WebControllerAttributeFullName = "AutoWasmApiGenerator.WebControllerAttribute";
public const string WebControllerAssemblyAttributeFullName = "AutoWasmApiGenerator.WebControllerAssemblyAttribute";
- public const string ApiInvokerAttributeFullName = "AutoWasmApiGenerator.ApiInvokerGenerateAttribute";
+ public const string ApiInvokerGenerateAttributeFullName = "AutoWasmApiGenerator.ApiInvokerGenerateAttribute";
public const string ApiInvokerAssemblyAttributeFullName = "AutoWasmApiGenerator.ApiInvokerAssemblyAttribute";
public const string WebMethodAttributeFullName = "AutoWasmApiGenerator.WebMethodAttribute";
public const string DisableWebApiGenerator = "DisableWebApiGenerator";
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs
new file mode 100644
index 0000000..333f327
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGenerator.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using AutoWasmApiGenerator.Options;
+using Generators.Shared;
+using Microsoft.CodeAnalysis;
+using static AutoWasmApiGenerator.GeneratorHelpers;
+
+namespace AutoWasmApiGenerator;
+
+[Generator(LanguageNames.CSharp)]
+public class HttpServiceInvokerGenerator : IIncrementalGenerator
+{
+ private static readonly HttpServiceInvokerGeneratorImpl Generator = new();
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // if (!Debugger.IsAttached)
+ // {
+ // Debugger.Launch();
+ // }
+ var globalOptions = context.AnalyzerConfigOptionsProvider.Select(GlobalOptions.Select);
+ context.RegisterSourceOutput(context.CompilationProvider.Combine(globalOptions), static (context, compilationData) =>
+ {
+ try
+ {
+ var compilation = compilationData.Left;
+ var globalOptions = compilationData.Right;
+ var generate = compilation.Assembly.HasAttribute(ApiInvokerAssemblyAttributeFullName)
+ || globalOptions.GenerateInvoker;
+ if (!generate)
+ {
+ return;
+ }
+
+ var all = compilation.GetAllSymbols(ApiInvokerGenerateAttributeFullName);
+
+ foreach (var item in all)
+ {
+ if (!item.HasAttribute(WebControllerAttributeFullName))
+ {
+ foreach (var location in item.Locations)
+ {
+ context.ReportDiagnostic(DiagnosticDefinitions.WAG00011(location));
+ }
+ continue;
+ }
+
+ var (generatedFileName, sourceCode, errorAndWarnings) = Generator.Generate(item);
+ if (errorAndWarnings.Any())
+ {
+ foreach (var item1 in errorAndWarnings)
+ {
+ context.ReportDiagnostic(item1);
+ }
+ }
+ else
+ {
+ context.AddSource(generatedFileName, sourceCode);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ context.ReportDiagnostic(DiagnosticDefinitions.WAG00010(Location.None, ex.Message));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGeneratorImpl.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGeneratorImpl.cs
new file mode 100644
index 0000000..bbc2491
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/HttpServiceInvokerGeneratorImpl.cs
@@ -0,0 +1,492 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using AutoWasmApiGenerator.Extensions;
+using Generators.Models;
+using Generators.Shared;
+using Generators.Shared.Builder;
+using Microsoft.CodeAnalysis;
+using static AutoWasmApiGenerator.GeneratorHelpers;
+
+namespace AutoWasmApiGenerator;
+
+public class HttpServiceInvokerGeneratorImpl : IHttpServiceInvokerGenerator
+{
+ // 0 - Query
+ // 1 - Route
+ // 2 - Form
+ // 3 - Body
+ // 4 - Header
+ private static class WebMethodConstants
+ {
+ public const string Get = "Get";
+ public const string Post = "Post";
+ public const string Put = "Put";
+ public const string Delete = "Delete";
+ }
+ private enum ParameterBindingType
+ {
+ Ignore = -1,
+ FromQuery = 0,
+ FromRoute = 1,
+ FromForm = 2,
+ FromBody = 3,
+ FromHeader = 4
+ }
+
+ public (string generatedFileName, string sourceCode, List errorAndWarnings) Generate(
+ INamedTypeSymbol interfaceSymbol)
+ {
+ var errorAndWarnings = new List();
+ var ret = CreateCodeFile(interfaceSymbol, errorAndWarnings, out var file);
+ return ret
+ ? (file!.FileName, file.ToString(), errorAndWarnings)
+ : ("", "", errorAndWarnings);
+ }
+
+ private static bool CreateCodeFile(INamedTypeSymbol interfaceSymbol, List errorAndWarnings,
+ [NotNullWhen(true)] out CodeFile? file)
+ {
+ var methods = interfaceSymbol.GetAllMethodWithAttribute(WebMethodAttributeFullName);
+ List members = new();
+ _ = interfaceSymbol.GetAttribute(WebControllerAttributeFullName, out var controllerAttrData);
+ var invokeClass = CreateHttpClassBuilder(interfaceSymbol);
+ var scopeName = interfaceSymbol.FormatClassName();
+ var route = controllerAttrData.GetNamedValue("Route") as string;
+ var needAuth = (bool)(controllerAttrData.GetNamedValue("Authorize") ?? false);
+ foreach (var method in methods)
+ {
+ var methodBuilder = BuildMethod(method, route, scopeName, needAuth, out var n, errorAndWarnings);
+ if (n && !needAuth)
+ {
+ needAuth = true;
+ }
+
+ if (errorAndWarnings.Count > 0)
+ {
+ file = null;
+ return false;
+ }
+
+ members.Add(methodBuilder!);
+ }
+
+ var fields = BuildField(needAuth);
+ var constructor = BuildConstructor(interfaceSymbol, needAuth);
+ members.AddRange(fields);
+ members.Add(constructor);
+
+ var classBuilder = invokeClass.AddMembers([.. members]);
+ var ns = NamespaceBuilder.Default.Namespace(interfaceSymbol.ContainingNamespace.ToDisplayString());
+ var namespaceBuilder = ns.AddMembers(classBuilder);
+ file = CodeFile.New($"{interfaceSymbol.FormatFileName()}ApiInvoker.g.cs")
+ .AddUsings("using Microsoft.Extensions.DependencyInjection;")
+ .AddFileHeader("""
+ //
+ #pragma warning disable
+ #nullable enable
+ """)
+ .AddMembers(namespaceBuilder);
+
+ return true;
+ }
+
+ private static MethodBuilder? BuildMethod((IMethodSymbol methodSymbol, AttributeData? methodAttribute) method, string? route,
+ string scopeName, bool controllerAuth, out bool needAuth, List errorAndWarnings)
+ {
+ var methodSymbol = method.Item1;
+ var methodAttribute = method.Item2;
+
+ var allowsAnonymous = (bool)(methodAttribute.GetNamedValue("AllowAnonymous") ?? false);
+ var authorize = (bool)(methodAttribute.GetNamedValue("Authorize") ?? false);
+ needAuth = !allowsAnonymous && (authorize || controllerAuth);
+ var cancellationTokenName = methodSymbol.Parameters.Where(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.Threading.CancellationToken").Select(p => p.Name).FirstOrDefault();
+ var hasCancellationToken = cancellationTokenName != null;
+
+ if (methodSymbol.HasAttribute(NotSupported))
+ {
+ TypeParameterInfo[] typeParameterInfos = [.. methodSymbol.GetTypeParameters()];
+ var rt = methodSymbol.ReturnType;
+ var type = rt.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ string[] parameters = [.. methodSymbol.Parameters.Select(p => $"{p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Name}")];
+ var b = MethodBuilder.Default
+ .MethodName(methodSymbol.Name)
+ .Generic(typeParameterInfos)
+ .ReturnType(type)
+ .AddParameter(parameters)
+ .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
+ .Lambda("throw new global::System.NotSupportedException()");
+ return b;
+ }
+
+ // 检查当前返回类型是否是Task或Task
+ // 如果检查类型不符合要求,说明不是异步方法
+ // 返回错误信息
+ var returnTypeInfo = methodSymbol.ReturnType;
+ var isTask = returnTypeInfo.Name == "Task";
+ var isGenericTask = returnTypeInfo.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ .StartsWith("global::System.Threading.Tasks.Task<");
+
+ if (!isTask && !isGenericTask)
+ {
+ errorAndWarnings.Add(DiagnosticDefinitions.WAG00005(methodSymbol.Locations.FirstOrDefault()));
+ return null;
+ }
+
+ var webMethod = methodAttribute.GetNamedValue("Method", out var v) ? WebMethod[(int)v!] : "Post";
+ var methodScoped = methodSymbol.Name.Replace("Async", "");
+ var customRoute = methodAttribute?.GetNamedValue("Route")?.ToString();
+ string methodRoute;
+ var useRouteParam = false;
+ if (string.IsNullOrEmpty(customRoute))
+ {
+ methodRoute = methodScoped;
+ }
+ else if (Regex.Match(customRoute, "{.+}").Success)
+ {
+ useRouteParam = true;
+ methodRoute = $"{methodScoped}/{customRoute}";
+ }
+ else
+ {
+ methodRoute = customRoute!;
+ }
+
+ //var methodRoute = $"{methodAttribute?.GetNamedValue("Route") ?? methodSymbol.Name.Replace("Async", "")}";
+ List statements =
+ [
+ // var url = "";
+ // var client = clientFactory.CreateClient(nameof());
+ $"var _client_gen = this.clientFactory.CreateClient(\"{scopeName}\")",
+ // var request = new HttpRequestMessage();
+ "var _request_gen = new global::System.Net.Http.HttpRequestMessage()",
+ // request.Method = HttpMethod.
+ $"_request_gen.Method = global::System.Net.Http.HttpMethod.{webMethod}"
+ ];
+ if (needAuth)
+ {
+ statements.Add($"await headerHandler.SetRequestHeaderAsync(_request_gen, {cancellationTokenName ?? "global::System.Threading.CancellationToken.None"})");
+ }
+
+ // 处理参数标签
+ var paramInfos = methodSymbol.Parameters.Select(p =>
+ {
+ if (p.GetAttribute(WebMethodParameterBindingAttribute, out var ad))
+ {
+ ad!.GetConstructorValue(0, out var bindingType);
+ var t = (ParameterBindingType)(int)bindingType!;
+ return (bindingType: t, p);
+ }
+
+ if (useRouteParam && customRoute!.Contains(p.Name))
+ {
+ return (bindingType: ParameterBindingType.FromRoute, p);
+ }
+
+ return (bindingType: ParameterBindingType.Ignore, p);
+ }).ToList();
+
+ #region 检查参数配置
+
+ var routerParameters = paramInfos.Where(t => t.bindingType == ParameterBindingType.FromRoute);
+ foreach (var item in routerParameters)
+ {
+ // 如果路由参数中包含方法名,则忽略
+ if (methodRoute.Contains($"{{{item.p.Name}}}")) continue;
+ errorAndWarnings.Add(DiagnosticDefinitions.WAG00006(methodSymbol.Locations.FirstOrDefault(),
+ methodSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)));
+ return null;
+ }
+
+ if (paramInfos.Any(t => t.bindingType == ParameterBindingType.FromForm) && paramInfos.Any(t => t.bindingType == ParameterBindingType.FromBody))
+ {
+ // 不能同时存在FromBody和FromForm
+ errorAndWarnings.Add(DiagnosticDefinitions.WAG00007(methodSymbol.Locations.FirstOrDefault(),
+ methodSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)));
+ return null;
+ }
+
+ #endregion
+
+ var url = $"""
+ var _url_gen = $"api/{route ?? scopeName}/{methodRoute}"
+ """;
+ statements.Add(url);
+
+ AddQueryParameters(statements, paramInfos, webMethod, errorAndWarnings);
+ AddFormParameters(statements, paramInfos);
+ AddBodyParameters(statements, paramInfos, webMethod, methodSymbol, errorAndWarnings);
+ AddHeaderParameters(statements, paramInfos);
+
+ statements.Add("_request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative)");
+ var returnType = methodSymbol.ReturnType.GetGenericTypes().FirstOrDefault() ?? methodSymbol.ReturnType;
+
+ if (methodSymbol.ReturnsVoid || (isTask && !isGenericTask))
+ {
+ statements.Add(hasCancellationToken
+ ? $"_ = await _client_gen.SendAsync(_request_gen, {cancellationTokenName})"
+ : "_ = await _client_gen.SendAsync(_request_gen)");
+ }
+ else
+ {
+ statements.Add(hasCancellationToken
+ ? $"var _response_gen = await _client_gen.SendAsync(_request_gen, {cancellationTokenName})"
+ : "var _response_gen = await _client_gen.SendAsync(_request_gen)");
+ statements.Add("_response_gen.EnsureSuccessStatusCode()");
+ AddResponseHandling(statements, returnType, errorAndWarnings, cancellationTokenName, methodSymbol);
+ }
+
+ var parameter = methodSymbol.Parameters.Select(p => $"{p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Name}").ToArray();
+ var builder = MethodBuilder.Default
+ .MethodName(methodSymbol.Name)
+ .Generic(methodSymbol.GetTypeParameters().ToArray())
+ .Async()
+ .ReturnType(methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
+ .AddParameter(parameter)
+ .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
+ .AddBody(statements.ToArray());
+ return builder;
+ }
+
+ private static IEnumerable BuildField(bool needAuth)
+ {
+ // private readonly JsonSerializerOptions jsonOptions;
+ yield return FieldBuilder.Default.MemberType("global::System.Text.Json.JsonSerializerOptions")
+ .FieldName("_JSON_OPTIONS_gen");
+ // private readonly IHttpClientFactory clientFactory;
+ yield return FieldBuilder.Default
+ .MemberType("global::System.Net.Http.IHttpClientFactory")
+ .FieldName("clientFactory");
+ if (needAuth)
+ {
+ yield return FieldBuilder.Default
+ .MemberType("global::AutoWasmApiGenerator.IHttpClientHeaderHandler")
+ .FieldName("headerHandler");
+ }
+ }
+
+ ///
+ /// 处理Query参数
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void AddQueryParameters(List statements,
+ IEnumerable<(ParameterBindingType i, IParameterSymbol p)> paramInfos, string webMethod, List errorAndWarnings)
+ {
+ var queryParameters = paramInfos.Where(t => (t.i == ParameterBindingType.Ignore && webMethod == WebMethodConstants.Get) || t.i == ParameterBindingType.FromQuery).ToList();
+
+ if (!queryParameters.Any()) return;
+ statements.Add("var _queries_gen = new global::System.Collections.Generic.List()");
+ foreach (var item in queryParameters)
+ {
+ var p = item.p;
+ if (p.Type is INamedTypeSymbol
+ {
+ TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String
+ } parameterClassType)
+ {
+ var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
+ foreach (var prop in properties)
+ {
+ statements.Add(
+ $$"""_queries_gen.Add($"{nameof({{p.Name}}.{{prop.Name}})}={{{p.Name}}.{{prop.Name}}}")""");
+ }
+ }
+ else
+ {
+ statements.Add($$"""_queries_gen.Add($"{nameof({{p.Name}})}={{{p.Name}}}")""");
+ }
+ }
+
+ var setUrl = """
+ _url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}"
+ """;
+ statements.Add(setUrl);
+ }
+
+ ///
+ /// 处理Form参数
+ ///
+ ///
+ ///
+ private static void AddFormParameters(List statements, IEnumerable<(ParameterBindingType i, IParameterSymbol p)> paramInfos)
+ {
+ var formParameters = paramInfos.Where(t => t.i == ParameterBindingType.FromForm).ToList();
+
+ if (!formParameters.Any()) return;
+ statements.Add(
+ "var _formData_gen = new List>()");
+ foreach (var item in formParameters)
+ {
+ var p = item.p;
+ if (p.Type is INamedTypeSymbol
+ {
+ TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String
+ } parameterClassType)
+ {
+ var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
+ foreach (var prop in properties)
+ {
+ statements.Add($$"""
+ _formData_gen.Add(new global::System.Collections.Generic.KeyValuePair(nameof({{p.Name}}.{{prop.Name}}), $"{{{p.Name}}.{{prop.Name}}}"))
+ """);
+ }
+ }
+ else
+ {
+ statements.Add(
+ $$"""_formData_gen.Add(new global::System.Collections.Generic.KeyValuePair(nameof({{p.Name}}), $"{{{p.Name}}}"))""");
+ }
+ }
+
+ statements.Add("var _formContent_gen = new global::System.Net.Http.FormUrlEncodedContent(_formData_gen)");
+ statements.Add("_formContent_gen.Headers.ContentType = new(\"application/x-www-form-urlencoded\")");
+ statements.Add("_request_gen.Content = _formContent_gen");
+ }
+
+ ///
+ /// 处理Body参数
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static bool AddBodyParameters(List statements, IEnumerable<(ParameterBindingType i, IParameterSymbol p)> paramInfos, string webMethod, IMethodSymbol methodSymbol, List errorAndWarnings)
+ {
+ // var bodyParameters = paramInfos.Where(t => (t.i == -1 && webMethod != WebMethodConstants.Get) || t.i == WebMethodConstants.Body).ToList();
+ var bodyParameters = paramInfos.Where(t => t.i == ParameterBindingType.FromBody).ToList();
+ return bodyParameters.Count switch
+ {
+ > 1 => DoReturnError(),
+ 0 => true,
+ 1 => DoAddBody(),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ bool DoReturnError()
+ {
+ errorAndWarnings.Add(DiagnosticDefinitions.WAG00008(methodSymbol.Locations.FirstOrDefault()));
+ return false;
+ }
+
+ bool DoAddBody()
+ {
+ var p = bodyParameters[0].p;
+ statements.Add($"var _json_gen = global::System.Text.Json.JsonSerializer.Serialize({p.Name})");
+ statements.Add(
+ """_request_gen.Content = new global::System.Net.Http.StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json")""");
+ return true;
+ }
+ }
+
+ ///
+ /// 处理Header参数
+ ///
+ ///
+ ///
+ private static bool AddHeaderParameters(List statements, IEnumerable<(ParameterBindingType i, IParameterSymbol p)> paramInfos)
+ {
+ var headerParameters = paramInfos.Where(t => t.i == ParameterBindingType.FromHeader).ToList();
+
+ if (!headerParameters.Any()) return true;
+ foreach (var item in headerParameters)
+ {
+ var p = item.p;
+ if (p.Type is INamedTypeSymbol
+ {
+ TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String
+ } parameterClassType)
+ {
+ var properties = parameterClassType.GetMembers().Where(m => m.Kind == SymbolKind.Property);
+ foreach (var prop in properties)
+ {
+ statements.Add(
+ $$"""_request_gen.Headers.Add(nameof({{p.Name}}.{{prop.Name}}), $"{{{p.Name}}.{{prop.Name}}}")""");
+ }
+ }
+ else
+ {
+ statements.Add($$"""_request_gen.Headers.Add(nameof({{p.Name}}), $"{{{p.Name}}}")""");
+ }
+ }
+
+ return true;
+ }
+
+ private static bool AddResponseHandling(List statements, ITypeSymbol returnType,
+ List errorAndWarnings, string? cancellationTokenName, IMethodSymbol methodSymbol)
+ {
+ cancellationTokenName ??= string.Empty;
+ if (returnType is { TypeKind: TypeKind.Class, SpecialType: not SpecialType.System_String })
+ {
+ statements.Add($"var _stream_gen = await _response_gen.Content.ReadAsStreamAsync({cancellationTokenName})");
+ statements.Add(
+ $"return global::System.Text.Json.JsonSerializer.Deserialize<{returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(_stream_gen, _JSON_OPTIONS_gen);");
+ }
+ else
+ {
+ statements.Add($"var _str_gen = await _response_gen.Content.ReadAsStringAsync({cancellationTokenName})");
+ if (returnType.SpecialType == SpecialType.System_String)
+ {
+ statements.Add("return _str_gen");
+ }
+ else if (returnType.HasTryParseMethod())
+ {
+ statements.Add($"{returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.TryParse(_str_gen, out var val)");
+ statements.Add("return val");
+ }
+ else
+ {
+ errorAndWarnings.Add(DiagnosticDefinitions.WAG00009(methodSymbol.Locations.FirstOrDefault(), returnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ private static ConstructorBuilder BuildConstructor(INamedTypeSymbol classSymbol, bool needAuth)
+ {
+ List parameters = ["global::System.Net.Http.IHttpClientFactory factory"];
+ List body = ["clientFactory = factory;"];
+ if (needAuth)
+ {
+ //parameters.Add("global::AutoWasmApiGenerator.IHttpClientHeaderHandler handler");
+ parameters.Add("global::System.IServiceProvider services");
+ body.Add(
+ "headerHandler = services.GetService() ?? global::AutoWasmApiGenerator.DefaultHttpClientHeaderHandler.Default");
+ }
+
+ return ConstructorBuilder.Default
+ .MethodName($"{FormatClassName(classSymbol.MetadataName)}ApiInvoker")
+ .AddParameter([.. parameters])
+ .AddBody([.. body])
+ .AddBody(
+ "_JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };");
+ }
+
+ private static ClassBuilder CreateHttpClassBuilder(INamedTypeSymbol interfaceSymbol)
+ {
+ IEnumerable additionalAttribute = [];
+ if (interfaceSymbol.GetAttribute(ApiInvokerGenerateAttributeFullName, out var data))
+ {
+ //var o = data.GetAttributeValue(nameof(ApiInvokerGeneraAttribute.Attribute));
+ additionalAttribute = interfaceSymbol.GetAttributeInitInfo(ApiInvokerGenerateAttributeFullName, data!);
+ }
+
+ return ClassBuilder.Default
+ .ClassName($"{FormatClassName(interfaceSymbol.MetadataName)}ApiInvoker")
+ .AddGeneratedCodeAttribute(typeof(HttpServiceInvokerGenerator))
+ .Attribute([.. additionalAttribute.Select(i => i.ToString())])
+ .BaseType(interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+ }
+}
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/IHttpServiceInvokerGenerator.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/IHttpServiceInvokerGenerator.cs
new file mode 100644
index 0000000..bb06939
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/IHttpServiceInvokerGenerator.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace AutoWasmApiGenerator;
+
+public interface IHttpServiceInvokerGenerator
+{
+ (string generatedFileName, string sourceCode, List errorAndWarnings) Generate(
+ INamedTypeSymbol interfaceSymbol);
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Options/GlobalOptions.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Options/GlobalOptions.cs
new file mode 100644
index 0000000..0b3e2d7
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Options/GlobalOptions.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Threading;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace AutoWasmApiGenerator.Options;
+
+public sealed class GlobalOptions
+{
+ public GlobalOptions(AnalyzerConfigOptions options)
+ {
+ if (options.TryGetValue("build_property.MSBuildProjectFullPath", out var projectFullPath))
+ {
+ ProjectFullPath = projectFullPath;
+ }
+
+ ProjectFullPath ??= string.Empty;
+
+ GenerateInvoker =
+ options.TryGetValue("build_property.AutoWasmApiGenerator_GenerateInvoker", out var generateInvoker) &&
+ generateInvoker is { Length: > 0 } &&
+ generateInvoker.Equals("true", StringComparison.OrdinalIgnoreCase);
+
+ GenerateWebController =
+ options.TryGetValue("build_property.AutoWasmApiGenerator_GenerateWebController", out var generateWebController) &&
+ generateWebController is { Length: > 0 } &&
+ generateWebController.Equals("true", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ ///
+ ///
+ public bool GenerateInvoker { get; }
+
+ ///
+ ///
+ ///
+ public bool GenerateWebController { get; }
+
+ ///
+ ///
+ ///
+ public string? ProjectFullPath { get; }
+
+ public static GlobalOptions Select(AnalyzerConfigOptionsProvider provider, CancellationToken token)
+ {
+ token.ThrowIfCancellationRequested();
+ return new GlobalOptions(provider.GlobalOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/AutoWasmApi.Roslyn/Properties/launchSettings.json b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Properties/launchSettings.json
similarity index 100%
rename from src/AutoWasmApi.Roslyn/Properties/launchSettings.json
rename to src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/Properties/launchSettings.json
diff --git a/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/build/AutoWasmApiGenerator.props b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/build/AutoWasmApiGenerator.props
new file mode 100644
index 0000000..eda792e
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/build/AutoWasmApiGenerator.props
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AutoWasmApi.Roslyn/usings.cs b/src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/usings.cs
similarity index 100%
rename from src/AutoWasmApi.Roslyn/usings.cs
rename to src/AutoWasmApiGenerator/AutoWasmApi.Roslyn/usings.cs
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/AutoWasmApiGenerator.Test.csproj b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/AutoWasmApiGenerator.Test.csproj
new file mode 100644
index 0000000..0a68c0a
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/AutoWasmApiGenerator.Test.csproj
@@ -0,0 +1,55 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/GlobalOptionsTest.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/GlobalOptionsTest.cs
new file mode 100644
index 0000000..a9d518a
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/GlobalOptionsTest.cs
@@ -0,0 +1,76 @@
+using System.Diagnostics.CodeAnalysis;
+using AutoWasmApiGenerator.Options;
+using FluentAssertions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace AutoWasmApiGenerator.Test;
+
+public class GlobalOptionsTest
+{
+ private static readonly GlobalOptions LocalGlobalOptionsForTests = CreateOptions(new AnalyzerConfigOptionsDummy());
+
+ private static GlobalOptions CreateOptions(AnalyzerConfigOptionsDummy dummy)
+ {
+ return GlobalOptions.Select(new AnalyzerConfigOptionsProviderDummy(dummy), default);
+ }
+
+ [Fact]
+ public void GlobalDefault()
+ {
+ var globalOptions = LocalGlobalOptionsForTests;
+ globalOptions.ProjectFullPath.Should().BeEmpty();
+ globalOptions.GenerateInvoker.Should().BeFalse();
+ globalOptions.GenerateWebController.Should().BeFalse();
+ }
+
+ [Fact]
+ public void GlobalSettings_CanReadAll()
+ {
+ var globalOptions = CreateOptions(new AnalyzerConfigOptionsDummy()
+ {
+ MSBuildProjectFullPath = "projectFullPath.csproj",
+ AutoWasmApiGenerator_GenerateInvoker = "true",
+ AutoWasmApiGenerator_GenerateWebController = "true"
+ });
+ globalOptions.ProjectFullPath.Should().Be("projectFullPath.csproj");
+ globalOptions.GenerateInvoker.Should().BeTrue();
+ globalOptions.GenerateWebController.Should().BeTrue();
+ }
+}
+
+public class AnalyzerConfigOptionsDummy : AnalyzerConfigOptions
+{
+ // ReSharper disable InconsistentNaming
+ public string? MSBuildProjectFullPath { get; init; }
+ public string? AutoWasmApiGenerator_GenerateInvoker { get; init; }
+ public string? AutoWasmApiGenerator_GenerateWebController { get; init; }
+ // ReSharper restore InconsistentNaming
+
+ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
+ {
+ value = key switch
+ {
+ "build_property.MSBuildProjectFullPath" => MSBuildProjectFullPath,
+ "build_property.AutoWasmApiGenerator_GenerateInvoker" => AutoWasmApiGenerator_GenerateInvoker,
+ "build_property.AutoWasmApiGenerator_GenerateWebController" => AutoWasmApiGenerator_GenerateWebController,
+ _ => null
+ };
+ return value != null;
+ }
+}
+
+public sealed class AnalyzerConfigOptionsProviderDummy(AnalyzerConfigOptions globalOptions) : AnalyzerConfigOptionsProvider
+{
+ public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
+ {
+ return GlobalOptions;
+ }
+
+ public override AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.Constants.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.Constants.cs
new file mode 100644
index 0000000..58f8520
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.Constants.cs
@@ -0,0 +1,385 @@
+// ReSharper disable InconsistentNaming
+namespace AutoWasmApiGenerator.Test;
+
+public partial class HttpServiceInvokerGeneratorTest
+{
+ private const string AssemblyTag = @" [assembly: AutoWasmApiGenerator.ApiInvokerAssembly]
+ [assembly: AutoWasmApiGenerator.WebControllerAssembly]
+";
+
+ private const string Usings = @"global using AutoWasmApiGenerator;
+global using System;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using System.Text.Json;
+global using System.Net.Http;
+global using System.Collections.Generic;
+global using System.Text;
+global using System.Linq;
+// using Microsoft.Extensions.DependencyInjection;
+";
+
+ #region Wag00005
+
+ private const string TestWag00005 = @"namespace AutoWasmApiGenerator.Test;
+
+[WebController(Authorize = true)]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ void Log(string message);
+}";
+
+ #endregion
+
+ #region Wag00006
+
+ private const string TestWag00006 = @"using AutoWasmApiGenerator
+namespace AutoWasmApiGenerator.Test;
+
+[WebController]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ [WebMethod(Method = WebMethod.Post)]
+ Task LogAsync([WebMethodParameterBinding(BindingType.FromRoute)] string model0)] TestModel1 model1);
+}
+";
+
+ #endregion
+
+ #region Wag00007
+
+ private const string TestWag00007 = @"using AutoWasmApiGenerator
+namespace AutoWasmApiGenerator.Test;
+
+public record TestModel0(string Name, int Age);
+
+public record TestModel1(string Name, double Salary);
+
+[WebController]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ [WebMethod(Method = WebMethod.Post)]
+ Task LogAsync([WebMethodParameterBinding(BindingType.FromBody)] TestModel0 model0, [WebMethodParameterBinding(BindingType.FromForm)] TestModel1 model1);
+}
+
+";
+
+ #endregion
+
+ #region Wag00010
+
+ private const string TestWag00010 = @"using AutoWasmApiGenerator";
+ // private const string TestWag00010Result = @"using AutoWasmApiGenerator";
+
+ #endregion
+
+ #region Wag00011
+
+ private const string TestWag00011 = @"namespace AutoWasmApiGenerator.Test;
+
+[ApiInvokerGenerate]
+public interface ITest
+{
+ [WebMethod(Method = WebMethod.Get)]
+ Task LogAsync(string message);
+}";
+
+ #endregion
+
+ #region Wag00002
+
+ private const string TestWag00002 = @"using AutoWasmApiGenerator";
+
+ #endregion
+
+ #region Wag00003
+
+ private const string TestWag00003 = @"using AutoWasmApiGenerator";
+
+ #endregion
+
+ #region Success
+
+ private static void UpdateSuccessTestString()
+ {
+ TestSuccessResult =
+ $$"""
+ //
+ #pragma warning disable
+ #nullable enable
+
+ using Microsoft.Extensions.DependencyInjection;
+
+ namespace AutoWasmApiGenerator.Test
+ {
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ ///
+ public partial class TestApiInvoker : global::AutoWasmApiGenerator.Test.ITest
+ {
+ private readonly global::System.Text.Json.JsonSerializerOptions _JSON_OPTIONS_gen;
+
+ private readonly global::System.Net.Http.IHttpClientFactory clientFactory;
+
+ private readonly global::AutoWasmApiGenerator.IHttpClientHeaderHandler headerHandler;
+
+ public TestApiInvoker(global::System.Net.Http.IHttpClientFactory factory, global::System.IServiceProvider services)
+ {
+ clientFactory = factory;
+ headerHandler = services.GetService() ?? global::AutoWasmApiGenerator.DefaultHttpClientHeaderHandler.Default;
+ _JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public void Log(string message)
+ => throw new global::System.NotSupportedException();
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public async global::System.Threading.Tasks.Task LogAsync(string message)
+ {
+ var _client_gen = this.clientFactory.CreateClient("Test");
+ var _request_gen = new global::System.Net.Http.HttpRequestMessage();
+ _request_gen.Method = global::System.Net.Http.HttpMethod.Get;
+ await headerHandler.SetRequestHeaderAsync(_request_gen, global::System.Threading.CancellationToken.None);
+ var _url_gen = $"api/Test/Log";
+ var _queries_gen = new global::System.Collections.Generic.List();
+ _queries_gen.Add($"{nameof(message)}={message}");
+ _url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}";
+ _request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative);
+ var _response_gen = await _client_gen.SendAsync(_request_gen);
+ _response_gen.EnsureSuccessStatusCode();
+ var _str_gen = await _response_gen.Content.ReadAsStringAsync();
+ bool.TryParse(_str_gen, out var val);
+ return val;
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public async global::System.Threading.Tasks.Task Log2Async(string message, global::System.Threading.CancellationToken token)
+ {
+ var _client_gen = this.clientFactory.CreateClient("Test");
+ var _request_gen = new global::System.Net.Http.HttpRequestMessage();
+ _request_gen.Method = global::System.Net.Http.HttpMethod.Post;
+ await headerHandler.SetRequestHeaderAsync(_request_gen, token);
+ var _url_gen = $"api/Test/Log2";
+ var _json_gen = global::System.Text.Json.JsonSerializer.Serialize(message);
+ _request_gen.Content = new global::System.Net.Http.StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json");
+ _request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative);
+ var _response_gen = await _client_gen.SendAsync(_request_gen, token);
+ _response_gen.EnsureSuccessStatusCode();
+ var _str_gen = await _response_gen.Content.ReadAsStringAsync(token);
+ bool.TryParse(_str_gen, out var val);
+ return val;
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public async global::System.Threading.Tasks.Task Log3Async(string message, string path, global::System.Threading.CancellationToken token)
+ {
+ var _client_gen = this.clientFactory.CreateClient("Test");
+ var _request_gen = new global::System.Net.Http.HttpRequestMessage();
+ _request_gen.Method = global::System.Net.Http.HttpMethod.Delete;
+ await headerHandler.SetRequestHeaderAsync(_request_gen, token);
+ var _url_gen = $"api/Test/Log3";
+ var _queries_gen = new global::System.Collections.Generic.List();
+ _queries_gen.Add($"{nameof(path)}={path}");
+ _url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}";
+ var _json_gen = global::System.Text.Json.JsonSerializer.Serialize(message);
+ _request_gen.Content = new global::System.Net.Http.StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json");
+ _request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative);
+ var _response_gen = await _client_gen.SendAsync(_request_gen, token);
+ _response_gen.EnsureSuccessStatusCode();
+ var _str_gen = await _response_gen.Content.ReadAsStringAsync(token);
+ bool.TryParse(_str_gen, out var val);
+ return val;
+ }
+ }
+ }
+ """;
+ }
+
+ private const string TestSuccessCode = @"using AutoWasmApiGenerator;
+using System.Threading.Tasks;
+namespace AutoWasmApiGenerator.Test;
+
+[WebController(Authorize = true)]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ [ApiInvokeNotSupported]
+ void Log(string message);
+ [WebMethod(Method = WebMethod.Get)]
+ Task LogAsync(string message);
+
+ [WebMethod(Method = WebMethod.Post)]
+ Task Log2Async([WebMethodParameterBinding(BindingType.FromBody)] string message, [WebMethodParameterBinding(BindingType.Ignore)] CancellationToken token);
+
+ [WebMethod(Method = WebMethod.Delete)]
+ Task Log3Async([WebMethodParameterBinding(BindingType.FromBody)] string message, [WebMethodParameterBinding(BindingType.FromQuery)] string path, CancellationToken token);
+}
+";
+
+ private static string TestSuccessResult = null!; // Updated in UpdateTestString
+
+ #endregion
+
+ #region FromRoute
+
+ private const string TestFromRoute =
+ """
+ using AutoWasmApiGenerator
+ namespace AutoWasmApiGenerator.Test;
+
+ [WebController]
+ [ApiInvokerGenerate]
+ public interface ITest
+ {
+ [WebMethod(Method = WebMethod.Post, Route="Log/{model0}")]
+ Task LogAsync([WebMethodParameterBinding(BindingType.FromRoute)] string model0);
+ }
+ """;
+
+ private static string TestFromRouteResult = null!; // Updated in UpdateFromRouteTestString
+
+ private static void UpdateFromRouteTestString()
+ {
+ TestFromRouteResult =
+ $$"""
+ //
+ #pragma warning disable
+ #nullable enable
+
+ using Microsoft.Extensions.DependencyInjection;
+
+ namespace AutoWasmApiGenerator.Test
+ {
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ ///
+ public partial class TestApiInvoker : global::AutoWasmApiGenerator.Test.ITest
+ {
+ private readonly global::System.Text.Json.JsonSerializerOptions _JSON_OPTIONS_gen;
+
+ private readonly global::System.Net.Http.IHttpClientFactory clientFactory;
+
+ public TestApiInvoker(global::System.Net.Http.IHttpClientFactory factory)
+ {
+ clientFactory = factory;
+ _JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public async global::System.Threading.Tasks.Task LogAsync(string model0)
+ {
+ var _client_gen = this.clientFactory.CreateClient("Test");
+ var _request_gen = new global::System.Net.Http.HttpRequestMessage();
+ _request_gen.Method = global::System.Net.Http.HttpMethod.Post;
+ var _url_gen = $"api/Test/Log/Log/{model0}";
+ _request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative);
+ var _response_gen = await _client_gen.SendAsync(_request_gen);
+ _response_gen.EnsureSuccessStatusCode();
+ var _str_gen = await _response_gen.Content.ReadAsStringAsync();
+ bool.TryParse(_str_gen, out var val);
+ return val;
+ }
+ }
+ }
+ """;
+ }
+
+ #endregion
+
+ #region Wag00008
+
+ private const string TestWag00008 = @"using AutoWasmApiGenerator
+namespace AutoWasmApiGenerator.Test;
+
+public record TestModel0(string Name, int Age);
+
+public record TestModel1(string Name, double Salary);
+
+[WebController]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ [WebMethod(Method = WebMethod.Post)]
+ Task LogAsync([WebMethodParameterBinding(BindingType.FromBody)] TestModel0 model0, [WebMethodParameterBinding(BindingType.FromBody)] TestModel1 model1);
+}
+";
+
+ private const string TestWag00008Result = @"using AutoWasmApiGenerator";
+
+ #endregion
+
+ #region Wag00009
+
+ private const string TestWag00009_0 = @"namespace AutoWasmApiGenerator.Test;
+
+[WebController(Authorize = true)]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ Task Log(string message);
+}";
+
+ private const string TestWag00009_ValidReturnType = @"namespace AutoWasmApiGenerator.Test;
+
+[WebController(Authorize = true)]
+[ApiInvokerGenerate]
+public interface ITest
+{
+ Task Log(string message);
+}";
+
+ private static string TestWag00009_ValidReturnTypeResult = null!; // Updated in UpdateWag00009TestString
+
+ private static void UpdateWag00009TestString()
+ {
+ TestWag00009_ValidReturnTypeResult =
+ $$"""
+ //
+ #pragma warning disable
+ #nullable enable
+
+ using Microsoft.Extensions.DependencyInjection;
+
+ namespace AutoWasmApiGenerator.Test
+ {
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ ///
+ public partial class TestApiInvoker : global::AutoWasmApiGenerator.Test.ITest
+ {
+ private readonly global::System.Text.Json.JsonSerializerOptions _JSON_OPTIONS_gen;
+
+ private readonly global::System.Net.Http.IHttpClientFactory clientFactory;
+
+ private readonly global::AutoWasmApiGenerator.IHttpClientHeaderHandler headerHandler;
+
+ public TestApiInvoker(global::System.Net.Http.IHttpClientFactory factory, global::System.IServiceProvider services)
+ {
+ clientFactory = factory;
+ headerHandler = services.GetService() ?? global::AutoWasmApiGenerator.DefaultHttpClientHeaderHandler.Default;
+ _JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "{{GeneratorVersion}}")]
+ public async global::System.Threading.Tasks.Task Log(string message)
+ {
+ var _client_gen = this.clientFactory.CreateClient("Test");
+ var _request_gen = new global::System.Net.Http.HttpRequestMessage();
+ _request_gen.Method = global::System.Net.Http.HttpMethod.Post;
+ await headerHandler.SetRequestHeaderAsync(_request_gen, global::System.Threading.CancellationToken.None);
+ var _url_gen = $"api/Test/Log";
+ _request_gen.RequestUri = new global::System.Uri(_url_gen, UriKind.Relative);
+ var _response_gen = await _client_gen.SendAsync(_request_gen);
+ _response_gen.EnsureSuccessStatusCode();
+ var _str_gen = await _response_gen.Content.ReadAsStringAsync();
+ bool.TryParse(_str_gen, out var val);
+ return val;
+ }
+ }
+ }
+ """;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.cs
new file mode 100644
index 0000000..f82b1b0
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/HttpServiceInvokerGeneratorTest.cs
@@ -0,0 +1,282 @@
+using System.Reflection;
+using FluentAssertions;
+using Generators.Shared;
+using Microsoft.CodeAnalysis;
+
+namespace AutoWasmApiGenerator.Test;
+
+public static class ExecutableReferenceExtension
+{
+ public static PortableExecutableReference[] DistinctReferences(this IEnumerable types)
+ {
+ return types.Select(t => t.Assembly.Location).Distinct().Select(t => MetadataReference.CreateFromFile(t))
+ .ToArray();
+ }
+}
+
+public partial class HttpServiceInvokerGeneratorTest : IncrementalSourceGeneratorTestBase
+{
+ private static readonly PortableExecutableReference[] References;
+ private static readonly string GeneratorVersion;
+
+ static HttpServiceInvokerGeneratorTest()
+ {
+ References = new List
+ {
+ typeof(HttpServiceInvokerGenerator),
+ typeof(WebControllerAttribute)
+ }.DistinctReferences();
+ GeneratorVersion = typeof(HttpServiceInvokerGenerator).Assembly.GetCustomAttribute()
+ ?.Version ?? throw new Exception("Unknown generator version");
+ UpdateSuccessTestString();
+ UpdateWag00009TestString();
+ UpdateFromRouteTestString();
+ }
+
+ [Fact]
+ public void Test_ReferenceValidity()
+ {
+ var (compilation, _) = CreateDriver([TestSuccessCode, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+
+ var ret = compilation.GetAllSymbols(typeof(WebControllerAttribute).FullName!);
+ ret.Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void Test_SuccessGenerate()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestSuccessCode, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+
+ // Run the generator
+ driver = driver.RunGenerators(compilation);
+ var results = driver.GetRunResult();
+ var result = results.Results.Single();
+
+ // Assert that the generated sources are not empty
+ Assert.NotEmpty(result.GeneratedSources);
+ result.GeneratedSources.Should().HaveCount(1).And.Contain(r => r.HintName == "AutoWasmApiGenerator_Test_TestApiInvoker.g.cs");
+ result.GeneratedSources[0].SourceText.ToString().ReplaceLineEndings().Should()
+ .BeEquivalentTo(TestSuccessResult.ReplaceLineEndings());
+ }
+
+ [Fact]
+ public void Test_Wag00002()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00002, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ // TODO: Add the test, however current error code is not used
+ }
+
+ ///
+ /// ԣ
+ ///
+ [Fact]
+ public void Test_Wag00003()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00003, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ // TODO: Add the test, however current error code is not used
+ }
+
+ ///
+ /// ԣ֧첽
+ ///
+ [Fact]
+ public void Test_Wag00005()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00005, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var result = driver.RunGenerators(compilation)
+ .GetRunResult();
+ // result.Results.Should().BeEmpty();
+ result.GeneratedTrees.Should().BeEmpty();
+ result.Diagnostics.Should().HaveCount(1);
+ result.Diagnostics.Should().Contain(diagnostic => diagnostic.Id == "WAG00005");
+ }
+
+ ///
+ /// ԣ·δ·ɲ
+ ///
+ ///
+ [Fact]
+ public void Test_Wag00006_Fail()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00006, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should()
+ .HaveCount(1)
+ .And.Contain(diagnostic => diagnostic.Id == "WAG00006");
+ results.GeneratedTrees.Should().BeEmpty();
+ }
+
+ ///
+ /// ԣ·δ·ɲ
+ ///
+ ///
+ [Fact]
+ public void Test_FromRoute()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestFromRoute, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should().BeEmpty();
+ results.GeneratedTrees.Should().NotBeEmpty();
+ results.Results.Should().HaveCount(1);
+ var result = results.Results[0];
+ result.GeneratedSources.Should().HaveCount(1);
+ string generated = result.GeneratedSources[0].SourceText.ToString().ReplaceLineEndings();
+ generated.Should().BeEquivalentTo(TestFromRouteResult.ReplaceLineEndings());
+ }
+
+
+ [Fact]
+ public void Test_Wag00007()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00007, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should()
+ .HaveCount(1)
+ .And.Contain(diagnostic => diagnostic.Id == "WAG00007");
+ results.GeneratedTrees.Should().BeEmpty();
+ }
+
+ ///
+ /// ԣö[FromBody]
+ ///
+ ///
+ [Fact]
+ public void Test_Wag00008()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00008, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should()
+ .HaveCount(1)
+ .And.Contain(diagnostic => diagnostic.Id == "WAG00008");
+ results.GeneratedTrees.Should().BeEmpty();
+ }
+
+ ///
+ /// ԣݲֵ֧ķֵ
+ ///
+ [Fact]
+ public void Test_Wag00009()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00009_0, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should()
+ .HaveCount(1)
+ .And.Contain(diagnostic => diagnostic.Id == "WAG00009");
+ results.GeneratedTrees.Should().BeEmpty();
+
+ // Test valid return type, wag00009 should not be triggered
+ (compilation, driver) = CreateDriver([TestWag00009_ValidReturnType, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+
+ results = driver.RunGenerators(compilation).GetRunResult();
+ results.Diagnostics.Should()
+ .BeEmpty();
+ results.Results.Should().HaveCount(1);
+ var result = results.Results[0];
+
+ result.GeneratedSources.Should().HaveCount(1);
+ result.GeneratedSources[0].SourceText.ToString().ReplaceLineEndings().Should()
+ .BeEquivalentTo(TestWag00009_ValidReturnTypeResult.ReplaceLineEndings());
+ }
+
+ [Fact]
+ public void Test_Wag00010()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00010, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ // TODO: Add the test, however current error code may not be triggered
+ }
+
+ [Fact]
+ public void Test_Wag00011()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([TestWag00011, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+ var results = driver.RunGenerators(compilation)
+ .GetRunResult();
+ results.Diagnostics.Should()
+ .HaveCount(1)
+ .And.Contain(diagnostic => diagnostic.Id == "WAG00011");
+ results.GeneratedTrees.Should().BeEmpty();
+ }
+
+
+ #region Example
+
+ /* // this just an example snippet, not a real test
+ public void Test_Example()
+ {
+ // Get AssemblyMeta from AutoWasmApiGenerator
+ var (compilation, driver) = CreateDriver([Test0, AssemblyTag, Usings],
+ new HttpServiceInvokerGenerator().AsSourceGenerator(), References);
+
+ // Run the generator
+ driver = driver.RunGenerators(compilation);
+ var results = driver.GetRunResult();
+ var result = results.Results.Single();
+ //
+ // Log the diagnostics
+ foreach (var diagnostic in result.Diagnostics)
+ {
+ Console.WriteLine(diagnostic.ToString());
+ }
+
+ // Log the generated sources
+ foreach (var generatedSource in result.GeneratedSources)
+ {
+ Console.WriteLine($"HintName: {generatedSource.HintName}");
+ Console.WriteLine(generatedSource.SourceText.ToString());
+ }
+
+ // Assert that the generated sources are not empty
+ Assert.NotEmpty(result.GeneratedSources);
+
+ // Update the compilation and rerun the generator
+ compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText("// dummy"));
+ driver = driver.RunGenerators(compilation);
+
+ // Assert the driver doesn't recompute the output
+ results = driver.GetRunResult();
+ result = results.Results.Single();
+ var allOutputs = result.TrackedOutputSteps.SelectMany(outputStep => outputStep.Value)
+ .SelectMany(output => output.Outputs);
+ Assert.Collection(allOutputs, output => Assert.Equal(IncrementalStepRunReason.Cached, output.Reason));
+
+ // Assert the driver use the cached result from AssemblyName and Syntax
+ var assemblyNameOutputs = result.TrackedSteps["AssemblyName"].Single().Outputs;
+ Assert.Collection(assemblyNameOutputs,
+ output => Assert.Equal(IncrementalStepRunReason.Unchanged, output.Reason));
+
+ var syntaxOutputs = result.TrackedSteps["Syntax"].Single().Outputs;
+ Assert.Collection(syntaxOutputs, output => Assert.Equal(IncrementalStepRunReason.Unchanged, output.Reason));
+ }
+ */
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/IncrementalSourceGeneratorTestBase.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/IncrementalSourceGeneratorTestBase.cs
new file mode 100644
index 0000000..f9dc3d8
--- /dev/null
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.Test/IncrementalSourceGeneratorTestBase.cs
@@ -0,0 +1,22 @@
+using Basic.Reference.Assemblies;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace AutoWasmApiGenerator.Test;
+
+public abstract class IncrementalSourceGeneratorTestBase
+{
+ protected static (Compilation compilation, GeneratorDriver driver) CreateDriver(string[] source, ISourceGenerator sourceGenerator, params PortableExecutableReference[] references)
+ {
+
+ var compilation = CSharpCompilation.Create("TestProject",
+ source.Select(t => CSharpSyntaxTree.ParseText(t)),
+ NetStandard20.References.All.AddRange(references),
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ // Create the generator driver
+ var driver = CSharpGeneratorDriver.Create(
+ [sourceGenerator],
+ driverOptions: new GeneratorDriverOptions(disabledOutputs: IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true));
+ return (compilation, driver);
+ }
+}
\ No newline at end of file
diff --git a/src/AutoWasmApiGenerator/Attributes/ApiInvokeNotSupportedAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokeNotSupportedAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/ApiInvokeNotSupportedAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokeNotSupportedAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/ApiInvokerAssemblyAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokerAssemblyAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/ApiInvokerAssemblyAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokerAssemblyAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/ApiInvokerGenerateAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokerGenerateAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/ApiInvokerGenerateAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/ApiInvokerGenerateAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/NullableAttributes.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/NullableAttributes.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/NullableAttributes.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/NullableAttributes.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/WebControllerAssemblyAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebControllerAssemblyAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/WebControllerAssemblyAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebControllerAssemblyAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/WebControllerAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebControllerAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/WebControllerAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebControllerAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/WebMethodAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebMethodAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/WebMethodAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebMethodAttribute.cs
diff --git a/src/AutoWasmApiGenerator/Attributes/WebMethodParameterBindingAttribute.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebMethodParameterBindingAttribute.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Attributes/WebMethodParameterBindingAttribute.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Attributes/WebMethodParameterBindingAttribute.cs
diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj
similarity index 76%
rename from src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj
index ec5a6cc..5d1a101 100644
--- a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj
@@ -1,15 +1,9 @@
-
+
netstandard2.0;net6.0;net8.0
- latest
- enable
- true
- Generated
true
True
- MarvelTiter
- 0.1.1
MIT
readme.md
True
@@ -22,6 +16,11 @@
+
+
+ true
+ build
+
diff --git a/src/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs
similarity index 66%
rename from src/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs
index 2bb6003..83acf99 100644
--- a/src/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/IHttpClientHeaderHandler.cs
@@ -1,5 +1,6 @@
using System;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
namespace AutoWasmApiGenerator;
@@ -13,8 +14,9 @@ public interface IHttpClientHeaderHandler
///
///
///
+ ///
///
- Task SetRequestHeaderAsync(HttpRequestMessage request);
+ Task SetRequestHeaderAsync(HttpRequestMessage request, CancellationToken token = default);
}
///
@@ -22,21 +24,21 @@ public interface IHttpClientHeaderHandler
///
public class DefaultHttpClientHeaderHandler : IHttpClientHeaderHandler
{
- private DefaultHttpClientHeaderHandler()
- {
-
- }
- private static readonly Lazy lazy = new(() => new DefaultHttpClientHeaderHandler());
+ private DefaultHttpClientHeaderHandler() { }
+
+ private static readonly Lazy Lazy = new(() => new DefaultHttpClientHeaderHandler());
///
///
///
- public static IHttpClientHeaderHandler Default => lazy.Value;
+ public static IHttpClientHeaderHandler Default => Lazy.Value;
+
///
///
///
///
+ ///
///
- public Task SetRequestHeaderAsync(HttpRequestMessage request)
+ public Task SetRequestHeaderAsync(HttpRequestMessage request, CancellationToken token = default)
{
return Task.CompletedTask;
}
diff --git a/src/AutoWasmApiGenerator/Models/BindingType.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/BindingType.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Models/BindingType.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/BindingType.cs
diff --git a/src/AutoWasmApiGenerator/Models/IsExternalInit.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/IsExternalInit.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Models/IsExternalInit.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/IsExternalInit.cs
diff --git a/src/AutoWasmApiGenerator/Models/WebMethod.cs b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/WebMethod.cs
similarity index 100%
rename from src/AutoWasmApiGenerator/Models/WebMethod.cs
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/Models/WebMethod.cs
diff --git a/src/AutoWasmApiGenerator/readme.md b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/readme.md
similarity index 91%
rename from src/AutoWasmApiGenerator/readme.md
rename to src/AutoWasmApiGenerator/AutoWasmApiGenerator/readme.md
index 0a319ee..6573127 100644
--- a/src/AutoWasmApiGenerator/readme.md
+++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator/readme.md
@@ -111,3 +111,14 @@ Task Log3Async([WebMethodParameterBinding(BindingType.FromBody)] string me
+ FromBody 从请求正文中获取值。
+ FromHeader 从 HTTP 标头中获取值。
+ FromServices 从服务容器中获取值。
+
+
+## TODOs
+
+- [ ] 支持类partial功能
+- [ ] 支持方法partial功能
+- [ ] 支持方法名称自定义功能
+- [ ] 支持同步方法
+- [ ] 支持同时设置`[FromBody]`和`[FromForm]` (需要参考一下此功能是否可以实现)
+- [ ] 增加全局配置
+- [ ] 整理Analyzer中的错误提示
diff --git a/src/AutoWasmApiGenerator/Directory.Build.props b/src/AutoWasmApiGenerator/Directory.Build.props
new file mode 100644
index 0000000..699419b
--- /dev/null
+++ b/src/AutoWasmApiGenerator/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+ 0.1.1
+ latest
+ enable
+ MarvelTiter
+ AutoWasmApiGenerator
+
+
diff --git a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj
index b476d21..093d759 100644
--- a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj
+++ b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj
@@ -19,8 +19,8 @@
-
-
+
+
diff --git a/src/Blazor.Test/Blazor.Test/Blazor.Test.csproj b/src/Blazor.Test/Blazor.Test/Blazor.Test.csproj
index acef5ec..88e5920 100644
--- a/src/Blazor.Test/Blazor.Test/Blazor.Test.csproj
+++ b/src/Blazor.Test/Blazor.Test/Blazor.Test.csproj
@@ -8,8 +8,8 @@
-
-
+
+
diff --git a/src/Blazor.Test/Blazor.Test/Class1.cs b/src/Blazor.Test/Blazor.Test/Class1.cs
index 74e5609..fc807f7 100644
--- a/src/Blazor.Test/Blazor.Test/Class1.cs
+++ b/src/Blazor.Test/Blazor.Test/Class1.cs
@@ -49,7 +49,7 @@ public Task Log2Async(string message, CancellationToken token)
public Task Log3Async(string message, string path, CancellationToken token)
{
throw new NotImplementedException();
- return Task.FromResult(message.Length > 5);
+ // return Task.FromResult(message.Length > 5);
}
}
diff --git a/src/InjectTest/InjectTest.csproj b/src/InjectTest/InjectTest.csproj
index dd31cb2..836cdc4 100644
--- a/src/InjectTest/InjectTest.csproj
+++ b/src/InjectTest/InjectTest.csproj
@@ -7,8 +7,8 @@
-
+
diff --git a/src/MT.Generators.sln b/src/MT.Generators.sln
index f6c2c61..91a50cb 100644
--- a/src/MT.Generators.sln
+++ b/src/MT.Generators.sln
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.11.35111.106
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject1", "TestProject1\TestProject1.csproj", "{6994C35E-E92F-418C-B5C9-B437FF25A6A0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoWasmApiGenerator", "AutoWasmApiGenerator\AutoWasmApiGenerator.csproj", "{9C1A1324-0111-468F-9644-F80F72A66DC5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoWasmApiGenerator", "AutoWasmApiGenerator\AutoWasmApiGenerator\AutoWasmApiGenerator.csproj", "{9C1A1324-0111-468F-9644-F80F72A66DC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoInjectGenerator", "AutoInjectGenerator\AutoInjectGenerator.csproj", "{1CA123D0-5B2A-4BBD-A90E-B58E01A7B2CA}"
EndProject
@@ -16,8 +16,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoInject.Roslyn", "AutoInject.Roslyn\AutoInject.Roslyn.csproj", "{E48C1C4F-8111-4513-86EC-0C968E5437CD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AutoWasm", "AutoWasm", "{0D1AD616-22D2-4D10-BD23-92040AFD90D5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoWasmApi.Roslyn", "AutoWasmApi.Roslyn\AutoWasmApi.Roslyn.csproj", "{625157FF-90BB-4950-9643-7BAEF5CEF6A8}"
+ ProjectSection(SolutionItems) = preProject
+ AutoWasmApiGenerator\Directory.Build.props = AutoWasmApiGenerator\Directory.Build.props
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Test", "Blazor.Test\Blazor.Test\Blazor.Test.csproj", "{4A47713C-2332-40C1-9AAB-7B2A14D148A2}"
EndProject
@@ -43,6 +44,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{71D54F
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Generators.Shared", "Shared\Generators.Shared\Generators.Shared.shproj", "{0C3325BB-C186-4E45-A567-69DAF5411E8B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoWasmApiGenerator.Test", "AutoWasmApiGenerator\AutoWasmApiGenerator.Test\AutoWasmApiGenerator.Test.csproj", "{3ED9CDA7-B256-4B4C-85DF-E1EA62715428}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoWasmApi.Roslyn", "AutoWasmApiGenerator\AutoWasmApi.Roslyn\AutoWasmApi.Roslyn.csproj", "{144DCF75-901E-40EE-8449-FC404D57017D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,10 +74,6 @@ Global
{E48C1C4F-8111-4513-86EC-0C968E5437CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E48C1C4F-8111-4513-86EC-0C968E5437CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E48C1C4F-8111-4513-86EC-0C968E5437CD}.Release|Any CPU.Build.0 = Release|Any CPU
- {625157FF-90BB-4950-9643-7BAEF5CEF6A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {625157FF-90BB-4950-9643-7BAEF5CEF6A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {625157FF-90BB-4950-9643-7BAEF5CEF6A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {625157FF-90BB-4950-9643-7BAEF5CEF6A8}.Release|Any CPU.Build.0 = Release|Any CPU
{4A47713C-2332-40C1-9AAB-7B2A14D148A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A47713C-2332-40C1-9AAB-7B2A14D148A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A47713C-2332-40C1-9AAB-7B2A14D148A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -101,6 +102,14 @@ Global
{6FFF3104-CCD7-4916-A911-AEB9A5D35536}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FFF3104-CCD7-4916-A911-AEB9A5D35536}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FFF3104-CCD7-4916-A911-AEB9A5D35536}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3ED9CDA7-B256-4B4C-85DF-E1EA62715428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3ED9CDA7-B256-4B4C-85DF-E1EA62715428}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3ED9CDA7-B256-4B4C-85DF-E1EA62715428}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3ED9CDA7-B256-4B4C-85DF-E1EA62715428}.Release|Any CPU.Build.0 = Release|Any CPU
+ {144DCF75-901E-40EE-8449-FC404D57017D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {144DCF75-901E-40EE-8449-FC404D57017D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {144DCF75-901E-40EE-8449-FC404D57017D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {144DCF75-901E-40EE-8449-FC404D57017D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -110,7 +119,6 @@ Global
{9C1A1324-0111-468F-9644-F80F72A66DC5} = {0D1AD616-22D2-4D10-BD23-92040AFD90D5}
{1CA123D0-5B2A-4BBD-A90E-B58E01A7B2CA} = {FD7A81EE-6E54-461B-8766-BB36EB5F31D8}
{E48C1C4F-8111-4513-86EC-0C968E5437CD} = {FD7A81EE-6E54-461B-8766-BB36EB5F31D8}
- {625157FF-90BB-4950-9643-7BAEF5CEF6A8} = {0D1AD616-22D2-4D10-BD23-92040AFD90D5}
{4A47713C-2332-40C1-9AAB-7B2A14D148A2} = {47FB0C42-25F4-49C1-A98C-D6536116B894}
{20CA19C8-3036-4D1B-B095-C59A4C76B8D0} = {47FB0C42-25F4-49C1-A98C-D6536116B894}
{8638056F-E36D-4EBC-A4EA-A7AE22DDC161} = {FCEAEFA9-132B-4945-9E97-53612B8A5CBF}
@@ -119,14 +127,17 @@ Global
{26DD2068-DD76-4FF9-8FB6-8E354188F7F7} = {B1852D0E-A185-491C-B81D-03D1AC5EF97C}
{6FFF3104-CCD7-4916-A911-AEB9A5D35536} = {B1852D0E-A185-491C-B81D-03D1AC5EF97C}
{0C3325BB-C186-4E45-A567-69DAF5411E8B} = {71D54FEF-E30B-4829-9EF2-C27DF766E235}
+ {3ED9CDA7-B256-4B4C-85DF-E1EA62715428} = {47FB0C42-25F4-49C1-A98C-D6536116B894}
+ {144DCF75-901E-40EE-8449-FC404D57017D} = {0D1AD616-22D2-4D10-BD23-92040AFD90D5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E16846FC-C2B2-43ED-A534-5726B5C9DDEE}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Shared\Generators.Shared\Generators.Shared.projitems*{0c3325bb-c186-4e45-a567-69daf5411e8b}*SharedItemsImports = 13
+ Shared\Generators.Shared\Generators.Shared.projitems*{144dcf75-901e-40ee-8449-fc404d57017d}*SharedItemsImports = 5
Shared\Generators.Shared\Generators.Shared.projitems*{26dd2068-dd76-4ff9-8fb6-8e354188f7f7}*SharedItemsImports = 5
- Shared\Generators.Shared\Generators.Shared.projitems*{625157ff-90bb-4950-9643-7baef5cef6a8}*SharedItemsImports = 5
+ Shared\Generators.Shared\Generators.Shared.projitems*{3ed9cda7-b256-4b4c-85df-e1ea62715428}*SharedItemsImports = 5
Shared\Generators.Shared\Generators.Shared.projitems*{ba595e23-bd3a-4b36-a094-06e5814fe5e9}*SharedItemsImports = 5
Shared\Generators.Shared\Generators.Shared.projitems*{e48c1c4f-8111-4513-86ec-0c968e5437cd}*SharedItemsImports = 5
EndGlobalSection
diff --git a/src/Shared b/src/Shared
index 559b46e..e48adca 160000
--- a/src/Shared
+++ b/src/Shared
@@ -1 +1 @@
-Subproject commit 559b46e1d4721bb665d35acc66fbc36536f7989e
+Subproject commit e48adcac152e0a66651a03f5395b4023ec333247