Skip to main content

What’s new in DCM 1.26.0

· 12 min read

Cover

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

This release includes 17 new rules (4 for Flutter), new JSON output format for all commands, improved help output for "dcm init preview", new rule tags, configuration autocompletion in analysis_options.yaml (IntelliJ/AS), rule pages improvements, and more UX improvements and bug fixes! 🚀

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

Website Improvements

In addition to some minor updates, such as a "last updated" date for each documentation page or a redesigned list of rules, this release includes a set of new tags to further simplify finding useful DCM rules.

  • collections
  • encapsulation
  • assignments
  • async
  • conditions
  • types
  • control-flow
  • testing
  • naming
  • imports
  • nullability
  • error-handing
  • abstractions

For example, selecting conditions will show you all rules related to binary and conditional expressions (e.g. avoid-equal-expressions, avoid-negated-conditions, avoid-unnecessary-conditionals and so on).

New Tags

Additionally, we've updated all rule pages and added labels for the rule version, severity, preset, and whether the rule has configuration or auto-fixes.

New Badges

We hope these changes help you with discovering new and existing DCM rules!

Preview Improvements

"dcm init preview" now also supports new tags, and has improved help output, which should make it even easier to work with the command option (there are quite a lot of them):

...

--rule-types="common,flutter,..." Rule categories to include (all by default).

[common] (default) Rules for Dart.
[flutter] (default) Rules for Flutter.
[intl] (default) Rules for the 'intl' package.
[flame] (default) Rules for the 'flame' package.
[fake-async] (default) Rules for the 'fake_async' package.
[provider] (default) Rules for the 'provider' package.
[bloc] (default) Rules for the 'bloc' package.
[equatable] (default) Rules for the 'equatable' package.
[patrol] (default) Rules for the 'patrol' package.
[get-it] (default) Rules for the 'get_it' package.
[flutter-hooks] (default) Rules for the 'flutter_hooks' package.
[riverpod] (default) Rules for the 'riverpod' package.
[getx] (default) Rules for the 'getx' package.
[easy-localization] (default) Rules for the 'easy_localization' package.
[firebase-analytics] (default) Rules for the 'firebase_analytics' package.

--rule-version="1.26.0" Rule version to include (all by default).

[1.0.0] Rules added in 1.0.0
[1.1.0] Rules added in 1.1.0
[1.2.0] Rules added in 1.2.0
[1.3.0] Rules added in 1.3.0
[...]
[1.23.0] Rules added in 1.23.0
[1.24.0] Rules added in 1.24.0
[1.25.0] Rules added in 1.25.0
[1.26.0] Rules added in 1.26.0

--rule-tags="correctness,consistency,..." Rule tags to include (all by default).

[abstractions] Rules for detecting abstraction issues.
[accessibility] Rules for detecting accessibility issues.
[architecture] Rules for enforcing architecture agreements.
[assignments] Rules for detecting issues with assignments.
[async] Rules for detecting issues with asynchronous code and Futures.

...

New JSON Output Format

To simply working with JSON output of all DCM commands, this release adds a shared common structure for all reported issue (id, message, location and effortInMinutes):

{
"id": "banned-usage",
"message": "Use context.pop() instead. (NavigatorState.pop is banned).",
"location": {
...
},
"effortInMinutes": 6,
},

Plus, all reporters now provide a summary with the number of issues:

{
"title": "Total lint warning issues",
"value": 5
},
{
"title": "Total lint style issues",
"value": 6
}

Additionally, the "--json-path" CLI option has been replaced with "--output-to" which now writes the passed report output format to a separate file (can be any file type, not just JSON), and also writes to stdout (console).

For example, dcm analyze lib -r json --output-to=result.json will print all issues to stdout and create the result.json with all issues in JSON format.

This option can be useful when running DCM on CI/CD and also reading the output (e.g. in JSON or checkstyle) and uploading it to other system.

Configuration Autocompletion in IntelliJ/AS Plugin

On top of fixing a few Intellij-related issues, this version includes support for configuration autocompletion in analysis_options.yaml.

note

To get configuration autocompletion working, install DCM 1.24 + and update your DCM plugin to 1.19.0.

Support for "dcm run" in GitHub Actions

With this release we are excited to announce that DCM GitHub Actions now supports running "dcm run". This means that you can run several DCM commands at once (and also will get a single report).

For example,

- name: Run DCM
uses: CQLabs/dcm-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
ci-key: ${{ secrets.DCM_CI_KEY }}
email: ${{ secrets.DCM_EMAIL }}
analyze: true
folders: lib

this configuration will work as dcm-action@v1 running only the analyze command, but the following one:

- name: Run DCM
uses: CQLabs/dcm-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
ci-key: ${{ secrets.DCM_CI_KEY }}
email: ${{ secrets.DCM_EMAIL }}
analyze: true
analyze-assets: true
check-unused-code: true
folders: lib

will also run dcm analyze-assets and dcm check-unused-code. Plus, it supports various configuration options for each DCM command.

You can find all supported option in the updated documentation.

Rule updates

"prefer-simpler-boolean-expressions"

Now supports more cases, including the simplification of conditional expressions. For example,

void fn(bool condition, bool foo) {
final value = condition ? foo : !foo;
final another = condition ? !foo : foo;
}

can be simplified to (without changing the meaning):

void fn(bool condition, bool foo) {
final value = condition == foo;
final another = condition != foo;
}

"no-equal-then-else"

Now supports one new case for conditional expressions. For example,

void fn(bool a, bool b) {
print(a == b ? b : a);
}

Although at first glance it appears that the two parts of the expression are not equal, upon closer inspection you will notice that a is chosen when a == b (because b == a) and when it is not equal to b. This means that the entire expression can be simplified to just a.

"use-existing-variable"

Now supports finding expressions that can be replaced by an existing variable from a pattern destructuring. For example,

void fn(Element input) {
final Source(:fullName) = input.source;

print(input.source.fullName);
}

here, input.source.fullName can be replaced by fullName without changing the meaning.

"arguments-ordering"

Now supports super constructor invocations. For example,

class Child extends Person {
const Child() : super(age: 1, name: '');
}

class Person {
Person({required this.name, required this.age});
}

here, the arguments of the super call do not match the order of parameters and should instead be declared as super(name: '', age: 1).

"avoid-duplicate-initializers"

Now supports variables from pattern destructuring.

final SomeClass(:value) = instance;
final SomeClass(value: another) = instance;

New rules

Common

avoid-commented-out-code. Warns when a section of code is commented out.

Keeping commented out code is usually unnecessary as the modern development environment includes a version control system (e.g. git) and it's preferable to keep incomplete code in a separate branch.

Overall, commented out code can be a sign of an incomplete or missing implementation which is usually a bug or hidden technical debt.

For example,

class Some {
// LINT: Avoid commented out code. Try using or removing it.
// void apply(String value) {
// print(value);
// }

void another() {
...
}
}

here, the rule will highlight 3 commented out lines of code.

avoid-future-ignore. Warns when the result of any Future is ignored by calling .ignore().

Calling .ignore() on a Future not only ignores the result, but also suppresses any exception from that Future which can lead to such exceptions not being logged or ever noticed.

In cases where .ignore() is correct and expected, consider adding a comment to provide all necessary context for the future reader.

For example,

void main(Future myFuture) {
myFuture.ignore();
}

should be rewritten to unawaited(myFuture) or have a descriptive comment.

prefer-switch-expression. Suggests converting switch statements to switch expressions.

In cases where all branches of a switch statement have a return statement or assign to the same variable, using a switch expression can make the code more compact and easier to understand.

For example,

AssetSensorType convert(AssetSensorCategory sensorCategory) {
switch (sensorCategory) {
case AssetSensorCategory.vibration:
return AssetSensorType.first;
case AssetSensorCategory.energy:
return AssetSensorType.second;
}
}

should be rewritten to

AssetSensorType convert(AssetSensorCategory sensorCategory) {
return switch (sensorCategory) {
AssetSensorCategory.vibration => AssetSensorType.first,
AssetSensorCategory.energy => AssetSensorType.second,
};
}

avoid-unnecessary-continue. Warns when a continue statement is unnecessary and can be removed.

For example,

for (final value in _values) {
...

if (_value == 1) {
...
} else {
continue;
}
}

here, the continue is the last statement in its block and in the for-loop's block, which makes it redundant.

no-empty-string. Warns when a string literal is empty.

While empty string literals are quite common, in some cases they are a refactoring leftover (or a value that has not been updated with a real value). This rule highlights all usages of empty strings, and for any valid usage, consider creating a shared constant variable and using it instead.

For example,

void fn() {
Uri.https('');
Error('');
}

here, both string literals are unwanted and should be changed to correct string values.

avoid-continue. Warns when the continue statement is used.

continue statements can be confusing (especially, if used under certain conditions) and can complicate refactoring. Consider rewriting your code without them.

For example,

for (final value in _values) {
if (_value == 1) {
continue;
}
}

pattern-fields-ordering. Ensures consistent alphabetical order of pattern fields by their names.

For example,

void fn() {
final Person(name: name, age: age) = _getPerson();
final Person(:name, :age) = _getPerson();
}

class Person {
Person({
required this.name,
required this.age,
});

final String name;
final int age;
}

should be rewritten to

void fn() {
final Person(age: age, name: name) = _getPerson();
final Person(:age, :name) = _getPerson();
}

use-existing-destructuring. Warns when there is an existing class or record destructuring that can be used instead of accessing the property directly.

For example,

void fn(SomeClass variable) {
final SomeClass(:value) = variable;

print(variable.another);
}

should be rewritten to

void fn(SomeClass variable) {
final SomeClass(:value, :another) = variable;

print(another);
}

prefer-class-destructuring. Warns when several different property accesses can be replaced by a single class destructuring declaration.

Using class destructuring can help simplify the code and reduce duplicate property accesses.

For example,

void fn(SomeClass variable) {
final value = variable.another;
final another = variable.value;

print(variable.inner);
}

should be rewritten to

void fn(SomeClass variable) {
final SomeClass(:another, :value, :inner) = variable;
}

prefer-static-method. Suggests converting an instance method to a static method if it does not reference any instance or inherited members.

For example,

class Child extends Parent {
const Child() : super('hello');

void some() {
print('hi');
}
}

should be rewritten to

class Child extends Parent {
const Child() : super('hello');

static void some() {
print('hi');
}
}

avoid-if-with-many-branches. Warns when an if statement has too many branches.

For example,

void fn(int value) {
if (value == 1) {
...
} else if (value == 2) {
...
} else if (value == 3) {
...
} else {
...
}
}

should be rewritten to

void fn(int value) {
switch (value) {
case 1:
...
case 2:
...
case 3:
...
default:
...
}
}

prefer-commenting-future-delayed. Warns when Future.delayed invocations do not have a descriptive comment.

Adding a comment to the Future.delayed invocation can help a future reader understand the reason for the delay.

For example,

Future<void> fn() async {
...

await Future.delayed(Duration.zero);
}

should be rewritten to

Future<void> fn() async {
...

// the reason for the delay is ...
await Future.delayed(Duration.zero);
}

avoid-complex-arithmetic-expressions. Warns when an arithmetic expression has too many terms.

For example,

int calc(int a, int b) {
return a + a + a + b + b + b + b;
}

should be rewritten to

int calc(int a, int b) {
return calcA(a) + calcB(b);
}

int calcA(int a) => a + a + a;

int calcB(int b) => b + b + b + b;

Flutter

prefer-spacing. Suggests using the spacing argument of a Row, Column or Flex instead of using SizedBox widgets for adding gaps between children widgets.

info

This argument is available only since Flutter 3.27. Enable this rule only if you have already migrated to that (or later) version.

For example,

Column(children: [
Widget(),
SizedBox(height: 10),
Widget(),
]);

Column(
children: [Widget(), Widget()].separatedBy(
const SizedBox(height: 10),
),
);

here, both Column widgets can be updated to use spacing instead of having SizedBox children.

use-closest-build-context. Warns when the referenced BuildContext variable is not the closest BuildContext.

Using incorrect BuildContext reference can lead to hard to spot bugs and can happen if the variable was renamed from the usual context name (e.g. to _ due to being previously unused).

For example,

class MyOtherWidget extends StatelessWidget {

Widget build(BuildContext context) {
return Builder(builder: (_) {
return _buildMyWidget(context);
});
}
}

here, the referenced context is not the closest context (_ is) and using can lead to unexpected errors.

prefer-transform-over-container. Suggests using a Transform widget instead of the Container widget with only the transform argument.

For example,

class SomeWidget {
Widget build() {
Container(transform: Matrix4.skewY(0.3)..rotateZ(-math.pi / 12.0), child: Widget());
}
}

here, the Container widget can be replaced with the Transform widget.

prefer-align-over-container. Suggests using a Align widget instead of the Container widget with only the alignment argument.

For example,

class SomeWidget {
Widget build() {
Container(alignment: Alignment.topRight, child: Widget());
}
}

here, the Container widget can be replaced with the Align widget.

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!