Skip to content

Commit 06f3c41

Browse files
authored
Fix OpenAPI XML code gen (#60977)
1 parent 298493e commit 06f3c41

12 files changed

+109
-13
lines changed

src/OpenApi/gen/Helpers/ISymbolExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,26 @@ public static IEnumerable<INamedTypeSymbol> GetBaseTypes(this ITypeSymbol? type)
189189
current = current.BaseType;
190190
}
191191
}
192+
193+
public static bool IsAccessibleType(this ISymbol symbol)
194+
{
195+
// Check if the symbol itself is public
196+
if (symbol.DeclaredAccessibility != Accessibility.Public)
197+
{
198+
return false;
199+
}
200+
201+
// Check if all containing types are public as well
202+
var containingType = symbol.ContainingType;
203+
while (containingType != null)
204+
{
205+
if (containingType.DeclaredAccessibility != Accessibility.Public)
206+
{
207+
return false;
208+
}
209+
containingType = containingType.ContainingType;
210+
}
211+
212+
return true;
213+
}
192214
}

src/OpenApi/gen/XmlCommentGenerator.Emitter.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ internal static string GenerateXmlCommentSupportSource(string commentsFromXmlFil
2727
// </auto-generated>
2828
//------------------------------------------------------------------------------
2929
#nullable enable
30+
// Suppress warnings about obsolete types and members
31+
// in generated code
32+
#pragma warning disable CS0612, CS0618
3033
3134
namespace System.Runtime.CompilerServices
3235
{
@@ -487,7 +490,7 @@ internal static string EmitSourceGeneratedXmlComment(XmlComment comment)
487490
else
488491
{
489492
codeWriter.Write("[");
490-
for (int i = 0; i < comment.Examples.Count; i++)
493+
for (var i = 0; i < comment.Examples.Count; i++)
491494
{
492495
var example = comment.Examples[i];
493496
codeWriter.Write(FormatStringForCode(example));
@@ -506,13 +509,18 @@ internal static string EmitSourceGeneratedXmlComment(XmlComment comment)
506509
else
507510
{
508511
codeWriter.Write("[");
509-
for (int i = 0; i < comment.Parameters.Count; i++)
512+
for (var i = 0; i < comment.Parameters.Count; i++)
510513
{
511514
var parameter = comment.Parameters[i];
512515
var exampleLiteral = string.IsNullOrEmpty(parameter.Example)
513516
? "null"
514517
: FormatStringForCode(parameter.Example!);
515-
codeWriter.Write($"new XmlParameterComment(@\"{parameter.Name}\", @\"{parameter.Description}\", {exampleLiteral}, {(parameter.Deprecated == true ? "true" : "false")})");
518+
codeWriter.Write("new XmlParameterComment(");
519+
codeWriter.Write(FormatStringForCode(parameter.Name) + ", ");
520+
codeWriter.Write(FormatStringForCode(parameter.Description) + ", ");
521+
codeWriter.Write(exampleLiteral + ", ");
522+
codeWriter.Write(parameter.Deprecated == true ? "true" : "false");
523+
codeWriter.Write(")");
516524
if (i < comment.Parameters.Count - 1)
517525
{
518526
codeWriter.Write(", ");
@@ -528,10 +536,13 @@ internal static string EmitSourceGeneratedXmlComment(XmlComment comment)
528536
else
529537
{
530538
codeWriter.Write("[");
531-
for (int i = 0; i < comment.Responses.Count; i++)
539+
for (var i = 0; i < comment.Responses.Count; i++)
532540
{
533541
var response = comment.Responses[i];
534-
codeWriter.Write($"new XmlResponseComment(@\"{response.Code}\", @\"{response.Description}\", {(response.Example is null ? "null" : FormatStringForCode(response.Example))})");
542+
codeWriter.Write("new XmlResponseComment(");
543+
codeWriter.Write(FormatStringForCode(response.Code) + ", ");
544+
codeWriter.Write(FormatStringForCode(response.Description) + ", ");
545+
codeWriter.Write(response.Example is null ? "null)" : FormatStringForCode(response.Example) + ")");
535546
if (i < comment.Responses.Count - 1)
536547
{
537548
codeWriter.Write(", ");

src/OpenApi/gen/XmlCommentGenerator.Parser.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ public sealed partial class XmlCommentGenerator
9191
var comments = new List<(MemberKey, XmlComment?)>();
9292
foreach (var (name, value) in input.RawComments)
9393
{
94-
if (DocumentationCommentId.GetFirstSymbolForDeclarationId(name, compilation) is ISymbol symbol)
94+
if (DocumentationCommentId.GetFirstSymbolForDeclarationId(name, compilation) is ISymbol symbol &&
95+
// Only include symbols that are declared in the application assembly or are
96+
// accessible from the application assembly.
97+
(SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, input.Compilation.Assembly) || symbol.IsAccessibleType()) &&
98+
// Skip static classes that are just containers for members with annotations
99+
// since they cannot be instantiated.
100+
symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsStatic: true })
95101
{
96102
var parsedComment = XmlComment.Parse(symbol, compilation, value, cancellationToken);
97103
if (parsedComment is not null)

src/OpenApi/gen/XmlComments/MemberKey.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static MemberKey FromMethodSymbol(IMethodSymbol method, Compilation compi
4646

4747
returnType = actualReturnType.TypeKind == TypeKind.TypeParameter
4848
? "typeof(object)"
49-
: $"typeof({actualReturnType.ToDisplayString(_typeKeyFormat)})";
49+
: $"typeof({ReplaceGenericArguments(actualReturnType.ToDisplayString(_typeKeyFormat))})";
5050
}
5151

5252
// Handle extension methods by skipping the 'this' parameter
@@ -62,10 +62,10 @@ public static MemberKey FromMethodSymbol(IMethodSymbol method, Compilation compi
6262
// For params arrays, use the array type
6363
if (p.IsParams && p.Type is IArrayTypeSymbol arrayType)
6464
{
65-
return $"typeof({arrayType.ToDisplayString(_typeKeyFormat)})";
65+
return $"typeof({ReplaceGenericArguments(arrayType.ToDisplayString(_typeKeyFormat))})";
6666
}
6767

68-
return $"typeof({p.Type.ToDisplayString(_typeKeyFormat)})";
68+
return $"typeof({ReplaceGenericArguments(p.Type.ToDisplayString(_typeKeyFormat))})";
6969
})
7070
.ToArray();
7171

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/AdditionalTextsTests.Schemas.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public async Task CanHandleXmlForSchemasInAdditionalTexts()
3030
app.MapPost("/project-record", (ProjectRecord project) => { });
3131
app.MapPost("/todo-with-description", (TodoWithDescription todo) => { });
3232
app.MapPost("/type-with-examples", (TypeWithExamples typeWithExamples) => { });
33+
app.MapPost("/external-method", ClassLibrary.Endpoints.ExternalMethod);
3334
3435
app.Run();
3536
""";
@@ -61,6 +62,9 @@ public class ProjectBoard
6162
/// </summary>
6263
public class BoardItem
6364
{
65+
/// <summary>
66+
/// The identifier of the board item. Defaults to "name".
67+
/// </summary>
6468
public string Name { get; set; }
6569
}
6670
}
@@ -120,6 +124,37 @@ public class TypeWithExamples
120124
/// <example>https://example.com</example>
121125
public Uri UriType { get; set; }
122126
}
127+
128+
public class Holder<T>
129+
{
130+
/// <summary>
131+
/// The value to hold.
132+
/// </summary>
133+
public T Value { get; set; }
134+
135+
public Holder(T value)
136+
{
137+
Value = value;
138+
}
139+
}
140+
141+
public static class Endpoints
142+
{
143+
/// <summary>
144+
/// An external method.
145+
/// </summary>
146+
/// <param name="name">The name of the tester. Defaults to "Tester".</param>
147+
public static void ExternalMethod(string name = "Tester") { }
148+
149+
/// <summary>
150+
/// Creates a holder for the specified value.
151+
/// </summary>
152+
/// <typeparam name="T">The type of the value.</typeparam>
153+
/// <param name="value">The value to hold.</param>
154+
/// <returns>A holder for the specified value.</returns>
155+
/// <example>{ value: 42 }</example>
156+
public static Holder<T> CreateHolder<T>(T value) => new(value);
157+
}
123158
""";
124159
var references = new Dictionary<string, List<string>>
125160
{

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public class InheritAllButRemarks
330330
/// In generic classes and methods, you'll often want to reference the
331331
/// generic type, or the type parameter.
332332
/// </remarks>
333-
class GenericClass<T>
333+
public class GenericClass<T>
334334
{
335335
// Fields and members.
336336
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// </auto-generated>
99
//------------------------------------------------------------------------------
1010
#nullable enable
11+
// Suppress warnings about obsolete types and members
12+
// in generated code
13+
#pragma warning disable CS0612, CS0618
1114

1215
namespace System.Runtime.CompilerServices
1316
{

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// </auto-generated>
99
//------------------------------------------------------------------------------
1010
#nullable enable
11+
// Suppress warnings about obsolete types and members
12+
// in generated code
13+
#pragma warning disable CS0612, CS0618
1114

1215
namespace System.Runtime.CompilerServices
1316
{
@@ -186,6 +189,7 @@ private static Dictionary<MemberKey, XmlComment> GenerateCacheEntries()
186189
_cache.Add(new MemberKey(typeof(global::ClassLibrary.Project), MemberType.Type, null, null, []), new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, null, null));
187190
_cache.Add(new MemberKey(typeof(global::ClassLibrary.Project), MemberType.Method, ".ctor", typeof(void), [typeof(global::System.String), typeof(global::System.String)]), new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, null, null));
188191
_cache.Add(new MemberKey(typeof(global::ClassLibrary.ProjectBoard.BoardItem), MemberType.Type, null, null, []), new XmlComment(@"An item on the board.", null, null, null, null, false, null, null, null));
192+
_cache.Add(new MemberKey(typeof(global::ClassLibrary.ProjectBoard.BoardItem), MemberType.Property, "Name", null, []), new XmlComment(@"The identifier of the board item. Defaults to ""name"".", null, null, null, null, false, null, null, null));
189193
_cache.Add(new MemberKey(typeof(global::ClassLibrary.ProjectRecord), MemberType.Type, null, null, []), new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, [new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false)], null));
190194
_cache.Add(new MemberKey(typeof(global::ClassLibrary.ProjectRecord), MemberType.Method, ".ctor", typeof(void), [typeof(global::System.String), typeof(global::System.String)]), new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, [new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false)], null));
191195
_cache.Add(new MemberKey(typeof(global::ClassLibrary.ProjectRecord), MemberType.Property, "Name", null, []), new XmlComment(@"The name of the project.", null, null, null, null, false, null, null, null));
@@ -207,6 +211,9 @@ private static Dictionary<MemberKey, XmlComment> GenerateCacheEntries()
207211
_cache.Add(new MemberKey(typeof(global::ClassLibrary.TypeWithExamples), MemberType.Property, "ByteType", null, []), new XmlComment(null, null, null, null, null, false, [@"255"], null, null));
208212
_cache.Add(new MemberKey(typeof(global::ClassLibrary.TypeWithExamples), MemberType.Property, "DecimalType", null, []), new XmlComment(null, null, null, null, null, false, [@"3.14159265359"], null, null));
209213
_cache.Add(new MemberKey(typeof(global::ClassLibrary.TypeWithExamples), MemberType.Property, "UriType", null, []), new XmlComment(null, null, null, null, null, false, [@"https://example.com"], null, null));
214+
_cache.Add(new MemberKey(typeof(global::ClassLibrary.Holder<>), MemberType.Property, "Value", null, []), new XmlComment(@"The value to hold.", null, null, null, null, false, null, null, null));
215+
_cache.Add(new MemberKey(typeof(global::ClassLibrary.Endpoints), MemberType.Method, "ExternalMethod", typeof(void), [typeof(global::System.String)]), new XmlComment(@"An external method.", null, null, null, null, false, null, [new XmlParameterComment(@"name", @"The name of the tester. Defaults to ""Tester"".", null, false)], null));
216+
_cache.Add(new MemberKey(typeof(global::ClassLibrary.Endpoints), MemberType.Method, "CreateHolder", typeof(global::ClassLibrary.Holder<>), [typeof(object)]), new XmlComment(@"Creates a holder for the specified value.", null, null, @"A holder for the specified value.", null, false, [@"{ value: 42 }"], [new XmlParameterComment(@"value", @"The value to hold.", null, false)], null));
210217

211218

212219
return _cache;

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// </auto-generated>
99
//------------------------------------------------------------------------------
1010
#nullable enable
11+
// Suppress warnings about obsolete types and members
12+
// in generated code
13+
#pragma warning disable CS0612, CS0618
1114

1215
namespace System.Runtime.CompilerServices
1316
{
@@ -268,7 +271,7 @@ Note that there isn't a way to provide a ""cref"" to
268271
{
269272
Console.WriteLine(c);
270273
}```"], [new XmlParameterComment(@"left", @"The left operand of the addition.", null, false), new XmlParameterComment(@"right", @"The right operand of the addition.", null, false)], null));
271-
_cache.Add(new MemberKey(typeof(global::ExampleClass), MemberType.Method, "AddAsync", typeof(global::System.Threading.Tasks.Task<global::System.Int32>), [typeof(global::System.Int32), typeof(global::System.Int32)]), new XmlComment(@"This method is an example of a method that
274+
_cache.Add(new MemberKey(typeof(global::ExampleClass), MemberType.Method, "AddAsync", typeof(global::System.Threading.Tasks.Task<>), [typeof(global::System.Int32), typeof(global::System.Int32)]), new XmlComment(@"This method is an example of a method that
272275
returns an awaitable item.", null, null, null, null, false, null, null, null));
273276
_cache.Add(new MemberKey(typeof(global::ExampleClass), MemberType.Method, "DoNothingAsync", typeof(global::System.Threading.Tasks.Task), []), new XmlComment(@"This method is an example of a method that
274277
returns a Task which should map to a void return type.", null, null, null, null, false, null, null, null));

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// </auto-generated>
99
//------------------------------------------------------------------------------
1010
#nullable enable
11+
// Suppress warnings about obsolete types and members
12+
// in generated code
13+
#pragma warning disable CS0612, CS0618
1114

1215
namespace System.Runtime.CompilerServices
1316
{

0 commit comments

Comments
 (0)