What’s new in DCM 1.8.0
Today we’re excited to announce the release of DCM 1.8.0!
New command to simplify integrating DCM into existing projects, 17 new rules (2 for the Patrol testing library 🚀), support for dart_frog
files for check-unused-code and check-unused-files, Gitlab reporter format for all existing commands and more 🔥.
And with this release, DCM crossed the 200 rule mark 🥳 (currently 215)!
Let’s go on a quick journey together to explore all updates!
Unused code in the IDE (Beta)
We are excited to announce that you can now enable the display of unused code in the IDE!
To do so, open the IDE extension settings and search for "Show unused code" option.
There are plenty areas of improvements (for example, this feature only supports the "monorepo" mode for now, can be a bit annoying when you just created a new class and so on). That's why we are releasing it as "Beta" (and disabled by default) and will work on improving it in the upcoming releases.
But we'd also love to hear your feedback!
Command updates
New command "dcm init preview"
This command allows you to run all available (or explicitly passed) DCM rules against your codebase and see which rules show violations, how many of these violations you have and whether these violations can be automatically fixed.
Here is the output example,
You can change the output format to one of the following: console (default), table or analysis_options. The latter can help you generate a list of rules that you can copy and paste into the analysis_options.yaml
file you have.
Here is the analysis_options
output example,
You can also change the list of the rules that DCM runs. To do so, you can pass --severity
, --rule-types
and --only-fixable
options.
For example, to get only for Flutter
and Bloc
rule violations with warning
and style
severities, pass --severity=warning,style --rule-types=flutter,bloc
.
Note: this command is a subcommand to the dcm init
command, you can call it via dcm init preview
. And to generate the baseline, call dcm init baseline
.
Flutter plugins and dart_frog support for check-unused-code and check-unused-files commands
Check unused code and Check unused files commands rely on how files within the project are imported (and exported) to build the usage graph. But when it comes to specific conventions or packages, fully relying on this concept can produce unwanted false-positives.
That's why with this release to reduce this unwanted noise DCM supports Flutter federated plugins (the fileName
field in the pubspec file is now used to correctly mark the give file as used) and dart_frog
middlewares, requests and the entrypoint file.
GitLab format support
Initially, GitLab format was supported by only two commands, but with this release this format is supported by all other commands that are meant to be used on CI.
These commands are: check-dependencies
, check-exports-completeness
, check-unnecessary-nullable
, check-unused-code
, check-unused-files
and check-unused-l10n
.
Check dependencies
To reduce the need of passing excluded packages manually, with this release the "check-dependencies" command supports an internal list of packages that are used with other packages.
For example, if a package has a freezed_annotation
or json_serializable
then build_runner
and freezed
(only for freezed_annotation
) will be automatically marked as used.
If you have other packages in mind, that can be automatically excluded based on the presence of other packages in pubspec, don't hesitate to share your feedback!
New rules
Patrol
prefer-custom-finder-over-find. Suggests using custom finders instead of find in Patrol tests.
Patrol provides its own way of finding widgets, which is the idiomatic way to write Patrol tests.
For example,
void main() {
patrolTest('widget test', () {
...
find.byType(SomeType);
find.text('SomeText');
});
}
should be rewritten to
void main() {
patrolTest('widget test', () {
...
$(SomeType);
$('SomeText');
});
}
You can read more about Patrol's custom finders here.
prefer-symbol-over-key. Suggests using symbols instead of string keys in Patrol tests.
When it comes to using Keys, Patrol has its own way as well. It's recommended to stick up to the Patrol's idiomatic way here and use symbols over string keys.
For example,
void main() {
patrolTest('widget test', () {
...
$(Key('button'));
});
}
should be rewritten to
void main() {
patrolTest('widget test', () {
...
$(#button);
});
}
Common
avoid-map-keys-contains. Warns when Map's .keys.contains
is used instead of containsKey
.
Although it might be convenient to use .keys.contains
, it comes with a huge performance downside (it is 6000 times slower than containsKey
) and is not recommended to be used.
For example,
void main() {
final map = {'hello': 'world'};
map.keys.contains('hello');
}
should be rewritten to
void main() {
final map = {'hello': 'world'};
map.containsKey('hello');
}
prefer-correct-json-casts. Warns when a JSON object type cast is done in an unsafe way that will throw at runtime.
Casting a JSON object to a map or list with generic types specified as anything except Object?
/ Object
/ dynamic
can lead to a runtime exception. Instead, cast first to Object?
and then to the desired type.
For example,
void main() {
final jsonRaw = '{"items": {"hello": 1}}';
final items = json['items'] as Map<String, int>;
final jsonRaw = '{"items": [1, 2, 3]}';
final items = json['items'] as List<int>;
if (json case Object() as List<String>) {}
}
should be rewritten to
import 'dart:convert';
void main() {
final jsonRaw = '{"items": {"hello": 1}}';
final json = jsonDecode(jsonRaw) as Map<String, Object?>;
final items = (json['items'] as Map<String, Object?>).cast<String, int>();
final jsonRaw = '{"items": [1, 2, 3]}';
final json = jsonDecode(jsonRaw) as Map<String, Object?>;
final items = (json['items'] as List<Object?>).cast<int>().toList();
}
avoid-async-call-in-sync-function. Warns when an async function is invoked in non-async blocks.
Making asynchronous calls in non-async
functions is usually the sign of a programming error. In general these functions should be marked async
and such futures should likely be awaited.
For example,
Future<void> asyncValue() async => 'value';
class SomeClass {
SomeClass() {
asyncValue();
}
Future<void> asyncMethod() => asyncValue();
Future<void> anotherAsyncMethod() async => asyncValue();
void syncFn() => asyncValue();
}
here, the constructor of SomeClass
and the syncFn
method invoke a function that is asynchronous and therefore will be executed later. If the code that calls them relies on this function result, it can lead to the wrong behavior.
It's recommended to await all async functions (and for the exceptional cases you can wrap them with unawaited
or .ignore()
).
So, the example above should be rewritten to
Future<void> asyncValue() async => 'value';
class SomeClass {
SomeClass() {
unawaited(asyncValue());
}
Future<void> asyncMethod() => asyncValue();
Future<void> anotherAsyncMethod() async => asyncValue();
}
This rule is an improved version of the standard rule discarded_futures
.
avoid-duplicate-mixins. Warns when a class has a mixin that is already present in that class's hierarchy.
Duplicated mixins do not add any meaning and are usually a sign of a mistake, but they are also hard to spot.
For example,
mixin class MC {}
mixin M {}
class C1 = MC with MC;
class C2 = Object with M, M;
class C3 extends MC with MC {}
class C4 extends C1 with MC;
class C5 extends C6 with MC;
class C6 extends C1;
should be rewritten to
mixin class MC {}
mixin M {}
class C1 = MC;
class C2 = Object with M;
class C3 extends MC {}
class C4 extends C1;
class C5 extends C6;
class C6 extends C1;
avoid-nullable-interpolation. Warns when an interpolation string contains a nullable value.
Displaying null
instead of the actual value is rarely a desired outcome. This rule helps you stop cases like that and add a null check before the value is used.
For example,
void function(String? value) {
print('$value hello');
}
should be rewritten to
void function(String? value) {
if (value != null) {
print('$value hello');
}
print('${value != null ? value : ''} hello');
}
avoid-unused-instances.Warns when a newly created object is not being used.
Sometimes due to a newly created object becomes unused (instead of being returned or thrown). And cases like that can be really hard to spot.
For example,
class SomeClass {
...
}
SomeClass function() {
if (someCondition) {
SomeClass();
}
return SomeClass();
}
here, SomeClass
creation inside the if statement is unused and therefore is a bug. It's expected to be returned instead.
class SomeClass {
...
}
SomeClass function() {
if (someCondition) {
return SomeClass();
}
return SomeClass();
}
enum-constants-ordering. Ensures consistent alphabetical order of Enum constants.
Together with the map-keys-ordering
this rule helps you delegate the ordering check to DCM and not think about it.
For example,
enum SomeEnum {
second,
first,
}
should be updated to
enum SomeEnum {
first,
second,
}
to keep the alphabetical order.
prefer-named-boolean-parameters. Warns when a declaration has a boolean positional parameter.
Converting positional boolean parameters to named parameters helps you avoid situations with a wrong value passed to the parameter.
For example,
void someFunction(String name, bool isExternal, bool isTemporary) {
...
}
someFunction('User', true, false);
should be rewritten to
void someFunction(
String name, {
required bool isExternal,
required bool isTemporary,
}) {
...
}
someFunction('User', isExternal: true, isTemporary: false);
prefer-correct-for-loop-increment. Warns when a for loop increments a wrong variable.
For example,
void main() {
var j = 1;
for (var i = 0; i < 10; j++) {
...
}
}
here, it's really hard to notice that it's j
that is being incremented and not i
therefore resulting in an endless loop.
prefer-public-exception-classes. Warns when an exception class declaration is not public.
If a private exception is thrown, the external code will have to catch instances of the nearest accessible parent class. In this case, it becomes more difficult to handle specific exceptions, because the external code will not be able to clearly identify the problem that has arisen.
Lack of clear identification of exceptions poses an additional security risk because some specific exceptions may require specific handling rather than general handling.
For example,
class _MyPrivateException implements Exception {}
class _MyPrivateException extends Exception {}
should be rewritten to
class MyException implements Exception {}
class MyException extends Exception {}
match-class-name-pattern. Configure class names to match the given pattern.
Helps you ensure that classes within a given directory or file are named in a specific way.
With the given config:
dart_code_metrics:
...
rules:
...
- match-class-name-pattern:
entries:
- path: '.*state.dart'
pattern: 'State$'
ignore-private: true
will trigger on
class SomeClass {
...
}
class MyState {}
since SomeClass
is expected to end with State
.
newline-before-case. Enforces a blank line between cases in a switch statement.
For example,
final value = 'some-value';
switch (value) {
case '1': {
...
}
case '2':
...
case '3':
}
should be rewritten to
final value = 'some-value';
switch (value) {
case '1': {
...
}
case '2':
case '3':
...
}
avoid-unnecessary-reassignment. Warns when a value is reassigned to a variable without using the initial value.
For example,
String alpha = '';
if (something) {
alpha = 'true value';
} else {
alpha = 'false value';
}
String beta = '';
beta = something ? 'true value' : 'false value';
SomeObject object = SomeObject();
object = something ? SomeObject() : SomeObject();
here, since the alpha
, beta
and object
variables are not used before their value is reassigned, the code can be simplified to
final alpha = something ? 'true value' : 'false value';
final beta = something ? 'true value' : 'false value';
final object = SomeObject();
Flutter
avoid-recursive-widget-calls. Warns when a Widget is recursively used within the same Widget.
Recursive use of a widget is most often a sign of a bug and can get your application stuck in an endless loop.
For example,
class MyHomePage extends StatefulWidget {
const MyHomePage();
_MyHomePageState createState() => _MyHomePageState<MyHomePage>();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build() {
return MyHomePage();
}
}
class MyOtherWidget extends StatelessWidget {
Widget build() {
return MyOtherWidget();
}
}
here, _MyHomePageState
and MyOtherWidget
recursively call themselves.
prefer-text-rich. Warns when a RichText
widget is used instead of Text.rich
.
RichText
does not handle text scaling very well, so if accessability is important for you, use Text.rich
instead.
For example,
RichText(
text: TextSpan(
text: 'Hello ',
children: [
TextSpan(text: 'bold'),
TextSpan(text: ' world!'),
],
),
);
should be rewritten to
Text.rich(
TextSpan(
text: 'Hello ',
children: [
TextSpan(text: 'bold'),
TextSpan(text: ' world!'),
],
),
)
Other rules that got updates
Rules below got several updates (including bug fixes, new options and new use-cases supported):
- avoid-barrel-files
- arguments-ordering
- avoid-importing-entrypoint-exports
- format-comment
- avoid-unnecessary-futures
- avoid-redundant-async
- function-always-returns-null
- unnecessary-trailing-comma
- avoid-mutating-parameters
- prefer-first
- prefer-last
- avoid-unsafe-collection-methods
- prefer-explicit-parameter-names
- prefer-correct-error-name
- newline-before-return
- avoid-unnecessary-return
- member-ordering
- prefer-moving-to-variable
- list-all-equatable-fields
- avoid-long-functions
- avoid-explicit-type-declaration
- no-equal-then-else
- prefer-early-return
- dispose-fields
- avoid-unrelated-type-assertions
- no-equal-arguments
- avoid-unused-parameters
New quick fixes for existing rules 🔥
Rules below got quick fixes support (by dcm fix
, in the IDE or both):
- map-keys-ordering
What’s next
Improving the "unused code in the IDE" feature. There are plenty areas of improvements (for example, this feature only supports the "monorepo" mode for now, can be a bit annoying when you just created a new class and so on). That's why we are releasing it as "Beta" (and disabled by default) and will work on improving it in the upcoming releases.
New commands. Reworking metric HTML reports unblocked other commands that need an HTML report as well. We hope to add them in the upcoming release to make DCM cover even more use-cases.
Move performance improvements. We received a several reports about DCM performance issues on very large codebase. This is a serious problems that we also want to address. There is still some room to make DCM better.
Sharing your feedback
If there is something you miss from DCM right now or want us to make something better or have any general feedback - join our Discord server!