diff --git a/.github/workflows/analyzers.yml b/.github/workflows/analyzers.yml index f624671c..303d4102 100644 --- a/.github/workflows/analyzers.yml +++ b/.github/workflows/analyzers.yml @@ -106,3 +106,19 @@ jobs: # does not depend on the trailing diagnostic-id list, which is a Roslyn-version formatting detail. working-directory: tools/floor-check run: grep -qE 'FirstClassErrors\.Analyzers\.[A-Za-z]+Analyzer' build.log + + readme-count: + name: Guard the advertised analyzer count + runs-on: ubuntu-latest + # Reads two tracked files with a POSIX shell; seconds. Cap a stuck runner. + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + # Derive the count and FCExxx range from DiagnosticIds.cs (the source of + # truth) and fail if the package README's flagship "N Roslyn analyzers in + # the box (FCE001-FCENNN)" bullet disagrees — the claim that drifted to a + # stale 16 while the code shipped 22. + - name: Check the package README count against DiagnosticIds.cs + run: tools/analyzer-count-check/check-analyzer-count.sh diff --git a/FirstClassErrors.Cli/README.nuget.md b/FirstClassErrors.Cli/README.nuget.md index 9a86794c..c71a862d 100644 --- a/FirstClassErrors.Cli/README.nuget.md +++ b/FirstClassErrors.Cli/README.nuget.md @@ -33,9 +33,18 @@ format does not need it. Other commands: +- `fce catalog update` — create or refresh the committed catalog baseline (deliberately accept the current contract) +- `fce catalog diff` — compare the catalog against the baseline and report the changes - `fce config init` — create an `fce.json` so options need not be repeated on every run -- `fce config show` — print the resolved configuration -- `fce renderer add|list|remove` — manage custom renderer libraries +- `fce config show` — print the raw configuration file (no defaults merged in) +- `fce config renderer add|remove|list` — manage custom renderer libraries + +`fce catalog update`/`diff` track your error catalog as a versioned contract, so a breaking +change to your errors is caught in review rather than in production — see the +catalog-versioning guides: +[overview](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioning.en.md), +[command reference](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioningReference.en.md), +[CI/CD integration](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioningCI.en.md) Run `fce --help` or `fce generate --help` for the full option list. diff --git a/FirstClassErrors.Cli/RendererAddCommand.cs b/FirstClassErrors.Cli/RendererAddCommand.cs index eec95c21..b0631ddd 100644 --- a/FirstClassErrors.Cli/RendererAddCommand.cs +++ b/FirstClassErrors.Cli/RendererAddCommand.cs @@ -12,7 +12,7 @@ namespace FirstClassErrors.Cli; -/// Settings for fce renderer add <path>. +/// Settings for fce config renderer add <path>. internal sealed class RendererReferenceSettings : ConfigScopedSettings { [CommandArgument(0, "")] diff --git a/FirstClassErrors.Cli/RendererRemoveCommand.cs b/FirstClassErrors.Cli/RendererRemoveCommand.cs index 9f20408a..850c2f8e 100644 --- a/FirstClassErrors.Cli/RendererRemoveCommand.cs +++ b/FirstClassErrors.Cli/RendererRemoveCommand.cs @@ -16,7 +16,7 @@ protected override int Execute(CommandContext context, RendererReferenceSettings string path = ConfigurationStore.Resolve(settings.ConfigPath); if (!ConfigurationStore.Exists(path)) { - Console.Error.WriteLine($"error: no configuration at '{path}'. Run 'fce init' first."); + Console.Error.WriteLine($"error: no configuration at '{path}'. Run 'fce config init' first."); return 1; } diff --git a/FirstClassErrors/README.nuget.md b/FirstClassErrors/README.nuget.md index 8cec5349..ad75f4f5 100644 --- a/FirstClassErrors/README.nuget.md +++ b/FirstClassErrors/README.nuget.md @@ -27,7 +27,7 @@ checked at build time and can be extracted into a living error catalog. opt-in per project, multi-language) with the companion `fce` tool. Run it in CI so the catalog is published as a build artifact and never drifts from the deployed system. See *Generate the catalog in CI* below. -- **16 Roslyn analyzers in the box (`FCE001`–`FCE016`).** Bundled in the package — no +- **22 Roslyn analyzers in the box (`FCE001`–`FCE022`).** Bundled in the package — no separate install. They catch, at build time, what would otherwise surface late: duplicate error codes, unresolved `[DocumentedBy]` references, documented errors missing from the catalog, an unused `ToException()` result, and more. @@ -73,6 +73,8 @@ The documentation generator ships as a .NET tool, [`FirstClassErrors.Cli`](https://www.nuget.org/packages/FirstClassErrors.Cli), so a pipeline can produce the error catalog as a build artifact: +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the commands below will work once it ships. + dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release fce generate --solution MyApp.sln --no-build \ diff --git a/README.md b/README.md index e6c1ba7a..073f6cf3 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,16 @@ To bind incoming requests into typed commands or queries at the primary-adapter dotnet add package FirstClassErrors.RequestBinder ``` +> **Not yet on nuget.org.** `FirstClassErrors.RequestBinder` is not published yet — the command above will work once it ships. + To generate documentation, install the CLI: ```bash dotnet tool install --global FirstClassErrors.Cli ``` +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the command above will work once it ships. + Then follow the [Getting Started guide](doc/handwritten/for-users/GettingStarted.en.md) to create and generate your first documented error. ## 🎯 When it is a good fit diff --git a/doc/handwritten/for-users/GettingStarted.en.md b/doc/handwritten/for-users/GettingStarted.en.md index 02c54670..6f776cfc 100644 --- a/doc/handwritten/for-users/GettingStarted.en.md +++ b/doc/handwritten/for-users/GettingStarted.en.md @@ -27,6 +27,8 @@ Install the documentation CLI once on your machine: dotnet tool install --global FirstClassErrors.Cli ``` +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the command above will work once it ships. + ## 2. Opt the project into documentation generation Add this property directly to the project file that contains your errors: diff --git a/doc/handwritten/for-users/GettingStarted.fr.md b/doc/handwritten/for-users/GettingStarted.fr.md index 2af2c6c2..75b06d58 100644 --- a/doc/handwritten/for-users/GettingStarted.fr.md +++ b/doc/handwritten/for-users/GettingStarted.fr.md @@ -27,6 +27,8 @@ Installez une fois le CLI de documentation sur votre machine : dotnet tool install --global FirstClassErrors.Cli ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.Cli` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + ## 2. Activer la génération pour le projet Ajoutez cette propriété directement dans le fichier du projet qui contient vos erreurs : diff --git a/doc/handwritten/for-users/OperationalIntegration.en.md b/doc/handwritten/for-users/OperationalIntegration.en.md index fc756252..25b89bad 100644 --- a/doc/handwritten/for-users/OperationalIntegration.en.md +++ b/doc/handwritten/for-users/OperationalIntegration.en.md @@ -44,6 +44,8 @@ For ambiguous declarations, project discovery, and worker behavior, see [Archite Install the CLI, build, then generate from the existing binaries: +> **Not yet on nuget.org.** The `fce` CLI (`FirstClassErrors.Cli`) is not published yet — the install commands on this page (here and in the CI example below) will work once it ships. + ```bash dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release diff --git a/doc/handwritten/for-users/OperationalIntegration.fr.md b/doc/handwritten/for-users/OperationalIntegration.fr.md index 4e00a13b..9457dae8 100644 --- a/doc/handwritten/for-users/OperationalIntegration.fr.md +++ b/doc/handwritten/for-users/OperationalIntegration.fr.md @@ -44,6 +44,8 @@ Pour les déclarations ambiguës, la découverte de projets et le fonctionnement Installez le CLI, compilez, puis générez depuis les binaires existants : +> **Pas encore sur nuget.org.** Le CLI `fce` (`FirstClassErrors.Cli`) n'est pas encore publié — les commandes d'installation de cette page (ici et dans l'exemple CI ci-dessous) fonctionneront une fois qu'il sera disponible. + ```bash dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release diff --git a/doc/handwritten/for-users/README.fr.md b/doc/handwritten/for-users/README.fr.md index 1f016da6..d89309ca 100644 --- a/doc/handwritten/for-users/README.fr.md +++ b/doc/handwritten/for-users/README.fr.md @@ -118,12 +118,16 @@ Pour lier les requêtes entrantes en commandes ou requêtes typées à la fronti dotnet add package FirstClassErrors.RequestBinder ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.RequestBinder` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Pour générer la documentation, installez le CLI : ```bash dotnet tool install --global FirstClassErrors.Cli ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.Cli` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Suivez ensuite le guide [Premiers pas](GettingStarted.fr.md) pour créer et générer votre première erreur documentée. ## 🎯 Quand la bibliothèque est adaptée diff --git a/doc/handwritten/for-users/RequestBinder.en.md b/doc/handwritten/for-users/RequestBinder.en.md index ca954173..04331288 100644 --- a/doc/handwritten/for-users/RequestBinder.en.md +++ b/doc/handwritten/for-users/RequestBinder.en.md @@ -42,6 +42,8 @@ single envelope error carrying all of them. dotnet add package FirstClassErrors.RequestBinder ``` +> **Not yet on nuget.org.** `FirstClassErrors.RequestBinder` is not published yet — the command above will work once it ships. + It targets **.NET Standard 2.0** and ships on the same release train as `FirstClassErrors`, at the same version — so the two always resolve together. diff --git a/doc/handwritten/for-users/RequestBinder.fr.md b/doc/handwritten/for-users/RequestBinder.fr.md index ae494417..a736fe63 100644 --- a/doc/handwritten/for-users/RequestBinder.fr.md +++ b/doc/handwritten/for-users/RequestBinder.fr.md @@ -44,6 +44,8 @@ unique qui les porte toutes. dotnet add package FirstClassErrors.RequestBinder ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.RequestBinder` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Il cible **.NET Standard 2.0** et voyage sur le même train de release que `FirstClassErrors`, à la même version — les deux se résolvent donc toujours ensemble. diff --git a/tools/analyzer-count-check/check-analyzer-count.sh b/tools/analyzer-count-check/check-analyzer-count.sh new file mode 100755 index 00000000..01da5364 --- /dev/null +++ b/tools/analyzer-count-check/check-analyzer-count.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# Guard the analyzer count advertised in the package README against the real set +# of diagnostics, so the storefront claim can never silently drift again (it did: +# the bullet sat at "16 (FCE001-FCE016)" long after the code reached FCE022). +# +# Source of truth: FirstClassErrors.Analyzers/DiagnosticIds.cs — every FCExxx +# constant it declares is one shipped diagnostic. The flagship bullet in +# FirstClassErrors/README.nuget.md advertises both a count and a range: +# +# **22 Roslyn analyzers in the box (`FCE001`-`FCE022`).** +# +# This check derives the count and the low/high ids from DiagnosticIds.cs and +# fails if the README's number or either range endpoint disagrees. Numbering is +# a stable handle, not a contiguous contract (see the DiagnosticIds.cs summary), +# so the count is the number of declared ids and the range is their numeric +# min-max — a retired id in the middle keeps this correct. +# +# Usage: tools/analyzer-count-check/check-analyzer-count.sh +# Exit: 0 = README matches the code, 1 = drift found, 2 = usage/parse error. +# +# Runs in the CI check (.github/workflows/analyzers.yml). It reads only two +# tracked files and needs no toolchain, so it runs anywhere a POSIX shell does. + +set -u + +# --- locate the repo files, independent of the caller's CWD ------------------- +script_dir=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$script_dir/../.." && pwd) +ids_file="$root/FirstClassErrors.Analyzers/DiagnosticIds.cs" +readme="$root/FirstClassErrors/README.nuget.md" + +fail() { printf 'analyzer-count-check: %s\n' "$1" >&2; exit "${2:-1}"; } + +[ -f "$ids_file" ] || fail "diagnostic ids not found: $ids_file" 2 +[ -f "$readme" ] || fail "package README not found: $readme" 2 + +# --- source of truth: the declared FCExxx ids -------------------------------- +ids=$(grep -oE '"FCE[0-9]+"' "$ids_file" | tr -d '"' | sort -u) +[ -n "$ids" ] || fail "no FCExxx constants found in $ids_file" 2 + +count=$(printf '%s\n' "$ids" | grep -c .) +low_num=$(printf '%s\n' "$ids" | sed -E 's/^FCE0*//' | sort -n | head -1) +high_num=$(printf '%s\n' "$ids" | sed -E 's/^FCE0*//' | sort -n | tail -1) +low=$(printf 'FCE%03d' "$low_num") +high=$(printf 'FCE%03d' "$high_num") + +# --- the claim advertised in the README -------------------------------------- +claim=$(grep -E 'Roslyn analyzers in the box' "$readme" | head -1) +[ -n "$claim" ] || fail "no 'N Roslyn analyzers in the box (...)' claim found in $readme" 2 + +claimed_count=$(printf '%s\n' "$claim" | grep -oE '[0-9]+ Roslyn analyzers in the box' | grep -oE '^[0-9]+') +claimed_codes=$(printf '%s\n' "$claim" | grep -oE 'FCE[0-9]+') +claimed_low=$(printf '%s\n' "$claimed_codes" | head -1) +claimed_high=$(printf '%s\n' "$claimed_codes" | tail -1) + +# --- compare ----------------------------------------------------------------- +status=0 +if [ "${claimed_count:-}" != "$count" ]; then + printf 'analyzer-count-check: README says %s analyzers, DiagnosticIds.cs declares %s\n' \ + "${claimed_count:-?}" "$count" >&2 + status=1 +fi +if [ "${claimed_low:-}" != "$low" ] || [ "${claimed_high:-}" != "$high" ]; then + printf 'analyzer-count-check: README range (%s-%s) does not match the code range (%s-%s)\n' \ + "${claimed_low:-?}" "${claimed_high:-?}" "$low" "$high" >&2 + status=1 +fi + +if [ "$status" -ne 0 ]; then + printf '\nUpdate the bullet in FirstClassErrors/README.nuget.md to read:\n' >&2 + printf ' **%s Roslyn analyzers in the box (`%s`-`%s`).**\n' "$count" "$low" "$high" >&2 + exit 1 +fi + +printf 'analyzer-count-check: OK - %s analyzers (%s-%s), README matches DiagnosticIds.cs\n' \ + "$count" "$low" "$high"