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 | |
| 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')
| -rw-r--r-- | docs/DEVELOPMENT.md | 10 | ||||
| -rw-r--r-- | docs/LIMITATIONS.md | 6 | ||||
| -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 |
8 files changed, 198 insertions, 15 deletions
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 608411f..022af77 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -118,11 +118,15 @@ version, so a local run and CI use the same tool. ```text dotnet tool restore dotnet docfx docs/api/docfx.json --warningsAsErrors +dotnet run docs/api/CheckDocumentation.cs -- docs/api ``` -or `just api-docs`. The output goes to `docs/api/_site`, which is ignored by Git. A broken link or an unresolved -reference fails the build. The gate does not yet require documentation comments on the C# declarations; see -[Known limitations](LIMITATIONS.md). +or `just api-docs`. The output goes to `docs/api/_site`, which is ignored by Git. + +The gate fails on a broken link or an unresolved reference, on a public declaration without a documentation comment, +and on any namespace, type or member of the reference that has no summary, internal and private ones included. A new +declaration therefore needs its `<summary>` in the same change. [The API reference page](api/index.md) describes the +three parts of the gate and how a namespace is described. ## Conventions diff --git a/docs/LIMITATIONS.md b/docs/LIMITATIONS.md index 6e6eed5..6d09e7d 100644 --- a/docs/LIMITATIONS.md +++ b/docs/LIMITATIONS.md @@ -56,12 +56,6 @@ that route moves here, the legacy process has to keep running. The authentication rate limit is counted in the memory of one API process, by client address. It is reset by a restart and would not be shared between several instances. -## Documentation comments are not required - -The DocFX gate fails on a broken link or an unresolved reference. It does not report a declaration without a -documentation comment, and most C# declarations have none, so the generated reference lists members without -descriptions. - ## The Hebrew text has not been reviewed by a native speaker The Hebrew translation was written and revised without a native reader. It may contain wording a native speaker would 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. |