Skip to content

Commit 0b520c3

Browse files
authored
Populate XML doc comments into OpenAPI document (#60326)
1 parent a85b55b commit 0b520c3

File tree

56 files changed

+7022
-215
lines changed

Some content is hidden

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

56 files changed

+7022
-215
lines changed

AspNetCore.sln

Lines changed: 67 additions & 111 deletions
Large diffs are not rendered by default.

eng/Dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ and are generated based on the last package release.
222222
<LatestPackageReference Include="StackExchange.Redis" />
223223
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
224224
<LatestPackageReference Include="System.Reactive.Linq" />
225+
<LatestPackageReference Include="Verify.SourceGenerators" />
225226
<LatestPackageReference Include="Verify.Xunit" />
226227
<LatestPackageReference Include="xunit.abstractions" />
227228
<LatestPackageReference Include="xunit.analyzers" />

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@
325325
<StackExchangeRedisVersion>2.7.27</StackExchangeRedisVersion>
326326
<SystemReactiveLinqVersion>5.0.0</SystemReactiveLinqVersion>
327327
<SwashbuckleAspNetCoreVersion>6.6.2</SwashbuckleAspNetCoreVersion>
328+
<VerifySourceGeneratorsVersion>2.2.0</VerifySourceGeneratorsVersion>
328329
<VerifyXunitVersion>19.14.0</VerifyXunitVersion>
329330
<XunitAbstractionsVersion>2.0.3</XunitAbstractionsVersion>
330331
<XunitAnalyzersVersion>1.15.0</XunitAnalyzersVersion>

src/OpenApi/OpenApi.slnf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
1111
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
1212
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
13+
"src\\OpenApi\\gen\\Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj",
1314
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj",
1415
"src\\OpenApi\\sample\\Sample.csproj",
1516
"src\\OpenApi\\src\\Microsoft.AspNetCore.OpenApi.csproj",
1617
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
18+
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests.csproj",
1719
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Tests\\Microsoft.AspNetCore.OpenApi.Tests.csproj",
1820
"src\\OpenApi\\sample\\Sample.csproj",
1921
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8" standalone="no"?>
2+
<Project>
3+
<PropertyGroup>
4+
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
5+
</PropertyGroup>
6+
<Target Name="GenerateAdditionalXmlFilesForOpenApi"
7+
AfterTargets="ResolveReferences">
8+
<ItemGroup>
9+
<AdditionalFiles
10+
Include="@(ReferencePath->'%(RootDir)%(Directory)%(Filename).xml')"
11+
Condition="'%(ReferencePath.Extension)' == '.dll' AND
12+
Exists('%(ReferencePath.RootDir)%(ReferencePath.Directory)%(ReferencePath.Filename).xml') AND
13+
('%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference')"
14+
KeepMetadata="Identity" />
15+
</ItemGroup>
16+
</Target>
17+
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis.CSharp;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;
8+
9+
/// <summary>
10+
/// Represents an invocation of the AddOpenApi method.
11+
/// </summary>
12+
/// <param name="Variant">The variant of the AddOpenApi method.</param>
13+
/// <param name="InvocationExpression">The invocation expression.</param>
14+
/// <param name="Location">The location of the invocation that can be intercepted.</param>
15+
internal sealed record AddOpenApiInvocation(
16+
AddOpenApiOverloadVariant Variant,
17+
InvocationExpressionSyntax InvocationExpression,
18+
InterceptableLocation? Location);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
6+
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;
7+
8+
internal class AddOpenApiInvocationComparer : IEqualityComparer<AddOpenApiInvocation>
9+
{
10+
public static AddOpenApiInvocationComparer Instance { get; } = new();
11+
public bool Equals(AddOpenApiInvocation? x, AddOpenApiInvocation? y)
12+
{
13+
if (ReferenceEquals(x, y))
14+
{
15+
return true;
16+
}
17+
if (x is null || y is null)
18+
{
19+
return false;
20+
}
21+
22+
return x.Variant.Equals(y.Variant);
23+
}
24+
25+
public int GetHashCode(AddOpenApiInvocation obj) =>
26+
HashCode.Combine(obj.Variant);
27+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;
5+
6+
internal enum AddOpenApiOverloadVariant
7+
{
8+
AddOpenApi,
9+
AddOpenApiDocumentName,
10+
AddOpenApiDocumentNameConfigureOptions,
11+
AddOpenApiConfigureOptions,
12+
Unknown
13+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Threading;
7+
using Microsoft.CodeAnalysis;
8+
9+
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;
10+
11+
/// <summary>
12+
/// Visits the assembly symbols to collect public types, properties, and methods that might
13+
/// contain XML documentation comments.
14+
/// </summary>
15+
internal sealed class AssemblyTypeSymbolsVisitor(IAssemblySymbol assemblySymbol, CancellationToken cancellation) : SymbolVisitor
16+
{
17+
private readonly CancellationToken _cancellationToken = cancellation;
18+
private readonly HashSet<INamedTypeSymbol> _exportedTypes = new(SymbolEqualityComparer.Default);
19+
private readonly HashSet<IPropertySymbol> _exportedProperties = new(SymbolEqualityComparer.Default);
20+
private readonly HashSet<IMethodSymbol> _exportedMethods = new(SymbolEqualityComparer.Default);
21+
22+
public ImmutableArray<INamedTypeSymbol> GetPublicTypes() => [.. _exportedTypes];
23+
public ImmutableArray<IPropertySymbol> GetPublicProperties() => [.. _exportedProperties];
24+
public ImmutableArray<IMethodSymbol> GetPublicMethods() => [.. _exportedMethods];
25+
26+
public void VisitAssembly() => VisitAssembly(assemblySymbol);
27+
28+
public override void VisitAssembly(IAssemblySymbol symbol)
29+
{
30+
_cancellationToken.ThrowIfCancellationRequested();
31+
symbol.GlobalNamespace.Accept(this);
32+
}
33+
34+
public override void VisitNamespace(INamespaceSymbol symbol)
35+
{
36+
foreach (var namespaceOrType in symbol.GetMembers())
37+
{
38+
_cancellationToken.ThrowIfCancellationRequested();
39+
namespaceOrType.Accept(this);
40+
}
41+
}
42+
43+
public override void VisitNamedType(INamedTypeSymbol type)
44+
{
45+
_cancellationToken.ThrowIfCancellationRequested();
46+
47+
if (!IsDeclaredInAssembly(type) || !_exportedTypes.Add(type))
48+
{
49+
return;
50+
}
51+
52+
var nestedTypes = type.GetTypeMembers();
53+
54+
foreach (var nestedType in nestedTypes)
55+
{
56+
_cancellationToken.ThrowIfCancellationRequested();
57+
nestedType.Accept(this);
58+
}
59+
60+
var properties = type.GetMembers().OfType<IPropertySymbol>();
61+
foreach (var property in properties)
62+
{
63+
_cancellationToken.ThrowIfCancellationRequested();
64+
if (IsDeclaredInAssembly(property) && _exportedProperties.Add(property))
65+
{
66+
property.Type.Accept(this);
67+
}
68+
}
69+
var methods = type.GetMembers().OfType<IMethodSymbol>();
70+
foreach (var method in methods)
71+
{
72+
_cancellationToken.ThrowIfCancellationRequested();
73+
if (IsDeclaredInAssembly(method) && _exportedMethods.Add(method))
74+
{
75+
method.Accept(this);
76+
}
77+
}
78+
}
79+
80+
private bool IsDeclaredInAssembly(ISymbol symbol) =>
81+
SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assemblySymbol);
82+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Xml;
7+
8+
internal static class DocumentationCommentXmlNames
9+
{
10+
public const string CElementName = "c";
11+
public const string CodeElementName = "code";
12+
public const string CompletionListElementName = "completionlist";
13+
public const string DescriptionElementName = "description";
14+
public const string ExampleElementName = "example";
15+
public const string ExceptionElementName = "exception";
16+
public const string IncludeElementName = "include";
17+
public const string InheritdocElementName = "inheritdoc";
18+
public const string ItemElementName = "item";
19+
public const string ListElementName = "list";
20+
public const string ListHeaderElementName = "listheader";
21+
public const string ParaElementName = "para";
22+
public const string ParameterElementName = "param";
23+
public const string ParameterReferenceElementName = "paramref";
24+
public const string PermissionElementName = "permission";
25+
public const string PlaceholderElementName = "placeholder";
26+
public const string PreliminaryElementName = "preliminary";
27+
public const string RemarksElementName = "remarks";
28+
public const string ReturnsElementName = "returns";
29+
public const string SeeElementName = "see";
30+
public const string SeeAlsoElementName = "seealso";
31+
public const string SummaryElementName = "summary";
32+
public const string TermElementName = "term";
33+
public const string ThreadSafetyElementName = "threadsafety";
34+
public const string TypeParameterElementName = "typeparam";
35+
public const string TypeParameterReferenceElementName = "typeparamref";
36+
public const string ValueElementName = "value";
37+
public const string CrefAttributeName = "cref";
38+
public const string HrefAttributeName = "href";
39+
public const string FileAttributeName = "file";
40+
public const string InstanceAttributeName = "instance";
41+
public const string LangwordAttributeName = "langword";
42+
public const string NameAttributeName = "name";
43+
public const string PathAttributeName = "path";
44+
public const string StaticAttributeName = "static";
45+
public const string TypeAttributeName = "type";
46+
47+
public static bool ElementEquals(string name1, string name2, bool fromVb = false)
48+
{
49+
return string.Equals(name1, name2, fromVb ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
50+
}
51+
52+
public static bool AttributeEquals(string name1, string name2)
53+
{
54+
return string.Equals(name1, name2, StringComparison.Ordinal);
55+
}
56+
57+
public static new bool Equals(object left, object right)
58+
{
59+
return object.Equals(left, right);
60+
}
61+
}

0 commit comments

Comments
 (0)