Skip to content

Add MA0222 and MA0223 to require the System.Text.Json Respect* options to be configured - #1432

Merged
meziantou merged 2 commits into
mainfrom
feature/json-source-generation-options-rule-006f46
Sep 7, 2026
Merged

meziantou merged 2 commits into
mainfrom
feature/json-source-generation-options-rule-006f46

Conversation

@meziantou

Copy link
Copy Markdown
Owner

What

Two new rules, one per option, both disabled by default:

Id Title
MA0222 JsonSourceGenerationOptions should set RespectNullableAnnotations
MA0223 JsonSourceGenerationOptions should set RespectRequiredConstructorParameters

They report a JsonSerializerContext whose [JsonSourceGenerationOptions] attribute does not set the corresponding option, including the contexts that have no attribute at all. A code fixer sets the option to true, adding the attribute when the context has none.

Why

System.Text.Json ignores the nullable annotations of the types it (de)serializes, and treats every constructor parameter as optional. RespectNullableAnnotations and RespectRequiredConstructorParameters (both .NET 9) enable those validations, but default to false for backward compatibility — so a context that never mentions them silently produces instances whose non-nullable members are null, or that were built from an incomplete payload.

Notes for the reviewer

  • The rules require the option to be set, not to be set to true. RespectNullableAnnotations = false is a deliberate choice and satisfies MA0222 exactly as = true does. The rules are about making the decision explicit; the fixer is the one that opts for true.
  • JsonSerializerDefaults.Strict counts as setting both. It configures them through the constructor, so [JsonSourceGenerationOptions(JsonSerializerDefaults.Strict)] stays clean, and a named argument still wins over it. Web and General do not touch the options and still report. Verified against the real generator that Strict sets both, and that an explicit RespectNullableAnnotations = false overrides it.
  • One analyzer reports both rules, following the ValidateFixedAddressValueTypeAttributeUsageAnalyzer (MA0207/MA0208) precedent — the symbol lookups and the JsonSerializerContext walk are shared, so two analyzer types would double the per-compilation cost for the same work. Each rule is still gated on its own property existing on the attribute type, so they can diverge across target frameworks.
  • The diagnostic on a context without the attribute skips the partial declaration the source generator emits, so it never points at (or carries an additional location in) a .g.cs.
  • Nothing is reported when the target framework predates .NET 9.
  • When both rules fire, fix-all needs two iterations — the two fixes touch the same node. The tests assert this explicitly rather than papering over it.

Tests

JsonSourceGenerationOptionsAnalyzerTests drives the real System.Text.Json source generator (UseFrameworkSourceGenerators), without which the JsonSerializerContext snippets would not compile.

Reference assemblies are pinned to Net90 (the minimum version with the options) rather than the default pack, because the newer generators cannot be loaded by the older Roslyn test hosts. The JsonSerializerDefaults.Strict cases need Net100, whose generators require Roslyn 4.14+, so they are behind #if ROSLYN_4_14_OR_GREATER.

  • 21/21 pass on roslyn4.14, 5.0, 5.6 and 5.9; 17/17 on roslyn4.8
  • Full roslyn5.9 suite: 4042/4042
  • Solution builds with 0 warnings, and dotnet run --project src/DocumentationGenerator exits 0 on a re-run

…equiredConstructorParameters options

System.Text.Json ignores the nullable annotations of the types it
(de)serializes and treats every constructor parameter as optional, unless
RespectNullableAnnotations and RespectRequiredConstructorParameters are
configured. Both were introduced in .NET 9 and default to false for backward
compatibility, so a JsonSerializerContext that does not configure them
silently produces instances whose non-nullable members are null, or that were
built from an incomplete payload.

MA0222 and MA0223 report the JsonSerializerContext whose
[JsonSourceGenerationOptions] attribute does not set the corresponding option,
including the ones that have no attribute at all. They require the option to
be set, not to be set to true: keeping the legacy behavior is a valid choice,
as long as it is explicit. JsonSerializerDefaults.Strict, introduced in
.NET 10, sets both options, and a named argument wins over it.

Both rules are disabled by default, as they require every context to configure
the options. The code fixer sets the option to true, adding the attribute when
the context has none.

The diagnostic reported on a context without the attribute skips the
declaration the source generator emits, so it never points at generated code.
…et framework

The tests pinned .NET 9, the version that introduced the options, so they never
exercised the newer ones. They now use the target framework the harness
provides by default, and only lower it when they have a reason to: the .NET 8
test asserting that nothing is reported when the options do not exist, and the
older versions of Roslyn, whose test host cannot load the source generators
shipped with the latest .NET.
@meziantou
meziantou enabled auto-merge (squash) September 7, 2026 05:22
@meziantou
meziantou merged commit f226da0 into main Sep 7, 2026
13 checks passed
@meziantou
meziantou deleted the feature/json-source-generation-options-rule-006f46 branch September 7, 2026 05:24
This was referenced Sep 7, 2026
This was referenced Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant