Skip to main content

What’s new in DCM 1.25.0

· 8 min read

Cover

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

This release includes 14 new rules (9 for Bloc), additional filters and estimated effort for "dcm init preview", support for adding new seats directly from the DCM Teams Console, and more UX improvements! 🚀

Plus, starting from this version, all rules for Bloc are now included into our Free plan!

warning

❗️ With the next release we plan to discontinue all DCM versions prior to 1.17.3.

If you're still using one of those, consider upgrading to a newer version.

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

Adding Seats from DCM Teams Console

With this release you can now add new seats directly from the DCM Teams Console!

To do that, navigate to the billing page, click the "Add seats" button, and select the number of seats. Submitting this form will automatically update your subscription and add the new seats to your license.

Add Seats Modal

note

We've also reduced the price for adding annual subscription seats back to $14 / seat.

Preview Improvements

New filters

As the main purpose of the dcm init preview command is to help you quickly choose new rules, in this release we've added even more filters to bring the capabilities of this command closer to what is available on the "Rules" page. The command now supports filters for release versions, rule tags, and also for showing only not enabled rules.

To filter rules by the release version, use the --rule-version (all by default) cli option:

Version Filter Example

And to filter by the rule tags, use the --rule-tags (all by default) cli option:

Tags Filter Example

Plus, to get the output for only not enabled rules, use the --only-absent flag:

Only Absent Filter Example

And, of course, you can apply multiple filters at once!

Estimated Effort

To further help you quickly choose the list of rules that can easily be enabled, each rule with violations now also have the estimated effort:

Estimated Effort Example

This effort also depends on whether the rule can be fixed from the CLI or not.

note

As we are still evaluating the estimated effort numbers, there is a high chance they will change in the future releases. If you have any feedback, please let us know!

Rule updates

"banned-usage"

The rule configuration now supports banning only the creation of new instances.

For example,

dart_code_metrics:
rules:
- banned-usage:
entries:
- name: SomeClass
entries:
- name: new
description: Do not create
class SomeClass {
static void apply() {}
}

void main() {
SomeClass();

SomeClass.apply();
}

will highlight only the instantiation of SomeClass while ignoring SomeClass.apply.

"avoid-banned-types"

The rule now supports a new configuration entry called positions for scoping the rule's warnings to a particular expression.

For example,

dart_code_metrics:
rules:
- avoid-banned-types:
entries:
- paths: ['some/folder/.*\.dart']
types: ['AnotherType']
positions: ['is']
message: 'Do not type check with "is", use checkForType() instead.'

will highlight AnotherType usages only in is expressions (is AnotherType).

note

Only the is expression is supported. If you need another options, e.g. type arguments, parameter types, etc., please let us know!

"parameters-ordering"

The rule now support declaring the order in which multiple first or last entries will be sorted.

For example,

dart_code_metrics:
rules:
- parameters-ordering:
required: 'first'
super: 'first'

will put required parameters before super parameters and if changed to:

dart_code_metrics:
rules:
- parameters-ordering:
super: 'first'
required: 'first'

will put the super parameters first.

New rules

Common

avoid-unnecessary-patterns. Warns when a pattern variable declaration does not affect the type of an existing local variable.

Such declarations are often missing a type annotation or a null-check pattern.

For example,

final value = 1;

void fn() {
if (value case final val) {}

if (value case var val) {}
}

here, pattern matching does not change the type of the variable and therefore the variable can be used directly without introducing an additional nesting level. In cases where value is nullable, such cases indicate a missing null-check (?) pattern.

prefer-assigning-await-expressions. Suggests moving await expressions to dedicated local variables.

Await expressions can reduce readability and it's recommended to move them out to dedicated variables.

For example,

Future<void> fn(SomeClass input) async {
if ((await input.asyncMethod()).isEmpty) {}
}

should be rewritten to

Future<void> fn(SomeClass input) async {
final result = await input.asyncMethod();
if (result.isEmpty) {}
}

avoid-single-field-destructuring. Suggests using a regular variable declaration instead of a single field destructuring.

It's recommended to use the good old variable declaration when it comes to a single field destructuring, as it can be confusing when it comes to order of execution (e.g. a destructured prop is executed after the expression, but is placed on the left-hand side) and usually requires importing the class declaration.

For example,

void fn(SomeClass input) {
final SomeClass(:value) = input;
final List(:length) = [input];
}

class SomeClass {
final String value;
...
}

should be rewritten to

void fn(SomeClass input) {
final value = input.value;
}

record-fields-ordering. Ensured consistent alphabetical order of named record fields.

For example,

typedef Record = (String hello, {int b, int a});

(String hello, {int b, int a}) fn() => ('hi', b: 1, a: 2);

should be rewritten to:

typedef Record = (String hello, {int a, int b});

(String hello, {int a, int b}) fn() => ('hi', b: 1, a: 2);

Flutter

prefer-container. Suggests replacing a sequence of widgets with a single Container widget.

The Container widget uses various widgets under the hood (e.g. Align, Padding, ClipPath, etc.), and if a widget tree has a matching sequence, such sequence can be replaced with the Container widget, which helps to reduce the overall nesting level.

For example,

Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsetsGeometry.right,
child: Transform(
transform: Matrix4(),
child: Padding(padding: EdgeInsetsGeometry.right),
),
),
);

here, the sequence of Align, Padding and Transform matches the Container's internal implementation and does not pass any additional arguments. So replacing this sequence with the Container widget won't change the meaning of the code, but will simplify it.

Container(
padding: EdgeInsetsGeometry.right,
alignment: Alignment.center,
transform: Matrix4(),
child: Padding(padding: EdgeInsetsGeometry.right),
);

Bloc

emit-new-bloc-state-instances. Warns when an emit invocation receives the existing state instead of a newly created instance.

Passing existing state objects can create issues when the state is not properly updating.

For example,

abstract class CounterEvent {}

class CounterDecrementEvent extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterDecrementEvent>((event, emit) async {
emit(state);
});
}
}

here, emit(state) emits the already existing state object and is most likely a bug.

avoid-bloc-public-fields. Warns when a Bloc or Cubit has public fields.

It's recommended to keep the state or your Blocs private and only update it in event handlers.

For example,

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

int value = 1;
}

here, the value field should either be made private or removed from the Bloc declaration completely.

prefer-bloc-state-suffix. Warns when a Bloc state class name does not match the configured pattern.

For example,

class SomeClass {}

class CounterBloc extends Bloc<CounterEvent, SomeClass> {
...
}

should be rewritten to

class SomeState {}

class CounterBloc extends Bloc<CounterEvent, SomeState> {
...
}

prefer-immutable-bloc-state. Warns when a Bloc state does not have the @immutable annotation.

Having immutable state objects helps ensure you always pass a newly create object to emit invocations and avoid any issues with the state not being updated.

For example,

sealed class CounterState {}

class CounterIncrement extends CounterState {}

should be rewritten to:


sealed class CounterState {}


class CounterIncrement extends CounterState {}

prefer-bloc-event-suffix. Warns when a Bloc event class name does not match the configured pattern.

For example,

class SomeClass {}

class CounterBloc extends Bloc<SomeClass, int> {
...
}

should be rewritten to:

class SomeEvent {}

class CounterBloc extends Bloc<SomeEvent, int> {
...
}

prefer-immutable-bloc-events. Warns when a Bloc event does not have the @immutable annotation.

For example,

sealed class CounterEvent {}

class CounterIncrement extends CounterEvent {}

should be rewritten to:


sealed class CounterEvent {}


class CounterIncrement extends CounterEvent {}

avoid-instantiating-in-bloc-value-provider. Warns when a BlocProvider.value returns a new instance instead of reusing an existing one.

For example,

BlocProvider.value(
value: RegularService(),
);

should be rewritten to:

BlocProvider.value(
value: existingInstance,
);

avoid-existing-instances-in-bloc-provider. Warns when a BlocProvider returns an existing instance instead of creating a new one.

For example,

final existing = RegularService();

BlocProvider(
create: () => existing,
dispose: (value) => value.dispose(),
);

should be rewritten to:

BlocProvider(
create: () => RegularService(),
dispose: (value) => value.dispose(),
);

avoid-returning-value-from-cubit-methods. Warns when a Cubit method returns a value.

For example,

class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);

String getAsString() => state.toString();
}

here, getAsString is a public method with a non-void return type which should be either changed to have the void return type or completely removed. And to get any state changes, try listening to the state change instead.

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!