diff --git a/CHANGELOG.md b/CHANGELOG.md index 0641b5b..6422f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,37 +10,37 @@ A version's heading reads `## — Unreleased` until the release is pre the release date. The release workflow refuses a tag whose section still says `Unreleased`, and the section is the GitHub release's notes. -## 2.3.0 — Unreleased +## 2.3.0 — 2026-10-29 ### Added -- AspNetCore: `FaultResponse.WithoutFaultName()` answers a member without naming it: no `fault`, no `rule` and no member description in the problem, so two causes on one status look alike; the document says that status names no fault, and `IFaultFeature.NameWithheld` tells the host ([Faults a client must not tell apart](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#faults-a-client-must-not-tell-apart)). -- `Result.CatchAsync` takes a call that returns a `ValueTask`, `stream.ReadAsync(buffer)` without `.AsTask()`; an `async` lambda binds as before ([An exception as an outcome](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/result.md#an-exception-as-an-outcome)). -- `TraverseAsync` on `Option` and `Patch`: an asynchronous check of an optional input or a patch field, with a token form; nothing is called without a value ([Collect many results](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/result.md#collect-many-results)). -- `Choose()`, `Choose(selector)`, `SequenceAsync` and `TraverseAsync` read an `IAsyncEnumerable`, in order and without a list first; the token reaches the stream, nothing is read after the first error, and the enumerator is disposed however they stop ([Chain steps](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit/README.md#chain-steps)). -- `Fault.Cause`: `Fault.Of(reason, detail, exception)` keeps the exception underneath for your log; equality and `ToString()` leave it out, and `ToHttpResult()` hands it to the host as `IFaultFeature.Cause` and `FaultProblem.Cause`, never into the problem ([What failed underneath](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-failed-underneath)). -- Testing: every assertion works on a `Task` or `ValueTask` of what it checks, `await service.FindAsync(id).ShouldBeSome()`; a task that throws fails the test with its own exception, and an assertion left without `await` is reported (CMTK0003): through `ConfigureAwait` or `WaitAsync`, in a branch of a `?:`, `??` or `switch`, and handed to `Task.WhenAll`, `Task.Run` or `Task.WhenEach` and dropped, or to `Task.WhenAny` or `Task.WaitAny`, nested at any depth, or awaited in an `async` lambda whose `catch` throws again or can't catch it ([CodoMetis.TypeKit.Testing](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Testing/README.md)). -- OpenAPI: `AddTypeKit(typeKit => typeKit.InlineValueObjects = true)` describes every value object in place by its underlying type's schema, in every position and in OpenAPI 3.1, 3.0 and 2.0, with no component of its own; closed sets keep theirs, and without the setting the document is unchanged ([Describe value objects as plain types](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/aspnet-core.md#describe-value-objects-as-plain-types)). -- Analyzers: CMTK0009 warns at `FirstOrNone`, `SingleOrNone` or `FirstOrError` on a query given a `Func` variable or a method group, which binds to the `IEnumerable` overload and reads every row; a lambda at the call or an `Expression` stays silent ([A condition that reads every row](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#a-condition-that-reads-every-row)). +- AspNetCore: `FaultResponse.WithoutFaultName()` answers a member without naming it: no `fault`, no `rule` and no member description in the problem, so two causes on one status look alike; the document says that status names no fault, and `IFaultFeature.NameWithheld` tells the host ([Faults a client must not tell apart](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#faults-a-client-must-not-tell-apart)). +- `Result.CatchAsync` takes a call that returns a `ValueTask`, `stream.ReadAsync(buffer)` without `.AsTask()`; an `async` lambda binds as before ([An exception as an outcome](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#an-exception-as-an-outcome)). +- `TraverseAsync` on `Option` and `Patch`: an asynchronous check of an optional input or a patch field, with a token form; nothing is called without a value ([Collect many results](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#collect-many-results)). +- `Choose()`, `Choose(selector)`, `SequenceAsync` and `TraverseAsync` read an `IAsyncEnumerable`, in order and without a list first; the token reaches the stream, nothing is read after the first error, and the enumerator is disposed however they stop ([Chain steps](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit/README.md#chain-steps)). +- `Fault.Cause`: `Fault.Of(reason, detail, exception)` keeps the exception underneath for your log; equality and `ToString()` leave it out, and `ToHttpResult()` hands it to the host as `IFaultFeature.Cause` and `FaultProblem.Cause`, never into the problem ([What failed underneath](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#what-failed-underneath)). +- Testing: every assertion works on a `Task` or `ValueTask` of what it checks, `await service.FindAsync(id).ShouldBeSome()`; a task that throws fails the test with its own exception, and an assertion left without `await` is reported (CMTK0003): through `ConfigureAwait` or `WaitAsync`, in a branch of a `?:`, `??` or `switch`, and handed to `Task.WhenAll`, `Task.Run` or `Task.WhenEach` and dropped, or to `Task.WhenAny` or `Task.WaitAny`, nested at any depth, or awaited in an `async` lambda whose `catch` throws again or can't catch it ([CodoMetis.TypeKit.Testing](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Testing/README.md)). +- OpenAPI: `AddTypeKit(typeKit => typeKit.InlineValueObjects = true)` describes every value object in place by its underlying type's schema, in every position and in OpenAPI 3.1, 3.0 and 2.0, with no component of its own; closed sets keep theirs, and without the setting the document is unchanged ([Describe value objects as plain types](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/aspnet-core.md#describe-value-objects-as-plain-types)). +- Analyzers: CMTK0009 warns at `FirstOrNone`, `SingleOrNone` or `FirstOrError` on a query given a `Func` variable or a method group, which binds to the `IEnumerable` overload and reads every row; a lambda at the call or an `Expression` stays silent ([A condition that reads every row](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#a-condition-that-reads-every-row)). - Docs: the guides, the package READMEs, the changelog and the API of every run-time package as one site with a menu and search, built from the latest release ([caffeinatedcoder.github.io/CodoMetis.TypeKit](https://caffeinatedcoder.github.io/CodoMetis.TypeKit/)). -- Analyzers: a validated value object without `Create` gets a code fix, **Add Create**, that writes a `Create` accepting every value, ready for your rules ([Add Create](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#add-create)). -- Docs: a walkthrough follows one orders service from request to response, saying what changes at each boundary; the service is a sample that CI builds from the packages and runs, and the answers the page states are checked against it ([Walkthrough](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/walkthrough.md)). -- Docs: the root README and the Generators README show, folded, the code the generators write for `OrderId`, `Email` and a closed set; a test holds each block to what the build emits ([Declare a value object](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#declare-a-value-object)). -- EF Core: queries, keys and round trips are tested on a real SQL Server, and the README says what differs there from memory: text compared ignoring case, Guids sorted by their last group, `decimal(18,2)` by default, a `TimeSpan` under a day, `Greatest` from compatibility level 160 ([On SQL Server](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#on-sql-server)). +- Analyzers: a validated value object without `Create` gets a code fix, **Add Create**, that writes a `Create` accepting every value, ready for your rules ([Add Create](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#add-create)). +- Docs: a walkthrough follows one orders service from request to response, saying what changes at each boundary; the service is a sample that CI builds from the packages and runs, and the answers the page states are checked against it ([Walkthrough](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/walkthrough.md)). +- Docs: the root README and the Generators README show, folded, the code the generators write for `OrderId`, `Email` and a closed set; a test holds each block to what the build emits ([Declare a value object](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#declare-a-value-object)). +- EF Core: queries, keys and round trips are tested on a real SQL Server, and the README says what differs there from memory: text compared ignoring case, Guids sorted by their last group, `decimal(18,2)` by default, a `TimeSpan` under a day, `Greatest` from compatibility level 160 ([On SQL Server](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#on-sql-server)). ### Changed -- Analyzers: CMTK0003 now reports four forms that code built on 2.2.0 can contain, so your build can fail with it when you upgrade. They are a `Task.WhenAny` of `Result` tasks you don't hold (dropped, awaited, waited for or returned as a plain `Task`), a dropped `Task.WhenEach` of them, `Wait()` on a `?:`, `??` or `switch` of them, and an array of them written in a `Task.WaitAll` or `Task.WaitAny` call. Each one loses results that nobody reads: handle them, or discard them with `_ =`. An awaited `Task.WhenAny` of tasks you hold is no longer reported ([What a new version may change](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#configuration)). -- EF Core: a default value-object key over `ulong` gets a temporary value at `Add`, as a plain `ulong` key does, where it threw `OverflowException` on SQL Server and PostgreSQL; a `char` key configured `ValueGeneratedOnAdd()` gets a temporary `char` on SQL Server, not a character of a GUID string. HiLo and your own generator still come first. A compiled model made with `--nativeaot`, and a key you configure with `ValueObjectConverter` yourself, still throw ([Keys](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)). +- Analyzers: CMTK0003 now reports four forms that code built on 2.2.0 can contain, so your build can fail with it when you upgrade. They are a `Task.WhenAny` of `Result` tasks you don't hold (dropped, awaited, waited for or returned as a plain `Task`), a dropped `Task.WhenEach` of them, `Wait()` on a `?:`, `??` or `switch` of them, and an array of them written in a `Task.WaitAll` or `Task.WaitAny` call. Each one loses results that nobody reads: handle them, or discard them with `_ =`. An awaited `Task.WhenAny` of tasks you hold is no longer reported ([What a new version may change](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#configuration)). +- EF Core: a default value-object key over `ulong` gets a temporary value at `Add`, as a plain `ulong` key does, where it threw `OverflowException` on SQL Server and PostgreSQL; a `char` key configured `ValueGeneratedOnAdd()` gets a temporary `char` on SQL Server, not a character of a GUID string. HiLo and your own generator still come first. A compiled model made with `--nativeaot`, and a key you configure with `ValueObjectConverter` yourself, still throw ([Keys](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)). - Packages: nuget.org's Release Notes tab and Visual Studio's Updates tab show the version's section of this changelog, as plain text, where they showed a link to the file. - Changelog: a released version's links name its tag, `blob/v2.2.0/`, so its notes open the pages as they were released, not as they read on `main` today. -- Comparison: SuperStrong.Types 1.0.0 joins the value-object table, Thinktecture.Runtime.Extensions is compared at 10.6.0 and LanguageExt at 5.0.0-beta-78, and the page's facts are re-read before every release ([How it compares](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/comparison.md)). -- README: introduces TypeKit by example, a one-line `OrderId`, a validated `Email`, an `Option` lookup and a `Result` chain, and thanks Vogen, which inspired the value objects; every README says what Metalama is where it first names it ([CodoMetis.TypeKit](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/README.md#what-you-get)). +- Comparison: SuperStrong.Types 1.0.0 joins the value-object table, Thinktecture.Runtime.Extensions is compared at 10.6.0 and LanguageExt at 5.0.0-beta-78, and the page's facts are re-read before every release ([How it compares](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/comparison.md)). +- README: introduces TypeKit by example, a one-line `OrderId`, a validated `Email`, an `Option` lookup and a `Result` chain, and thanks Vogen, which inspired the value objects; every README says what Metalama is where it first names it ([CodoMetis.TypeKit](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/README.md#what-you-get)). ### Fixed - Generators: the aspects keep no compilation objects in static fields, which in an IDE that keeps Metalama's process across edits held every earlier compilation in memory. -- Generators: a generated `TryParse(string?, IFormatProvider?, out)` ends at its `return`; the unreachable statements after it are gone from the woven code ([Reading the generated code](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#reading-the-generated-code)). +- Generators: a generated `TryParse(string?, IFormatProvider?, out)` ends at its `return`; the unreachable statements after it are gone from the woven code ([Reading the generated code](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#reading-the-generated-code)). ## 2.2.0 — 2026-10-18 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9fbb81..4cc4477 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -193,7 +193,9 @@ The packages share one version and are released together, only through anything but its tag or a full commit id. 3. Move the rules in `src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Unshipped.md` to `AnalyzerReleases.Shipped.md`, under `## Release `. Their ids and severities are public - contract from then on. `ChangelogTests` fails until steps 2 and 3 agree. + contract from then on. `ChangelogTests` fails until steps 2 and 3 agree. A release that ships no + rule gets the header alone, and loses it when the next release adds its own: the analyzer build + refuses any header after an empty release (RS2007). 4. Commit on the release branch, open its PR into `main` (`chore` and `area/release`, the version's milestone, `Closes #`), and merge it with a merge commit. Tag that merge commit and push the tag: `git tag v1.0.0 && git push origin v1.0.0`. The workflow runs every diff --git a/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md b/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md index 7721d6b..10d875a 100644 --- a/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md @@ -62,4 +62,4 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------------------------------------------------------------- CMTK0014 | Usage | Warning | UndefinedEnumValueAnalyzer, a FaultKind or ConstructorAccessibility that names no member -## Release 2.2.0 +## Release 2.3.0