Skip to content

fix: cap dart_style below 3.1.10 so generated code matches dart format (v2.8.1) - #51

Merged
gmpassos merged 1 commit into
masterfrom
fix/dart_style-format-match-sdk
Aug 1, 2026
Merged

fix: cap dart_style below 3.1.10 so generated code matches dart format (v2.8.1)#51
gmpassos merged 1 commit into
masterfrom
fix/dart_style-format-match-sdk

Conversation

@gmpassos

@gmpassos gmpassos commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Problem

The generated *.reflection.g.dart is formatted by DartFormatter, using the dart_style that pub resolves for this package, while consumers check that same code with the dart format bundled in their Dart SDK.

Those two are not the same version, and dart_style does not guarantee identical output across its own versions.

  • Dart SDK 3.12.2 bundles dart_style 3.1.6 (dart format --version3.1.6)
  • reflection_factory 2.8.0 requires dart_style: ^3.1.9 → pub resolves 3.1.12
  • dart_style 3.1.10 changed the splitting of an argument list containing a collection literal

That shape is exactly the generated proxy code:

// dart_style 3.1.12 (what the builder writes) — hugs:
var ret = onCall(this, 'mapKeys', <String, dynamic>{
  'map': map,
}, const __TR<Future<List<String>>>(Future, <__TR>[__TR.tListString]));

// dart_style 3.1.6 (what the consumer's `dart format` wants) — splits:
var ret = onCall(
  this,
  'mapKeys',
  <String, dynamic>{'map': map},
  const __TR<Future<List<String>>>(Future, <__TR>[__TR.tListString]),
);

Why it breaks consumers

It makes two ordinary CI checks mutually exclusive:

Committed content dart format --set-exit-if-changed build_verify (build is up to date)
Generator output ❌ fails ✅ passes
dart format output ✅ passes ❌ fails — build_runner rewrites it back

Hit in bones_api, where CI's dart pub upgrade picked up 2.8.0 and the test_vm job started failing on ensure_build_test.

Fix

dart_style: ^3.1.9'>=3.1.9 <3.1.10'

This is the narrowest range that still satisfies analyzer: ^13.0.0 — 3.1.9 is the first dart_style that accepts analyzer 13 — so nothing else is rolled back (analyzer stays at 13.3.0).

What this is not

Not a language-version bug. The builder already resolves and passes the correct language version to DartFormatter:

  • reflection_factory_builder.dart:301codeTable.resolveLanguageVersion(), single _writeGeneratedCode call site, so the ?? DartFormatter.latestLanguageVersion fallback is not reached
  • Verified against a real 3.7 package: logs Resolved languageVersion: 3.7.0 for every generated file

And raising the consumer's SDK constraint does not avoid it. Regenerating a real package's *.g.dart with dart_style 3.1.12 at each language version:

Package sdk: lower bound dart_style SDK dart format
>=3.7.0 3.1.12 ❌ DIVERGE
>=3.10.0 3.1.12 ❌ DIVERGE
>=3.11.0 3.1.12 ❌ DIVERGE
>=3.12.0 3.1.12 ❌ DIVERGE

Tests

New test/reflection_factory_format_test.dart, tagged build:

  1. The committed *.g.dart are left untouched by the SDK's dart format.
  2. DartFormatter agrees with the SDK's dart format on an argument list containing a collection literal — the generated-proxy shape — formatted at this package's own language version.

Test 2 is the one that actually catches this. Test 1 alone is not sufficient: this repository's own generated code happens not to contain a diverging shape, so it passes even on dart_style 3.1.12 while a downstream package with a proxy breaks. Verified by running both against 3.1.12 — only test 2 fails, with:

`DartFormatter` (`dart_style` 3.1.12) and the `dart format` of the Dart SDK (3.1.6)
format the same code differently, at language version 3.10.0.

Full suite: 272 tests pass; dart format and dart analyze --fatal-infos --fatal-warnings clean.

Follow-up

Lift the upper bound once the Dart SDK bundles dart_style >= 3.1.10 — at that point test 2 keeps the two aligned automatically.

🤖 Generated with Claude Code

The generated `*.reflection.g.dart` is formatted by `DartFormatter`, using
the `dart_style` that `pub` resolves for *this* package, while consumers
check that code with the `dart format` bundled in *their* Dart SDK.

`dart_style` 3.1.10 changed the splitting of an argument list containing a
collection literal — exactly the shape of the generated proxy code:

    onCall(this, 'mapKeys', <String, dynamic>{
      'map': map,
    }, const __TR<Future<List<String>>>(Future, <__TR>[__TR.tListString]))

The Dart SDK 3.12.2 still bundles `dart_style` 3.1.6 (`dart format
--version`), which splits that instead. So a package generating with
3.1.10+ gets generated code that its own `dart format` wants to rewrite,
making 2 common CI checks mutually exclusive: committing the generator
output fails `dart format --set-exit-if-changed`, and committing the
formatted output fails a `build_verify` check, since `build_runner`
rewrites the file back. Hit in `bones_api`.

`>=3.1.9 <3.1.10` is the narrowest range that still satisfies
`analyzer: ^13.0.0` — 3.1.9 is the first `dart_style` accepting analyzer
13 — so nothing else is rolled back.

NOT a language-version bug: the builder already resolves and passes the
correct language version to `DartFormatter` (verified: it logs 3.7.0 for a
3.7 package, single call site). The divergence reproduces identically at
language versions 3.7, 3.10, 3.11 and 3.12, so raising a consumer's SDK
constraint does not avoid it.

Tests: `reflection_factory_format_test.dart` guards both directions —
the committed `*.g.dart` are untouched by the SDK's `dart format`, and
`DartFormatter` agrees with the SDK's `dart format` on the generated-proxy
shape. The second test is the one that catches this: the repository's own
generated code happens not to contain a diverging shape, so it passed on
3.1.12 while `bones_api` broke.

Lift the bound once the Dart SDK bundles `dart_style` >= 3.1.10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gmpassos
gmpassos merged commit 213b967 into master Aug 1, 2026
3 checks passed
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.51%. Comparing base (53a8c3a) to head (a24e40b).

Additional details and impacted files
@@           Coverage Diff           @@
##           master      #51   +/-   ##
=======================================
  Coverage   89.51%   89.51%           
=======================================
  Files          14       14           
  Lines        6398     6398           
=======================================
  Hits         5727     5727           
  Misses        671      671           
Flag Coverage Δ
unittests 89.51% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

gmpassos added a commit to Colossus-Services/bones_api that referenced this pull request Aug 1, 2026
`^2.7.5` let CI's `dart pub upgrade` resolve 2.8.0, whose bundled
`dart_style` 3.1.12 formats the generated code differently from the
`dart format` of the Dart SDK (3.1.6), breaking `ensure_build_test`.

reflection_factory 2.8.1 caps `dart_style` below 3.1.10
(gmpassos/reflection_factory#51), so the generated code is byte-identical
to what the SDK's `dart format` wants again. Requiring `^2.8.1` -- and not
`^2.7.5` -- also keeps consumers of the released bones_api off the broken
2.8.0.

The regenerated files only change the builder version stamp
(2.7.5 -> 2.8.1); there is no formatting change. Verified:
`dart format --set-exit-if-changed` 0 changed, `dart analyze
--fatal-infos --fatal-warnings` clean, `ensure_build_test` green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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