diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c9fd45536c..be147658f9d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -45,6 +45,11 @@ jobs:
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
+ - uses: codecov/codecov-action@v5
+ if: matrix.os == 'ubuntu-latest'
+ with:
+ fail_ci_if_error: true
+
- run: echo "DOTNET_DbgEnableMiniDump=1" >> $GITHUB_ENV
if: matrix.os == 'ubuntu-latest'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000000..027be8fe3a0
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,43 @@
+name: lint
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ - ready_for_review
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Lint
+ runs-on: ubuntu-latest
+ if: github.event.pull_request.draft == false
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 9.x
+
+ - name: Run `dotnet format` command
+ run: |
+ dotnet restore
+ dotnet format --no-restore --verify-no-changes
+
+ - name: Report failures as Job Summary
+ if: ${{ failure() }}
+ shell: pwsh
+ run: |
+ $content = '
+ ## Failed to run the `lint.yml` workflow
+ To fix workflow errors. Please follow the steps below.
+ 1. Run `dotnet format` command.
+ 2. Commit changes as separated commit.
+ 3. Push changes to source branch of PR.
+ '
+ Write-Output $content >> $env:GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 6ba3f724aba..944d0da0fcc 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -1,5 +1,6 @@
name: nightly
on:
+ workflow_dispatch:
schedule:
- cron: '0 0 * * *'
@@ -40,3 +41,44 @@ jobs:
run: |
dotnet nuget push drop/nuget/*.nupkg --api-key "${{ secrets.GITHUB_TOKEN }}" --skip-duplicate --source https://nuget.pkg.github.com/dotnet/index.json
+ test-nightly-package:
+ if: github.repository == 'dotnet/docfx'
+ runs-on: ubuntu-latest
+ needs: [publish-github-packages]
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Create NuGet.config
+ shell: pwsh
+ run: |
+ @'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '@ | Out-File NuGet.config -Encoding UTF8
+
+ - name: Install nightly build package
+ run: |
+ dotnet tool install docfx -g --prerelease
+
+ - name: Run docfx commands for test
+ working-directory: samples/seed
+ run: |
+ docfx metadata
+ docfx build
+ docfx pdf
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 9ec7dd187a7..c2c1f4bd877 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
true
net8.0;net9.0
- net8.0;net9.0
+ net8.0;net9.0
Preview
enable
true
@@ -19,9 +19,8 @@
warning NU1507: There are 2 package sources defined in your configuration.
warning NU5104: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency "PdfPig [0.1.9-alpha-20240510-d86c2, )" or update the version field in the nuspec.
warning NU5111: The script file 'tools\.playwright\package\bin\install_media_pack.ps1' is not recognized by NuGet and hence will not be executed during installation of this package.
- warning CS0436: IgnoresAccessChecksTo redefinition due to InternalsVisibleTo
-->
- $(NoWarn);NU1507;NU5104;NU5111;CS0436
+ $(NoWarn);NU1507;NU5104;NU5111
@@ -30,8 +29,8 @@
true
true
snupkg
- true
-
+ true
+
.NET Foundation and Contributors
Copyright (c) .NET Foundation and Contributors
Technical documentation tool with markdown, API docs for .NET, REST API and more.
@@ -45,13 +44,6 @@
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 864a2c893f5..78c7cbe693c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,14 +4,12 @@
true
-
+
-
-
-
+
@@ -20,32 +18,25 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
diff --git a/Dockerfile b/Dockerfile
index d46998ee16a..ec5ca671b1c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,34 +1,28 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+FROM mcr.microsoft.com/dotnet/sdk:8.0-noble
# Add dotnet tools to path.
ENV PATH="${PATH}:/root/.dotnet/tools"
+# Set Node.js path
+ENV PLAYWRIGHT_NODEJS_PATH="/usr/bin/node"
+
# Set target docfx version.
-ARG DOCFX_VERSION=2.77.0
+ARG DOCFX_VERSION=2.78.2
# Install DocFX as a dotnet tool.
RUN dotnet tool install docfx -g --version ${DOCFX_VERSION} && \
docfx --version && \
rm -f /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/docfx.nupkg && \
rm -f /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/docfx.${DOCFX_VERSION}.nupkg && \
- rm -rf /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net6.0
-
-# Install Node.js and dependences for chromium PDF.
-RUN apt-get update -qq && \
- apt-get install -y -qq --no-install-recommends \
- nodejs \
- libglib2.0-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
- libdbus-1-3 libxcb1 libxkbcommon0 libatspi2.0-0 libx11-6 libxcomposite1 libxdamage1 \
- libxext6 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 && \
- rm -rf /var/lib/apt/lists/* /tmp/*
+ rm -rf /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net9.0
-# Install Chromium.
-RUN PLAYWRIGHT_NODEJS_PATH="/usr/bin/node" && \
- ln -s /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/.playwright /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net8.0/any/.playwright && \
- pwsh -File /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net8.0/any/playwright.ps1 install chromium && \
- unlink /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net8.0/any/.playwright
+# Install Node.js and browser(chromium) with dependencies
+RUN apt-get install -y -qq --update --no-install-recommends nodejs && \
+ pwsh -File /root/.dotnet/tools/.store/docfx/${DOCFX_VERSION}/docfx/${DOCFX_VERSION}/tools/net8.0/any/playwright.ps1 install --with-deps chromium && \
+ rm -rf /var/lib/apt/lists/* && \
+ rm -rf /tmp/*
WORKDIR /opt/prj
VOLUME [ "/opt/prj" ]
-ENTRYPOINT [ "docfx" ]
\ No newline at end of file
+ENTRYPOINT [ "docfx" ]
diff --git a/docfx.sln b/docfx.sln
index 1d0d61dd072..d6b801de64a 100644
--- a/docfx.sln
+++ b/docfx.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{926A0726-B
ProjectSection(SolutionItems) = preProject
test\Directory.Build.props = test\Directory.Build.props
test\xunit.runner.json = test\xunit.runner.json
+ test\Directory.Packages.props = test\Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docfx", "src\docfx\docfx.csproj", "{EF53214F-BA98-4026-BEED-CF771865C312}"
@@ -98,6 +99,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docfx.Build.OverwriteDocume
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docfx.Build.OverwriteDocuments.Tests", "test\Docfx.Build.OverwriteDocuments.Tests\Docfx.Build.OverwriteDocuments.Tests.csproj", "{CAECA6C3-3317-4E6E-8927-9186857B23E8}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/docs/index.md b/docs/index.md
index ee497ffed05..0ea52aeee8d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -9,7 +9,7 @@ In this section we will build a simple documentation site on your local machine.
> Prerequisites
> - Familiarity with the command line
> - Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) 8.0 or higher
-> - Install [Node.js](https://nodejs.org/) v20 or higher
+> - Install [Node.js](https://nodejs.org/) v20 or higher (Optional: It's required when using [Create PDF Files](https://filzrev.github.io/docfx/docs/pdf.html))
Make sure you have [.NET SDK](https://dotnet.microsoft.com/en-us/download) installed, then open a terminal and enter the following command to install the latest docfx:
@@ -114,6 +114,53 @@ await Docfx.Docset.Build("docfx.json");
See [API References](api/Docfx.yml) for additional APIs.
+## How to use prerelease version of docfx
+
+Docfx publishes nightly builds to [GitHub Packages](https://github.com/dotnet/docfx/pkgs/nuget/docfx).
+If you want to use prerelease version, you can install package with following steps.
+
+### Prerequisite
+
+1. Install [GitHub CLI](https://github.com/cli/cli) command.
+2. Install PowerShell 7.x or later.
+
+### Steps
+
+1. Open PowerShell on working directory.
+
+2. Login to GitHub with additional scope request
+
+ ```pwsh
+ gh auth login --scopes "read:packages" --host github.com
+ ```
+
+3. Follow the instructions and complete the login steps.
+
+4. Download docfx nuget package from GitHub Packages
+
+ ```pwsh
+ # Gets Access Token
+ $token = gh auth token
+
+ # Gets the version of latest nightly build
+ $version = gh api /orgs/dotnet/packages/nuget/docfx/versions --jq '.[0].name'
+
+ # Gets nupkg download URL.
+ $downloadUrl = "https://nuget.pkg.github.com/dotnet/download/docfx/${version}/${version}.nupkg"
+
+ # Download nupkg to current directory.
+ Write-Host ('Download nupkg from: {0}' -f $downloadUrl)
+ Invoke-RestMethod -Method Get -Uri $downloadUrl -OutFile "docfx.${version}.nupkg" -Headers @{
+ Authorization = "Bearer $token"
+ }
+ ```
+
+5. Install docfx as .NET Global Package from local source
+
+ ```pwsh
+ dotnet tool update docfx -g --prerelease --source ./
+ ```
+
## Next Steps
- [Write Articles](docs/markdown.md)
diff --git a/docs/reference/docfx-cli-reference/docfx-metadata.md b/docs/reference/docfx-cli-reference/docfx-metadata.md
index fc679ed5142..fcf55620fd2 100644
--- a/docs/reference/docfx-cli-reference/docfx-metadata.md
+++ b/docs/reference/docfx-cli-reference/docfx-metadata.md
@@ -77,6 +77,10 @@ Run `docfx metadata --help` or `docfx -h` to get a list of all available options
Disable the default API filter (default filter only generate public or protected APIs).
+- **--noRestore**
+
+ Do not run `dotnet restore` before building the projects.
+
- **--namespaceLayout**
Determines the namespace layout in table of contents.
diff --git a/samples/seed/docfx.json b/samples/seed/docfx.json
index 6dcf2b76192..3dfed8aca10 100644
--- a/samples/seed/docfx.json
+++ b/samples/seed/docfx.json
@@ -70,7 +70,7 @@
{ "files": [ "**" ], "src": "obj/md", "dest": "md" },
{ "files": [ "**" ], "src": "obj/apipage", "dest": "apipage" },
{ "files": [ "articles/**/*.{md,yml}", "*.md", "toc.yml", "restapi/**" ] },
- { "files": [ "pdf/**" ] }
+ { "files": [ "pdf/*.{md,yml}" ] }
],
"resource": [
{
diff --git a/samples/seed/dotnet/assembly/BuildFromAssembly.csproj b/samples/seed/dotnet/assembly/BuildFromAssembly.csproj
index b42d68e6ba4..1707423aa11 100644
--- a/samples/seed/dotnet/assembly/BuildFromAssembly.csproj
+++ b/samples/seed/dotnet/assembly/BuildFromAssembly.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
enable
enable
true
diff --git a/src/Docfx.App/Config/ListWithStringFallbackConverter.SystemTextJson.cs b/src/Docfx.App/Config/ListWithStringFallbackConverter.SystemTextJson.cs
index 219ec035958..742164a8ffc 100644
--- a/src/Docfx.App/Config/ListWithStringFallbackConverter.SystemTextJson.cs
+++ b/src/Docfx.App/Config/ListWithStringFallbackConverter.SystemTextJson.cs
@@ -35,7 +35,7 @@ public override ListWithStringFallback Read(ref Utf8JsonReader reader, Type type
{
using var document = JsonDocument.ParseValue(ref reader);
JsonElement root = document.RootElement;
- var values = root.EnumerateObject().Select(x=>x.ToString());
+ var values = root.EnumerateObject().Select(x => x.ToString());
return new ListWithStringFallback(values);
}
default:
diff --git a/src/Docfx.App/Config/MergeJsonConfigConverter.NewtonsoftJson.cs b/src/Docfx.App/Config/MergeJsonConfigConverter.NewtonsoftJson.cs
index 344c352c61f..2c72f7efef4 100644
--- a/src/Docfx.App/Config/MergeJsonConfigConverter.NewtonsoftJson.cs
+++ b/src/Docfx.App/Config/MergeJsonConfigConverter.NewtonsoftJson.cs
@@ -65,4 +65,4 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
serializer.Serialize(writer, ((MergeJsonConfig)value).ToArray());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Docfx.App/Docfx.App.csproj b/src/Docfx.App/Docfx.App.csproj
index 12db285be2e..e4d0f5d7804 100644
--- a/src/Docfx.App/Docfx.App.csproj
+++ b/src/Docfx.App/Docfx.App.csproj
@@ -18,11 +18,6 @@
-
-
-
-
-
diff --git a/src/Docfx.App/Helpers/PdfPigTypeExtensions.cs b/src/Docfx.App/Helpers/PdfPigTypeExtensions.cs
new file mode 100644
index 00000000000..8b0391424c0
--- /dev/null
+++ b/src/Docfx.App/Helpers/PdfPigTypeExtensions.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using UglyToad.PdfPig.Content;
+using UglyToad.PdfPig.Outline.Destinations;
+
+#nullable enable
+
+namespace Docfx;
+
+internal static class PdfPigTypeExtensions
+{
+ public static NamedDestinations GetNamedDestinations(this Catalog catalog)
+ => GetNamedDestinationsProperty(catalog);
+
+ public static bool TryGet(this NamedDestinations namedDestinations, string name, out ExplicitDestination dest)
+ => TryGetNamedDestinations(namedDestinations, name, out dest);
+
+ // Gets property value of catalog.NamedDestination.
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_NamedDestinations")]
+ private static extern NamedDestinations GetNamedDestinationsProperty(Catalog value);
+
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "TryGet")]
+ private static extern bool TryGetNamedDestinations(NamedDestinations namedDestinations, string name, out ExplicitDestination dest);
+}
+
diff --git a/src/Docfx.App/PdfBuilder.cs b/src/Docfx.App/PdfBuilder.cs
index fdb1312dc36..2e4123f6a3b 100644
--- a/src/Docfx.App/PdfBuilder.cs
+++ b/src/Docfx.App/PdfBuilder.cs
@@ -48,11 +48,6 @@ class Outline
public string? pdfFooterTemplate { get; init; }
}
- static PdfBuilder()
- {
- PlaywrightHelper.EnsurePlaywrightNodeJsPath();
- }
-
public static Task Run(BuildJsonConfig config, string configDirectory, string? outputDirectory = null)
{
var outputFolder = Path.GetFullPath(Path.Combine(
@@ -70,7 +65,9 @@ public static async Task CreatePdf(string outputFolder)
if (pdfTocs.Count == 0)
return;
- Program.Main(["install", "chromium"]);
+ PlaywrightHelper.EnsurePlaywrightNodeJsPath();
+
+ Program.Main(["install", "chromium", "--only-shell"]);
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
@@ -109,7 +106,7 @@ await Parallel.ForEachAsync(pdfTocs, async (item, _) =>
var outputPath = Path.Combine(outputFolder, outputName);
await CreatePdf(
- PrintPdf, PrintHeaderFooter, task, new(baseUrl, url), toc, outputPath,
+ PrintPdf, PrintHeaderFooter, task, new(baseUrl, url), toc, outputFolder, outputPath,
pageNumbers => pdfPageNumbers[url] = pageNumbers);
task.Value = task.MaxValue;
@@ -259,7 +256,7 @@ static string ExpandTemplate(string? pdfTemplate, int pageNumber, int totalPages
static async Task CreatePdf(
Func> printPdf, Func> printHeaderFooter, ProgressTask task,
- Uri outlineUrl, Outline outline, string outputPath, Action> updatePageNumbers)
+ Uri outlineUrl, Outline outline, string outputFolder, string outputPath, Action> updatePageNumbers)
{
var tempDirectory = Path.Combine(Path.GetTempPath(), ".docfx", "pdf", "pages");
Directory.CreateDirectory(tempDirectory);
@@ -301,7 +298,7 @@ await Parallel.ForEachAsync(pages, async (item, _) =>
var key = CleanUrl(url);
if (!pagesByUrl.TryGetValue(key, out var dests))
pagesByUrl[key] = dests = new();
- dests.Add((node, document.Structure.Catalog.NamedDestinations));
+ dests.Add((node, document.Structure.Catalog.GetNamedDestinations()));
pageBytes[node] = bytes;
pageNumbers[node] = numberOfPages + 1;
@@ -360,6 +357,8 @@ async Task MergePdf()
if (!pageBytes.TryGetValue(node, out var bytes))
continue;
+ var isCoverPage = IsCoverPage(url, outputFolder, outline.pdfCoverPage);
+
var isTocPage = IsTocPage(url);
if (isTocPage)
{
@@ -378,6 +377,9 @@ async Task MergePdf()
var pageBuilder = builder.AddPage(document, i, x => CopyLink(node, x));
+ if (isCoverPage)
+ continue;
+
if (isTocPage)
continue;
@@ -438,6 +440,19 @@ PdfAction HandleUriAction(UriAction url)
static Uri CleanUrl(Uri url) => new UriBuilder(url) { Query = null, Fragment = null }.Uri;
+ static bool IsCoverPage(Uri pageUri, string baseFolder, string? pdfCoverPage)
+ {
+ Debug.Assert(Path.IsPathFullyQualified(baseFolder));
+
+ if (string.IsNullOrEmpty(pdfCoverPage))
+ return false;
+
+ string pagePath = pageUri.AbsolutePath.TrimStart('/');
+ string covePagePath = PathUtility.MakeRelativePath(baseFolder, Path.GetFullPath(Path.Combine(baseFolder, pdfCoverPage)));
+
+ return pagePath.Equals(covePagePath, GetStringComparison());
+ }
+
static bool IsTocPage(Uri url) => url.AbsolutePath.StartsWith("/_pdftoc/");
Bookmarks CreateBookmarks(Outline[]? items)
@@ -618,4 +633,12 @@ static string getMillimeter(double pt)
return $"{Math.Round(pt * MillimeterPerInch / Dpi)}mm";
}
}
+
+ // Gets StringComparison instance for path string.
+ private static StringComparison GetStringComparison()
+ {
+ return PathUtility.IsPathCaseInsensitive()
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal;
+ }
}
diff --git a/src/Docfx.Build.Common/ModelAttributeHandlers/Handlers/BaseModelAttributeHandler.cs b/src/Docfx.Build.Common/ModelAttributeHandlers/Handlers/BaseModelAttributeHandler.cs
index 1178c9062bf..50ca4526e80 100644
--- a/src/Docfx.Build.Common/ModelAttributeHandlers/Handlers/BaseModelAttributeHandler.cs
+++ b/src/Docfx.Build.Common/ModelAttributeHandlers/Handlers/BaseModelAttributeHandler.cs
@@ -78,7 +78,7 @@ public object Handle(object obj, HandleModelAttributesContext context)
protected virtual bool ShouldHandle(object currentObj, object declaringObject, PropInfo currentPropInfo, HandleModelAttributesContext context)
{
- return currentPropInfo is {Attr: not null};
+ return currentPropInfo is { Attr: not null };
}
///
diff --git a/src/Docfx.Build.ManagedReference/BuildOutputs/ApiReferenceBuildOutput.cs b/src/Docfx.Build.ManagedReference/BuildOutputs/ApiReferenceBuildOutput.cs
index 14416d04deb..487ea9af900 100644
--- a/src/Docfx.Build.ManagedReference/BuildOutputs/ApiReferenceBuildOutput.cs
+++ b/src/Docfx.Build.ManagedReference/BuildOutputs/ApiReferenceBuildOutput.cs
@@ -300,7 +300,7 @@ public void Expand(Dictionary references, strin
public static List GetSpecNames(string xref, string[] supportedLanguages, SortedList> specs = null)
{
- if (specs is {Count: > 0})
+ if (specs is { Count: > 0 })
{
return (from spec in specs
where supportedLanguages.Contains(spec.Key)
diff --git a/src/Docfx.Build.ManagedReference/FillReferenceInformation.cs b/src/Docfx.Build.ManagedReference/FillReferenceInformation.cs
index 96268da6666..fdb4e53d15a 100644
--- a/src/Docfx.Build.ManagedReference/FillReferenceInformation.cs
+++ b/src/Docfx.Build.ManagedReference/FillReferenceInformation.cs
@@ -111,10 +111,10 @@ private void TraceSourceInfo(PageViewModel model, string file)
private static IEnumerable GetUidsToFill(PageViewModel pageViewModel)
{
return from i in pageViewModel.Items
- from c in (i.Children ?? Enumerable.Empty())
- .Concat(i.ExtensionMethods ?? Enumerable.Empty())
- .Concat(i.InheritedMembers ?? Enumerable.Empty())
- select c;
+ from c in (i.Children ?? Enumerable.Empty())
+ .Concat(i.ExtensionMethods ?? Enumerable.Empty())
+ .Concat(i.InheritedMembers ?? Enumerable.Empty())
+ select c;
}
#endregion
diff --git a/src/Docfx.Build.OverwriteDocuments/OverwriteUtility.cs b/src/Docfx.Build.OverwriteDocuments/OverwriteUtility.cs
index 869b012e0a7..2dc08df726d 100644
--- a/src/Docfx.Build.OverwriteDocuments/OverwriteUtility.cs
+++ b/src/Docfx.Build.OverwriteDocuments/OverwriteUtility.cs
@@ -93,7 +93,7 @@ public static void AddOrUpdateFragmentProperty(this MarkdownFragment fragment, s
{
if (!fragment.Properties.TryGetValue(oPath, out var property))
{
- fragment.Properties[oPath] = property = new MarkdownProperty { OPath = oPath};
+ fragment.Properties[oPath] = property = new MarkdownProperty { OPath = oPath };
}
if (string.IsNullOrEmpty(property.Content))
diff --git a/src/Docfx.Build.RestApi/BuildRestApiDocument.cs b/src/Docfx.Build.RestApi/BuildRestApiDocument.cs
index 479fa53a900..621072c8990 100644
--- a/src/Docfx.Build.RestApi/BuildRestApiDocument.cs
+++ b/src/Docfx.Build.RestApi/BuildRestApiDocument.cs
@@ -102,7 +102,7 @@ private static void MarkupRecursive(JToken jToken, IHostService host, FileModel
{
if (MarkupKeys.Contains(pair.Key) && pair.Value != null)
{
- if (pair.Value is JValue {Type: JTokenType.String} jValue)
+ if (pair.Value is JValue { Type: JTokenType.String } jValue)
{
jObject[pair.Key] = Markup(host, (string)jValue, model, filter);
}
diff --git a/src/Docfx.Build.RestApi/ValidateRestApiDocumentMetadata.cs b/src/Docfx.Build.RestApi/ValidateRestApiDocumentMetadata.cs
index 8494910a04a..f082944c26d 100644
--- a/src/Docfx.Build.RestApi/ValidateRestApiDocumentMetadata.cs
+++ b/src/Docfx.Build.RestApi/ValidateRestApiDocumentMetadata.cs
@@ -29,10 +29,9 @@ public override void Build(FileModel model, IHostService host)
case DocumentType.Overwrite:
foreach (var item in (List)model.Content)
{
- host.ValidateInputMetadata(
- model.OriginalFileAndType.File,
- // use RestApiChildItemViewModel because it contains all properties for REST.
- item.ConvertTo().Metadata.ToImmutableDictionary());
+ // use RestApiChildItemViewModel because it contains all properties for REST
+ var metadata = item.ConvertTo().Metadata.ToImmutableDictionary();
+ host.ValidateInputMetadata(model.OriginalFileAndType.File, metadata);
}
break;
default:
diff --git a/src/Docfx.Build.SchemaDriven/Models/JsonPointer.cs b/src/Docfx.Build.SchemaDriven/Models/JsonPointer.cs
index 99bb7ddba2d..ea62b760e06 100644
--- a/src/Docfx.Build.SchemaDriven/Models/JsonPointer.cs
+++ b/src/Docfx.Build.SchemaDriven/Models/JsonPointer.cs
@@ -49,7 +49,7 @@ public JsonPointer GetParentPointer()
public static bool TryCreate(string raw, out JsonPointer pointer)
{
pointer = null;
- if (raw is {Length: > 0} && raw[0] != '/')
+ if (raw is { Length: > 0 } && raw[0] != '/')
{
return false;
}
diff --git a/src/Docfx.Build.SchemaDriven/Processors/FileInterpreter.cs b/src/Docfx.Build.SchemaDriven/Processors/FileInterpreter.cs
index d67f99742ed..5f34be6c817 100644
--- a/src/Docfx.Build.SchemaDriven/Processors/FileInterpreter.cs
+++ b/src/Docfx.Build.SchemaDriven/Processors/FileInterpreter.cs
@@ -19,7 +19,7 @@ public FileInterpreter(bool exportFileLink, bool updateValue)
public bool CanInterpret(BaseSchema schema)
{
- return schema is {ContentType: ContentType.File};
+ return schema is { ContentType: ContentType.File };
}
public object Interpret(BaseSchema schema, object value, IProcessContext context, string path)
diff --git a/src/Docfx.Build.SchemaDriven/Processors/HrefInterpreter.cs b/src/Docfx.Build.SchemaDriven/Processors/HrefInterpreter.cs
index afe370d804d..5cf6ad2c171 100644
--- a/src/Docfx.Build.SchemaDriven/Processors/HrefInterpreter.cs
+++ b/src/Docfx.Build.SchemaDriven/Processors/HrefInterpreter.cs
@@ -21,7 +21,7 @@ public HrefInterpreter(bool exportFileLink, bool updateValue, string siteHostNam
public bool CanInterpret(BaseSchema schema)
{
- return schema is {ContentType: ContentType.Href};
+ return schema is { ContentType: ContentType.Href };
}
public object Interpret(BaseSchema schema, object value, IProcessContext context, string path)
diff --git a/src/Docfx.Build.SchemaDriven/Processors/MarkdownInterpreter.cs b/src/Docfx.Build.SchemaDriven/Processors/MarkdownInterpreter.cs
index 2ac605ee96e..8e21df466ac 100644
--- a/src/Docfx.Build.SchemaDriven/Processors/MarkdownInterpreter.cs
+++ b/src/Docfx.Build.SchemaDriven/Processors/MarkdownInterpreter.cs
@@ -9,7 +9,7 @@ public class MarkdownInterpreter : IInterpreter
{
public bool CanInterpret(BaseSchema schema)
{
- return schema is {ContentType: ContentType.Markdown};
+ return schema is { ContentType: ContentType.Markdown };
}
public object Interpret(BaseSchema schema, object value, IProcessContext context, string path)
diff --git a/src/Docfx.Build.SchemaDriven/Processors/XrefInterpreter.cs b/src/Docfx.Build.SchemaDriven/Processors/XrefInterpreter.cs
index e4c0f1618f3..a61b9d6f7f2 100644
--- a/src/Docfx.Build.SchemaDriven/Processors/XrefInterpreter.cs
+++ b/src/Docfx.Build.SchemaDriven/Processors/XrefInterpreter.cs
@@ -19,7 +19,7 @@ public XrefInterpreter(bool aggregateXrefs, bool resolveXref)
public bool CanInterpret(BaseSchema schema)
{
- return schema is {ContentType: ContentType.Xref};
+ return schema is { ContentType: ContentType.Xref };
}
public object Interpret(BaseSchema schema, object value, IProcessContext context, string path)
diff --git a/src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs b/src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs
index 79172edce58..cc8d371c1d0 100644
--- a/src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs
+++ b/src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs
@@ -91,7 +91,7 @@ private void UpdateTocItemHref(TocItemViewModel toc, FileModel model, IDocumentB
toc.OriginalTopicHref = null;
includedFrom = toc.IncludedFrom ?? includedFrom;
- if (toc.Items is {Count: > 0})
+ if (toc.Items is { Count: > 0 })
{
foreach (var item in toc.Items)
{
diff --git a/src/Docfx.Build/TableOfContents/TocResolver.cs b/src/Docfx.Build/TableOfContents/TocResolver.cs
index 6ea34f31a3b..24cec8aa4d3 100644
--- a/src/Docfx.Build/TableOfContents/TocResolver.cs
+++ b/src/Docfx.Build/TableOfContents/TocResolver.cs
@@ -122,7 +122,7 @@ private TocItemInfo ResolveItemCore(TocItemInfo wrapper, Stack stac
{
case HrefType.AbsolutePath:
case HrefType.RelativeFile:
- if (item.Items is {Count: > 0})
+ if (item.Items is { Count: > 0 })
{
item.Items = new List(from i in item.Items
select ResolveItem(new TocItemInfo(file, i), stack) into r
diff --git a/src/Docfx.Build/TableOfContents/TocRestructureUtility.cs b/src/Docfx.Build/TableOfContents/TocRestructureUtility.cs
index ead7255750e..1ce0abf42c4 100644
--- a/src/Docfx.Build/TableOfContents/TocRestructureUtility.cs
+++ b/src/Docfx.Build/TableOfContents/TocRestructureUtility.cs
@@ -28,7 +28,7 @@ private static void RestructureCore(TocItemViewModel item, List 0})
+ if (item.Items is { Count: > 0 })
{
var parentItems = new List(item.Items);
foreach (var i in item.Items)
diff --git a/src/Docfx.Build/TemplateProcessors/TemplateManager.cs b/src/Docfx.Build/TemplateProcessors/TemplateManager.cs
index 40f422f120b..e1f1c3537c6 100644
--- a/src/Docfx.Build/TemplateProcessors/TemplateManager.cs
+++ b/src/Docfx.Build/TemplateProcessors/TemplateManager.cs
@@ -78,7 +78,7 @@ private IEnumerable GetTemplateDirectories(IEnumerable names)
public void ProcessTheme(string outputDirectory, bool overwrite)
{
- if (_themes is {Count: > 0})
+ if (_themes is { Count: > 0 })
{
TryExportResourceFiles(_themes, outputDirectory, overwrite);
Logger.LogInfo($"Theme(s) {_themes.ToDelimitedString()} applied.");
diff --git a/src/Docfx.Build/XRefMaps/XRefArchiveBuilder.cs b/src/Docfx.Build/XRefMaps/XRefArchiveBuilder.cs
index 61d2f99d36e..8ad9e3932c4 100644
--- a/src/Docfx.Build/XRefMaps/XRefArchiveBuilder.cs
+++ b/src/Docfx.Build/XRefMaps/XRefArchiveBuilder.cs
@@ -53,7 +53,7 @@ private async Task DownloadCoreAsync(Uri uri, XRefArchive xa, Cancellati
// Sort is not needed if `map.Sorted == true`.
// But there are some xrefmap files that is not propery sorted by using InvariantCulture.
// (e.g. Unity xrefmap that maintained by community)
- if (map.References is {Count: > 0})
+ if (map.References is { Count: > 0 })
{
map.References.Sort(XRefSpecUidComparer.Instance);
map.Sorted = true;
diff --git a/src/Docfx.Common/Path/RelativePath.cs b/src/Docfx.Common/Path/RelativePath.cs
index be23480f3a3..e1961551f48 100644
--- a/src/Docfx.Common/Path/RelativePath.cs
+++ b/src/Docfx.Common/Path/RelativePath.cs
@@ -54,7 +54,7 @@ public static RelativePath FromUrl(string path)
public static bool IsRelativePath(string path)
{
// TODO : to merge with the PathUtility one
- return path is {Length: > 0} &&
+ return path is { Length: > 0 } &&
path[0] != '/' &&
path[0] != '\\' &&
path.IndexOfAny(PathUtility.InvalidPathChars) == -1;
diff --git a/src/Docfx.Dotnet/Docfx.Dotnet.csproj b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
index 9dbc6ec171a..d28ca0fb4ae 100644
--- a/src/Docfx.Dotnet/Docfx.Dotnet.csproj
+++ b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
@@ -16,10 +16,6 @@
-
-
-
-
@@ -32,7 +28,6 @@
-
diff --git a/src/Docfx.Dotnet/DotnetApiCatalog.Toc.cs b/src/Docfx.Dotnet/DotnetApiCatalog.Toc.cs
index 788da38ef94..9d61e3c50a6 100644
--- a/src/Docfx.Dotnet/DotnetApiCatalog.Toc.cs
+++ b/src/Docfx.Dotnet/DotnetApiCatalog.Toc.cs
@@ -61,7 +61,7 @@ IEnumerable CreateToc(ISymbol symbol, Compilation compilation)
switch (symbol)
{
- case INamespaceSymbol {IsGlobalNamespace: true} ns:
+ case INamespaceSymbol { IsGlobalNamespace: true } ns:
foreach (var child in ns.GetNamespaceMembers())
foreach (var item in CreateToc(child, compilation))
yield return item;
@@ -248,7 +248,7 @@ void InsertCategory(TocNodeType type, string name)
case CategoryLayout.Nested:
{
// Skip when parent node is category node.
- if (parentTocNode is {type: TocNodeType.None})
+ if (parentTocNode is { type: TocNodeType.None })
return;
// If items contains specified type node. Create new TocNode for category. and move related node to child node.
diff --git a/src/Docfx.Dotnet/ExtensionMethods/ISymbolExtensions.cs b/src/Docfx.Dotnet/ExtensionMethods/ISymbolExtensions.cs
new file mode 100644
index 00000000000..5a91077fa80
--- /dev/null
+++ b/src/Docfx.Dotnet/ExtensionMethods/ISymbolExtensions.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Globalization;
+using System.Reflection;
+using System.Reflection.Emit;
+using Microsoft.CodeAnalysis;
+
+#nullable enable
+
+namespace Docfx.Dotnet;
+
+internal static class ISymbolExtensions
+{
+ public static ImmutableArray GetParameters(this ISymbol? symbol)
+ {
+ return symbol switch
+ {
+ IMethodSymbol m => m.Parameters,
+ IPropertySymbol nt => nt.Parameters,
+ _ => [],
+ };
+ }
+
+ public static ImmutableArray GetTypeParameters(this ISymbol? symbol)
+ {
+ return symbol switch
+ {
+ IMethodSymbol m => m.TypeParameters,
+ INamedTypeSymbol nt => nt.TypeParameters,
+ _ => [],
+ };
+ }
+
+ public static DocumentationComment GetDocumentationComment(this ISymbol symbol, Compilation compilation, CultureInfo? preferredCulture = null, bool expandIncludes = false, bool expandInheritdoc = false, CancellationToken cancellationToken = default)
+ {
+ // Gets FullXmlFragment by calling `symbol.DocumentationComment(...).FullXmlFragment`
+ string fullXmlFragment = Helpers.GetFullXmlFragment(symbol, compilation, preferredCulture, expandIncludes, expandInheritdoc, cancellationToken);
+
+ return new DocumentationComment
+ {
+ FullXmlFragment = fullXmlFragment,
+ };
+ }
+
+ internal class DocumentationComment
+ {
+ public required string FullXmlFragment { get; init; }
+ }
+
+ private static class Helpers
+ {
+ ///
+ /// Gets result of `symbol.GetDocumentationComment(args).FullXmlFragment`
+ ///
+ public static string GetFullXmlFragment(ISymbol symbol, Compilation compilation, CultureInfo? preferredCulture = null, bool expandIncludes = false, bool expandInheritdoc = false, CancellationToken cancellationToken = default)
+ => CachedDelegate(symbol, compilation, preferredCulture, expandIncludes, expandInheritdoc, cancellationToken);
+
+ static Helpers()
+ {
+ CachedDelegate = GetDelegate();
+ }
+
+ private delegate string GetFullXmlFragmentDelegate(ISymbol symbol, Compilation compilation, CultureInfo? preferredCulture, bool expandIncludes, bool expandInheritdoc, CancellationToken cancellationToken);
+ private static readonly GetFullXmlFragmentDelegate CachedDelegate;
+
+ private static GetFullXmlFragmentDelegate GetDelegate()
+ {
+ // Gets Microsoft.CodeAnalysis.Workspaces assembly
+ var workspaceAssembly = typeof(Workspace).Assembly;
+
+ // Gets MethodInfo for GetDocumentationComment
+ var type = workspaceAssembly.GetType("Microsoft.CodeAnalysis.Shared.Extensions.ISymbolExtensions", throwOnError: true)!;
+ var methodInfo = type.GetMethod("GetDocumentationComment", BindingFlags.Public | BindingFlags.Static);
+
+ // Gets PropertyInfo for DocumentationComment.
+ var docCommentType = workspaceAssembly.GetType("Microsoft.CodeAnalysis.Shared.Utilities.DocumentationComment", throwOnError: true)!;
+ var propertyInfo = docCommentType.GetProperty("FullXmlFragment", BindingFlags.Instance | BindingFlags.Public)!;
+
+ // Reflection may fail when updating the Microsoft.CodeAnalysis.Workspaces.Common package..
+ if (methodInfo == null || propertyInfo == null)
+ throw new InvalidOperationException("Failed to get required MethodInfo/PropertyInfo via reflection.");
+
+ var dm = new DynamicMethod(string.Empty, returnType: typeof(string), parameterTypes: [
+ typeof(ISymbol),
+ typeof(Compilation),
+ typeof(CultureInfo), // preferredCulture
+ typeof(bool), // expandIncludes
+ typeof(bool), // expandInheritdoc
+ typeof(CancellationToken),
+ ]);
+
+ ILGenerator il = dm.GetILGenerator();
+
+ // call Microsoft.CodeAnalysis.Shared.Extensions.ISymbolExtensions::GetDocumentationComment(args)
+ il.Emit(OpCodes.Ldarg_0); // symbol
+ il.Emit(OpCodes.Ldarg_1); // compilation
+ il.Emit(OpCodes.Ldarg_2); // preferredCulture
+ il.Emit(OpCodes.Ldarg_3); // expandIncludes
+ il.Emit(OpCodes.Ldarg_S, 4); // expandInheritdoc
+ il.Emit(OpCodes.Ldarg_S, 5); // cancellationToken
+ il.EmitCall(OpCodes.Call, methodInfo, null);
+
+ // callvirt DocumentationComment::get_FullXmlFragment()
+ il.EmitCall(OpCodes.Callvirt, propertyInfo.GetMethod!, null);
+
+ // return FullXmlFragment
+ il.Emit(OpCodes.Ret);
+
+ return dm.CreateDelegate();
+ }
+ }
+}
diff --git a/src/Docfx.Dotnet/ManagedReference/Resolvers/NormalizeSyntax.cs b/src/Docfx.Dotnet/ManagedReference/Resolvers/NormalizeSyntax.cs
index 61794226839..3c2a0f5948c 100644
--- a/src/Docfx.Dotnet/ManagedReference/Resolvers/NormalizeSyntax.cs
+++ b/src/Docfx.Dotnet/ManagedReference/Resolvers/NormalizeSyntax.cs
@@ -17,7 +17,7 @@ public void Run(MetadataModel yaml, ResolverContext context)
(member, parent) =>
{
// get all the possible places where link is possible
- if (member.Syntax is {Content: not null})
+ if (member.Syntax is { Content: not null })
{
SyntaxLanguage[] keys = new SyntaxLanguage[member.Syntax.Content.Count];
member.Syntax.Content.Keys.CopyTo(keys, 0);
diff --git a/src/Docfx.Dotnet/ManagedReference/Resolvers/ResolveReference.cs b/src/Docfx.Dotnet/ManagedReference/Resolvers/ResolveReference.cs
index 1b59b26ee8c..9f3c6eab950 100644
--- a/src/Docfx.Dotnet/ManagedReference/Resolvers/ResolveReference.cs
+++ b/src/Docfx.Dotnet/ManagedReference/Resolvers/ResolveReference.cs
@@ -27,7 +27,7 @@ public void Run(MetadataModel yaml, ResolverContext context)
page = parent;
current.References = null;
}
- if (documentReferences is {Count: > 0})
+ if (documentReferences is { Count: > 0 })
{
foreach (var key in documentReferences.Keys)
{
diff --git a/src/Docfx.Dotnet/ManagedReference/Resolvers/SetDerivedClass.cs b/src/Docfx.Dotnet/ManagedReference/Resolvers/SetDerivedClass.cs
index c812f1a6e0d..3f380805f2a 100644
--- a/src/Docfx.Dotnet/ManagedReference/Resolvers/SetDerivedClass.cs
+++ b/src/Docfx.Dotnet/ManagedReference/Resolvers/SetDerivedClass.cs
@@ -11,7 +11,7 @@ internal class SetDerivedClass : IResolverPipeline
public void Run(MetadataModel yaml, ResolverContext context)
{
- if (yaml.Members is {Count: > 0})
+ if (yaml.Members is { Count: > 0 })
{
UpdateDerivedClassMapping(yaml.Members, context.References);
AppendDerivedClass(yaml.Members);
@@ -23,7 +23,7 @@ private void UpdateDerivedClassMapping(List items, Dictionary())
{
var inheritance = item.Inheritance;
- if (inheritance is {Count: > 0})
+ if (inheritance is { Count: > 0 })
{
var superClass = inheritance[inheritance.Count - 1];
diff --git a/src/Docfx.Dotnet/ManagedReference/Visitors/SymbolVisitorAdapter.cs b/src/Docfx.Dotnet/ManagedReference/Visitors/SymbolVisitorAdapter.cs
index db222e49182..a81d5a930a1 100644
--- a/src/Docfx.Dotnet/ManagedReference/Visitors/SymbolVisitorAdapter.cs
+++ b/src/Docfx.Dotnet/ManagedReference/Visitors/SymbolVisitorAdapter.cs
@@ -240,7 +240,7 @@ public override MetadataItem VisitMethod(IMethodSymbol symbol)
}
_generator.GenerateSyntax(symbol, result.Syntax, _filter);
- if (symbol is {IsOverride: true, OverriddenMethod: not null})
+ if (symbol is { IsOverride: true, OverriddenMethod: not null })
{
result.Overridden = AddSpecReference(symbol.OverriddenMethod, typeGenericParameters, methodGenericParameters);
}
@@ -298,7 +298,7 @@ public override MetadataItem VisitEvent(IEventSymbol symbol)
var typeGenericParameters = symbol.ContainingType.IsGenericType ? symbol.ContainingType.Accept(TypeGenericParameterNameVisitor.Instance) : EmptyListOfString;
- if (symbol is {IsOverride: true, OverriddenEvent: not null})
+ if (symbol is { IsOverride: true, OverriddenEvent: not null })
{
result.Overridden = AddSpecReference(symbol.OverriddenEvent, typeGenericParameters);
}
@@ -352,7 +352,7 @@ public override MetadataItem VisitProperty(IPropertySymbol symbol)
Debug.Assert(result.Syntax.Return.Type != null);
}
- if (symbol is {IsOverride: true, OverriddenProperty: not null})
+ if (symbol is { IsOverride: true, OverriddenProperty: not null })
{
result.Overridden = AddSpecReference(symbol.OverriddenProperty, typeGenericParameters);
}
diff --git a/src/Docfx.Dotnet/ManagedReference/Visitors/VisitorHelper.cs b/src/Docfx.Dotnet/ManagedReference/Visitors/VisitorHelper.cs
index 51e0a8afba1..04cc202c6d7 100644
--- a/src/Docfx.Dotnet/ManagedReference/Visitors/VisitorHelper.cs
+++ b/src/Docfx.Dotnet/ManagedReference/Visitors/VisitorHelper.cs
@@ -31,7 +31,7 @@ public static string GetId(ISymbol symbol)
return null;
}
- if (symbol is INamespaceSymbol {IsGlobalNamespace: true})
+ if (symbol is INamespaceSymbol { IsGlobalNamespace: true })
{
return GlobalNamespaceId;
}
diff --git a/src/Docfx.Dotnet/Parsers/XmlComment.cs b/src/Docfx.Dotnet/Parsers/XmlComment.cs
index da4467e3fcc..5cf6105c619 100644
--- a/src/Docfx.Dotnet/Parsers/XmlComment.cs
+++ b/src/Docfx.Dotnet/Parsers/XmlComment.cs
@@ -365,7 +365,7 @@ private void ResolveCrefLink(XNode node, string nodeSelector, Action 0})
+ if (model.Value.NameParts is { Count: > 0 })
{
result.Name = GetName(model.Value.NameParts, SyntaxLanguage.Default);
var nameForCSharp = GetName(model.Value.NameParts, SyntaxLanguage.CSharp);
@@ -270,7 +270,7 @@ public static ItemViewModel ToItemViewModel(this MetadataItem model, ExtractMeta
Attributes = model.Attributes,
};
- if (model.Parent is {Name: not null} && !model.Name.StartsWith(model.Parent.Name))
+ if (model.Parent is { Name: not null } && !model.Name.StartsWith(model.Parent.Name))
{
result.Id = model.Name.Substring(model.Name.LastIndexOf('.') + 1);
}
@@ -330,7 +330,7 @@ public static SyntaxDetailViewModel ToSyntaxDetailViewModel(this SyntaxDetail mo
TypeParameters = model.TypeParameters,
Return = model.Return,
};
- if (model.Content is {Count: > 0})
+ if (model.Content is { Count: > 0 })
{
result.Content = model.Content.GetLanguageProperty(SyntaxLanguage.Default);
var contentForCSharp = model.Content.GetLanguageProperty(SyntaxLanguage.CSharp);
@@ -379,7 +379,7 @@ public static TValue GetLanguageProperty(this SortedList 0})
+ if (model.Items is { Count: > 0 })
{
foreach (var item in model.Items)
{
diff --git a/src/Docfx.MarkdigEngine.Extensions/Aggregator/TabGroupAggregator.cs b/src/Docfx.MarkdigEngine.Extensions/Aggregator/TabGroupAggregator.cs
index 486ab2cc718..f3cda27bcb3 100644
--- a/src/Docfx.MarkdigEngine.Extensions/Aggregator/TabGroupAggregator.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/Aggregator/TabGroupAggregator.cs
@@ -107,7 +107,7 @@ private static TabItemBlock CreateTabItem(
private static Tuple ParseHeading(HeadingBlock block)
{
var child = block.Inline.FirstChild;
- if (child is {NextSibling: null} and LinkInline link)
+ if (child is { NextSibling: null } and LinkInline link)
{
var m = HrefRegex().Match(link.Url);
if (m.Success)
diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs
index 2b5b4b514d9..9434a18f1b5 100644
--- a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
namespace Docfx.MarkdigEngine.Extensions;
diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs
index bd903601a1b..d2636d8d96e 100644
--- a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs
@@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
using System.Xml.Linq;
namespace Docfx.MarkdigEngine.Extensions;
diff --git a/src/Docfx.MarkdigEngine.Extensions/MonikerRange/MonikerRangeParser.cs b/src/Docfx.MarkdigEngine.Extensions/MonikerRange/MonikerRangeParser.cs
index 3e8a92f5756..ec73184fe3a 100644
--- a/src/Docfx.MarkdigEngine.Extensions/MonikerRange/MonikerRangeParser.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/MonikerRange/MonikerRangeParser.cs
@@ -142,7 +142,7 @@ public override BlockState TryContinue(BlockProcessor processor, Block block)
public override bool Close(BlockProcessor processor, Block block)
{
var monikerRange = (MonikerRangeBlock)block;
- if (monikerRange is {Closed: false})
+ if (monikerRange is { Closed: false })
{
_context.LogWarning("invalid-moniker-range", $"No \"::: {EndString}\" found for \"{monikerRange.MonikerRange}\", MonikerRange does not end explicitly.", block);
}
diff --git a/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlCodeBlockRenderer.cs b/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlCodeBlockRenderer.cs
index 5957c059df4..0f6154d6dca 100644
--- a/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlCodeBlockRenderer.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlCodeBlockRenderer.cs
@@ -44,7 +44,7 @@ public PlantUmlCodeBlockRenderer(MarkdownContext context, PlantUmlOptions settin
protected override void Write(HtmlRenderer renderer, CodeBlock obj)
{
- if (obj is FencedCodeBlock {Info: string info} fencedCodeBlock
+ if (obj is FencedCodeBlock { Info: string info } fencedCodeBlock
&& info.Equals("plantuml", StringComparison.OrdinalIgnoreCase))
{
IPlantUmlRenderer plantUmlRenderer = rendererFactory.CreateRenderer(_settings);
diff --git a/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs b/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs
index 469d9bc73bc..56ebdbe8a88 100644
--- a/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/PlantUml/PlantUmlExtension.cs
@@ -18,7 +18,7 @@ public class PlantUmlOptions
[JsonProperty("remoteUrl")]
[JsonPropertyName("remoteUrl")]
- public string RemoteUrl { get; set; }
+ public string RemoteUrl { get; set; } = "http://www.plantuml.com/plantuml/";
[JsonProperty("localPlantUmlPath")]
[JsonPropertyName("localPlantUmlPath")]
@@ -30,7 +30,7 @@ public class PlantUmlOptions
[JsonProperty("renderingMode")]
[JsonPropertyName("renderingMode")]
- public RenderingMode RenderingMode { get; set; }
+ public RenderingMode RenderingMode { get; set; } = RenderingMode.Remote;
[JsonProperty("delimitor")]
[JsonPropertyName("delimitor")]
diff --git a/src/Docfx.MarkdigEngine.Extensions/QuoteSectionNote/QuoteSectionNoteRender.cs b/src/Docfx.MarkdigEngine.Extensions/QuoteSectionNote/QuoteSectionNoteRender.cs
index d96394903bb..b5c19ad5073 100644
--- a/src/Docfx.MarkdigEngine.Extensions/QuoteSectionNote/QuoteSectionNoteRender.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/QuoteSectionNote/QuoteSectionNoteRender.cs
@@ -1,13 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
using System.Web;
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace Docfx.MarkdigEngine.Extensions;
-public class QuoteSectionNoteRender : HtmlObjectRenderer
+public partial class QuoteSectionNoteRender : HtmlObjectRenderer
{
private readonly MarkdownContext _context;
private readonly Dictionary _notes;
@@ -100,13 +103,14 @@ private static void WriteVideo(HtmlRenderer renderer, QuoteSectionNoteBlock obj)
public static string FixUpLink(string link)
{
- if (!link.Contains("https"))
+ if (link.StartsWith("http:"))
{
- link = link.Replace("http", "https");
+ link = "https:" + link.Substring("http:".Length);
}
if (Uri.TryCreate(link, UriKind.Absolute, out Uri videoLink))
{
var host = videoLink.Host;
+ var path = videoLink.LocalPath;
var query = videoLink.Query;
if (query.Length > 1)
{
@@ -125,16 +129,115 @@ public static string FixUpLink(string link)
query += "&nocookie=true";
}
}
- else if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
+ else if (hostsYouTube.Contains(host, StringComparer.OrdinalIgnoreCase))
{
// case 2, YouTube video
- host = "www.youtube-nocookie.com";
+ var idYouTube = GetYouTubeId(host, path, ref query);
+ if (idYouTube != null)
+ {
+ host = "www.youtube-nocookie.com";
+ path = "/embed/" + idYouTube;
+ query = AddYouTubeRel(query);
+ }
+ else
+ {
+ //YouTube Playlist
+ var listYouTube = GetYouTubeList(query);
+ if (listYouTube != null)
+ {
+ host = "www.youtube-nocookie.com";
+ path = "/embed/videoseries";
+ query = "list=" + listYouTube;
+ query = AddYouTubeRel(query);
+ }
+ }
+
+ //Keep this to preserve previous behavior
+ if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
+ {
+ host = "www.youtube-nocookie.com";
+ }
}
- var builder = new UriBuilder(videoLink) { Host = host, Query = query };
+ var builder = new UriBuilder(videoLink) { Host = host, Path = path, Query = query };
link = builder.Uri.ToString();
}
return link;
}
+
+ ///
+ /// Only related videos from the same channel
+ /// https://developers.google.com/youtube/player_parameters
+ ///
+ private static string AddYouTubeRel(string query)
+ {
+ // Add rel=0 unless specified in the original link
+ if (query.Split('&').Any(q => q.StartsWith("rel=")) == false)
+ {
+ if (query.Length == 0)
+ return "rel=0";
+ else
+ return query + "&rel=0";
+ }
+
+ return query;
+ }
+
+ private static readonly ReadOnlyCollection hostsYouTube = new string[] {
+ "youtube.com",
+ "www.youtube.com",
+ "youtu.be",
+ "www.youtube-nocookie.com",
+ }.AsReadOnly();
+
+ private static string GetYouTubeId(string host, string path, ref string query)
+ {
+ if (host == "youtu.be")
+ {
+ return path.Substring(1);
+ }
+
+ var match = ReYouTubeQueryVideo().Match(query);
+ if (match.Success)
+ {
+ //Remove from query
+ query = query.Replace(match.Groups[0].Value, "").Trim('&').Replace("&&", "&");
+ return match.Groups[2].Value;
+ }
+
+ match = ReYouTubePathId().Match(path);
+ if (match.Success)
+ {
+ var id = match.Groups[1].Value;
+
+ if (id == "videoseries")
+ return null;
+
+ return id;
+ }
+
+ return null;
+ }
+
+ [GeneratedRegex(@"(^|&)v=([^&]+)")]
+ private static partial Regex ReYouTubeQueryVideo();
+
+ [GeneratedRegex(@"(^|&)list=([^&]+)")]
+ private static partial Regex ReYouTubeQueryList();
+
+ [GeneratedRegex(@"/embed/([^/]+)$")]
+ private static partial Regex ReYouTubePathId();
+
+ private static string GetYouTubeList(string query)
+ {
+ var match = ReYouTubeQueryList().Match(query);
+ if (match.Success)
+ {
+ return match.Groups[2].Value;
+ }
+
+ return null;
+ }
+
}
diff --git a/src/Docfx.MarkdigEngine.Extensions/ResolveLink/ResolveLinkExtension.cs b/src/Docfx.MarkdigEngine.Extensions/ResolveLink/ResolveLinkExtension.cs
index 321d577d9ad..6eab26c3c14 100644
--- a/src/Docfx.MarkdigEngine.Extensions/ResolveLink/ResolveLinkExtension.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/ResolveLink/ResolveLinkExtension.cs
@@ -49,7 +49,7 @@ private void UpdateLinks(MarkdownObject markdownObject)
}
break;
- case LeafBlock {Inline: not null} leafBlock:
+ case LeafBlock { Inline: not null } leafBlock:
foreach (var subInline in leafBlock.Inline)
{
UpdateLinks(subInline);
diff --git a/src/Docfx.MarkdigEngine.Extensions/Rewriter/MarkdownDocumentVisitor.cs b/src/Docfx.MarkdigEngine.Extensions/Rewriter/MarkdownDocumentVisitor.cs
index fa3e396666e..5d40f352a7d 100644
--- a/src/Docfx.MarkdigEngine.Extensions/Rewriter/MarkdownDocumentVisitor.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/Rewriter/MarkdownDocumentVisitor.cs
@@ -43,7 +43,7 @@ private void RewriteContainerBlock(ContainerBlock blocks)
for (var i = 0; i < blocks.Count; i++)
{
var block = blocks[i];
- if (block is LeafBlock {Inline: not null} leafBlock)
+ if (block is LeafBlock { Inline: not null } leafBlock)
{
RewriteContainerInline(leafBlock.Inline);
}
diff --git a/src/Docfx.MarkdigEngine.Extensions/TripleColon/ImageExtension.cs b/src/Docfx.MarkdigEngine.Extensions/TripleColon/ImageExtension.cs
index b8057c314ce..bf7d4efac61 100644
--- a/src/Docfx.MarkdigEngine.Extensions/TripleColon/ImageExtension.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/TripleColon/ImageExtension.cs
@@ -158,7 +158,7 @@ public bool Render(HtmlRenderer renderer, MarkdownObject obj, Action log
{
renderer.Write("
");
- if (tripleColonObj is ContainerBlock {LastChild: not null} block)
+ if (tripleColonObj is ContainerBlock { LastChild: not null } block)
{
var inline = (block.LastChild as ParagraphBlock).Inline;
renderer.WriteChildren(inline);
diff --git a/src/Docfx.MarkdigEngine.Extensions/TripleColon/TripleColonBlockParser.cs b/src/Docfx.MarkdigEngine.Extensions/TripleColon/TripleColonBlockParser.cs
index 69ed2936180..bc7b35ff8a1 100644
--- a/src/Docfx.MarkdigEngine.Extensions/TripleColon/TripleColonBlockParser.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/TripleColon/TripleColonBlockParser.cs
@@ -105,7 +105,7 @@ public override BlockState TryOpen(BlockProcessor processor)
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var slice = processor.Line;
- var colonBlock = (TripleColonBlock) block;
+ var colonBlock = (TripleColonBlock)block;
var endingTripleColons = colonBlock.EndingTripleColons;
Type type = ((TripleColonBlock)block).Extension.GetType();
diff --git a/src/Docfx.MarkdigEngine.Extensions/TripleColon/VideoExtension.cs b/src/Docfx.MarkdigEngine.Extensions/TripleColon/VideoExtension.cs
index cf6be57a68d..76900fae9bf 100644
--- a/src/Docfx.MarkdigEngine.Extensions/TripleColon/VideoExtension.cs
+++ b/src/Docfx.MarkdigEngine.Extensions/TripleColon/VideoExtension.cs
@@ -155,7 +155,7 @@ public bool Render(HtmlRenderer renderer, MarkdownObject markdownObject, Action<
renderer.WriteLine("");
renderer.Write($"");
renderer.WriteLine("
");
- if (tripleColonObj is ContainerBlock {LastChild: not null} block)
+ if (tripleColonObj is ContainerBlock { LastChild: not null } block)
{
var inline = (block.LastChild as ParagraphBlock).Inline;
renderer.WriteChildren(inline);
diff --git a/src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs b/src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs
index 3a7ebcf566a..4623b4b0476 100644
--- a/src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs
+++ b/src/Docfx.MarkdigEngine/MarkdigMarkdownService.cs
@@ -135,7 +135,7 @@ private MarkdownPipeline CreateMarkdownPipeline(bool isInline, bool multipleYaml
builder.UseInlineOnly();
}
- if (_parameters?.Extensions?.MarkdigExtensions is {Length: > 0} extensions)
+ if (_parameters?.Extensions?.MarkdigExtensions is { Length: > 0 } extensions)
{
builder.UseOptionalExtensions(extensions);
}
diff --git a/src/Docfx.YamlSerialization/Docfx.YamlSerialization.csproj b/src/Docfx.YamlSerialization/Docfx.YamlSerialization.csproj
index 4fdad984229..fa53905d37c 100644
--- a/src/Docfx.YamlSerialization/Docfx.YamlSerialization.csproj
+++ b/src/Docfx.YamlSerialization/Docfx.YamlSerialization.csproj
@@ -1,4 +1,8 @@
+
+ enable
+
+
diff --git a/src/Docfx.YamlSerialization/ExtensibleMemberAttribute.cs b/src/Docfx.YamlSerialization/ExtensibleMemberAttribute.cs
index 13c1eabe81a..f6d101faa74 100644
--- a/src/Docfx.YamlSerialization/ExtensibleMemberAttribute.cs
+++ b/src/Docfx.YamlSerialization/ExtensibleMemberAttribute.cs
@@ -9,12 +9,12 @@ public sealed class ExtensibleMemberAttribute : Attribute
public string Prefix { get; }
public ExtensibleMemberAttribute()
- : this(null)
+ : this(string.Empty)
{
}
public ExtensibleMemberAttribute(string prefix)
{
- Prefix = prefix ?? string.Empty;
+ Prefix = prefix;
}
}
diff --git a/src/Docfx.YamlSerialization/Helpers/ReflectionUtility.cs b/src/Docfx.YamlSerialization/Helpers/ReflectionUtility.cs
index 6c36e33d24b..da63545b1d6 100644
--- a/src/Docfx.YamlSerialization/Helpers/ReflectionUtility.cs
+++ b/src/Docfx.YamlSerialization/Helpers/ReflectionUtility.cs
@@ -5,7 +5,7 @@ namespace Docfx.YamlSerialization.Helpers;
internal static class ReflectionUtility
{
- public static Type GetImplementedGenericInterface(Type type, Type genericInterfaceType)
+ public static Type? GetImplementedGenericInterface(Type type, Type genericInterfaceType)
{
foreach (var interfaceType in GetImplementedInterfaces(type))
{
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
index 513d15c718e..0dc273ac031 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
@@ -13,11 +13,11 @@ namespace Docfx.YamlSerialization.NodeDeserializers;
public class EmitArrayNodeDeserializer : INodeDeserializer
{
private static readonly MethodInfo DeserializeHelperMethod =
- typeof(EmitArrayNodeDeserializer).GetMethod(nameof(DeserializeHelper));
- private static readonly ConcurrentDictionary, object>> _funcCache =
+ typeof(EmitArrayNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
+ private static readonly ConcurrentDictionary, object?>> _funcCache =
new();
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
{
if (!expectedType.IsArray)
{
@@ -31,22 +31,22 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func(IParser reader, Type expectedType, Func nestedObjectDeserializer)
+ public static TItem[] DeserializeHelper(IParser reader, Type expectedType, Func nestedObjectDeserializer)
{
var items = new List();
EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items);
return items.ToArray();
}
- private static Func, object> AddItem(Type expectedType)
+ private static Func, object?> AddItem(Type expectedType)
{
var dm = new DynamicMethod(string.Empty, typeof(object), [typeof(IParser), typeof(Type), typeof(Func)]);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
- il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(expectedType.GetElementType()));
+ il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(expectedType.GetElementType()!));
il.Emit(OpCodes.Ret);
- return (Func, object>)dm.CreateDelegate(typeof(Func, object>));
+ return (Func, object?>)dm.CreateDelegate(typeof(Func, object?>));
}
}
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
index 1863ba70ba9..791becbd3c7 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
@@ -17,11 +17,11 @@ namespace Docfx.YamlSerialization.NodeDeserializers;
public class EmitGenericCollectionNodeDeserializer : INodeDeserializer
{
private static readonly MethodInfo DeserializeHelperMethod =
- typeof(EmitGenericCollectionNodeDeserializer).GetMethod(nameof(DeserializeHelper));
+ typeof(EmitGenericCollectionNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private readonly IObjectFactory _objectFactory;
- private readonly Dictionary _gpCache =
+ private readonly Dictionary _gpCache =
new();
- private readonly Dictionary, object>> _actionCache =
+ private readonly Dictionary, object?>> _actionCache =
new();
public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory)
@@ -29,9 +29,9 @@ public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory)
_objectFactory = objectFactory;
}
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
{
- if (!_gpCache.TryGetValue(expectedType, out Type gp))
+ if (!_gpCache.TryGetValue(expectedType, out var gp))
{
var collectionType = ReflectionUtility.GetImplementedGenericInterface(expectedType, typeof(ICollection<>));
if (collectionType != null)
@@ -66,7 +66,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func).MakeGenericType(gp));
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(gp));
il.Emit(OpCodes.Ret);
- action = (Action, object>)dm.CreateDelegate(typeof(Action, object>));
+ action = (Action, object?>)dm.CreateDelegate(typeof(Action, object?>));
_actionCache[gp] = action;
}
@@ -75,13 +75,11 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func(IParser reader, Type expectedType, Func nestedObjectDeserializer, ICollection result)
+ public static void DeserializeHelper(IParser reader, Type expectedType, Func nestedObjectDeserializer, ICollection result)
{
reader.Consume();
while (!reader.Accept(out _))
{
- var current = reader.Current;
-
var value = nestedObjectDeserializer(reader, typeof(TItem));
if (value is not IValuePromise promise)
{
@@ -90,11 +88,12 @@ public static void DeserializeHelper(IParser reader, Type expectedType, F
else if (result is IList list)
{
var index = list.Count;
- result.Add(default);
+ result.Add(default!);
promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType(v, NullNamingConvention.Instance);
}
else
{
+ var current = reader.Current!;
throw new ForwardAnchorNotSupportedException(
current.Start,
current.End,
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
index 4094f4d174d..6d0fbc26658 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
@@ -14,11 +14,11 @@ namespace Docfx.YamlSerialization.NodeDeserializers;
public class EmitGenericDictionaryNodeDeserializer : INodeDeserializer
{
private static readonly MethodInfo DeserializeHelperMethod =
- typeof(EmitGenericDictionaryNodeDeserializer).GetMethod(nameof(DeserializeHelper));
+ typeof(EmitGenericDictionaryNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private readonly IObjectFactory _objectFactory;
- private readonly Dictionary _gpCache =
+ private readonly Dictionary _gpCache =
new();
- private readonly Dictionary, Action, object>> _actionCache =
+ private readonly Dictionary, Action, object?>> _actionCache =
new();
public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
@@ -26,9 +26,9 @@ public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
_objectFactory = objectFactory;
}
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
{
- if (!_gpCache.TryGetValue(expectedType, out Type[] gp))
+ if (!_gpCache.TryGetValue(expectedType, out var gp))
{
var dictionaryType = ReflectionUtility.GetImplementedGenericInterface(expectedType, typeof(IDictionary<,>));
if (dictionaryType != null)
@@ -67,7 +67,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func).MakeGenericType(gp));
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(gp));
il.Emit(OpCodes.Ret);
- action = (Action, object>)dm.CreateDelegate(typeof(Action, object>));
+ action = (Action, object?>)dm.CreateDelegate(typeof(Action, object?>));
_actionCache[cacheKey] = action;
}
action(reader, expectedType, nestedObjectDeserializer, value);
@@ -78,7 +78,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func(IParser reader, Type expectedType, Func nestedObjectDeserializer, IDictionary result)
+ public static void DeserializeHelper(IParser reader, Type expectedType, Func nestedObjectDeserializer, IDictionary result)
{
while (!reader.Accept(out _))
{
@@ -92,12 +92,12 @@ public static void DeserializeHelper(IParser reader, Type expected
if (valuePromise == null)
{
// Happy path: both key and value are known
- result[(TKey)key] = (TValue)value;
+ result[(TKey)key!] = (TValue)value!;
}
else
{
// Key is known, value is pending
- valuePromise.ValueAvailable += v => result[(TKey)key] = (TValue)v;
+ valuePromise.ValueAvailable += v => result[(TKey)key!] = (TValue)v!;
}
}
else
@@ -105,7 +105,7 @@ public static void DeserializeHelper(IParser reader, Type expected
if (valuePromise == null)
{
// Key is pending, value is known
- keyPromise.ValueAvailable += v => result[(TKey)v] = (TValue)value;
+ keyPromise.ValueAvailable += v => result[(TKey)v!] = (TValue)value!;
}
else
{
@@ -116,7 +116,7 @@ public static void DeserializeHelper(IParser reader, Type expected
{
if (hasFirstPart)
{
- result[(TKey)v] = (TValue)value;
+ result[(TKey)v!] = (TValue)value!;
}
else
{
@@ -129,7 +129,7 @@ public static void DeserializeHelper(IParser reader, Type expected
{
if (hasFirstPart)
{
- result[(TKey)key] = (TValue)v;
+ result[(TKey)key] = (TValue)v!;
}
else
{
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
index 0e0c4802005..24980f3120d 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
@@ -22,7 +22,7 @@ public ExtensibleObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspe
_ignoreUnmatched = ignoreUnmatched;
}
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
{
if (!reader.TryConsume(out _))
{
diff --git a/src/Docfx.YamlSerialization/NodeTypeResolvers/ScalarYamlNodeTypeResolver.cs b/src/Docfx.YamlSerialization/NodeTypeResolvers/ScalarYamlNodeTypeResolver.cs
index 8e7293e5781..30115993df1 100644
--- a/src/Docfx.YamlSerialization/NodeTypeResolvers/ScalarYamlNodeTypeResolver.cs
+++ b/src/Docfx.YamlSerialization/NodeTypeResolvers/ScalarYamlNodeTypeResolver.cs
@@ -9,11 +9,11 @@ namespace Docfx.YamlSerialization.NodeTypeResolvers;
internal sealed class ScalarYamlNodeTypeResolver : INodeTypeResolver
{
- bool INodeTypeResolver.Resolve(NodeEvent nodeEvent, ref Type currentType)
+ bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType)
{
if (currentType == typeof(string) || currentType == typeof(object))
{
- if (nodeEvent is Scalar {IsPlainImplicit: true} scalar)
+ if (nodeEvent is Scalar { IsPlainImplicit: true } scalar)
{
if (Regexes.BooleanLike().IsMatch(scalar.Value))
{
diff --git a/src/Docfx.YamlSerialization/ObjectDescriptors/BetterObjectDescriptor.cs b/src/Docfx.YamlSerialization/ObjectDescriptors/BetterObjectDescriptor.cs
index 0cde707a7b8..93e5f2a1d02 100644
--- a/src/Docfx.YamlSerialization/ObjectDescriptors/BetterObjectDescriptor.cs
+++ b/src/Docfx.YamlSerialization/ObjectDescriptors/BetterObjectDescriptor.cs
@@ -9,21 +9,21 @@ namespace Docfx.YamlSerialization.ObjectDescriptors;
public class BetterObjectDescriptor : IObjectDescriptor
{
- public BetterObjectDescriptor(object value, Type type, Type staticType)
+ public BetterObjectDescriptor(object? value, Type type, Type staticType)
: this(value, type, staticType, ScalarStyle.Any)
{
}
- public BetterObjectDescriptor(object value, Type type, Type staticType, ScalarStyle scalarStyle)
+ public BetterObjectDescriptor(object? value, Type type, Type staticType, ScalarStyle scalarStyle)
{
Value = value;
Type = type;
StaticType = staticType;
ScalarStyle = scalarStyle == ScalarStyle.Any && NeedQuote(value) ? ScalarStyle.DoubleQuoted : scalarStyle;
- static bool NeedQuote(object val)
+ static bool NeedQuote(object? val)
{
- if (val is not string s)
+ if (val is not string s || s == null)
return false;
return Regexes.BooleanLike().IsMatch(s)
@@ -42,5 +42,5 @@ static bool NeedQuote(object val)
public Type Type { get; }
- public object Value { get; }
+ public object? Value { get; }
}
diff --git a/src/Docfx.YamlSerialization/ObjectFactories/DefaultEmitObjectFactory.cs b/src/Docfx.YamlSerialization/ObjectFactories/DefaultEmitObjectFactory.cs
index 300a7cf9cbb..f5466f1d099 100644
--- a/src/Docfx.YamlSerialization/ObjectFactories/DefaultEmitObjectFactory.cs
+++ b/src/Docfx.YamlSerialization/ObjectFactories/DefaultEmitObjectFactory.cs
@@ -15,10 +15,10 @@ public class DefaultEmitObjectFactory : ObjectFactoryBase
public override object Create(Type type)
{
- if (!_cache.TryGetValue(type, out Func
-
@@ -27,16 +25,11 @@
-
-
diff --git a/templates/modern/partials/class.header.tmpl.partial b/templates/modern/partials/class.header.tmpl.partial
index 64168b21533..7f442c64c5f 100644
--- a/templates/modern/partials/class.header.tmpl.partial
+++ b/templates/modern/partials/class.header.tmpl.partial
@@ -139,7 +139,8 @@
{{#children}}
{{syntax.content.0.value}}
- - {{{summary}}}
+ {{#remarks}}- {{{summary}}}{{{remarks}}}
{{/remarks}}
+ {{^remarks}}- {{{summary}}}
{{/remarks}}
{{/children}}
{{/children}}
diff --git a/templates/modern/src/docfx.scss b/templates/modern/src/docfx.scss
index 67c36fb035f..a4a6d6d90ac 100644
--- a/templates/modern/src/docfx.scss
+++ b/templates/modern/src/docfx.scss
@@ -3,13 +3,12 @@
* The .NET Foundation licenses this file to you under the MIT license.
*/
-$enable-important-utilities: false;
-$container-max-widths: (
- xxl: 1768px
-) !default;
-
@use "mixins";
-@use "bootstrap/scss/bootstrap";
+@use "bootstrap/scss/bootstrap" with (
+ $container-max-widths: (
+ xxl: 1768px
+ )
+);
@use "highlight";
@use "layout";
@use "nav";
diff --git a/templates/package-lock.json b/templates/package-lock.json
index 81f6a513640..6637a0443d2 100644
--- a/templates/package-lock.json
+++ b/templates/package-lock.json
@@ -27,21 +27,21 @@
"lunr": "2.3.9",
"lunr-languages": "^1.14.0",
"mathjax": "^3.2.2",
- "mermaid": "^11.4.0",
+ "mermaid": "^11.4.1",
"tsx": "^4.19.2"
},
"devDependencies": {
"@types/lunr": "^2.3.7",
- "@typescript-eslint/eslint-plugin": "^8.13.0",
- "@typescript-eslint/parser": "^8.13.0",
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
+ "@typescript-eslint/parser": "^8.18.0",
"browser-sync": "^3.0.3",
"esbuild": "~0.24.0",
"esbuild-sass-plugin": "~3.3.1",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
- "stylelint": "^16.10.0",
- "stylelint-config-standard-scss": "^13.1.0",
- "typescript": "^5.6.3",
+ "stylelint": "^16.11.0",
+ "stylelint-config-standard-scss": "^14.0.0",
+ "typescript": "^5.7.2",
"yargs": "^17.7.2"
}
},
@@ -220,9 +220,9 @@
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.2.tgz",
- "integrity": "sha512-6tC/MnlEvs5suR4Ahef4YlBccJDHZuxGsAlxXmybWjZ5jPxlzLSMlRZ9mVHSRvlD+CmtE7+hJ+UQbfXrws/rUQ==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
+ "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
"dev": true,
"funding": [
{
@@ -234,17 +234,18 @@
"url": "https://opencollective.com/csstools"
}
],
+ "license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.2"
+ "@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@csstools/css-tokenizer": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.2.tgz",
- "integrity": "sha512-IuTRcD53WHsXPCZ6W7ubfGqReTJ9Ra0yRRFmXYP/Re8hFYYfoIYIK4080X5luslVLWimhIeFq0hj09urVMQzTw==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
+ "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
"dev": true,
"funding": [
{
@@ -256,37 +257,15 @@
"url": "https://opencollective.com/csstools"
}
],
+ "license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/media-query-list-parser": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz",
- "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.1",
- "@csstools/css-tokenizer": "^3.0.1"
- }
- },
- "node_modules/@csstools/selector-specificity": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz",
- "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz",
+ "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==",
"dev": true,
"funding": [
{
@@ -298,11 +277,13 @@
"url": "https://opencollective.com/csstools"
}
],
+ "license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
- "postcss-selector-parser": "^6.1.0"
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@default/anchor-js": {
@@ -891,14 +872,6 @@
"@types/d3-selection": "*"
}
},
- "node_modules/@types/dompurify": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
- "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
- "dependencies": {
- "@types/trusted-types": "*"
- }
- },
"node_modules/@types/geojson": {
"version": "7946.0.14",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
@@ -932,16 +905,16 @@
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz",
- "integrity": "sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
+ "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.13.0",
- "@typescript-eslint/type-utils": "8.13.0",
- "@typescript-eslint/utils": "8.13.0",
- "@typescript-eslint/visitor-keys": "8.13.0",
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/type-utils": "8.18.0",
+ "@typescript-eslint/utils": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -956,24 +929,20 @@
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.13.0.tgz",
- "integrity": "sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
+ "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "8.13.0",
- "@typescript-eslint/types": "8.13.0",
- "@typescript-eslint/typescript-estree": "8.13.0",
- "@typescript-eslint/visitor-keys": "8.13.0",
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/typescript-estree": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"debug": "^4.3.4"
},
"engines": {
@@ -984,22 +953,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz",
- "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
+ "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "8.13.0",
- "@typescript-eslint/visitor-keys": "8.13.0"
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1010,13 +975,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.13.0.tgz",
- "integrity": "sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
+ "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.13.0",
- "@typescript-eslint/utils": "8.13.0",
+ "@typescript-eslint/typescript-estree": "8.18.0",
+ "@typescript-eslint/utils": "8.18.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1027,16 +992,15 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz",
- "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
+ "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
"dev": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1047,13 +1011,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz",
- "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
+ "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "8.13.0",
- "@typescript-eslint/visitor-keys": "8.13.0",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/visitor-keys": "8.18.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -1068,22 +1032,20 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz",
- "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
+ "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.13.0",
- "@typescript-eslint/types": "8.13.0",
- "@typescript-eslint/typescript-estree": "8.13.0"
+ "@typescript-eslint/scope-manager": "8.18.0",
+ "@typescript-eslint/types": "8.18.0",
+ "@typescript-eslint/typescript-estree": "8.18.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1093,17 +1055,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz",
- "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
+ "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "8.13.0",
- "eslint-visitor-keys": "^3.4.3"
+ "@typescript-eslint/types": "8.18.0",
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1113,6 +1076,18 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -1872,12 +1847,13 @@
}
},
"node_modules/css-tree": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz",
- "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz",
+ "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "mdn-data": "2.10.0",
+ "mdn-data": "2.12.1",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -2546,9 +2522,13 @@
}
},
"node_modules/dompurify": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
- "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.1.tgz",
+ "integrity": "sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
},
"node_modules/easy-extender": {
"version": "2.3.4",
@@ -4720,10 +4700,11 @@
}
},
"node_modules/mdn-data": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz",
- "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==",
- "dev": true
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz",
+ "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==",
+ "dev": true,
+ "license": "CC0-1.0"
},
"node_modules/meow": {
"version": "13.2.0",
@@ -4747,15 +4728,15 @@
}
},
"node_modules/mermaid": {
- "version": "11.4.0",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.0.tgz",
- "integrity": "sha512-mxCfEYvADJqOiHfGpJXLs4/fAjHz448rH0pfY5fAoxiz70rQiDSzUUy4dNET2T08i46IVpjohPd6WWbzmRHiPA==",
+ "version": "11.4.1",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.1.tgz",
+ "integrity": "sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==",
+ "license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "^0.3.0",
"@types/d3": "^7.4.3",
- "@types/dompurify": "^3.0.5",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
@@ -4763,7 +4744,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.10",
- "dompurify": "^3.0.11 <3.1.7",
+ "dompurify": "^3.2.1",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -5261,9 +5242,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
@@ -5279,9 +5260,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -6427,9 +6409,9 @@
}
},
"node_modules/stylelint": {
- "version": "16.10.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz",
- "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==",
+ "version": "16.11.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.11.0.tgz",
+ "integrity": "sha512-zrl4IrKmjJQ+h9FoMp69UMCq5SxeHk0URhxUBj4d3ISzo/DplOFBJZc7t7Dr6otB+1bfbbKNLOmCDpzKSlW+Nw==",
"dev": true,
"funding": [
{
@@ -6441,17 +6423,18 @@
"url": "https://github.com/sponsors/stylelint"
}
],
+ "license": "MIT",
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.1",
- "@csstools/css-tokenizer": "^3.0.1",
- "@csstools/media-query-list-parser": "^3.0.1",
- "@csstools/selector-specificity": "^4.0.0",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "@csstools/media-query-list-parser": "^4.0.2",
+ "@csstools/selector-specificity": "^5.0.0",
"@dual-bundle/import-meta-resolve": "^4.1.0",
"balanced-match": "^2.0.0",
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
"css-functions-list": "^3.2.3",
- "css-tree": "^3.0.0",
+ "css-tree": "^3.0.1",
"debug": "^4.3.7",
"fast-glob": "^3.3.2",
"fastest-levenshtein": "^1.0.16",
@@ -6463,16 +6446,16 @@
"ignore": "^6.0.2",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
- "known-css-properties": "^0.34.0",
+ "known-css-properties": "^0.35.0",
"mathml-tag-names": "^2.1.3",
"meow": "^13.2.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
- "picocolors": "^1.0.1",
- "postcss": "^8.4.47",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.49",
"postcss-resolve-nested-selector": "^0.1.6",
"postcss-safe-parser": "^7.0.1",
- "postcss-selector-parser": "^6.1.2",
+ "postcss-selector-parser": "^7.0.0",
"postcss-value-parser": "^4.2.0",
"resolve-from": "^5.0.0",
"string-width": "^4.2.3",
@@ -6559,20 +6542,21 @@
}
},
"node_modules/stylelint-config-standard-scss": {
- "version": "13.1.0",
- "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-13.1.0.tgz",
- "integrity": "sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-14.0.0.tgz",
+ "integrity": "sha512-6Pa26D9mHyi4LauJ83ls3ELqCglU6VfCXchovbEqQUiEkezvKdv6VgsIoMy58i00c854wVmOw0k8W5FTpuaVqg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "stylelint-config-recommended-scss": "^14.0.0",
- "stylelint-config-standard": "^36.0.0"
+ "stylelint-config-recommended-scss": "^14.1.0",
+ "stylelint-config-standard": "^36.0.1"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"postcss": "^8.3.3",
- "stylelint": "^16.3.1"
+ "stylelint": "^16.11.0"
},
"peerDependenciesMeta": {
"postcss": {
@@ -6602,11 +6586,28 @@
"stylelint": "^16.0.2"
}
},
- "node_modules/stylelint-scss/node_modules/mdn-data": {
- "version": "2.11.1",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.11.1.tgz",
- "integrity": "sha512-Hdx3wmyqPFrhd6YHVuSkUK2eIGAcxR0xlndcgZqjA68yMJTbfXrjJwbgsBOsNjI7LnBIVUQnmyMVSdi/ob0GpQ==",
- "dev": true
+ "node_modules/stylelint/node_modules/@csstools/selector-specificity": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
+ "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss-selector-parser": "^7.0.0"
+ }
},
"node_modules/stylelint/node_modules/balanced-match": {
"version": "2.0.0",
@@ -6648,6 +6649,27 @@
"node": ">= 4"
}
},
+ "node_modules/stylelint/node_modules/known-css-properties": {
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
+ "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stylelint/node_modules/postcss-selector-parser": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
+ "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/stylelint/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@@ -6991,9 +7013,9 @@
}
},
"node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+ "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
diff --git a/templates/package.json b/templates/package.json
index 55dd78f663d..87021359ac8 100644
--- a/templates/package.json
+++ b/templates/package.json
@@ -36,21 +36,21 @@
"lunr": "2.3.9",
"lunr-languages": "^1.14.0",
"mathjax": "^3.2.2",
- "mermaid": "^11.4.0",
+ "mermaid": "^11.4.1",
"tsx": "^4.19.2"
},
"devDependencies": {
"@types/lunr": "^2.3.7",
- "@typescript-eslint/eslint-plugin": "^8.13.0",
- "@typescript-eslint/parser": "^8.13.0",
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
+ "@typescript-eslint/parser": "^8.18.0",
"browser-sync": "^3.0.3",
"esbuild": "~0.24.0",
"esbuild-sass-plugin": "~3.3.1",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
- "stylelint": "^16.10.0",
- "stylelint-config-standard-scss": "^13.1.0",
- "typescript": "^5.6.3",
+ "stylelint": "^16.11.0",
+ "stylelint-config-standard-scss": "^14.0.0",
+ "typescript": "^5.7.2",
"yargs": "^17.7.2"
}
}
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index 1ddd0557ab1..5fbeb4b8bcb 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -13,6 +13,8 @@
false
+
+
true
@@ -30,8 +32,6 @@
$(TestingPlatformCommandLineArguments) --ignore-exit-code 8
-
-
@@ -49,15 +49,11 @@
$(MSBuildThisFileDirectory)TestResults
- $(VSTestLogger);trx%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework).trx
- $(VSTestLogger);html%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework).html
+ $(VSTestLogger);trx%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).trx
+ $(VSTestLogger);html%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).html
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props
new file mode 100644
index 00000000000..3fca3350206
--- /dev/null
+++ b/test/Directory.Packages.props
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/QuoteSectionNoteTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/QuoteSectionNoteTest.cs
index de4d5279a1d..43a42ff810e 100644
--- a/test/Docfx.MarkdigEngine.Extensions.Tests/QuoteSectionNoteTest.cs
+++ b/test/Docfx.MarkdigEngine.Extensions.Tests/QuoteSectionNoteTest.cs
@@ -421,6 +421,19 @@ public void TestVideoBlock_Normal()
TestUtility.VerifyMarkup(source, expected);
}
+ [Fact]
+ [Trait("Related", "DfmMarkdown")]
+ public void TestVideoBlock_Http()
+ {
+ var source = @"# Article 2
+> [!VIDEO http://microsoft.com:8080?query=http+A#bookmark]
+";
+ var expected = @"Article 2
+
+";
+ TestUtility.VerifyMarkup(source, expected);
+ }
+
[Fact]
[Trait("Related", "DfmMarkdown")]
public void TestVideoBlock_Channel9()
diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/VideoTest.cs b/test/Docfx.MarkdigEngine.Extensions.Tests/VideoTest.cs
index 544216da32f..f09d0034673 100644
--- a/test/Docfx.MarkdigEngine.Extensions.Tests/VideoTest.cs
+++ b/test/Docfx.MarkdigEngine.Extensions.Tests/VideoTest.cs
@@ -13,14 +13,18 @@ public class VideoTest
")]
[InlineData(@":::video source=""https://www.youtube.com/embed/wV11_nbT2XE"" title=""Video: Build-Your-First-Android-App-with-Visual-Studio-2019-and-Xamarin"" thumbnail=""media/3-eclipse-install-button.png"" upload-date=""07/27/2020"":::", @"
-
+
+
+")]
+ [InlineData(@":::video source=""https://www.youtube.com/embed/wV11_nbT2XE?rel=1"" title=""Video: Build-Your-First-Android-App-with-Visual-Studio-2019-and-Xamarin"" thumbnail=""media/3-eclipse-install-button.png"" upload-date=""07/27/2020"":::", @"
+
")]
[InlineData(
@":::video source=""https://www.youtube.com/embed/wV11_nbT2XE"" title=""Video: Build-Your-First-Android-App-with-Visual-Studio-2019-and-Xamarin"" thumbnail=""media/3-eclipse-install-button.png"" upload-date=""07/27/2020"":::
:::video source=""https://channel9.msdn.com/Shows/XamarinShow/Build-Your-First-Android-App-with-Visual-Studio-2019-and-Xamarin/player?nocookie=true"" title=""Video: Build-Your-First-Android-App-with-Visual-Studio-2019-and-Xamarin"" max-width=""400"" thumbnail=""media/3-eclipse-install-button.png"" upload-date=""07/27/2020"":::
", @"
-