Skip to main content

What’s new in DCM 1.9.0

· 9 min read

Cover

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

New "Recommended" presets, 13 new rules and many more improved, dedicated GitHub Action to run DCM checks 🚀.

Let’s go on a quick journey together to explore all updates!

Considering the number of rules and metrics DCM provides, it might be hard to pick the ones you want to start with.

To address this problem this release adds two new presets: recommended and metrics_recommended.

The first one provides the core set of rules with a main focus on highlighting potential errors. There are also some other rules to simplify code and just a few to enforce style. We are not intended to update this preset regularly (aside from the all preset which gets updated with every release).

And the metrics_recommended preset provides all metrics with the default recommended values to simplify enabling them.

DCM GitHub Action

To simplify setting up DCM on GitHub, you can now use two dedicated GitHub actions: setup-dcm and dcm-action.

setup-dcm

setup-dcm installs and sets up DCM for use in GitHub Actions.

Usage example

name: DCM

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install Dart and Flutter
uses: subosito/flutter-action@v2

- name: Install dependencies
run: flutter pub get

- name: Install DCM
uses: CQLabs/setup-dcm@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Run DCM
run: dcm analyze --ci-key="${{ secrets.DCM_CI_KEY }}" --email="${{ secrets.DCM_EMAIL }}" lib

Inputs

The action takes the following inputs:

  • github_token: Used to get the latest DCM version from GitHub releases (required).
  • version: Which DCM version to setup:
    • A specific DCM version (ex. 1.6.0)
    • or latest (default)

Outputs

The action produces the following output:

  • dcm-version: The version of the DCM executable that was installed.

dcm-action

dcm-action runs DCM checks in GitHub Actions. It can also add a comment with dcm checks status to your Pull Requests.

Usage example (combined with setup-dcm)

name: DCM

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install Dart and Flutter
uses: subosito/flutter-action@v2

- name: Install dependencies
run: flutter pub get

- name: Install DCM
uses: CQLabs/setup-dcm@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Run DCM
uses: CQLabs/dcm-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
ci_key: ${{ secrets.DCM_CI_KEY }}
email: ${{ secrets.DCM_EMAIL }}
folders: lib

Inputs

The action takes the following inputs:

NameRequiredDescriptionDefault
github_token☑️Required to post a report on GitHub. Note: the secret GITHUB_TOKEN is already provided by GitHub and you don't have to set it up yourself.
github_patRequired if you had private GitHub repository in the package dependenciesPersonal access token must access to repo and read:user scopes
ci_key☑️License key to run on CI server.
email☑️Email used to purchase the license.
foldersList of folders and files (separated by commas) to scan.[lib]
pull_request_commentPublish report overview directly into your pull request.true
package_namePackage name to differentiate the report. Set when running several DCM runs at once.
fatal_performanceTreat performance level issues as fatal.true
fatal_styleTreat style level issues as fatal.true
fatal_warningsTreat warning level issues as fatal.true

Outputs

Action console

Action console

PR summary

PR summary

PR comment

PR comment

New rules

Fake Async

avoid-async-callback-in-fake-async. Warns when an async callback is passed to FakeAsync.

FakeAsync implementation does not await an async callback and the test will pass even if it should not.

For example,

void main() {
FakeAsync().run((fakeAsync) async {
...
});

fakeAsync((fakeAsync) async {
...
});
}

should be rewritten to

void main() {
FakeAsync().run((fakeAsync) {});

fakeAsync((fakeAsync) {});
}

Common

move-variable-closer-to-its-usage. Warns when a variable is declared in the outer block, but used only in the inner one.

Declaring variables too early can result in additional memory being allocated and potentially heavy calculations being performed.

For example,

void withIf(bool condition) {
final value = 1;
if (condition) {
print(value);
}
}

should be rewritten to

void withIf(bool condition) {
if (condition) {
final value = 1;
print(value);
}
}

avoid-passing-self-as-argument. Warns when an object is used as an argument to its own method.

For example,

final list = [1, 2, 3];
final another = [4, 5, 6];

list.addAll(list);

should be rewritten to

final list = [1, 2, 3];
final another = [4, 5, 6];

list.addAll(another);

avoid-passing-default-values. Warns when an invocation has an argument that matches the parameter's default value.

Explicitly passing a value that is equal to the default value can be considered redundant, but if you depend on external API, you might want to keep explicitly passed default values in case this API changes. Consider that before enabling this rule.

For example,

class WithString {
final String value;

const WithString({this.value = 'some'});
}

void main() {
WithString(value: 'some');
}

should be rewritten to

class WithString {
final String value;

const WithString({this.value = 'some'});
}

void main() {
WithString();
// OR
WithString(value: 'another');
}

prefer-getter-over-method. Suggests to convert a method that has no parameters and side-effects to a getter.

For example,

extension StringExtension on String {
String useCorrectEllipsis() {
return replaceAll('', '\u200B');
}

String removeSpaces() => replaceAll(' ', '');

String removeHyphens() => replaceAll('-', '');
}

here, all these methods do not have any parameters, side-effects and other limitations for them to be converted to getters.

avoid-referencing-discarded-variables. Warns when a variable with the name that has only underscores (ex. _, __, etc.) is referenced.

For example,

void main() {
final _ = 'some value';

print(_);
}

should be rewritten to

void main() {
final value = 'some value';

print(value);
}

void main() {
final _ = 'some value';

// no other code that references _
}

avoid-unconditional-break. Warns when a break, continue, return or throw are used unconditionally in a for loop.

For example,

void main() {
final list = <int>[];

for (final item in list) {
continue;
}

for (final item in list) {
break;
}

for (final item in list) {
return;
}

for (final item in list) {
throw '';
}
}

here, all loops will get only one iteration. It's rarely a desired outcome and usually is an indicator of a mistake.

avoid-weak-cryptographic-algorithms. Warns when a weak cryptographic algorithm (ex. md5 or sha1) is used.

Although there are still some valid cases to use those algorithms, it's best to avoid them if you need to secure the data. Consider using more advanced algorithms instead.

For example,

import 'dart:convert';

import 'package:crypto/crypto.dart';

void main() {
final key = utf8.encode('password1234');
final hmacSha1 = Hmac(sha1, key);
}

here, it's preferable to use sha256 in order to avoid any vulnerabilities sha1 has.

avoid-identical-exception-handling-blocks. Warns when a try / catch has multiple catch blocks with the same body.

For example,

void main() {
try {
print('hello');
} on StateError catch (error) {
print(error);
} on Exception catch (error) {
print(error);
}
}

should be rewritten to

void main() {
try {
print('hello');
} on StateError catch (error) {
print('Got StateError $error');
} on Exception catch (error) {
print('Got Exception $error');
}
}

avoid-recursive-calls. Warns when a function calls itself recursively.

Dart does not support tail call optimization, so when working with a recursion there is always a chance to hit a StackOverflow error. Consider rewriting recursion to a stack instead.

avoid-missing-interpolation. Warns when a string is equal to a variable name that is available in the current scope but is not wrapped into an interpolation.

For example,

void main() {
final someClass = SomeClass('hello');

print('someClass');
print('value: $someClass.value');
print('isNotEmpty: $someClass.value.isNotEmpty');

print('$function(value)');
}

String function(String value) {
return 'result value';
}

class SomeClass {
final String value;

const SomeClass(this.value);
}

here, the rule will highlight not only someClass being printed instead of the someClass.toString(), but also that $someClass.value is an incomplete interpolation and misses {}.

avoid-unnecessary-if. Warns when a return statement inside an if block is equal to the return statement after it.

For example,

final value = 1;

int function() {
if (value == 2) {
return 1;
}

return 1;
}

here, both returns are equal which makes the if statement redundant.

parameters-ordering. Ensures consistent alphabetical order of parameters by their names.

For example,

void named({String b, String a}) {}

here, the rule will trigger since a should come first.

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!