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
41 changes: 41 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,47 @@ library produces. This guide defines how commits are written here.
See [`CLAUDE.md`](CLAUDE.md) for the project layout and the broader change
guidelines.

## Public API baseline

The shipping libraries — `FirstClassErrors`, `FirstClassErrors.Testing`,
`FirstClassErrors.RequestBinder` (the `lib` train) and `Dummies` (the `dum`
train) — carry a committed public-API baseline, so every change to their public
surface is a reviewed diff and an accidental breaking change (a removed overload,
a narrowed return type, a renamed member) cannot ship silently under a version
number that promises compatibility. Two guards, wired once in
[`build/PublicApiBaseline.props`](build/PublicApiBaseline.props):

* **`Microsoft.CodeAnalysis.PublicApiAnalyzers`** tracks the surface in committed
`PublicAPI.Shipped.txt` / `PublicAPI.Unshipped.txt` files under each project's
`PublicAPI/<target-framework>/` folder. An undeclared public symbol raises
`RS0016`; a declared symbol that no longer exists raises `RS0017`. Both are
warnings locally and errors in CI (the warnings-as-errors ratchet), so a
surface change fails the build until the same change updates the baseline.
* **Package validation** (`EnablePackageValidation`) runs ApiCompat during
`dotnet pack`. With no baseline version set it performs the same-package
cross-target-framework check (it proves `Dummies`' net8.0 surface never drops
API a netstandard2.0 consumer sees). To additionally gate against a published
version, set `PackageValidationBaselineVersion`; `0.1.0-preview.1` is the first
such baseline available for the `lib` train.

**Accepting an intended surface change.** Update the baseline in the same commit:

* **In an IDE**: apply the *"Add to public API"* code fix on the `RS0016`
diagnostic.
* **From the CLI**: `dotnet format analyzers <project> --diagnostics RS0016`
appends the new entries to `PublicAPI.Unshipped.txt`; for a **removal**, delete
the matching line by hand.
* **Multi-target (`Dummies`)**: each framework has its own baseline under
`PublicAPI/<tfm>/`, and `dotnet format` rewrites only one framework's file per
run — update `net8.0` and `netstandard2.0` separately when a change touches both.
* **At release**: promote the accumulated `PublicAPI.Unshipped.txt` entries into
`PublicAPI.Shipped.txt` (the *"mark shipped"* fix, or by hand).

`RS0026`/`RS0027` (the "optional parameters across overloads" design rules) are
disabled deliberately: they fire on the library's central fluent APIs
(`Outcome.Then`/`Recover`/`Finally`/`Try`, `Any.Reproducibly`), where enforcing
them would demand a breaking redesign — a separate decision, not a baseline chore.

## Enabling the commit-message hook

A `commit-msg` hook checks every message against the convention below before it
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="5.6.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" />
Expand Down
4 changes: 4 additions & 0 deletions Dummies/Dummies.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Committed public-API baseline + package validation (see build/PublicApiBaseline.props).
The per-TFM subfolders track the two divergent surfaces (net8.0 carries the modern generators). -->
<Import Project="..\build\PublicApiBaseline.props" />

<PropertyGroup>
<!-- Build. netstandard2.0 keeps the widest reach; the net8.0 leg additionally carries the modern
types that do not exist downlevel (DateOnly, TimeOnly, Int128, UInt128, Half). -->
Expand Down
1 change: 1 addition & 0 deletions Dummies/PublicAPI/net8.0/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
412 changes: 412 additions & 0 deletions Dummies/PublicAPI/net8.0/PublicAPI.Unshipped.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Dummies/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
342 changes: 342 additions & 0 deletions Dummies/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Committed public-API baseline + package validation (see build/PublicApiBaseline.props). -->
<Import Project="..\build\PublicApiBaseline.props" />

<PropertyGroup>
<!-- Build -->
<TargetFramework>netstandard2.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions FirstClassErrors.Testing/FirstClassErrors.Testing.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Committed public-API baseline + package validation (see build/PublicApiBaseline.props). -->
<Import Project="..\build\PublicApiBaseline.props" />

<PropertyGroup>
<!-- Build -->
<TargetFramework>netstandard2.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#nullable enable
FirstClassErrors.Testing.Clock
FirstClassErrors.Testing.DetailedMessageFactory
FirstClassErrors.Testing.DiagnosticMessageFactory
FirstClassErrors.Testing.ErrorAssertion
FirstClassErrors.Testing.ErrorAssertion.Subject.get -> FirstClassErrors.Error!
FirstClassErrors.Testing.ErrorAssertion.WithCode(FirstClassErrors.ErrorCode! expectedCode) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorAssertion.WithCode(string! expectedCode) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorAssertion.WithContextEntry(string! key) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorAssertion.WithContextEntry(string! key, object? expectedValue) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorAssertion.WithDiagnosticMessage(string! expected) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorAssertion.WithShortMessage(string! expected) -> FirstClassErrors.Testing.ErrorAssertion!
FirstClassErrors.Testing.ErrorCodeFactory
FirstClassErrors.Testing.ErrorOriginFactory
FirstClassErrors.Testing.IClock
FirstClassErrors.Testing.IClock.UtcNow.get -> System.DateTimeOffset
FirstClassErrors.Testing.InstanceIds
FirstClassErrors.Testing.InteractionDirectionFactory
FirstClassErrors.Testing.OutcomeAssertionException
FirstClassErrors.Testing.OutcomeAssertionException.OutcomeAssertionException(string! message) -> void
FirstClassErrors.Testing.OutcomeAssertions
FirstClassErrors.Testing.ShortMessageFactory
FirstClassErrors.Testing.TransienceFactory
static FirstClassErrors.Testing.Clock.Use(FirstClassErrors.Testing.IClock! clock) -> System.IDisposable!
static FirstClassErrors.Testing.Clock.UseAny() -> System.IDisposable!
static FirstClassErrors.Testing.Clock.UseFixed(System.DateTimeOffset instant) -> System.IDisposable!
static FirstClassErrors.Testing.DetailedMessageFactory.Any() -> string!
static FirstClassErrors.Testing.DiagnosticMessageFactory.Any() -> string!
static FirstClassErrors.Testing.ErrorCodeFactory.Any() -> FirstClassErrors.ErrorCode!
static FirstClassErrors.Testing.ErrorOriginFactory.Any() -> FirstClassErrors.ErrorOrigin
static FirstClassErrors.Testing.InstanceIds.Use(System.Func<System.Guid>! next) -> System.IDisposable!
static FirstClassErrors.Testing.InstanceIds.UseAny() -> System.IDisposable!
static FirstClassErrors.Testing.InstanceIds.UseFixed(System.Guid id) -> System.IDisposable!
static FirstClassErrors.Testing.InstanceIds.UseSequential() -> System.IDisposable!
static FirstClassErrors.Testing.InteractionDirectionFactory.Any() -> FirstClassErrors.InteractionDirection
static FirstClassErrors.Testing.OutcomeAssertions.ShouldFail(this FirstClassErrors.Outcome! outcome) -> FirstClassErrors.Testing.ErrorAssertion!
static FirstClassErrors.Testing.OutcomeAssertions.ShouldFail<T>(this FirstClassErrors.Outcome<T>! outcome) -> FirstClassErrors.Testing.ErrorAssertion!
static FirstClassErrors.Testing.OutcomeAssertions.ShouldSucceed(this FirstClassErrors.Outcome! outcome) -> void
static FirstClassErrors.Testing.OutcomeAssertions.ShouldSucceed<T>(this FirstClassErrors.Outcome<T>! outcome) -> T
static FirstClassErrors.Testing.ShortMessageFactory.Any() -> string!
static FirstClassErrors.Testing.TransienceFactory.Any() -> FirstClassErrors.Transience
3 changes: 3 additions & 0 deletions FirstClassErrors/FirstClassErrors.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Committed public-API baseline + package validation (see build/PublicApiBaseline.props). -->
<Import Project="..\build\PublicApiBaseline.props" />

<PropertyGroup>
<!-- Build -->
<TargetFramework>netstandard2.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Loading