dart_modernize 0.11.0
dart_modernize: ^0.11.0 copied to clipboard
A CLI tool that modernizes Dart and Flutter codebases by applying idiomatic patterns, enforcing conventions, and automating tedious upgrades.
Changelog #
0.11.0 #
Breaking: the minimum Dart SDK is now 3.13.0, up from 3.12.0. The tool refuses to run on a project whose SDK constraint allows anything older. Primary constructors are stable in 3.13 and the promotion pass emits that syntax, so the floor moved with it rather than leaving one pass silently inert.
- Fixed three bugs in
primary-constructors, all found by running the tool over its own sources:- Promotion deleted a factory or redirecting constructor, because the retained-member loop skipped every constructor instead of only the one being promoted. This could remove public API from a class and still compile.
- A promoted field lost its doc comment, plain comment, and annotations, since only its type and name reach the header. Such a class is now left alone.
- Members left in the class body lost the blank lines between them, and any
//comment written above them.
- Fixed a convergence bug:
private-named-parametersrewrites a constructor into thethis._fieldform thatprimary-constructorspromotes, but promotion ran first, so the class only collapsed on a second run and the pipeline was not idempotent.primary-constructorsnow runs after it. The 3.12 floor had hidden this, because the pass was inert on the test projects. - Primary constructors went stable in Dart 3.13. The
primary-constructorspass now promotesconstclasses too, emitting the constant primary constructor form (class const Point(final int x, final int y)), where before it skipped every class with aconstconstructor. This is safe by construction: Dart already requires every instance field of a const-constructor class to be final, so the header never needsvar, and members left in the class body are copied over unchanged. - The pass still gates on the resolved language version as a second line of defence, even though the validator now rejects sub-3.13 projects up front.
- Added a semantic test that runs the pass over a 3.13 project and re-analyzes the result. Golden files are compared byte for byte but never compiled, and the semantic suite excluded this pass, so primary-constructor output had no compile check anywhere until now.
- Documented the Dart 3.13 lints that
fix-allpicks up from a target project, and whyuse_primary_constructorscannot fight theprimary-constructorspass. - Golden expectation files are now named
<case>.expected.dartinstead of<case>.expected. They were extensionless to keep Dart tooling away from syntax the package's own SDK could not parse; with the floor at 3.13 that no longer applies, and the analyzer and formatter already skiptest/fixtures/**by configuration. - Dropped the 3.12/3.13 dual-pubspec machinery in the test harness now that every fixture project is on 3.13.
- The
modern-syntaxrobustness suite now runs primary constructors too. It pins no bytes, only that the result still compiles and a second run is a no-op, so there was no reason to keep the pass out once its language version was the floor. - Swapped the deprecated
unnecessary_await_in_returnlint, which Dart 3.13 flags, for the newasync_return_with_no_await.
0.10.0 #
- #15 Turn sort-members off by default
- #16 Read configuration from analysis_options.yaml
- #17 Explain which passes are cosmetic and which preserve behavior
- #19 Destructure intermediate variables with record/object patterns
- #20 Destructure for-in loop variables with patterns
- #21 Rewrite conditionals into null-aware operators
- #22 Build collections with if/for elements (opt-in, off by default)
- #23 Extend final_locals to for-in loop variables
- #30 Select transformations with --only instead of positional pass names
- #32 Final pre-release audit and cleanup for 0.10.0
0.9.2 #
- prefer-inferred-types now removes a duplicated type annotation from a declaration whose initializer is a dot-shorthand constructor, expanding the shorthand to its explicit form:
final Foo a = .new()becomesfinal a = Foo(), andlate final Foo a = .named()becomeslate final a = Foo.named(). Thelatemodifier and thefinal/constkeyword are preserved.
0.9.1 #
- Rewrote the README in a plainer, less promotional style.
- Modernized the package's own sources with the tool and simplified the CI workflows. No functional changes.
0.9.0 #
0.8.0 #
0.7.4 #
- Bump analyzer from 14.0.0 to 14.1.0.
0.7.3 #
- Dot shorthands now collapse a static member or method on a generic class.
WidgetStateProperty.resolveWith(...)in aWidgetStateProperty<Color?>?context becomes.resolveWith(...), and a static getter such asWidgetStateProperty.nonebecomes.none. A static member never uses the class's type arguments, so the collapse resolves against the same element the context type already names; previously every generic-owner static was left qualified.
0.7.2 #
- Dot shorthands now collapse a constant inside an object or record pattern
field, matching it against the field's type:
NetworkException(kind: NetworkFailureKind.timeout)becomesNetworkException(kind: .timeout). Previously only a constant matched against the whole scrutinee collapsed. - Dot shorthands now collapse a return whose type is the enclosing generic
member's own type variable. Inside
Future<Result<T>> guarded<T>(...),return Result.success(...)becomesreturn .success(...). The type-equality check treatedResult<T>(context) andResult<T>(written) as different because both mention the type variableT, so such returns never collapsed.
0.7.1 #
- Dot shorthands now collapse inside a factory constructor's body. A
returnor=>that builds the class becomes a shorthand, sofactory AuthTokens.fromJson(...) { return AuthTokens(...); }becomes... { return .new(...); }. The context is the constructor's own class type, matching the handling of function, method, and getter returns; a factory that returns a subtype is left qualified, since.new()would build the wrong type.
0.7.0 #
- Dot shorthands now collapse two patterns common to service-locator and
dependency-injection code. An argument to a generic call fixed by an explicit
<...>gets a real context, sosl.registerSingleton<CrashReporter>(reporter ?? CrashReporter())becomes...(reporter ?? .new()); and the body of a factory closure takes its context from the function type the closure is written against, sosl.registerLazySingleton<ThemeCubit>(() => ThemeCubit(storage: sl()))becomes...(() => .new(storage: sl())). Both are skipped when the type is still inferred from that very argument or closure (a generic call with no explicit<...>, such asxs.map((x) => Foo(x))), since collapsing would leave nothing to infer the type from. abstract-final-classesnow recognizes a dot-shorthand.new(...)/.named(...)as instantiating its class, so a class the dot-shorthands pass reduced to.new()is no longer mislabelledabstract final(which would have made that.new()construct an abstract class).
0.6.3 #
- Dot shorthands now collapse a static member or enum value reached through an
import prefix. A switch over an import-prefixed enum, such as
permission_handler.PermissionStatus.granted => PermissionResult.granted, becomes.granted => .granted. Through a prefix a static access parses as a three-part property access ((prefix.Type).member) rather than the two-part form the pass already handled, so it was previously left qualified while the unprefixed side collapsed. Import-prefixed constructors and static methods (prefix.Type(...),prefix.Type.method(...)) collapse the same way.
0.6.2 #
- Dot shorthands now collapse a default parameter value against the parameter's
type.
Stream<T> watch({LocationAccuracy accuracy = LocationAccuracy.high})becomesStream<T> watch({LocationAccuracy accuracy = .high}). Named, optional-positional, and field/super formals (this.x = ...) are all covered; the parameter element's type is used, so a field formal whose type comes from its field still resolves.
0.6.1 #
- Dot shorthands now collapse a chain head on the left operand of
??, not only the right.ThemeMode.values.asNameMap()[saved] ?? ThemeMode.systemnow becomes.values.asNameMap()[saved] ?? .system; previously only the right operand was shortened while the left kept its type name. Both operands take the??expression's context type (Dart infers the left in its nullable form, but the shorthand only needs the element), so a qualified chain on either side resolves against the same type.
0.6.0 #
- Dot shorthands now collapse the head of a selector chain, not just a
standalone expression. When the whole chain's context type names the head's
type, the leading
TypeNameis dropped and the trailing selectors are kept:expiry.difference(DateTime.now().toUtc())becomesexpiry.difference(.now().toUtc()),DateTime.tryParse(s)?.toUtc()becomes.tryParse(s)?.toUtc(), andColor.values.firstbecomes.values.first. The context flows up through.method(...),.getter,[index], and!selectors to wherever the chain sits (argument, return,switchcase, and every other position already supported). The head is left qualified when its referenced type differs from the chain's context (int.parse(s).toDouble()in anumcontext) or when the position has no context type (the left of==).
0.5.0 #
- Select transformations by naming them as positional arguments and drop the
--onlyflag.dart_modernize cascades inline-returnnow runs just those two passes and skips the rest, replacing--only cascades,inline-return. Naming any pass turns every unnamed one off and overrides the individual--<name>flags, exactly as--onlydid; naming none still runs everything. A positional that is not a transformation name is the target path, sodart_modernize lib/anddart_modernize lib/ cascadesboth work, and an unrecognized name is rejected with a usage error listing the valid transformations. The individual--no-<name>toggles are unchanged.
0.4.1 #
- Fix
sort-membersandsort-constructors-firstdisagreeing about where constructors go.sort-membersused to order fields before constructors whilesort-constructors-firstlifts constructors before every other member, so running one after the other on the same file flip-flopped it forever and the tool never settled (--only sort-membersthen--only sort-constructors-firsteach undid the other).sort-membersnow emits constructors first, matching thesort_constructors_firstlint, so the two passes agree and each is a no-op on the other's output. A full default run is unaffected; only a standalone--only sort-memberschanges order.
0.4.0 #
- Add
--only, an allow-list that runs just the transformation(s) you name and skips every other one. Repeat the flag or comma-separate names to select several (--only cascades,inline-return). When given, it overrides the individual--<transformation>flags, so a single pass no longer requires spelling out--no-for all the others. Unknown names are rejected with a usage error listing the valid transformations.
0.3.0 #
- Dot shorthands now collapse record fields. A positional field takes its
context from the matching field of the record's type and a named field from
the same-named field, so a list like
[(StockReadingType.opening, label, Icons.sunny), ...]becomes[(.opening, label, Icons.sunny), ...]. When an untyped list of records has no element type to fall back on, the inferred record element type is hoisted onto the literal (<(Foo, String)>[...]) so the field shorthands have a context to resolve against. Hoisting happens only when every field of the record type is precise; a field typeddynamic,Object,Null, or an unresolved type variable leaves the record untouched. - Dot shorthands also derive a context type through a
??right-hand side (maybe ?? Color.bluebecomesmaybe ?? .blue) and ayieldin async*orasync*generator, matching the existing handling of returns and assignments. --prefer-inferred-typesnow drops a type annotation only when the initializer's type is obvious from the initializer (a literal, an explicitly-typed collection literal, a spelled-out constructor call, a cast, or a cascade or prefix over one of these), matching the analyzer'somit_obvious_*/specify_nonobvious_*rules. A non-obvious initializer such as a method call or property access keeps its annotation, so the pass no longer introduces aspecify_nonobvious_*diagnostic thatdart fixwould revert (which made a--no-fix-allrun disagree with a full run).- Reject a project whose pubspec SDK constraint allows a Dart version older than
3.12. The transformations emit 3.12+ idioms, so such a project would be broken
by them; raise the constraint to
>=3.12.0 <4.0.0before modernizing.
0.2.3 #
- Trim trailing whitespace from subprocess stderr in error messages (e.g.
dart fix,dart formatfailures).
0.2.2 #
- No user-facing changes; removes duplicate test run from the release workflow.
0.2.1 #
- Fix README code example: use explicit
Set<Permission>type annotation.
0.2.0 #
- Add
--sort-constructors-firstpass: moves every constructor before the other members of a class, enum, mixin, or extension type, satisfying thesort_constructors_firstlint. Attached doc comments and annotations move with their constructor, and the pass runs after--sort-membersso the two compose. Enabled by default; disable with--no-sort-constructors-first.
0.1.1 #
- Skip
build/and other excluded or hidden directories while scanning, so the tool no longer crashes on deep build trees (notably on Windows). - Keep a type annotation the initializer's dot shorthand needs (
final Foo x = .new(...)is left alone instead of droppingFoo). - Don't shorten an argument whose generic constructor infers its type from that argument, which would leave the shorthand without a context type.
- Don't add
abstract finalto a class that extends, implements, or mixes in another type (such as test mocks).
0.1.0 #
Initial release.
- Modernizes Dart and Flutter code with type-aware passes: dot shorthands, switch expressions, expression bodies, cascades, string interpolation, null-aware collections, inferred types, super parameters, private named parameters, primary constructors, and abstract final classes.
- Organizes imports, sorts members, applies
dart fix, and formats the result. - Preview every change with
--dry-run; toggle any pass with--no-<pass>. - Skips generated files and honors
analysis_options.yamlexcludes.