From d3311735b44a4035b9be4421364d5d2f173a0c8f Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 21 Mar 2025 17:54:31 -0700 Subject: [PATCH] Fix up handling for non-generic Task and ValueTask --- src/OpenApi/gen/XmlCommentGenerator.Parser.cs | 4 +- src/OpenApi/gen/XmlComments/MemberKey.cs | 14 +-- .../OperationTests.MinimalApis.cs | 105 ++++++++++++++++++ ...ApiXmlCommentSupport.generated.verified.cs | 8 ++ 4 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Parser.cs b/src/OpenApi/gen/XmlCommentGenerator.Parser.cs index 022fe4a67427..9b25cf9dd63c 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Parser.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Parser.cs @@ -94,7 +94,7 @@ public sealed partial class XmlCommentGenerator if (DocumentationCommentId.GetFirstSymbolForDeclarationId(name, compilation) is ISymbol symbol && // Only include symbols that are declared in the application assembly or are // accessible from the application assembly. - (SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, input.Compilation.Assembly) || symbol.IsAccessibleType()) && + (SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, compilation.Assembly) || symbol.IsAccessibleType()) && // Skip static classes that are just containers for members with annotations // since they cannot be instantiated. symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsStatic: true }) @@ -104,7 +104,7 @@ public sealed partial class XmlCommentGenerator { var memberKey = symbol switch { - IMethodSymbol methodSymbol => MemberKey.FromMethodSymbol(methodSymbol, input.Compilation), + IMethodSymbol methodSymbol => MemberKey.FromMethodSymbol(methodSymbol), IPropertySymbol propertySymbol => MemberKey.FromPropertySymbol(propertySymbol), INamedTypeSymbol typeSymbol => MemberKey.FromTypeSymbol(typeSymbol), _ => null diff --git a/src/OpenApi/gen/XmlComments/MemberKey.cs b/src/OpenApi/gen/XmlComments/MemberKey.cs index 9117d02af393..b4be920d9c63 100644 --- a/src/OpenApi/gen/XmlComments/MemberKey.cs +++ b/src/OpenApi/gen/XmlComments/MemberKey.cs @@ -21,7 +21,7 @@ internal sealed record MemberKey( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters); - public static MemberKey FromMethodSymbol(IMethodSymbol method, Compilation compilation) + public static MemberKey FromMethodSymbol(IMethodSymbol method) { string returnType; if (method.ReturnsVoid) @@ -32,16 +32,10 @@ public static MemberKey FromMethodSymbol(IMethodSymbol method, Compilation compi { // Handle Task/ValueTask for async methods var actualReturnType = method.ReturnType; - if (method.IsAsync && actualReturnType is INamedTypeSymbol namedType) + if (method.IsAsync + && actualReturnType is INamedTypeSymbol { TypeArguments.Length: 1 } namedType) { - if (namedType.TypeArguments.Length > 0) - { - actualReturnType = namedType.TypeArguments[0]; - } - else - { - actualReturnType = compilation.GetSpecialType(SpecialType.System_Void); - } + actualReturnType = namedType.ConstructedFrom; } returnType = actualReturnType.TypeKind == TypeKind.TypeParameter diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs index 76fd57471901..6dfdcb5fa879 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs @@ -14,6 +14,7 @@ public async Task SupportsXmlCommentsOnOperationsFromMinimalApis() { var source = """ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http.HttpResults; @@ -32,6 +33,14 @@ public async Task SupportsXmlCommentsOnOperationsFromMinimalApis() app.MapGet("/5", RouteHandlerExtensionMethods.Get5); app.MapPost("/6", RouteHandlerExtensionMethods.Post6); app.MapPut("/7", RouteHandlerExtensionMethods.Put7); +app.MapGet("/8", RouteHandlerExtensionMethods.Get8); +app.MapGet("/9", RouteHandlerExtensionMethods.Get9); +app.MapGet("/10", RouteHandlerExtensionMethods.Get10); +app.MapGet("/11", RouteHandlerExtensionMethods.Get11); +app.MapGet("/12", RouteHandlerExtensionMethods.Get12); +app.MapGet("/13", RouteHandlerExtensionMethods.Get13); +app.MapGet("/14", RouteHandlerExtensionMethods.Get14); +app.MapGet("/15", RouteHandlerExtensionMethods.Get15); app.Run(); @@ -114,6 +123,73 @@ public static IResult Put7(int? id, string uuid) { return TypedResults.NoContent(); } + + /// + /// A summary of Get8. + /// + public static async Task Get8() + { + await Task.Delay(1000); + return; + } + + /// + /// A summary of Get9. + /// + public static async ValueTask Get9() + { + await Task.Delay(1000); + return; + } + + /// + /// A summary of Get10. + /// + public static Task Get10() + { + return Task.CompletedTask; + } + + /// + /// A summary of Get11. + /// + public static ValueTask Get11() + { + return ValueTask.CompletedTask; + } + + /// + /// A summary of Get12. + /// + public static Task Get12() + { + return Task.FromResult("Hello, World!"); + } + + /// + /// A summary of Get13. + /// + public static ValueTask Get13() + { + return new ValueTask("Hello, World!"); + } + + /// + /// A summary of Get14. + /// + public static async Task> Get14() + { + await Task.Delay(1000); + return new Holder { Value = "Hello, World!" }; + } + + /// + /// A summary of Get15. + /// + public static Task> Get15() + { + return Task.FromResult(new Holder { Value = "Hello, World!" }); + } } public class User @@ -121,6 +197,11 @@ public class User public string Username { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; } + +public class Holder +{ + public T Value { get; set; } = default!; +} """; var generator = new XmlCommentGenerator(); await SnapshotTestHelper.Verify(source, generator, out var compilation); @@ -159,6 +240,30 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => var idParam = path7.Parameters.First(p => p.Name == "id"); Assert.True(idParam.Deprecated); Assert.Equal("Legacy ID parameter - use uuid instead.", idParam.Description); + + var path8 = document.Paths["/8"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get8.", path8.Summary); + + var path9 = document.Paths["/9"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get9.", path9.Summary); + + var path10 = document.Paths["/10"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get10.", path10.Summary); + + var path11 = document.Paths["/11"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get11.", path11.Summary); + + var path12 = document.Paths["/12"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get12.", path12.Summary); + + var path13 = document.Paths["/13"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get13.", path13.Summary); + + var path14 = document.Paths["/14"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get14.", path14.Summary); + + var path15 = document.Paths["/15"].Operations[OperationType.Get]; + Assert.Equal("A summary of Get15.", path15.Summary); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 0d60c7298d22..4625c785aad1 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -198,6 +198,14 @@ private static Dictionary GenerateCacheEntries() ""email"": ""john@example.com"" }", null, null, false, null, [new XmlParameterComment(@"user", @"The user information.", @"{""username"": ""johndoe"", ""email"": ""john@example.com""}", false)], [new XmlResponseComment(@"201", @"Successfully created the user.", @""), new XmlResponseComment(@"400", @"If the user data is invalid.", @"")])); _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Put7", typeof(global::Microsoft.AspNetCore.Http.IResult), [typeof(global::System.Int32?), typeof(global::System.String)]), new XmlComment(@"Updates an existing record.", null, null, null, null, false, null, [new XmlParameterComment(@"id", @"Legacy ID parameter - use uuid instead.", null, true), new XmlParameterComment(@"uuid", @"Unique identifier for the record.", null, false)], [new XmlResponseComment(@"204", @"Update successful.", @""), new XmlResponseComment(@"404", @"Legacy response - will be removed.", @"")])); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get8", typeof(global::System.Threading.Tasks.Task), []), new XmlComment(@"A summary of Get8.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get9", typeof(global::System.Threading.Tasks.ValueTask), []), new XmlComment(@"A summary of Get9.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get10", typeof(global::System.Threading.Tasks.Task), []), new XmlComment(@"A summary of Get10.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get11", typeof(global::System.Threading.Tasks.ValueTask), []), new XmlComment(@"A summary of Get11.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get12", typeof(global::System.Threading.Tasks.Task<>), []), new XmlComment(@"A summary of Get12.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get13", typeof(global::System.Threading.Tasks.ValueTask<>), []), new XmlComment(@"A summary of Get13.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get14", typeof(global::System.Threading.Tasks.Task<>), []), new XmlComment(@"A summary of Get14.", null, null, null, null, false, null, null, null)); + _cache.Add(new MemberKey(typeof(global::RouteHandlerExtensionMethods), MemberType.Method, "Get15", typeof(global::System.Threading.Tasks.Task<>), []), new XmlComment(@"A summary of Get15.", null, null, null, null, false, null, null, null)); return _cache; }