Skip to content

Commit 12a2fd2

Browse files
authored
feat: support ExperimentalAttribute for API page (#9434)
1 parent a7b919b commit 12a2fd2

File tree

132 files changed

+1102
-1003
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+1102
-1003
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ We welcome code contributions through pull requests, issues tagged as **[`help-w
4848
### Prerequisites
4949
5050
- Install [Visual Studio 2022 (Community or higher)](https://www.visualstudio.com/) and make sure you have the latest updates.
51-
- Install [.NET SDK](https://dotnet.microsoft.com/download/dotnet) 6.x and 7.x.
51+
- Install [.NET SDK](https://dotnet.microsoft.com/download/dotnet) 6.x, 7.x and 8.x.
5252
- Install NodeJS (18.x.x).
5353
- Optional: Install wkhtmltopdf on Windows to test PDF using `choco install wkhtmltopdf`.
5454

samples/seed/dotnet/project/Project/Class1.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace BuildFromProject;
22

3+
[Experimental("DOCFX001", UrlFormat = "https://example.org/{0}")]
34
public class Class1 : IClass1
45
{
56
public class Test<T> { }
@@ -148,6 +149,7 @@ public void DoNothing<T>() { }
148149
/// <summary>
149150
/// <see cref="IConfiguration"/> related helper and extension routines.
150151
/// </summary>
152+
[Experimental("")]
151153
public void Issue1887() { }
152154

153155
/// <summary>
@@ -190,3 +192,14 @@ public enum Issue9260
190192
OldAndUnusedValue2,
191193
}
192194
}
195+
196+
class ExperimentalAttribute : Attribute
197+
{
198+
public ExperimentalAttribute(string diagnosticId)
199+
{
200+
DiagnosticId = diagnosticId;
201+
}
202+
203+
public string DiagnosticId { get; }
204+
public string? UrlFormat { get; set; }
205+
}

schemas/v1.0/ApiPage.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type Api = (
3636
/** Is this API deprecated, or the deprecation reason in markdown format */
3737
deprecated?: boolean | string;
3838

39+
/** Is this API experimental, or the preview disclaimer text */
40+
preview?: boolean | string;
41+
3942
/** API source URL */
4043
src?: string;
4144

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

92+
/** Is this parameter experimental, or the preview disclaimer text */
93+
preview?: boolean | string;
94+
8995
/** Is this parameter optional? */
9096
optional?: boolean;
9197
}

src/Docfx.Build/ApiPage/ApiPage.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ abstract class ApiBase
7171
{
7272
public string? id { get; init; }
7373
public OneOf<bool, string>? deprecated { get; init; }
74+
public OneOf<bool, string>? preview { get; init; }
7475
public string? src { get; init; }
7576
public Dictionary<string, string>? metadata { get; init; }
7677
}
@@ -129,6 +130,7 @@ class Parameter
129130
public string? @default { get; init; }
130131
public string? description { get; init; }
131132
public OneOf<bool, string>? deprecated { get; init; }
133+
public OneOf<bool, string>? preview { get; init; }
132134
public bool? optional { get; init; }
133135
}
134136

src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,22 @@ HtmlTemplate Api(Api api)
5656
Api4 a4 => (4, a4.api4),
5757
};
5858

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

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

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

70-
HtmlTemplate? DeprecatedBadge(OneOf<bool, string>? value, string fontSize = ".5em")
74+
HtmlTemplate? Badge(OneOf<bool, string>? value, string text, string cssClass, string fontSize)
7175
{
7276
var isDeprecated = value?.Value switch
7377
{
@@ -76,13 +80,13 @@ HtmlTemplate Api(Api api)
7680
_ => false,
7781
};
7882

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

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

@@ -119,7 +123,8 @@ HtmlTemplate Code(Code code)
119123

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

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

130135
return Html(
131136
$"""
132-
<dt>{title} {Inline(parameter.type)} {deprecated}</dt>
137+
<dt>{title} {Inline(parameter.type)} {deprecated} {preview}</dt>
133138
<dd>
134-
{DeprecatedReason(parameter.deprecated)}
139+
{Alert(parameter.deprecated, "alert-warning")}
140+
{Alert(parameter.preview, "alert-info")}
135141
{(string.IsNullOrEmpty(parameter.description) ? default : UnsafeHtml(markup(parameter.description)))}
136142
</dd>
137143
""");

src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ LinkSpan link when string.IsNullOrEmpty(link.url) => $"{Escape(link.text)}",
9696
};
9797
}
9898

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

src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,14 @@ void Api(int level, string title, ISymbol symbol, Compilation compilation)
126126
: new GitSource(source.Remote.Repo, source.Remote.Branch, source.Remote.Path, source.StartLine + 1);
127127
var src = git is null ? null : options.SourceUrl?.Invoke(git) ?? GitUtility.GetSourceUrl(git);
128128
var deprecated = Deprecated(symbol);
129+
var preview = Preview(symbol);
129130

130131
body.Add(level switch
131132
{
132-
1 => (Api)new Api1 { api1 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
133-
2 => (Api)new Api2 { api2 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
134-
3 => (Api)new Api3 { api3 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
135-
4 => (Api)new Api4 { api4 = title, id = id, src = src, deprecated = deprecated, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
133+
1 => (Api)new Api1 { api1 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
134+
2 => (Api)new Api2 { api2 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
135+
3 => (Api)new Api3 { api3 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
136+
4 => (Api)new Api4 { api4 = title, id = id, src = src, deprecated = deprecated, preview = preview, metadata = new() { ["uid"] = uid, ["commentId"] = commentId } },
136137
});
137138
}
138139

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

147+
OneOf<bool, string>? Preview(ISymbol symbol, ISymbol? originalSymbol = null)
148+
{
149+
if (symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "ExperimentalAttribute") is { } experimentalAttribute)
150+
{
151+
var diagnosticId = ApiPageMarkdownTemplate.Escape(experimentalAttribute.ConstructorArguments.FirstOrDefault().Value as string ?? "");
152+
var urlFormat = experimentalAttribute.NamedArguments.FirstOrDefault(a => a.Key == "UrlFormat").Value.Value as string;
153+
var link = string.IsNullOrEmpty(urlFormat) ? diagnosticId : $"[{diagnosticId}]({ApiPageMarkdownTemplate.Escape(string.Format(urlFormat, diagnosticId))})";
154+
var message = $"'{originalSymbol ?? symbol}' is for evaluation purposes only and is subject to change or removal in future updates.";
155+
return string.IsNullOrEmpty(diagnosticId) ? message : $"{link}: {message}";
156+
}
157+
158+
// Search containing namespace, module, assembly for named types
159+
if (symbol.ContainingSymbol is not null && symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.NetModule or SymbolKind.Assembly)
160+
return Preview(symbol.ContainingSymbol, originalSymbol ?? symbol);
161+
162+
return null;
163+
}
164+
146165
void Namespace()
147166
{
148167
var namespaceSymbols = symbols.Select(n => n.symbol).ToHashSet(SymbolEqualityComparer.Default);
@@ -592,6 +611,7 @@ Parameter ToParameter(IFieldSymbol item)
592611
{
593612
name = item.Name, @default = $"{item.ConstantValue}",
594613
deprecated = Deprecated(item),
614+
preview = Preview(item),
595615
description = docs,
596616
};
597617
}

test/docfx.Snapshot.Tests/SamplesTest.Seed.Linux/api/BuildFromProject.Class1.IIssue8948.html.view.verified.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
},
128128
"id": "DoNothing",
129129
"path": "dotnet/project/Project/Class1.cs",
130-
"startLine": 138.0,
130+
"startLine": 139.0,
131131
"endLine": 0.0
132132
},
133133
"assemblies": [
@@ -184,7 +184,7 @@
184184
"summary": "<p sourcefile=\"obj/api/BuildFromProject.Class1.IIssue8948.yml\" sourcestartlinenumber=\"1\">Does nothing with generic type <code class=\"typeparamref\">T</code>.</p>\n",
185185
"platform": null,
186186
"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",
187-
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L139",
187+
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L140",
188188
"remarks": "",
189189
"conceptual": "",
190190
"implements": "",
@@ -239,7 +239,7 @@
239239
},
240240
"id": "IIssue8948",
241241
"path": "dotnet/project/Project/Class1.cs",
242-
"startLine": 132.0,
242+
"startLine": 133.0,
243243
"endLine": 0.0
244244
},
245245
"assemblies": [
@@ -321,7 +321,7 @@
321321
"_tocRel": "toc.html",
322322
"yamlmime": "ManagedReference",
323323
"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",
324-
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L133",
324+
"sourceurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/dotnet/project/Project/Class1.cs/#L134",
325325
"summary": "",
326326
"remarks": "",
327327
"conceptual": "",

0 commit comments

Comments
 (0)