Skip to content

Commit

Permalink
Added delegate to resolve a cref in TripleSlashCommentModel.
Browse files Browse the repository at this point in the history
- Introduced ResolveCRef delegate in ITripleSlashCommentParserContext.
- It is called when a cref attribute from an XML comment is to be
  resolved.
- The delegate returns the name of a reference to the appropriate target.
- Necessary for F# support, since the F# compiler does not resolve
  the cref attribute to comment ids in triple slash comments.
- Introduced call to ResolveCrefLink for exceptions, since they need
  to be resolved as well.
  • Loading branch information
surban committed Mar 1, 2018
1 parent 65d1e0e commit 832437f
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DocAsCode.Metadata.ManagedReference
{
public class CRefTarget
{
public string Id { get; set; }
public string CommentId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public interface ITripleSlashCommentParserContext
{
bool PreserveRawInlineComments { get; set; }
Action<string, string> AddReferenceDelegate { get; set; }
Func<string, CRefTarget> ResolveCRef { get; }
SourceDetail Source { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ private TripleSlashCommentModel(string xml, SyntaxLanguage language, ITripleSlas
_context = context;
if (!context.PreserveRawInlineComments)
{
ResolveSeeCref(doc, context.AddReferenceDelegate);
ResolveSeeAlsoCref(doc, context.AddReferenceDelegate);
ResolveSeeCref(doc, context.AddReferenceDelegate, context.ResolveCRef);
ResolveSeeAlsoCref(doc, context.AddReferenceDelegate, context.ResolveCRef);
ResolveExceptionCref(doc, context.AddReferenceDelegate, context.ResolveCRef);
}
ResolveCodeSource(doc, context);
var nav = doc.CreateNavigator();
Expand Down Expand Up @@ -418,19 +419,24 @@ private Dictionary<string, string> GetTypeParameters(XPathNavigator navigator, I
return GetListContent(navigator, "/member/typeparam", "type parameter", context);
}

private void ResolveSeeAlsoCref(XNode node, Action<string, string> addReference)
private void ResolveSeeAlsoCref(XNode node, Action<string, string> addReference, Func<string, CRefTarget> resolveCRef)
{
// Resolve <see cref> to <xref>
ResolveCrefLink(node, "//seealso[@cref]", addReference);
ResolveCrefLink(node, "//seealso[@cref]", addReference, resolveCRef);
}

private void ResolveSeeCref(XNode node, Action<string, string> addReference)
private void ResolveSeeCref(XNode node, Action<string, string> addReference, Func<string, CRefTarget> resolveCRef)
{
// Resolve <see cref> to <xref>
ResolveCrefLink(node, "//see[@cref]", addReference);
ResolveCrefLink(node, "//see[@cref]", addReference, resolveCRef);
}

private void ResolveCrefLink(XNode node, string nodeSelector, Action<string, string> addReference)
private void ResolveExceptionCref(XNode node, Action<string, string> addReference, Func<string, CRefTarget> resolveCRef)
{
ResolveCrefLink(node, "//exception[@cref]", addReference, resolveCRef);
}

private void ResolveCrefLink(XNode node, string nodeSelector, Action<string, string> addReference, Func<string, CRefTarget> resolveCRef)
{
if (node == null || string.IsNullOrEmpty(nodeSelector))
{
Expand All @@ -443,29 +449,62 @@ private void ResolveCrefLink(XNode node, string nodeSelector, Action<string, str
foreach (var item in nodes)
{
var cref = item.Attribute("cref").Value;
// Strict check is needed as value could be an invalid href,
// e.g. !:Dictionary&lt;TKey, string&gt; when user manually changed the intellisensed generic type
var match = CommentIdRegex.Match(cref);
if (match.Success)
{
var id = match.Groups["id"].Value;
var type = match.Groups["type"].Value;
var success = false;

if (type == "Overload")
if (resolveCRef != null)
{
// The resolveCRef delegate resolves the cref and returns the name of a reference if successful.
var cRefTarget = resolveCRef.Invoke(cref);
if (cRefTarget != null)
{
id += '*';
if (item.Parent?.Parent == null)
{
// <see> or <seealso> is top-level tag. Keep it, but set resolved references.
item.SetAttributeValue("refId", cRefTarget.Id);
item.SetAttributeValue("cref", cRefTarget.CommentId);
}
else
{
// <see> occurs in text. Replace it with an <xref> node using the resolved reference.
var replacement = XElement.Parse($"<xref href=\"{HttpUtility.UrlEncode(cRefTarget.Id)}\" data-throw-if-not-resolved=\"false\"></xref>");
item.ReplaceWith(replacement);
}
success = true;
}

// When see and seealso are top level nodes in triple slash comments, do not convert it into xref node
if (item.Parent?.Parent != null)
else
{
var replacement = XElement.Parse($"<xref href=\"{HttpUtility.UrlEncode(id)}\" data-throw-if-not-resolved=\"false\"></xref>");
item.ReplaceWith(replacement);
item.Remove();
success = false;
}

addReference?.Invoke(id, cref);
}
else
{
// Strict check is needed as value could be an invalid href,
// e.g. !:Dictionary&lt;TKey, string&gt; when user manually changed the intellisensed generic type
var match = CommentIdRegex.Match(cref);
if (match.Success)
{
var id = match.Groups["id"].Value;
var type = match.Groups["type"].Value;

if (type == "Overload")
{
id += '*';
}

// When see and seealso are top level nodes in triple slash comments, do not convert it into xref node
if (item.Parent?.Parent != null)
{
var replacement = XElement.Parse($"<xref href=\"{HttpUtility.UrlEncode(id)}\" data-throw-if-not-resolved=\"false\"></xref>");
item.ReplaceWith(replacement);
}

addReference?.Invoke(id, cref);
success = true;
}
}

if (!success)
{
var detailedInfo = new StringBuilder();
if (_context != null && _context.Source != null)
Expand Down Expand Up @@ -562,7 +601,18 @@ private IEnumerable<LinkInfo> GetMultipleLinkInfo(XPathNavigator navigator, stri

string commentId = nav.GetAttribute("cref", string.Empty);
string url = nav.GetAttribute("href", string.Empty);
if (!string.IsNullOrEmpty(commentId))
string refId = nav.GetAttribute("refId", string.Empty);
if (!string.IsNullOrEmpty(refId))
{
yield return new LinkInfo
{
AltText = altText,
LinkId = refId,
CommentId = commentId,
LinkType = LinkType.CRef
};
}
else if (!string.IsNullOrEmpty(commentId))
{
// Check if cref type is valid and trim prefix
var match = CommentIdRegex.Match(commentId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class TripleSlashCommentParserContext : ITripleSlashCommentParserContext

public Action<string, string> AddReferenceDelegate { get; set; }

public Func<string, CRefTarget> ResolveCRef { get; set; }

public SourceDetail Source { get; set; }
}
}
4 changes: 3 additions & 1 deletion tools/MergeDeveloperComments/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,10 +480,12 @@ internal sealed class TripleSlashCommentParserContext : ITripleSlashCommentParse
{
public static readonly TripleSlashCommentParserContext Instance = new TripleSlashCommentParserContext
{
AddReferenceDelegate = (s, e) => { }
AddReferenceDelegate = (s, e) => { },
ResolveCRef = null
};

public Action<string, string> AddReferenceDelegate { get; set; }
public Func<string, CRefTarget> ResolveCRef { get; set; }
public bool PreserveRawInlineComments { get; set; }
public SourceDetail Source { get; set; }
}
Expand Down

0 comments on commit 832437f

Please sign in to comment.