What’s new in DCM 1.25.0
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!
❗️ 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.
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:
And to filter by the rule tags, use the --rule-tags
(all by default) cli option:
Plus, to get the output for only not enabled rules, use the --only-absent
flag:
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:
This effort also depends on whether the rule can be fixed from the CLI or not.
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
).
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!