What’s new in DCM 1.26.0
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).
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.
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
.
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.
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!