-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
Copy pathXmlCommentGenerator.Parser.cs
159 lines (150 loc) · 6.95 KB
/
XmlCommentGenerator.Parser.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Xml.Linq;
using Microsoft.AspNetCore.OpenApi.SourceGenerators.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;
public sealed partial class XmlCommentGenerator
{
internal static List<(string, string)> ParseXmlFile(AdditionalText additionalText, CancellationToken cancellationToken)
{
var text = additionalText.GetText(cancellationToken);
if (text is null)
{
return [];
}
XElement xml;
try
{
xml = XElement.Parse(text.ToString());
}
catch
{
return [];
}
var members = xml.Descendants("member");
var comments = new List<(string, string)>();
foreach (var member in members)
{
var name = member.Attribute(DocumentationCommentXmlNames.NameAttributeName)?.Value;
if (name is not null)
{
comments.Add((name, member.ToString()));
}
}
return comments;
}
internal static List<(string, string)> ParseCompilation(Compilation compilation, CancellationToken cancellationToken)
{
var visitor = new AssemblyTypeSymbolsVisitor(compilation.Assembly, cancellationToken);
visitor.VisitAssembly();
var types = visitor.GetPublicTypes();
var comments = new List<(string, string)>();
foreach (var type in types)
{
if (DocumentationCommentId.CreateDeclarationId(type) is string name &&
type.GetDocumentationCommentXml(CultureInfo.InvariantCulture, expandIncludes: true, cancellationToken: cancellationToken) is string xml)
{
comments.Add((name, xml));
}
}
var properties = visitor.GetPublicProperties();
foreach (var property in properties)
{
if (DocumentationCommentId.CreateDeclarationId(property) is string name &&
property.GetDocumentationCommentXml(CultureInfo.InvariantCulture, expandIncludes: true, cancellationToken: cancellationToken) is string xml)
{
comments.Add((name, xml));
}
}
var methods = visitor.GetPublicMethods();
foreach (var method in methods)
{
// If the method is a constructor for a record, skip it because we will have already processed the type.
if (method.MethodKind == MethodKind.Constructor)
{
continue;
}
if (DocumentationCommentId.CreateDeclarationId(method) is string name &&
method.GetDocumentationCommentXml(CultureInfo.InvariantCulture, expandIncludes: true, cancellationToken: cancellationToken) is string xml)
{
comments.Add((name, xml));
}
}
return comments;
}
internal static IEnumerable<(string, XmlComment?)> ParseComments(
(List<(string, string)> RawComments, Compilation Compilation) input,
CancellationToken cancellationToken)
{
var compilation = input.Compilation;
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, 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)
{
comments.Add((name, parsedComment));
}
}
}
return comments;
}
internal static bool FilterInvocations(SyntaxNode node, CancellationToken _)
=> node is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: "AddOpenApi" } };
internal static AddOpenApiInvocation GetAddOpenApiOverloadVariant(GeneratorSyntaxContext context, CancellationToken cancellationToken)
{
var invocationExpression = (InvocationExpressionSyntax)context.Node;
// Soft check to validate that the method is from the OpenApiServiceCollectionExtensions class
// in the Microsoft.AspNetCore.OpenApi assembly.
var symbol = context.SemanticModel.GetSymbolInfo(invocationExpression, cancellationToken).Symbol;
if (symbol is not IMethodSymbol methodSymbol
|| methodSymbol.ContainingType.Name != "OpenApiServiceCollectionExtensions"
|| methodSymbol.ContainingAssembly.Name != "Microsoft.AspNetCore.OpenApi")
{
return new(AddOpenApiOverloadVariant.Unknown, invocationExpression, null);
}
var interceptableLocation = context.SemanticModel.GetInterceptableLocation(invocationExpression, cancellationToken);
var argumentsCount = invocationExpression.ArgumentList.Arguments.Count;
if (argumentsCount == 0)
{
return new(AddOpenApiOverloadVariant.AddOpenApi, invocationExpression, interceptableLocation);
}
else if (argumentsCount == 2)
{
return new(AddOpenApiOverloadVariant.AddOpenApiDocumentNameConfigureOptions, invocationExpression, interceptableLocation);
}
else
{
// We need to disambiguate between the two overloads that take a string and a delegate
// AddOpenApi("v1") vs. AddOpenApi(options => { }). The implementation here is pretty naive and
// won't handle cases where the document name is provided by a variable or a method call.
var argument = invocationExpression.ArgumentList.Arguments[0];
if (argument.Expression is LiteralExpressionSyntax)
{
return new(AddOpenApiOverloadVariant.AddOpenApiDocumentName, invocationExpression, interceptableLocation);
}
else if (argument.Expression is LambdaExpressionSyntax)
{
return new(AddOpenApiOverloadVariant.AddOpenApiConfigureOptions, invocationExpression, interceptableLocation);
}
else
{
return new(AddOpenApiOverloadVariant.Unknown, invocationExpression, null);
}
}
}
}