Skip to content

Commit

Permalink
feat: support ExperimentalAttribute for API page (#9434)
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeih authored Nov 16, 2023
1 parent a7b919b commit 12a2fd2
Show file tree
Hide file tree
Showing 132 changed files with 1,102 additions and 1,003 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ We welcome code contributions through pull requests, issues tagged as **[`help-w
### Prerequisites

- Install [Visual Studio 2022 (Community or higher)](https://www.visualstudio.com/) and make sure you have the latest updates.
- Install [.NET SDK](https://dotnet.microsoft.com/download/dotnet) 6.x and 7.x.
- Install [.NET SDK](https://dotnet.microsoft.com/download/dotnet) 6.x, 7.x and 8.x.
- Install NodeJS (18.x.x).
- Optional: Install wkhtmltopdf on Windows to test PDF using `choco install wkhtmltopdf`.

Expand Down
13 changes: 13 additions & 0 deletions samples/seed/dotnet/project/Project/Class1.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace BuildFromProject;

[Experimental("DOCFX001", UrlFormat = "https://example.org/{0}")]
public class Class1 : IClass1
{
public class Test<T> { }
Expand Down Expand Up @@ -148,6 +149,7 @@ public void DoNothing<T>() { }
/// <summary>
/// <see cref="IConfiguration"/> related helper and extension routines.
/// </summary>
[Experimental("")]
public void Issue1887() { }

/// <summary>
Expand Down Expand Up @@ -190,3 +192,14 @@ public enum Issue9260
OldAndUnusedValue2,
}
}

class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId)
{
DiagnosticId = diagnosticId;
}

public string DiagnosticId { get; }
public string? UrlFormat { get; set; }
}
6 changes: 6 additions & 0 deletions schemas/v1.0/ApiPage.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Api = (
/** Is this API deprecated, or the deprecation reason in markdown format */
deprecated?: boolean | string;

/** Is this API experimental, or the preview disclaimer text */
preview?: boolean | string;

/** API source URL */
src?: string;

Expand Down Expand Up @@ -86,6 +89,9 @@ type Param = {
/** Is this parameter deprecated, or the deprecation reason */
deprecated?: boolean | string;

/** Is this parameter experimental, or the preview disclaimer text */
preview?: boolean | string;

/** Is this parameter optional? */
optional?: boolean;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Docfx.Build/ApiPage/ApiPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ abstract class ApiBase
{
public string? id { get; init; }
public OneOf<bool, string>? deprecated { get; init; }
public OneOf<bool, string>? preview { get; init; }
public string? src { get; init; }
public Dictionary<string, string>? metadata { get; init; }
}
Expand Down Expand Up @@ -129,6 +130,7 @@ class Parameter
public string? @default { get; init; }
public string? description { get; init; }
public OneOf<bool, string>? deprecated { get; init; }
public OneOf<bool, string>? preview { get; init; }
public bool? optional { get; init; }
}

Expand Down
26 changes: 16 additions & 10 deletions src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,22 @@ HtmlTemplate Api(Api api)
Api4 a4 => (4, a4.api4),
};

var deprecated = DeprecatedBadge(value.deprecated);
var deprecatedReason = DeprecatedReason(value.deprecated);
var deprecated = Badge(value.deprecated, "Deprecated", "text-bg-danger", ".4em");
var preview = Badge(value.preview, "Preview", "text-bg-info", ".4em");
var titleHtml = deprecated is null ? Html($"{title}") : Html($"<span style='text-decoration: line-through'>{title}</span>");

var src = string.IsNullOrEmpty(value.src)
? default
: Html($" <a class='header-action link-secondary' title='View source' href='{value.src}'><i class='bi bi-code-slash'></i></a>");

return Html($"<h{level} class='section api' {attributes} id='{value.id}'>{titleHtml} {deprecated} {src}</h{level}> {deprecatedReason}");
return Html(
$"""
<h{level} class='section api' {attributes} id='{value.id}'>{titleHtml} {deprecated} {preview} {src}</h{level}>
{Alert(value.deprecated, "alert-warning")} {Alert(value.preview, "alert-info")}
""");
}

HtmlTemplate? DeprecatedBadge(OneOf<bool, string>? value, string fontSize = ".5em")
HtmlTemplate? Badge(OneOf<bool, string>? value, string text, string cssClass, string fontSize)
{
var isDeprecated = value?.Value switch
{
Expand All @@ -76,13 +80,13 @@ HtmlTemplate Api(Api api)
_ => false,
};

return isDeprecated ? Html($" <span class='badge rounded-pill text-bg-danger' style='font-size: {fontSize}; vertical-align: middle'>Deprecated</span>") : null;
return isDeprecated ? Html($" <span class='badge rounded-pill {cssClass}' style='font-size: {fontSize}; vertical-align: middle'>{text}</span>") : null;
}

HtmlTemplate DeprecatedReason(OneOf<bool, string>? value)
HtmlTemplate Alert(OneOf<bool, string>? value, string cssClass)
{
return value?.Value is string ds && !string.IsNullOrEmpty(ds)
? Html($"\n<div class='alert alert-warning' role='alert'>{UnsafeHtml(markup(ds))}</div>")
? Html($"\n<div class='alert {cssClass}' role='alert'>{UnsafeHtml(markup(ds))}</div>")
: default;
}

Expand Down Expand Up @@ -119,7 +123,8 @@ HtmlTemplate Code(Code code)

HtmlTemplate Parameter(Parameter parameter)
{
var deprecated = DeprecatedBadge(parameter.deprecated, ".875em");
var deprecated = Badge(parameter.deprecated, "Deprecated", "text-bg-danger", ".875em");
var preview = Badge(parameter.preview, "Preview", "text-bg-info", ".875em");
var lineThrough = deprecated is not null ? UnsafeHtml(" style='text-decoration: line-through'") : default;

var title = string.IsNullOrEmpty(parameter.name) ? default
Expand All @@ -129,9 +134,10 @@ HtmlTemplate Parameter(Parameter parameter)

return Html(
$"""
<dt>{title} {Inline(parameter.type)} {deprecated}</dt>
<dt>{title} {Inline(parameter.type)} {deprecated} {preview}</dt>
<dd>
{DeprecatedReason(parameter.deprecated)}
{Alert(parameter.deprecated, "alert-warning")}
{Alert(parameter.preview, "alert-info")}
{(string.IsNullOrEmpty(parameter.description) ? default : UnsafeHtml(markup(parameter.description)))}
</dd>
""");
Expand Down
2 changes: 1 addition & 1 deletion src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ LinkSpan link when string.IsNullOrEmpty(link.url) => $"{Escape(link.text)}",
};
}

private static string Escape(string text)
internal static string Escape(string text)
{
const string EscapeChars = "\\`*_{}[]()#+-!>~\"'";

Expand Down
28 changes: 24 additions & 4 deletions src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,14 @@ void Api(int level, string title, ISymbol symbol, Compilation compilation)
: new GitSource(source.Remote.Repo, source.Remote.Branch, source.Remote.Path, source.StartLine + 1);
var src = git is null ? null : options.SourceUrl?.Invoke(git) ?? GitUtility.GetSourceUrl(git);
var deprecated = Deprecated(symbol);
var preview = Preview(symbol);

body.Add(level switch
{
1 => (Api)new Api1 { api1 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
2 => (Api)new Api2 { api2 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
3 => (Api)new Api3 { api3 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
4 => (Api)new Api4 { api4 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
1 => (Api)new Api1 { api1 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
2 => (Api)new Api2 { api2 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
3 => (Api)new Api3 { api3 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
4 => (Api)new Api4 { api4 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
});
}

Expand All @@ -143,6 +144,24 @@ void Api(int level, string title, ISymbol symbol, Compilation compilation)
return null;
}

OneOf<bool, string>? Preview(ISymbol symbol, ISymbol? originalSymbol = null)
{
if (symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "ExperimentalAttribute") is { } experimentalAttribute)
{
var diagnosticId = ApiPageMarkdownTemplate.Escape(experimentalAttribute.ConstructorArguments.FirstOrDefault().Value as string ?? "");
var urlFormat = experimentalAttribute.NamedArguments.FirstOrDefault(a => a.Key == "UrlFormat").Value.Value as string;
var link = string.IsNullOrEmpty(urlFormat) ? diagnosticId : $"[{diagnosticId}]({ApiPageMarkdownTemplate.Escape(string.Format(urlFormat, diagnosticId))})";
var message = $"'{originalSymbol ?? symbol}' is for evaluation purposes only and is subject to change or removal in future updates.";
return string.IsNullOrEmpty(diagnosticId) ? message : $"{link}: {message}";
}

// Search containing namespace, module, assembly for named types
if (symbol.ContainingSymbol is not null && symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.NetModule or SymbolKind.Assembly)
return Preview(symbol.ContainingSymbol, originalSymbol ?? symbol);

return null;
}

void Namespace()
{
var namespaceSymbols = symbols.Select(n => n.symbol).ToHashSet(SymbolEqualityComparer.Default);
Expand Down Expand Up @@ -592,6 +611,7 @@ Parameter ToParameter(IFieldSymbol item)
{
name = item.Name, @default = $"{item.ConstantValue}",
deprecated = Deprecated(item),
preview = Preview(item),
description = docs,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
},
"id": "DoNothing",
"path": "dotnet/project/Project/Class1.cs",
"startLine": 138.0,
"startLine": 139.0,
"endLine": 0.0
},
"assemblies": [
Expand Down Expand Up @@ -184,7 +184,7 @@
"summary": "<p sourcefile=\"obj/api/BuildFromProject.Class1.IIssue8948.yml\" sourcestartlinenumber=\"1\">Does nothing with generic type <code class=\"typeparamref\">T</code>.</p>\n",
"platform": null,
"docurl": "https://github.com/dotnet/docfx/new/main/apiSpec/new?filename=BuildFromProject_Class1_IIssue8948_DoNothing__1.md&value=---%0Auid%3A%20BuildFromProject.Class1.IIssue8948.DoNothing%60%601%0Asummary%3A%20'*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax'%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A",
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L139",
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L140",
"remarks": "",
"conceptual": "",
"implements": "",
Expand Down Expand Up @@ -239,7 +239,7 @@
},
"id": "IIssue8948",
"path": "dotnet/project/Project/Class1.cs",
"startLine": 132.0,
"startLine": 133.0,
"endLine": 0.0
},
"assemblies": [
Expand Down Expand Up @@ -321,7 +321,7 @@
"_tocRel": "toc.html",
"yamlmime": "ManagedReference",
"docurl": "https://github.com/dotnet/docfx/new/main/apiSpec/new?filename=BuildFromProject_Class1_IIssue8948.md&value=---%0Auid%3A%20BuildFromProject.Class1.IIssue8948%0Asummary%3A%20'*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax'%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A",
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L133",
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L134",
"summary": "",
"remarks": "",
"conceptual": "",
Expand Down
Loading

0 comments on commit 12a2fd2

Please sign in to comment.