Skip to content

Commit 6c667cb

Browse files
committed
Simplify cref resolution handling
1 parent cb35db3 commit 6c667cb

File tree

4 files changed

+25
-36
lines changed

4 files changed

+25
-36
lines changed

src/OpenApi/gen/XmlComments/XmlComment.cs

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Collections.Immutable;
88
using Microsoft.AspNetCore.Analyzers.Infrastructure;
99
using System.Linq;
10-
using System.Text.RegularExpressions;
1110
using System.Threading;
1211
using System.Xml;
1312
using System.Xml.Linq;
@@ -18,8 +17,6 @@ namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Xml;
1817

1918
internal sealed class XmlComment
2019
{
21-
private const string IdSelector = @"((?![0-9])[\w_])+[\w\(\)\.\{\}\[\]\|\*\^~#@!`,_<>:]*";
22-
private static readonly Regex CommentIdRegex = new(@"^(?<type>N|T|M|P|F|E|Overload):(?<id>" + IdSelector + ")$", RegexOptions.Compiled);
2320
public string? Summary { get; internal set; }
2421
public string? Description { get; internal set; }
2522
public string? Value { get; internal set; }
@@ -30,7 +27,7 @@ internal sealed class XmlComment
3027
public List<XmlParameterComment> Parameters { get; internal set; } = [];
3128
public List<XmlResponseComment> Responses { get; internal set; } = [];
3229

33-
private XmlComment(string xml)
30+
private XmlComment(Compilation compilation, string xml)
3431
{
3532
// Treat <doc> as <member>
3633
if (xml.StartsWith("<doc>", StringComparison.InvariantCulture) && xml.EndsWith("</doc>", StringComparison.InvariantCulture))
@@ -42,8 +39,8 @@ private XmlComment(string xml)
4239
// Transform triple slash comment
4340
var doc = XDocument.Parse(xml, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
4441

45-
ResolveCrefLink(doc, "//seealso[@cref]");
46-
ResolveCrefLink(doc, "//see[@cref]");
42+
ResolveCrefLink(compilation, doc, $"//{DocumentationCommentXmlNames.SeeAlsoElementName}[@cref]");
43+
ResolveCrefLink(compilation, doc, $"//{DocumentationCommentXmlNames.SeeElementName}[@cref]");
4744

4845
var nav = doc.CreateNavigator();
4946
Summary = GetSingleNodeValue(nav, "/member/summary");
@@ -68,7 +65,7 @@ private XmlComment(string xml)
6865
}
6966

7067
var resolvedComment = GetDocumentationComment(symbol, xmlText, [], compilation, cancellationToken);
71-
return !string.IsNullOrEmpty(resolvedComment) ? new XmlComment(resolvedComment!) : null;
68+
return !string.IsNullOrEmpty(resolvedComment) ? new XmlComment(compilation, resolvedComment!) : null;
7269
}
7370

7471
private static string? GetDocumentationComment(ISymbol symbol, string xmlText, HashSet<ISymbol>? visitedSymbols, Compilation compilation, CancellationToken cancellationToken)
@@ -419,7 +416,13 @@ static string BuildXPathForElement(XElement element)
419416
private static bool ElementNameIs(XElement element, string name)
420417
=> string.IsNullOrEmpty(element.Name.NamespaceName) && DocumentationCommentXmlNames.ElementEquals(element.Name.LocalName, name);
421418

422-
private static void ResolveCrefLink(XNode node, string nodeSelector)
419+
/// <summary>
420+
/// Resolves the cref links in the XML documentation into type names.
421+
/// </summary>
422+
/// <param name="compilation">The compilation to resolve type symbol declarations from.</param>
423+
/// <param name="node">The target node to process crefs in.</param>
424+
/// <param name="nodeSelector">The node type to process crefs for, can be `see` or `seealso`.</param>
425+
private static void ResolveCrefLink(Compilation compilation, XNode node, string nodeSelector)
423426
{
424427
if (node == null || string.IsNullOrEmpty(nodeSelector))
425428
{
@@ -429,31 +432,17 @@ private static void ResolveCrefLink(XNode node, string nodeSelector)
429432
var nodes = node.XPathSelectElements(nodeSelector + "[@cref]").ToList();
430433
foreach (var item in nodes)
431434
{
432-
var cref = item.Attribute("cref").Value;
433-
434-
// Strict check is needed as value could be an invalid href,
435-
// e.g. !:Dictionary&lt;TKey, string&gt; when user manually changed the intellisensed generic type
436-
var match = CommentIdRegex.Match(cref);
437-
if (match.Success)
435+
var cref = item.Attribute(DocumentationCommentXmlNames.CrefAttributeName).Value;
436+
if (string.IsNullOrEmpty(cref))
438437
{
439-
var id = match.Groups["id"].Value;
440-
var type = match.Groups["type"].Value;
441-
442-
if (type == "Overload")
443-
{
444-
id += '*';
445-
}
438+
continue;
439+
}
446440

447-
// When see and seealso are top level nodes in triple slash comments, do not convert it into xref node
448-
if (item.Parent?.Parent != null)
449-
{
450-
XElement replacement;
451-
if (type == "T")
452-
{
453-
replacement = XElement.Parse($"""<a href="#/components/schemas/{id}">{id}</a>""");
454-
item.ReplaceWith(replacement);
455-
}
456-
}
441+
var symbol = DocumentationCommentId.GetFirstSymbolForDeclarationId(cref, compilation);
442+
if (symbol is not null)
443+
{
444+
var type = symbol.ToDisplayString();
445+
item.ReplaceWith(new XText(type));
457446
}
458447
}
459448
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
149149

150150
path = document.Paths["/project"].Operations[OperationType.Post];
151151
var project = path.RequestBody.Content["application/json"].Schema;
152-
Assert.Equal("The project that contains <a href=\\\"#/components/schemas/Todo\\\">Todo</a> items.", project.Description);
152+
Assert.Equal("The project that contains Todo items.", project.Description);
153153

154154
path = document.Paths["/board"].Operations[OperationType.Post];
155155
var board = path.RequestBody.Content["application/json"].Schema;

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ file static class XmlCommentCache
7676
var _cache = new Dictionary<(Type?, string?), XmlComment>();
7777

7878
_cache.Add((typeof(global::Todo), null), new XmlComment("""This is a todo item.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
79-
_cache.Add((typeof(global::Project), null), new XmlComment("""The project that contains <a href=\"#/components/schemas/Todo\">Todo</a> items.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
79+
_cache.Add((typeof(global::Project), null), new XmlComment("""The project that contains Todo items.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
8080
_cache.Add((typeof(global::ProjectBoard.BoardItem), null), new XmlComment("""An item on the board.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
81-
_cache.Add((typeof(global::ProjectRecord), null), new XmlComment("""The project that contains <a href=\"#/components/schemas/Todo\">Todo</a> items.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false), } ,new List<XmlResponseComment>{}));
81+
_cache.Add((typeof(global::ProjectRecord), null), new XmlComment("""The project that contains Todo items.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false), } ,new List<XmlResponseComment>{}));
8282
_cache.Add((typeof(global::User), null), new XmlComment(null,null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
8383
_cache.Add((typeof(global::ProjectRecord), "Name"), new XmlComment("""The name of the project.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));
8484
_cache.Add((typeof(global::ProjectRecord), "Description"), new XmlComment("""The description of the project.""", null,null,null,null,false, new List<string>{}, new List<XmlParameterComment>{} ,new List<XmlResponseComment>{}));

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=xml.verified.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@
260260
"type": "string"
261261
}
262262
},
263-
"description": "The project that contains <a href=\\\"#/components/schemas/Todo\\\">Todo</a> items."
263+
"description": "The project that contains Todo items."
264264
},
265265
"ProjectRecord": {
266266
"required": [
@@ -278,7 +278,7 @@
278278
"description": "The description of the project."
279279
}
280280
},
281-
"description": "The project that contains <a href=\\\"#/components/schemas/Todo\\\">Todo</a> items."
281+
"description": "The project that contains Todo items."
282282
},
283283
"Todo": {
284284
"required": [

0 commit comments

Comments
 (0)