Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Harden handling for generic type arguments in source generator #61145

Merged
merged 7 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 205 additions & 119 deletions src/OpenApi/gen/XmlCommentGenerator.Emitter.cs

Large diffs are not rendered by default.

18 changes: 4 additions & 14 deletions src/OpenApi/gen/XmlCommentGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,36 +83,26 @@ public sealed partial class XmlCommentGenerator
return comments;
}

internal static IEnumerable<(MemberKey, XmlComment?)> ParseComments(
internal static IEnumerable<(string, XmlComment?)> ParseComments(
(List<(string, string)> RawComments, Compilation Compilation) input,
CancellationToken cancellationToken)
{
var compilation = input.Compilation;
var comments = new List<(MemberKey, XmlComment?)>();
var comments = new List<(string, XmlComment?)>();
foreach (var (name, value) in input.RawComments)
{
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 })
{
var parsedComment = XmlComment.Parse(symbol, compilation, value, cancellationToken);
if (parsedComment is not null)
{
var memberKey = symbol switch
{
IMethodSymbol methodSymbol => MemberKey.FromMethodSymbol(methodSymbol, input.Compilation),
IPropertySymbol propertySymbol => MemberKey.FromPropertySymbol(propertySymbol),
INamedTypeSymbol typeSymbol => MemberKey.FromTypeSymbol(typeSymbol),
_ => null
};
if (memberKey is not null)
{
comments.Add((memberKey, parsedComment));
}
comments.Add((name, parsedComment));
}
}
}
Expand Down
143 changes: 0 additions & 143 deletions src/OpenApi/gen/XmlComments/MemberKey.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task SupportsAllXmlTagsOnSchemas()
{
var source = """
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -36,6 +37,7 @@ public async Task SupportsAllXmlTagsOnSchemas()
app.MapPost("/inherit-only-returns", (InheritOnlyReturns returns) => { });
app.MapPost("/inherit-all-but-remarks", (InheritAllButRemarks remarks) => { });
app.MapPost("/generic-class", (GenericClass<string> generic) => { });
app.MapPost("/generic-parent", (GenericParent parent) => { });
app.MapPost("/params-and-param-refs", (ParamsAndParamRefs refs) => { });


Expand Down Expand Up @@ -335,6 +337,89 @@ public class GenericClass<T>
// Fields and members.
}

/// <summary>
/// This class validates the behavior for mapping
/// generic types to open generics for use in
/// typeof expressions.
/// </summary>
public class GenericParent
{
/// <summary>
/// This property is a nullable value type.
/// </summary>
public int? Id { get; set; }

/// <summary>
/// This property is a nullable reference type.
/// </summary>
public string? Name { get; set; }

/// <summary>
/// This property is a generic type containing a tuple.
/// </summary>
public Task<(int, string)> TaskOfTupleProp { get; set; }

/// <summary>
/// This property is a tuple with a generic type inside.
/// </summary>
public (int, Dictionary<int, string>) TupleWithGenericProp { get; set; }

/// <summary>
/// This property is a tuple with a nested generic type inside.
/// </summary>
public (int, Dictionary<int, Dictionary<string, int>>) TupleWithNestedGenericProp { get; set; }

/// <summary>
/// This method returns a generic type containing a tuple.
/// </summary>
public static Task<(int, string)> GetTaskOfTuple()
{
return Task.FromResult((1, "test"));
}

/// <summary>
/// This method returns a tuple with a generic type inside.
/// </summary>
public static (int, Dictionary<int, string>) GetTupleOfTask()
{
return (1, new Dictionary<int, string>());
}

/// <summary>
/// This method return a tuple with a generic type containing a
/// type parameter inside.
/// </summary>
public static (int, Dictionary<int, T>) GetTupleOfTask1<T>()
{
return (1, new Dictionary<int, T>());
}

/// <summary>
/// This method return a tuple with a generic type containing a
/// type parameter inside.
/// </summary>
public static (T, Dictionary<int, string>) GetTupleOfTask2<T>()
{
return (default, new Dictionary<int, string>());
}

/// <summary>
/// This method returns a nested generic with all types resolved.
/// </summary>
public static Dictionary<int, Dictionary<int, string>> GetNestedGeneric()
{
return new Dictionary<int, Dictionary<int, string>>();
}

/// <summary>
/// This method returns a nested generic with a type parameter.
/// </summary>
public static Dictionary<int, Dictionary<int, T>> GetNestedGeneric1<T>()
{
return new Dictionary<int, Dictionary<int, T>>();
}
}

/// <summary>
/// This shows examples of typeparamref and typeparam tags
/// </summary>
Expand Down Expand Up @@ -394,6 +479,15 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
var genericClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This is a generic class.", genericClass.Description);

path = document.Paths["/generic-parent"].Operations[OperationType.Post];
var genericParent = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This class validates the behavior for mapping\ngeneric types to open generics for use in\ntypeof expressions.", genericParent.Description, ignoreLineEndingDifferences: true);
Assert.Equal("This property is a nullable value type.", genericParent.Properties["id"].Description);
Assert.Equal("This property is a nullable reference type.", genericParent.Properties["name"].Description);
Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description);
Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description);
Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description);

path = document.Paths["/params-and-param-refs"].Operations[OperationType.Post];
var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This shows examples of typeparamref and typeparam tags", paramsAndParamRefs.Description);
Expand Down
Loading
Loading