diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index ba01ecfe561..ff6a22853ff 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -19,18 +19,14 @@ runs:
cache: ${{ inputs.cache == 'true' && 'npm' || '' }}
cache-dependency-path: templates/package-lock.json
- - uses: actions/setup-dotnet@v5
+ - uses: actions/setup-dotnet@v6
with:
dotnet-version: |
+ 11.x
10.x
9.x
8.x
- - uses: actions/setup-dotnet@v5
- if: env.DOCFX_PREVIEW_BUILD == 'true'
- with:
- dotnet-version: 11.0.100-rc.1.26425.128
-
- uses: actions/cache@v6
if: inputs.cache == 'true'
with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1c1e3f665a7..f57b4bece13 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,6 +19,8 @@ jobs:
os: [windows-latest, macos-latest, ubuntu-latest]
framework: [net10.0]
include:
+ - os: ubuntu-latest
+ framework: net11.0
- os: ubuntu-latest
framework: net9.0
- os: ubuntu-latest
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e29bf64eab6..c2aa15a4232 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -21,7 +21,7 @@ jobs:
- uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.x
+ dotnet-version: 11.x
- name: Run `dotnet format` command
run: |
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 827e87c6166..a4811cdd552 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
inputs:
validation_only:
- description: 'Compare latest stable and this ref without package publication or Pages deployment'
+ description: 'Compare latest stable and this ref without package publication or Pages deployment'
type: boolean
default: true
schedule:
@@ -75,9 +75,9 @@ jobs:
contents: read
packages: write
steps:
- - uses: actions/setup-dotnet@v5
+ - uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.x
+ dotnet-version: 11.x
- uses: actions/download-artifact@v8
with:
name: nightly-publication-packages
@@ -95,9 +95,9 @@ jobs:
actions: read
steps:
- uses: actions/checkout@v7
- - uses: actions/setup-dotnet@v5
+ - uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.x
+ dotnet-version: 11.x
- uses: actions/download-artifact@v8
with:
name: nightly-tool-package
@@ -140,24 +140,24 @@ jobs:
- name: Resolve exact SDK candidates from official metadata
id: matrix
run: node test/compatibility/report.mjs resolve drop/compatibility-matrix.json
- - uses: actions/setup-dotnet@v5
+ - uses: actions/setup-dotnet@v6
continue-on-error: true
with:
dotnet-version: ${{ steps.matrix.outputs.sdks }}
- name: Test compatibility reporting
run: node --test test/compatibility/report.test.mjs
- - name: Measure and check latest stable and exact nightly packages
+ - name: Measure and check latest stable and exact nightly packages
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
TOOL_VERSION: ${{ needs.build-nightly-package.outputs.version }}
run: |
- ./test/compatibility/Measure-Compatibility.ps1 -MatrixPath drop/compatibility-matrix.json -OutputDirectory drop/compatibility-report -NightlyPackage "drop/nightly/docfx.$env:TOOL_VERSION.nupkg" -NightlyFrameworks net8.0,net9.0,net10.0,net11.0
+ ./test/compatibility/Measure-Compatibility.ps1 -MatrixPath drop/compatibility-matrix.json -OutputDirectory drop/compatibility-report -NightlyPackage "drop/nightly/docfx.$env:TOOL_VERSION.nupkg" -NightlyFrameworks net8.0,net9.0,net10.0,net11.0
- name: Upload compatibility evidence, including failed cases
if: always()
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.validation_only && 'sdk-compatibility-validation-v1' || 'sdk-compatibility-v1' }}
path: drop/compatibility-report
- if-no-files-found: error
+ if-no-files-found: error
retention-days: 14
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index df9bb7a3098..0428b38a378 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -47,7 +47,7 @@ jobs:
steps:
- uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.x
+ dotnet-version: 11.x
- uses: actions/download-artifact@v8
with:
@@ -88,7 +88,7 @@ jobs:
steps:
- uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.x
+ dotnet-version: 11.x
- uses: actions/download-artifact@v8
with:
diff --git a/Directory.Build.props b/Directory.Build.props
index 20149bb8cb4..cfc63e069c1 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
true
- net8.0;net9.0;net10.0
+ net8.0;net9.0;net10.0;net11.0
net8.0;net9.0;net10.0;net11.0
Preview
enable
diff --git a/src/Docfx.Build/ApiPage/ApiPage.cs b/src/Docfx.Build/ApiPage/ApiPage.cs
index f6dcf16a216..aa676aa7f06 100644
--- a/src/Docfx.Build/ApiPage/ApiPage.cs
+++ b/src/Docfx.Build/ApiPage/ApiPage.cs
@@ -3,24 +3,50 @@
using System.Text.Json;
using System.Text.Json.Serialization;
-using OneOf;
#nullable enable
namespace Docfx.Build.ApiPage;
-struct LinkSpan
-{
- public required string text { get; init; }
- public string? url { get; init; }
-}
+#if NET11_0_OR_GREATER
+readonly union BoolOrString(bool, string);
+readonly union StringOrArrayOfString(string, string[]);
+readonly union Span(string, LinkSpan);
+[JsonUnion(TypeClassifier = typeof(InlineClassifier))]
+readonly union Inline(Span, Span[]);
+
+[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
+readonly union Heading(H1, H2, H3, H4, H5, H6);
+
+[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
+readonly union Api(Api1, Api2, Api3, Api4);
+
+[JsonUnion(TypeClassifier = typeof(BlockClassifier))]
+readonly union Block(Heading, Api, Markdown, Facts, Parameters, List, Inheritance, Code);
+#else
[GenerateOneOf]
partial class Span : OneOfBase { }
[GenerateOneOf]
partial class Inline : OneOfBase { }
+[GenerateOneOf]
+partial class Heading : OneOfBase { }
+
+[GenerateOneOf]
+partial class Api : OneOfBase { }
+
+[GenerateOneOf]
+partial class Block : OneOfBase { }
+#endif
+
+struct LinkSpan
+{
+ public required string text { get; init; }
+ public string? url { get; init; }
+}
+
struct Markdown
{
public required string markdown { get; init; }
@@ -62,14 +88,11 @@ struct H6
public string? id { get; init; }
}
-[GenerateOneOf]
-partial class Heading : OneOfBase { }
-
abstract class ApiBase
{
public string? id { get; init; }
- public OneOf? deprecated { get; init; }
- public OneOf? preview { get; init; }
+ public BoolOrString? deprecated { get; init; }
+ public BoolOrString? preview { get; init; }
public string? src { get; init; }
public Dictionary? metadata { get; init; }
}
@@ -94,10 +117,6 @@ class Api4 : ApiBase
public required string api4 { get; init; }
}
-
-[GenerateOneOf]
-partial class Api : OneOfBase { }
-
record struct Fact(string name, Inline value);
struct Facts
@@ -127,8 +146,8 @@ class Parameter
public Inline? type { get; init; }
public string? @default { get; init; }
public string? description { get; init; }
- public OneOf? deprecated { get; init; }
- public OneOf? preview { get; init; }
+ public BoolOrString? deprecated { get; init; }
+ public BoolOrString? preview { get; init; }
public bool? optional { get; init; }
}
@@ -137,9 +156,6 @@ struct Parameters
public required Parameter[] parameters { get; init; }
}
-[GenerateOneOf]
-partial class Block : OneOfBase { }
-
record ApiPage
{
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
@@ -147,14 +163,16 @@ record ApiPage
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull | JsonIgnoreCondition.WhenWritingDefault,
};
+#if !NET11_0_OR_GREATER
static ApiPage()
{
JsonSerializerOptions.Converters.Add(new OneOfJsonConverterFactory());
}
+#endif
public required string title { get; init; }
public required Block[] body { get; init; }
public string? languageId { get; init; }
- public Dictionary>? metadata { get; init; }
+ public Dictionary? metadata { get; init; }
}
diff --git a/src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs b/src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs
index ea1ab38d6d7..73ea533f26b 100644
--- a/src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs
+++ b/src/Docfx.Build/ApiPage/ApiPageHtmlTemplate.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net;
-using OneOf;
using static Docfx.Build.HtmlTemplate;
#nullable enable
@@ -69,7 +68,7 @@ HtmlTemplate Api(Api api)
""");
}
- HtmlTemplate? Badge(OneOf? value, string text, string cssClass, string fontSize)
+ HtmlTemplate? Badge(BoolOrString? value, string text, string cssClass, string fontSize)
{
var isDeprecated = value?.Value switch
{
@@ -81,7 +80,7 @@ HtmlTemplate Api(Api api)
return isDeprecated ? Html($" {text}") : null;
}
- HtmlTemplate Alert(OneOf? value, string cssClass)
+ HtmlTemplate Alert(BoolOrString? value, string cssClass)
{
return value?.Value is string ds && !string.IsNullOrEmpty(ds)
? Html($"\n{UnsafeHtml(markup(ds))}
")
diff --git a/src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs b/src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs
index 6972f574a94..527a4cc20cc 100644
--- a/src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs
+++ b/src/Docfx.Build/ApiPage/ApiPageMarkdownTemplate.cs
@@ -52,10 +52,10 @@ FormattableString Facts(Facts facts) =>
$"{string.Concat(facts.facts.Select(fact => $"{Escape(fact.name)}: {Inline(fact.value)} \n"))}\n";
FormattableString List(List list) =>
- $"{string.Join(", \n", list.list.Select(Inline))}\n\n";
+ $"{string.Join(", \n", list.list.Select(x => Inline(x)))}\n\n";
FormattableString Inheritance(Inheritance inheritance) =>
- $"{string.Join(" \u2190 \n", inheritance.inheritance.Select(Inline))}\n\n";
+ $"{string.Join(" \u2190 \n", inheritance.inheritance.Select(x => Inline(x)))}\n\n";
FormattableString Code(Code code) =>
$"""
diff --git a/src/Docfx.Build/ApiPage/CustomClassifiers.cs b/src/Docfx.Build/ApiPage/CustomClassifiers.cs
new file mode 100644
index 00000000000..19166b9a81f
--- /dev/null
+++ b/src/Docfx.Build/ApiPage/CustomClassifiers.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#if NET11_0_OR_GREATER
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+#nullable enable
+
+namespace Docfx.Build.ApiPage;
+
+
+///
+/// Custom classifier for inline types.
+/// It's required because built-in JsonUnionTypeStructuralClassifier don't support nested union type.
+///
+internal sealed class InlineClassifier : JsonTypeClassifierFactory
+{
+ public override JsonTypeClassifier CreateJsonClassifier(JsonTypeClassifierContext context, JsonSerializerOptions options)
+ {
+ return static (ref Utf8JsonReader reader) => reader.TokenType switch
+ {
+ JsonTokenType.String => typeof(Span),
+ JsonTokenType.StartObject => typeof(Span),
+ JsonTokenType.StartArray => typeof(Span[]),
+ _ => null,
+ };
+ }
+}
+
+///
+/// Custom classifier for block types.
+/// It's required because built-in JsonUnionTypeStructuralClassifier don't support nested union type.
+///
+internal sealed class BlockClassifier : JsonTypeClassifierFactory
+{
+ public override JsonTypeClassifier CreateJsonClassifier(JsonTypeClassifierContext context, JsonSerializerOptions options)
+ {
+ return static (ref Utf8JsonReader reader) =>
+ {
+ if (reader.TokenType is not JsonTokenType.StartObject)
+ return null;
+
+ while (reader.Read() && reader.TokenType is not JsonTokenType.EndObject)
+ {
+ if (reader.TokenType is not JsonTokenType.PropertyName)
+ continue;
+
+ Type? type = reader.GetString() switch
+ {
+ "h1" or "h2" or "h3" or "h4" or "h5" or "h6" => typeof(Heading),
+ "api1" or "api2" or "api3" or "api4" => typeof(Api),
+ "markdown" => typeof(Markdown),
+ "facts" => typeof(Facts),
+ "parameters" => typeof(Parameters),
+ "list" => typeof(List),
+ "inheritance" => typeof(Inheritance),
+ "code" => typeof(Code),
+ _ => null,
+ };
+
+ if (type is not null)
+ return type;
+
+ reader.Read();
+ reader.Skip();
+ }
+
+ return null;
+ };
+ }
+}
+#endif
diff --git a/src/Docfx.Build/Docfx.Build.csproj b/src/Docfx.Build/Docfx.Build.csproj
index 7ea4fddf800..28a3f72be2c 100644
--- a/src/Docfx.Build/Docfx.Build.csproj
+++ b/src/Docfx.Build/Docfx.Build.csproj
@@ -18,8 +18,17 @@
+
+
+
+
+
+
+
+
diff --git a/src/Docfx.Build/OneOfJsonConverterFactory.cs b/src/Docfx.Build/OneOfJsonConverterFactory.cs
index 497af6dc308..4d8aa4dea9e 100644
--- a/src/Docfx.Build/OneOfJsonConverterFactory.cs
+++ b/src/Docfx.Build/OneOfJsonConverterFactory.cs
@@ -1,5 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if !NET11_0_OR_GREATER
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -9,7 +10,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Docfx.Build.ApiPage;
-using OneOf;
#nullable enable
@@ -258,3 +258,4 @@ private static void Assert(
];
}
}
+#endif
diff --git a/src/Docfx.Dotnet/Docfx.Dotnet.csproj b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
index 7ff17fbca8d..ba184d8c2fe 100644
--- a/src/Docfx.Dotnet/Docfx.Dotnet.csproj
+++ b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
@@ -6,6 +6,7 @@
+
@@ -27,8 +28,6 @@
-
-
@@ -41,4 +40,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs b/src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs
index 5b3fba2fd0a..1aa912c22b2 100644
--- a/src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs
+++ b/src/Docfx.Dotnet/DotnetApiCatalog.ApiPage.cs
@@ -10,7 +10,6 @@
using Docfx.Plugins;
using HtmlAgilityPack;
using Microsoft.CodeAnalysis;
-using OneOf;
#nullable enable
@@ -98,7 +97,7 @@ _ when SymbolHelper.IsMember(method) => "Method",
throw new NotSupportedException($"Unknown symbol type kind {symbols[0].symbol}");
}
- var metadata = new Dictionary>();
+ var metadata = new Dictionary();
if (!string.IsNullOrEmpty(comment?.Summary))
metadata["description"] = HtmlInnerText(comment.Summary);
@@ -144,14 +143,14 @@ void Api(int level, string title, ISymbol symbol, Compilation compilation)
});
}
- OneOf? Deprecated(ISymbol symbol)
+ BoolOrString? Deprecated(ISymbol symbol)
{
if (symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "ObsoleteAttribute") is { } obsoleteAttribute)
- return obsoleteAttribute.ConstructorArguments.FirstOrDefault().Value is string reason && !string.IsNullOrEmpty(reason) ? (OneOf?)reason : true;
+ return obsoleteAttribute.ConstructorArguments.FirstOrDefault().Value is string reason && !string.IsNullOrEmpty(reason) ? (BoolOrString?)reason : true;
return null;
}
- OneOf? Preview(ISymbol symbol, ISymbol? originalSymbol = null)
+ BoolOrString? Preview(ISymbol symbol, ISymbol? originalSymbol = null)
{
if (symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "ExperimentalAttribute") is { } experimentalAttribute)
{
diff --git a/test/docfx.Snapshot.Tests/PercyTest.cs b/test/docfx.Snapshot.Tests/PercyTest.cs
index 76685bb7074..a83a5aeaf40 100644
--- a/test/docfx.Snapshot.Tests/PercyTest.cs
+++ b/test/docfx.Snapshot.Tests/PercyTest.cs
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if !NET11_0_OR_GREATER
using System.Net.Http.Json;
+#endif
using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;