diff options
| author | Leitwolf11 | 2026-10-03 18:46:59 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-03 18:46:59 +0300 |
| commit | b84a2216c26cc5392d0dc300b7a605ccb288d8c8 (patch) | |
| tree | 048ae623f49cc5f2f94234d747351f9e659f5267 /docs/api | |
| parent | ec774db15976385a6d5ea6f48d3622fe507d3843 (diff) | |
| parent | cf444eb3bccf99c10f59ed5330b61c0397e0aeaa (diff) | |
| download | progmaweb-fix/api-without-google.tar.gz | |
Merge remote-tracking branch 'origin/main' into fix/api-without-googlefix/api-without-google
Diffstat (limited to 'docs/api')
| -rw-r--r-- | docs/api/CheckDocumentation.cs | 118 | ||||
| -rw-r--r-- | docs/api/docfx.json | 22 | ||||
| -rw-r--r-- | docs/api/filterConfig.yml | 14 | ||||
| -rw-r--r-- | docs/api/index.md | 23 | ||||
| -rw-r--r-- | docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md | 10 | ||||
| -rw-r--r-- | docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md | 10 |
6 files changed, 191 insertions, 6 deletions
diff --git a/docs/api/CheckDocumentation.cs b/docs/api/CheckDocumentation.cs new file mode 100644 index 0000000..f359661 --- /dev/null +++ b/docs/api/CheckDocumentation.cs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +// Documentation coverage gate of the API reference. +// +// DocFX builds a reference from whatever comments exist and does not report a declaration that has none. This +// program reads the metadata DocFX generated and fails when a namespace, type or member is undocumented, so the +// reference cannot contain an entry without a description. +// +// Usage, after `dotnet docfx metadata docs/api/docfx.json`: +// +// dotnet run docs/api/CheckDocumentation.cs -- docs/api +// +// The exit code is 0 when everything is documented, 1 when something is not, and 2 when the metadata is missing. + +using System.Text.RegularExpressions; + +string root = args.Length > 0 ? args[0] : "docs/api"; +string referenceDirectory = Path.Combine(root, "reference"); +string namespaceDirectory = Path.Combine(root, "namespaces"); + +if (!Directory.Exists(referenceDirectory)) +{ + Console.Error.WriteLine($"No metadata in '{referenceDirectory}'. Run 'dotnet docfx metadata' first."); + return 2; +} + +// A namespace has no declaration that could carry a comment. Its description is an overwrite file named after it. +HashSet<string> describedNamespaces = Directory.Exists(namespaceDirectory) + ? Directory.EnumerateFiles(namespaceDirectory, "*.md") + .Where(HasDescription) + .Select(path => Path.GetFileNameWithoutExtension(path)!) + .ToHashSet(StringComparer.Ordinal) + : []; + +List<string> undocumented = []; +int checkedItems = 0; + +foreach (string file in Directory.EnumerateFiles(referenceDirectory, "*.yml").Order(StringComparer.Ordinal)) +{ + if (Path.GetFileName(file) == "toc.yml") + { + continue; + } + + // The items a file defines come first; the `references` section after them repeats names of other files. + string text = File.ReadAllText(file).ReplaceLineEndings("\n"); + int references = text.IndexOf("\nreferences:", StringComparison.Ordinal); + string definitions = references < 0 ? text : text[..references]; + + foreach (string item in ItemStart().Split(definitions).Skip(1)) + { + string uid = item[..item.IndexOf('\n')].Trim(); + string type = ItemType().Match(item) is { Success: true } match ? match.Groups[1].Value : "Item"; + checkedItems++; + + bool documented = type == "Namespace" + ? describedNamespaces.Contains(uid) + : ItemSummary().IsMatch(item); + if (!documented) + { + undocumented.Add($"{type} {uid}"); + } + } +} + +if (checkedItems == 0) +{ + Console.Error.WriteLine($"The metadata in '{referenceDirectory}' defines nothing; the gate would pass vacuously."); + return 2; +} + +if (undocumented.Count != 0) +{ + Console.Error.WriteLine($"{undocumented.Count} of {checkedItems} API items have no documentation:"); + foreach (string entry in undocumented) + { + Console.Error.WriteLine($" {entry}"); + } + + Console.Error.WriteLine("Add a <summary> to each declaration, or a file in docs/api/namespaces for a namespace."); + return 1; +} + +Console.WriteLine($"All {checkedItems} API items are documented."); +return 0; + +// An overwrite file describes its namespace when it has text after its front matter and comment header. +static bool HasDescription(string path) +{ + string text = File.ReadAllText(path).ReplaceLineEndings("\n"); + int frontMatterEnd = text.IndexOf("\n---", 3, StringComparison.Ordinal); + if (!text.StartsWith("---\n", StringComparison.Ordinal) || frontMatterEnd < 0) + { + return false; + } + + string body = HtmlComment().Replace(text[(frontMatterEnd + 4)..], string.Empty); + return text[..frontMatterEnd].Contains($"uid: {Path.GetFileNameWithoutExtension(path)}", StringComparison.Ordinal) + && !string.IsNullOrWhiteSpace(body); +} + +internal static partial class Program +{ + // Every defined item starts with its identifier at the left margin. + [GeneratedRegex(@"^- uid: ", RegexOptions.Multiline)] + private static partial Regex ItemStart(); + + [GeneratedRegex(@"^ type: (\w+)", RegexOptions.Multiline)] + private static partial Regex ItemType(); + + // A summary with content: text on the same line, or a block scalar followed by an indented line. + [GeneratedRegex(@"^ summary: (?:[^\s>|].*|[>|][-+]?\n \S)", RegexOptions.Multiline)] + private static partial Regex ItemSummary(); + + [GeneratedRegex(@"<!--.*?-->", RegexOptions.Singleline)] + private static partial Regex HtmlComment(); +} diff --git a/docs/api/docfx.json b/docs/api/docfx.json index e10a9b0..eb2ffe7 100644 --- a/docs/api/docfx.json +++ b/docs/api/docfx.json @@ -4,23 +4,37 @@ "src": [ { "src": "../../apps/api", - "files": ["Progmaweb.Api.csproj"] + "files": [ + "Progmaweb.Api.csproj" + ] } ], "dest": "reference", - "includePrivateMembers": true + "includePrivateMembers": true, + "filter": "filterConfig.yml" } ], "build": { "content": [ { - "files": ["index.md", "toc.yml", "reference/**/*.yml"] + "files": [ + "index.md", + "toc.yml", + "reference/**/*.yml" + ] } ], "dest": "_site", "globalMetadata": { "_appTitle": "Progmaweb API", "_enableSearch": false - } + }, + "overwrite": [ + { + "files": [ + "namespaces/*.md" + ] + } + ] } } diff --git a/docs/api/filterConfig.yml b/docs/api/filterConfig.yml new file mode 100644 index 0000000..5b7b327 --- /dev/null +++ b/docs/api/filterConfig.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +# Code that a source generator writes at build time is not part of the +# reference: nobody maintains it, and it cannot carry documentation. +apiRules: + - exclude: + uidRegex: ^System\.Text\.RegularExpressions\.Generated + # The regular-expression generator also writes the body of this partial + # method, and its generated comment replaces the one on the declaration, so + # the summary in the source never reaches the reference. + - exclude: + uidRegex: ^Progmasoft\.Progmaweb\.Api\.Accounts\.AccountNamePolicy\.CanonicalPattern$ + type: Method diff --git a/docs/api/index.md b/docs/api/index.md index 868fff1..71b532b 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -12,8 +12,27 @@ The service is an application, not a library, so most of its types are internal. The reference includes them: it documents the code for the people who maintain it. -Build it from the repository root: +Build and check it from the repository root: ```sh -docfx docs/api/docfx.json --warningsAsErrors +dotnet tool restore +dotnet docfx docs/api/docfx.json --warningsAsErrors +dotnet run docs/api/CheckDocumentation.cs -- docs/api ``` + +## What the gate requires + +Three things fail the check, locally and in CI: + +- **A broken link or an unresolved reference.** DocFX runs with every warning treated as an error. +- **An undocumented public declaration or a wrong `cref`.** The API project compiles its XML documentation, and the + build treats the compiler's documentation warnings as errors. +- **Any undocumented item of the reference.** `CheckDocumentation.cs` reads the metadata DocFX generated and fails + when a namespace, type or member has no summary. This covers internal and private members too, which the compiler + does not check. + +A namespace has no declaration that could hold a comment. Its description is a file in `namespaces/`, named after +the namespace. + +`filterConfig.yml` lists what is left out of the reference: code written by a source generator, which nobody +maintains and which cannot carry documentation. Do not add an entry there to get past the gate; write the summary. diff --git a/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md new file mode 100644 index 0000000..926cd64 --- /dev/null +++ b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md @@ -0,0 +1,10 @@ +--- +uid: Progmasoft.Progmaweb.Api.Accounts +summary: *content +--- + +<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> --> +<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 --> + +Accounts and sessions: the HTTP endpoints, the rules for account names, email addresses and passwords, the +registration and sign-in logic, and the store that keeps accounts and session digests. diff --git a/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md new file mode 100644 index 0000000..c4cf45a --- /dev/null +++ b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md @@ -0,0 +1,10 @@ +--- +uid: Progmasoft.Progmaweb.Api.Security +summary: *content +--- + +<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> --> +<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 --> + +Request-pipeline policy that is not specific to accounts: which forwarded headers the service trusts and which +security headers every response carries. |