What’s new in DCM 1.24.0
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! 🚀
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!
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:
And metrics now have a code action for opening the documentation page:
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.
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).
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!