Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Merge carlossanlop/main with jeffhandley/retain-directives by carlossanlop · Pull Request #99 · dotnet/api-docs-sync · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ bin
obj
.ionide/
artifacts/
*/*.user
*/*.user
*.received.*
3 changes: 3 additions & 0 deletions Directory.Build.targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
<Project>
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
6 changes: 4 additions & 2 deletions DocsPortingTool.sln
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295
# Visual Studio Version 17
VisualStudioVersion = 17.1.31926.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libraries", "Libraries\Libraries.csproj", "{87BBF4FD-260C-4AC4-802B-7D2B29629C07}"
EndProject
Expand All@@ -11,9 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
BackportInstructions.md = BackportInstructions.md
Directory.Build.targets = Directory.Build.targets
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
install-as-tool.ps1 = install-as-tool.ps1
Packages.props = Packages.props
README.md = README.md
EndProjectSection
EndProject
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Docs/APIKind.cs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
namespace Libraries.Docs
{
internal enum APIKind
public enum APIKind
{
Type,
Member
Expand Down
76 changes: 71 additions & 5 deletions Libraries/Docs/DocsAPI.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,11 @@

namespace Libraries.Docs
{
internal abstract class DocsAPI : IDocsAPI
public abstract class DocsAPI : IDocsAPI
{
private DocsSummary? _summary;
private DocsRemarks? _remarks;
private List<DocsExample>? _examples;
private List<DocsParam>? _params;
private List<DocsParameter>? _parameters;
private List<DocsTypeParameter>? _typeParameters;
Expand DownExpand Up@@ -156,7 +159,7 @@ public List<string> AltMembers
{
if (Docs != null)
{
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref").DocIdEscaped()).ToList();
_altMemberCrefs = Docs.Elements("altmember").Select(x => XmlHelper.GetAttributeValue(x, "cref")).ToList();
}
else
{
Expand DownExpand Up@@ -190,8 +193,71 @@ public List<DocsRelated> Relateds
public abstract string ReturnType { get; }
public abstract string Returns { get; set; }

public DocsSummary SummaryElement
{
get
{
if (_summary == null)
{
XElement? xe = Docs?.Element("summary");

if (xe != null)
{
_summary = new(xe);
}
else
{
throw new InvalidOperationException($"There was no <summary> element. Doc ID: {DocId}");
}
}

return _summary;
}
}

public abstract string Remarks { get; set; }

public DocsRemarks RemarksElement
{
get
{
if (_remarks == null)
{
XElement? xe = Docs?.Element("remarks");

if (xe != null)
{
_remarks = new(xe);

if (!_remarks.ExampleContent?.ParsedText?.IsDocsEmpty() ?? false)
{
ExampleElements.Add(_remarks.ExampleContent!);
}
}
else
{
_remarks = new(new XElement("remarks"));
}
}

return _remarks;
}
}

public List<DocsExample> ExampleElements
{
get
{
if (_examples == null)
{
IEnumerable<XElement> elems = Docs.Elements("example");
_examples = elems.Select(e => new DocsExample(e)).ToList();
}

return _examples;
}
}

public List<DocsAssemblyInfo> AssemblyInfos
{
get
Expand All@@ -206,10 +272,10 @@ public List<DocsAssemblyInfo> AssemblyInfos

public DocsParam SaveParam(XElement xeIntelliSenseXmlParam)
{
XElement xeDocsParam = new XElement(xeIntelliSenseXmlParam.Name);
XElement xeDocsParam = new(xeIntelliSenseXmlParam.Name);
xeDocsParam.ReplaceAttributes(xeIntelliSenseXmlParam.Attributes());
XmlHelper.SaveFormattedAsXml(xeDocsParam, xeIntelliSenseXmlParam.Value);
DocsParam docsParam = new DocsParam(this, xeDocsParam);
DocsParam docsParam = new(this, xeDocsParam);
Changed = true;
return docsParam;
}
Expand All@@ -229,7 +295,7 @@ public APIKind Kind

public DocsTypeParam AddTypeParam(string name, string value)
{
XElement typeParam = new XElement("typeparam");
XElement typeParam = new("typeparam");
typeParam.SetAttributeValue("name", name);
XmlHelper.AddChildFormattedAsXml(Docs, typeParam, value);
Changed = true;
Expand Down
129 changes: 129 additions & 0 deletions Libraries/Docs/DocsApiReference.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using static System.Net.WebUtility;

namespace Libraries.Docs
{
public class DocsApiReference
{
public bool IsOverload { get; private init; }

public char? Prefix { get; private init; }

public string Api { get; private init; }

// Generic parameters need to support both single and double backtick conventions
private const string GenericParameterPattern = @"`{1,2}(?<arity>\d+)";
private const string ApiChars = @"[A-Za-z0-9\-\._~:\/#\[\]\{\}@!\$&'\(\)\*\+,;`%]";
private const string ApiReferencePattern = @"((?<prefix>[A-Za-z]):)?(?<api>(" + ApiChars + @")+)?(?<extraVars>\?(" + ApiChars + @")+=(" + ApiChars + @")+)?";
private static readonly Regex XrefPattern = new("<xref:(?<api>" + ApiReferencePattern + ")\\s*>", RegexOptions.Compiled);

public DocsApiReference(string apiReference)
{
Api = UrlDecode(apiReference);
var match = Regex.Match(Api, ApiReferencePattern);

if (match.Success)
{
Api = match.Groups["api"].Value;

if (match.Groups["prefix"].Success)
{
Prefix = match.Groups["prefix"].Value[0];
IsOverload = Prefix == 'O';
}
}

if (Api.EndsWith('*'))
{
IsOverload = true;
Api = Api[..^1];
}

Api = ReplacePrimitivesWithShorthands(Api);
Api = ParseGenericTypes(Api);
}

public override string ToString()
{
if (Prefix is not null)
{
return $"{Prefix}:{Api}";
}

return Api;
}

private static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "System.Boolean", "bool" },
{ "System.Byte", "byte" },
{ "System.Char", "char" },
{ "System.Decimal", "decimal" },
{ "System.Double", "double" },
{ "System.Int16", "short" },
{ "System.Int32", "int" },
{ "System.Int64", "long" },
{ "System.Object", "object" }, // Ambiguous: could be 'object' or 'dynamic' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
{ "System.SByte", "sbyte" },
{ "System.Single", "float" },
{ "System.String", "string" },
{ "System.UInt16", "ushort" },
{ "System.UInt32", "uint" },
{ "System.UInt64", "ulong" },
{ "System.Void", "void" }
};

public static string ReplacePrimitivesWithShorthands(string apiReference)
{
foreach ((string key, string value) in PrimitiveTypes)
{
apiReference = Regex.Replace(apiReference, key, value);
}

return apiReference;
}

public static string ParseGenericTypes(string apiReference)
{
int genericParameterArity = 0;
return Regex.Replace(apiReference, GenericParameterPattern, MapGenericParameter);

string MapGenericParameter(Match match)
{
int arity = int.Parse(match.Groups["arity"].Value);

if (genericParameterArity == 0)
{
// This is the first match that declares the generic parameter arity of the method
// e.g. GenericMethod``3 ---> GenericMethod{T1,T2,T3}(...);
Debug.Assert(arity > 0);
genericParameterArity = arity;
return WrapInCurlyBrackets(string.Join(",", Enumerable.Range(0, arity).Select(CreateGenericParameterName)));
}

// Subsequent matches are references to generic parameters in the method signature,
// e.g. GenericMethod{T1,T2,T3}(..., List{``1} parameter, ...); ---> List{T2} parameter
return CreateGenericParameterName(arity);

// This naming scheme does not map to the exact generic parameter names;
// however this is still accepted by intellisense and backporters can rename
// manually with the help of tooling.
string CreateGenericParameterName(int index) => genericParameterArity == 1 ? "T" : $"T{index + 1}";

static string WrapInCurlyBrackets(string input) => $"{{{input}}}";
}
}

public static string ReplaceMarkdownXrefWithSeeCref(string markdown)
{
return XrefPattern.Replace(markdown, match =>
{
var api = new DocsApiReference(match.Groups["api"].Value);
return @$"<see cref=""{api}"" />";
});
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsAssemblyInfo.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@

namespace Libraries.Docs
{
internal class DocsAssemblyInfo
public class DocsAssemblyInfo
{
private readonly XElement XEAssemblyInfo;
public string AssemblyName
Expand Down
18 changes: 11 additions & 7 deletions Libraries/Docs/DocsAttribute.cs
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;

namespace Libraries.Docs
{
internal class DocsAttribute
public class DocsAttribute
{
private readonly XElement XEAttribute;

Expand All@@ -13,12 +14,15 @@ public string FrameworkAlternate
return XmlHelper.GetAttributeValue(XEAttribute, "FrameworkAlternate");
}
}
public string AttributeName

public string? AttributeName
{
get
{
return XmlHelper.GetChildElementValue(XEAttribute, "AttributeName");
}
get => GetAttributeName("C#");
}

public string? GetAttributeName(string language)
{
return XEAttribute.Elements("AttributeName").Where(x => XmlHelper.GetAttributeValue(x, "Language") == language).SingleOrDefault()?.Value;
}

public DocsAttribute(XElement xeAttribute)
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Docs/DocsCommentsContainer.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,14 +8,14 @@

namespace Libraries.Docs
{
internal class DocsCommentsContainer
public class DocsCommentsContainer
{
private Configuration Config { get; set; }

private XDocument? xDoc = null;

public readonly Dictionary<string, DocsType> Types = new();
public readonly Dictionary<string, DocsMember> Members = new();
internal readonly Dictionary<string, DocsType> Types = new();
internal readonly Dictionary<string, DocsMember> Members = new();

public DocsCommentsContainer(Configuration config)
{
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Docs/DocsExample.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Libraries.Docs
{
public class DocsExample : DocsMarkdownElement
{
public DocsExample(XElement xeExample) : base(xeExample)
{
}

protected override string ExtractElements(string markdown)
{
markdown = base.ExtractElements(markdown);
markdown = RemoveMarkdownHeading(markdown, "Examples?");

return markdown;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Docs/DocsException.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@

namespace Libraries.Docs
{
internal class DocsException
public class DocsException
{
private readonly XElement XEException;

Expand Down
Loading