Skip to main content

What’s new in DCM 1.24.0

· 10 min read

Cover

Today we’re excited to announce the release of DCM 1.24.0!

This release includes 17 new rules (6 for Bloc and 4 for Flutter), autocompletion for DCM configuration in analysis_options.yaml, improved validation of rules and metrics in DCM configuration, a new type of fixes called "unsafe fixes" and more! 🚀

info

The "recommended" preset has been updated and now includes some new rules from previous releases, so you may get new warnings if you use the build-in recommended preset or once you update the dart-code-metrics-presets package dependency.

Let's go through the highlights of this release! (And you can find the full list of changes in our changelog)

Configuration Improvements

Autocompletion Suggestions (VS Code only)

To simplify working with DCM configuration (and dealing with the list of more than 350 lint rules), the VS Code extension now supports autocompletion for all DCM configuration entries (including rules, rule configuration, and metrics).

Improved Validation

Prior to this release, only top-level entries and lint rules were properly checked for potential typos/duplication.

Now, all DCM configuration entries are validated for correct names and values!

Config Validation

New Code Actions

We've also expanded the list of available code actions for rules and metrics configuration!

Rules now have two new code actions for "include" and "exclude" entries:

New Code Actions for Rules

And metrics now have a code action for opening the documentation page:

New Code Actions for Metrics

note

As we working on a large update for metrics, more code actions will likely be added soon.

If you have any suggestions, please let us know on our Discord!

Excludes

To allow providing excludes for individual commands when using "dcm run" (since --exclude applies to all commands at once and does not help with that), the configuration now has a new section called "exclude" that supports excludes for lints, metrics, unused code, unused files, and parameters.

analysis_options.yaml
dart_code_metrics:
exclude:
rules:
- ... # configures the list of files that should be ignored by rules
metrics:
- ... # configures the list of files that should be ignored by metrics
unused-code:
- ... # configures the list of files that should be ignored by unused code checks
unused-files:
- ... # configures the list of files that should be ignored by unused files checks
parameters:
- ... # configures the list of files that should be ignored by parameters checks

While the old "rules-exclude" and "metrics-exclude" options are still supported (but no longer listed in the documentation), the "unused-code-exclude" configuration has been removed is no longer accepted by the tool. Please use "exclude:unused-code:" instead.

Unsafe Fixes

One of the annoying parts of the "Fix on Save" feature was that some fixes would modify your code before you finished writing it, and, for example, were removing parameters before they are referenced in the code.

With this release, some of the fixes are marked as "unsafe" and are no longer part of the default "Fix on Save" and "dcm fix" set.

Here is the list of such rules:

  • avoid-unused-parameters
  • avoid-unnecessary-super
  • avoid-unnecessary-nullable-return-type
  • use-existing-variable

To enable fixes for these rules for dcm fix, pass the new --unsafe flag. And to enable them for "Fix on Save", change the "source.dcm.fixAll": "explicit" configuration option to "source.dcm.unsafeFixAll": "explicit" (note, only one of these option should be present in the settings.json file).

note

With the future releases, the list of the unsafe fixes will most likely be expanded.

If you have ideas for rules that should be included there, please let us know on Discord!

Unused Code Improvements

The check-unused-code command now supports detecting unused assets generated by the flutter_gen package (even if the generated files are excluded from the analysis).

You can read more about detecting unused assets in this guide.

New rules

Common

avoid-negations-in-equality-checks. Warns when an equality check has a negated expression.

Negated boolean expressions reduce readability and can sometimes be a mistake (e.g. == !value instead of != value).

For example,

void fn(bool flag, bool another) {
if (!flag == another) {}

if (!flag != another) {}

if (flag == !another) {}

if (flag != !another) {}
}

should be rewritten to:

void fn(bool flag, bool another) {
if (flag != another) {}

if (flag == another) {}

if (flag != another) {}

if (flag == another) {}
}

function-always-returns-same-value. Warns when a function always returns the same constant value (e.g. true).

Returning the same constant value from all return branches is usually a bug.

For example,

bool withFlag(bool someCondition, bool another) {
if (someCondition) {
return true;
} else if (another) {
return true;
}

return true;
}

String withString(int value) {
if (value == 2) {
...

return 'hi';
} else {
return 'hi';
}
}

here, both withFlag and withString always return the same value. Either one of the return statements should be changed or the function updated to have only one return statement.

avoid-incorrect-uri. Warns when the Uri constructor receives incorrect arguments.

Uri is quite old and comes from Dart 1 days. For backward compatibility, some parameters do not have strong types and are only check at runtime. Passing values with not supported types will throw an exception.

For example,

Uri(queryParameters: {'hi', 1});

Uri.https('', '', {'hi': 1});

Uri.http('', '', {'hi': 1});

Uri(path: '', pathSegments: ['']);

here, the first 3 constructors allow queryParameters to be a Map or String or Iterable of Strings and passing a number (1) will throw at runtime.

For the last constructor, passing both path and pathSegments at the same time will also throw at runtime and should be avoided.

prefer-add-all. Suggests using .addAll() instead of multiple .add() invocations.

For example,

void fn(List<String> values) {
values.add('first');
values.add('second');
}

should be rewritten to:

void fn(List<String> values) {
values.addAll(['first', 'second']);
}

prefer-for-in. Suggests using for-in loop instead of the regular loop.

For example,

void fn(List<int> arr) {
for (var i = 0; i < arr.length; i += 1) {
...
}
}

here, if i is only used to access the element of the collection, the for loop can be replaced with the for-in loop:

void fn(List<int> arr) {
for (final item in arr) {
...
}
}

and it you still need the index, use the .indexed property:

void fn(List<int> arr) {
for (final (index, item) in arr.indexed) {
...
}
}

avoid-adjacent-strings. Warns against any usage of adjacent strings.

Adjacent strings can lead to unexpected bugs, especially when a function expects several optional string parameters and gets one adjacent string instead.

Flutter

prefer-sized-box-square. Suggests using the SizedBox.square constructor when height and width arguments have the same value.

For example,

SizedBox(height: 10, width: 10);

should be rewritten to:

SizedBox.square(dimension: 10);

avoid-flexible-outside-flex. Warns when a Flexible widget is used outside the Flex widget.

For example,

class SomeWidget {
Widget build() {
return Column(children: [
Expanded(),
Container(Expanded()),
]);
}
}

here, the Expanded widget should not be used inside the Container widget as the latter is not a Flex widget.

prefer-center-over-align. Suggests using a Center widget instead of the Align widget with no passed arguments.

For example,

class SomeWidget {
Widget build() {
return Align();
}
}

should be rewritten to:

class SomeWidget {
Widget build() {
return Center();
}
}

// or with another Alignment value
class SomeWidget {
Widget build() {
return Align(alignment: Alignment.left);
}
}

prefer-padding-over-container. Suggests using a Padding widget instead of the Container widget with only padding or margin arguments.

For example,

class SomeWidget {
Widget build() {
Container(padding: EdgeInsetsGeometry.left, child: Widget());
Container(margin: EdgeInsetsGeometry.left, child: Widget());
}
}

here, both Container widgets can be safely replaced with the Padding widget.

Bloc

prefer-bloc-extensions. Suggests using context.read() or context.watch() instead of BlocProvider.of(context).

Using context extensions is shorter, helps you keep the codebase consistent and makes it less likely to forget listen: true when watch behavior is expected.

For example,

BlocProvider.of(context);

BlocProvider.of<String>(context);

BlocProvider.of<String>(context, listen: true);

should be rewritten to

context.read();
context.read<String>();
context.watch<String>();

avoid-duplicate-bloc-event-handlers. Warns when a bloc declares multiple event handlers for the same event.

Adding multiple handlers is usually a mistake and should not happen. Try changing the event name or creating a single method that combines multiple handlers.

For example,

class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementEvent>(_handle);
this.on<CounterIncrementEvent>(_handleCorrect);
on<CounterIncrementEvent>(_handleSync);

on<CounterDecrementEvent>((event, emit) => emit(state - 1));
on<CounterDecrementEvent>((event, emit) async {
emit(state + 1);
await Future.delayed(const Duration(seconds: 3));
add(CounterDecrementEvent());
});
}
}

here, CounterIncrementEvent is handled 3 times and CounterDecrementEvent is handled 2 times. The proper approach would be to keep only 1 handler for each event with a single handler function.

handle-bloc-event-subclasses. Warns when a bloc does not handle all event subclasses.

Usually, blocs are expected to handle all event subclasses or the event class itself.

For example,

abstract class CounterEvent {}

class CounterIncrementEvent extends CounterEvent {}

class CounterDecrementEvent extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementEvent>(_handle);
}

Future<void> _handle(CounterEvent event, Emitter<int> emit) async {}
}

here, CounterDecrementEvent is not handled and should be either removed or added to the Bloc's constructor:

class CorrectBloc extends Bloc<CounterEvent, int> {
CorrectBloc() : super(0) {
on<CounterIncrementEvent>(_handle);
on<CounterDecrementEvent>(_handle);
}

Future<void> _handle(CounterEvent event, Emitter<int> emit) async {}
}

avoid-passing-build-context-to-blocs. Warns when a Bloc event or a Cubit method accept BuildContext.

Passing BuildContext creates unnecessary coupling between Blocs and widgets and should be avoided. Additionally, depending on BuildContext can introduce tricky bugs when context is not mounted.

For example,

bloc.add(CounterEvent(context));

final event = CounterEvent(context);
bloc.add(event);

...

class CounterCubit extends Cubit<int> {
void another(BuildContext context) async {
...
}
}

should be rewritten to:

bloc.add(CounterEvent());

class CounterCubit extends Cubit<int> {
void another() async {
...
}
}

prefer-sealed-bloc-events. Warns when Bloc events do not have a sealed or final modifier.

For example,

abstract class CounterEvent {}

class CounterIncrement extends CounterEvent {}

class CounterDecrementEvent extends CounterEvent {}

should be rewritten to:

sealed class CounterEvent {}

final class CounterIncrementEvent extends CounterEvent {}

final class CounterDecrementEvent extends CounterEvent {}

prefer-sealed-bloc-state. Warns when Bloc events do not have a sealed or final modifier.

For example,

abstract class CounterState {}

class CounterIncrementState extends CounterState {}

class CounterDecrementState extends CounterState {}

should be rewritten to:

sealed class CounterState {}

final class CounterIncrementState extends CounterState {}

final class CounterDecrementState extends CounterState {}

Provider

prefer-provider-extensions. Suggests using context.read() or context.watch() instead of Provider.of(context).

Using context extensions is shorter, helps you keep the codebase consistent and makes it less likely to forget listen: false when read behavior is expected.

For example,

Provider.of(context);

Provider.of<String>(context);

Provider.of<String>(context, listen: false);

should be rewritten to

context.watch();
context.watch<String>();
context.read<String>();

What’s next

To learn more about upcoming features, check out our public roadmap.

And to learn more about upcoming videos and "Rules of the Week" content, subscribe to our Youtube Channel.

Sharing your feedback

If there is something you miss from DCM right now, want us to make something better, or have any general feedback - join our Discord server!