Skip to main content

dart_modernize 0.12.0 copy "dart_modernize: ^0.12.0" to clipboard
dart_modernize: ^0.12.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.12.0 #

Fixes found by running the tool over eleven real projects, the Dart 3.13 syntax that was still missing, and a pipeline that can no longer leave code that does not compile. The minimum SDK stays at 3.13.0.

New passes

  • constructor-shorthands: declares constructors without repeating the class name, Point(...) becomes new(...), Point.origin() becomes new origin(), factory Point.fromJson becomes factory fromJson, and enum constructors drop their redundant const (Dart 3.13).
  • empty-bodies: an empty class, mixin, extension type or extension body {} becomes ; (Dart 3.13).
  • future-pause: an awaited Future.delayed(d) with no callback becomes Future.pause(d) (Dart 3.13).
  • wildcard-parameters: unused __ and ___ parameters become _, which can repeat since Dart 3.7.
  • null-aware-calls: if (x != null) x.f(); becomes x?.f();, and if (cb != null) cb!(); becomes cb?.call();.
  • unmodifiable-of, off by default: List.unmodifiable(x) becomes List.unmodifiableOf(x), and the same for Map, when the result keeps its static type.

primary-constructors

  • Fixed default values being dropped (this.port = 8080 became final int port, and a nullable field silently lost its default) and optional positional parameters losing their [...].
  • Fixed a constructor's doc comment, annotations and comments being deleted. Such a constructor is now left alone, and so is a field with a comment at the end of its line. A comment trailing the line above a member stays with the member it belongs to.
  • Now promotes a constructor with asserts (moved to this : assert(...);), a field set from a plain parameter in the initializer list (moved onto the field), a named or private constructor (class Vec._(...)), fields declared together (final int x, y;), and enums (enum Planet(final double mass)).
  • New super-parameters setting, off by default, to also promote classes whose constructor forwards super. parameters, which covers nearly every Flutter widget.
  • A non-const class that is or extends an @immutable class and keeps an initialized or late field in its body is no longer promoted: the Dart 3.13 lint prefer_const_constructors_in_immutables wrongly asks for const on the result, and dart fix added one that did not compile.

Other pass fixes

  • prefer-inferred-types no longer emits <T>const [...], no longer turns double x = -1 (or (1)) into an int, and keeps the type of a field that overrides an inherited member, which would otherwise take the inherited member's type.
  • final-locals now sees pattern assignments ((a, b) = pair) and for (x in xs) loops over an existing variable.
  • destructure-for-in and destructure-locals no longer write a type name the file does not import (and write prefix.Type for a prefixed import), and no longer destructure a nullable value or a ?. read.
  • super-parameters no longer folds a named parameter into a positional argument or the reverse, keeps the order of positional arguments, keeps : super.named() for a named super constructor, never mixes super. positionals with positional arguments, and no longer folds an optional parameter whose super parameter has a default.
  • dot-shorthands no longer shortens a record field returned from an async function, or an element of a collection passed to a generic call without <...> such as ISetConst({...}), where Dart gives no context type. New constructors setting to keep constructor calls written out while still shortening enum values and static members.
  • null-aware-conditionals no longer breaks a member chain written over several lines.
  • inline-return no longer moves an initializer whose inferred type depends on its context into a return where that context changes, such as a generic call in an async function.
  • abstract-final-classes leaves the public API of a published package alone, and keeps the private constructor when the project enables avoid_classes_with_only_static_members.
  • sort-constructors-first respects // ignore: sort_constructors_first and ignore_for_file, and treats a primary constructor body as a constructor (so does sort-members).
  • A library pinned to an older language version (// @dart = 2.12) is left alone by every pass.

Pipeline

  • With --verify (the default), every stage is now checked in memory before anything is written. A stage that adds an error has the edits behind it taken back, including when the error lands in another file, and a file several passes edited keeps the passes that are safe.
  • The final dart analyze check now covers the whole project, not only the changed files, and repeats after each revert. An error it cannot trace reverts the rest of the run, so the tool never leaves an error it caused.
  • A run that reverts files now prints the normal summary, then the reverted files with the passes behind them and the --no-<pass> switches to skip them, and exits with code 2 instead of 1.
  • New warnings and infos are listed at the end, without reverting anything.
  • Copies of every file a run may rewrite are saved to .dart_tool/dart_modernize/backup before the first write. A failure puts them back; after a crash, the new --restore does.
  • dart format now only touches the files the tool changed. New --no-format (or format under disabled:) and --line-length (or line_length:). When formatting breaks a line in a way a lint the project enables then flags (an if body moving under its condition without braces), dart fix runs once more, so a second run of the tool finds nothing left to do.
  • --check and --dry-run now take the finalize steps too (organize imports, sorting, and dart format on a scratch copy), and list the files dart fix would change, so --check no longer passes when a real run would still change files.
  • New --upgrade-sdk, which raises a project's SDK constraint to ^3.13.0, runs pub get, and removes the final and var that Dart 3.13 no longer allows on ordinary parameters, before modernizing.
  • New options: section in the dart_modernize: configuration to tune a pass, and lint_fixes: to apply chosen lints' dart fix corrections even when the project does not enable them.
  • A library whose resolution overflows the stack is skipped and reported instead of crashing the run.
  • Faster: a library is resolved once per stage with all its parts, the baseline dart analyze runs alongside the stages, and organize-imports reuses the stages' analyzer. --verbose prints how long each stage took.
  • A compiled executable now finds the Dart SDK (through DART_SDK, FLUTTER_ROOT or the PATH) and runs dart subcommands from it instead of relaunching itself. Every GitHub release now ships standalone executables for Linux, macOS and Windows.

Project

  • Updated analyzer to 14.6.0 and test to 1.33.0.
  • Added tool/campaign.dart and a manual Real-project campaign workflow, which run the tool over pinned public projects and fail on any revert, new error, or non-idempotent second run.

0.11.1 #

No behavior change. Dependency updates and a documentation pass.

  • Updated analyzer to 14.3.0, test to 1.32.0, glob to 2.2.0, pub_semver to 2.2.1, yaml to 3.1.4, and dart-lang/setup-dart to 1.8.1.
  • Rewrote the README in plainer language, with many more real before and after examples taken from the test suite. It also fixes what it used to get wrong: sort-members was called the only opt-in pass, when collection-elements is off by default too, and its --collection-elements switch was missing from the options table.
  • Fixed doc/ORDERING.md: cascades runs in stage 2, not stage 3, and the finalize section now lists sort-constructors-first and the real order of the steps.
  • Fixed test/fixtures/README.md, which counted seventeen passes, listed CLI flags that no longer exist, and omitted five passes. It now lists all twenty-two with their --only names.
  • Brought AGENTS.md in line with the source tree, and pointed its format command and the pull request template at the same fixture-sparing command CI uses, since a bare dart format . rewrites the deliberately unformatted golden fixtures.
  • Updated the defaultOffTransformations and FinalizeTransformation doc comments, which predated collection-elements and sort-constructors-first.
  • Added test/cli/version_sync_test.dart, which fails when the pubspec.yaml version, the --version constant in runner.dart, and the top CHANGELOG.md heading disagree. Nothing enforced that before.

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-parameters rewrites a constructor into the this._field form that primary-constructors promotes, but promotion ran first, so the class only collapsed on a second run and the pipeline was not idempotent. primary-constructors now 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-constructors pass now promotes const classes too, emitting the constant primary constructor form (class const Point(final int x, final int y)), where before it skipped every class with a const constructor. This is safe by construction: Dart already requires every instance field of a const-constructor class to be final, so the header never needs var, 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-all picks up from a target project, and why use_primary_constructors cannot fight the primary-constructors pass.
  • Golden expectation files are now named <case>.expected.dart instead 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 skip test/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-syntax robustness 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_return lint, which Dart 3.13 flags, for the new async_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() becomes final a = Foo(), and late final Foo a = .named() becomes late final a = Foo.named(). The late modifier and the final/const keyword 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 #

  • #13 Add a --check flag that exits non-zero when changes are needed
  • #14 Refuse to run on a dirty Git tree unless --allow-dirty is passed

0.8.0 #

  • #9 Generated files without a known suffix get rewritten
  • #10 Re-analyze after editing and roll back files that stop compiling
  • #11 The dart fix step ignores excludes and runs on the whole project
  • #12 Line endings and BOM are not preserved

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 a WidgetStateProperty<Color?>? context becomes .resolveWith(...), and a static getter such as WidgetStateProperty.none becomes .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) becomes NetworkException(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(...) becomes return .success(...). The type-equality check treated Result<T> (context) and Result<T> (written) as different because both mention the type variable T, so such returns never collapsed.

0.7.1 #

  • Dot shorthands now collapse inside a factory constructor's body. A return or => that builds the class becomes a shorthand, so factory 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, so sl.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, so sl.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 as xs.map((x) => Foo(x))), since collapsing would leave nothing to infer the type from.
  • abstract-final-classes now recognizes a dot-shorthand .new(...) / .named(...) as instantiating its class, so a class the dot-shorthands pass reduced to .new() is no longer mislabelled abstract 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}) becomes Stream<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.system now 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 TypeName is dropped and the trailing selectors are kept: expiry.difference(DateTime.now().toUtc()) becomes expiry.difference(.now().toUtc()), DateTime.tryParse(s)?.toUtc() becomes .tryParse(s)?.toUtc(), and Color.values.first becomes .values.first. The context flows up through .method(...), .getter, [index], and ! selectors to wherever the chain sits (argument, return, switch case, 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 a num context) 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 --only flag. dart_modernize cascades inline-return now 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 --only did; naming none still runs everything. A positional that is not a transformation name is the target path, so dart_modernize lib/ and dart_modernize lib/ cascades both 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-members and sort-constructors-first disagreeing about where constructors go. sort-members used to order fields before constructors while sort-constructors-first lifts 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-members then --only sort-constructors-first each undid the other). sort-members now emits constructors first, matching the sort_constructors_first lint, 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-members changes 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 typed dynamic, 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.blue becomes maybe ?? .blue) and a yield in a sync* or async* generator, matching the existing handling of returns and assignments.
  • --prefer-inferred-types now 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's omit_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 a specify_nonobvious_* diagnostic that dart fix would revert (which made a --no-fix-all run 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.0 before modernizing.

0.2.3 #

  • Trim trailing whitespace from subprocess stderr in error messages (e.g. dart fix, dart format failures).

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-first pass: moves every constructor before the other members of a class, enum, mixin, or extension type, satisfying the sort_constructors_first lint. Attached doc comments and annotations move with their constructor, and the pass runs after --sort-members so 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 dropping Foo).
  • 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 final to 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.yaml excludes.
1
likes
160
points
561
downloads

Documentation

API reference

Publisher

verified publisheribrahimasylla.com

Weekly Downloads

A CLI tool that modernizes Dart and Flutter codebases by applying idiomatic patterns, enforcing conventions, and automating tedious upgrades.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

analyzer, args, glob, path, pub_semver, tint, yaml

More

Packages that depend on dart_modernize