Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/analyzers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 11 additions & 2 deletions FirstClassErrors.Cli/README.nuget.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion FirstClassErrors.Cli/RendererAddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace FirstClassErrors.Cli;

/// <summary>Settings for <c>fce renderer add &lt;path&gt;</c>.</summary>
/// <summary>Settings for <c>fce config renderer add &lt;path&gt;</c>.</summary>
internal sealed class RendererReferenceSettings : ConfigScopedSettings {

[CommandArgument(0, "<PATH>")]
Expand Down
2 changes: 1 addition & 1 deletion FirstClassErrors.Cli/RendererRemoveCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion FirstClassErrors/README.nuget.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 \
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/GettingStarted.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/GettingStarted.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/OperationalIntegration.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/OperationalIntegration.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions doc/handwritten/for-users/README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/RequestBinder.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions doc/handwritten/for-users/RequestBinder.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
76 changes: 76 additions & 0 deletions tools/analyzer-count-check/check-analyzer-count.sh
Original file line number Diff line number Diff line change
@@ -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"