What’s new in DCM 1.23.0
Today we’re excited to announce the release of DCM 1.23.0!
This release includes 9 new rules, 9 new quick fixes for existing rules, new command for analyzing assets for various issues, detection of duplicate test cases, reworked rules documentation and much more! 🚀
❗️ the "--monorepo" flag was replaced with "--exclude-public-api", and now the default behavior for commands is if the "--monorepo" flag is passed
Let's go through the highlights of this release! (And you can find the full list of changes in our changelog)
New Command for Analyzing Assets
We are excited to present a new command for analyzing assets that should help you address various image-related issues: exceeding size, incorrect naming, incorrect placement, incorrect formats and issues with missing or unused high-resolution images.
Exceeding Size
To find images with exceeding size, pass the --size
CLI option. By default, this option is set to 500KB
, but is fully configurable and accepts different units (for example, 10KB, 1MB or 100B).
Here is an example of the output:
assets/dart-course-banner-small.webp (16.29 KB, 804 x 452):
⚠ this image size exceeds the 10KB limit.
Incorrect Naming
To find images with incorrect naming, pass the --naming
CLI option. This option accepts one of three different options: pascal
, kebab
and snake
.
Multiple naming conventions used simultaneously (for example, some images in "kebab-case" and others in "snake_case") create additional inconsistencies and choices to make. Choosing only one convention and sticking to it can eliminate such problems.
Incorrect Placement
To find images placed in incorrect folders, pass the --allowed-formats=
CLI option.
This option accepts a comma-separated list of key:value pairs that represent folders and expected formats.
Can be useful if you have strict rules for where to place images (for example, svgs in icons
only).
Incorrect Formats
For now, only reports images that can be converted to .webp
and thus have a smaller size by at least 25%.
To find images that can be converted to .webp
, pass the --webp
CLI flag.
While WebP
is a more efficient format than PNG and JPG, not all platforms recognize it as an image format (e.g. you might not be able to view it in a native image app). Keep this in mind before converting any image that you intend to use this way.
Issues with High-Resolution Images
To find incorrect, missing or unused high-resolution images, pass the --resolution
CLI flag.
A high-resolution image is considered incorrect when the resolution does not match the modifier. For example, the regular image is 200x100
and the 2.0x version of it is 300x150
(when it should be 400x200
).
An image is considered missing a high-resolution alternative when there are multiple folders for high-resolution versions (e.g. 2.0x and 3.0x), but only 2.0x image is present.
A high-resolution image is considered unused when there is no regular image for that resolution (for example, there is assets/images/2.0x/icon.png
but no assets/images/icon.png
).
HTML Report Example
While the command supports all available reports (console, json and so), here is an example of the HTML report:
Reworked Documentation for Rules
The rules documentation also got a new update!
We've updated all rule docs to have a better highlight region and a proper warning message to help you understand what problem the rule tries to address. The description for some rules has also been updated for that same reason.
Additionally, most rules with additional configuration now have new examples specifically for their config options (for example, arguments-ordering).
And since the tool now has more than 350 rules, all rules now have a list of related rules to improve the overall rule discoverability.
Bonus point: some rules now have a related CWE issue listed under the "Additional Resources" section (example). To find all CWE rules, use the "cwe" tag.
Reworked "--monorepo" Flag
The "monorepo" mode (with the --monorepo
flag) was first introduced over 3 years ago, and its behavior was one of the most confusing parts of the product.
With this release, we have finally addressed this issue.
From now on, the --monorepo
flag is replaced with a new flag called --exclude-public-api
(or --ep
). Same applies to the monorepo:
configuration entry in analysis_option.yaml
.
As for the behavior of all commands that accepted this flag, instead of excluding public APIs by default, they now analyze all code. And if you want to exclude public APIs, just pass the new flag.
Here is a short migration guide for you:
- If you were using
--monorepo
, you can simply remove it and the commands will behave the same. - If you were using the
monorepo: true
config entry, you can simply remove it or replace it withexclude-public-api: false
. Andmonorepo: false
needs to be replaced withexclude-public-api: true
to keep the old behavior. - If you want to exclude public APIs in some of your packages, pass
--exclude-public-api
to any command that previously accepted--monorepo
.
Detection of Code Used Only in Tests
Even though the implementations code (placed under lib
) that is only referenced in tests is technically in use, most of the time it's just a leftover of a refactoring that can be simply removed (and if not, should be properly marked as @visibleForTesting
).
Previously, such code would be skipped when the command was called on several folders (for example dcm uc lib test
) leaving it unnoticed, but with this release, the "check-unused-code" command now correctly reports such code as unused:
dcm uc lib test
Gives the following output:
lib/src/umbrella/umbrella_runner.dart:
⚠ constructor UmbrellaRunner is used only in tests
at lib/src/umbrella/umbrella_runner.dart:57:3
⚠ method runSelected is used only in tests
at lib/src/umbrella/umbrella_runner.dart:78:3
⚠ class UmbrellaRunner is used only in tests
at lib/src/umbrella/umbrella_runner.dart:44:1
Detection of Duplicate Tests
Another improvement of the existing command: "check-code-duplication" now supports detecting duplicate test cases!
To get such reports, simply run this command against your test folder:
dcm cd test
And here is an example of the output for the Flame repository:
test/game/game_widget/game_widget_test.dart:
⚠ test case "handles keys when game is KeyboardKeys"
at test/game/game_widget/game_widget_test.dart:387:59
has 1 duplicate declaration:
- test case "handles keys when KeyboardEvents"
at test/game/game_widget/game_widget_test.dart:425:53
which marks this and this one as duplicates.
New "--flat" Flag for Metrics HTML Reports
And a small update for Metrics HTML reports!
If you want to include only the list of files (skipping the folders), you can now use a new option called "--flat":
dcm cm . --reporter=html --open --flat
Rule updates
New Quick Fixes
This release includes 9 new quick fixes for already existing rules:
- prefer-correct-setter-parameter-name
- prefer-correct-throws
- avoid-suspicious-super-overrides
- avoid-non-final-exception-class-fields
- avoid-duplicate-patterns
- avoid-casting-to-extension-type
- avoid-unnecessary-super
- avoid-missing-tr
- avoid-missing-image-alt
Most of these fixes are not fixable via "dcm fix" as they either suggest several different options ("prefer-correct-setter-parameter-name" and "avoid-missing-tr") or can break you code.
"prefer-moving-to-variable" Update
In DCM 1.11.0 the "prefer-moving-to-variable" rule was changed to also highlight expressions that can be replaced by an existing variable.
With this release, this behavior is now a separate rule called "use-existing-variable" (which also has a quick-fix) and is removed from "prefer-moving-to-variable".
Consider enabling "use-existing-variable" if you still want to see such expressions highlighted.
New rules
Common
prefer-simpler-boolean-expressions. Suggests simplifying various boolean expressions.
Boolean conditions with redundant parts reduce readability and make the code overly complex. This rule helps simplifying such conditions without changing their result.
For example,
void fn(bool flag, bool anotherFlag) {
if (flag && _someMethod() || !flag && _anotherOne()) {}
if (_someMethod() && flag || _someMethod() && anotherFlag) {}
}
bool _someMethod() => false;
bool _anotherOne() => true;
here, the first condition uses flag
and !flag
in the logical OR expression. This condition can be replaced with a conditional expression flag ? _someMethod() : _anotherOne()
which does not change the result of the expression and makes it simpler.
For the second condition, _someMethod()
is present in both sides of the logical OR expression which means that it can be moved out like so: _someMethod() && (flag || anotherFlag)
. Again, the result stays the same, but the condition is now much simpler.
avoid-late-final-reassignment. Warns when a late final
variable is assigned multiple times in the same block.
Assigning to a late final
variables multiple times will throw at runtime and should be avoided.
For example,
class _TestState {
late final String _title;
void initState() {
_title = 'title';
_title = 'another';
}
}
here, the final late _title
field get assigned twice in a row, resulting in a runtime exception. Either the second assignment should removed, or they both should be placed in different branches of one if statement making these assignments conditional.
avoid-referencing-subclasses. Warns when a parent class references its child class.
Subclass references can be confusing and violate several rules of object-oriented design. Consider moving such references out of the parent class.
For example,
class Parent {
void doWork(Child sub) {}
}
class Child extends Parent {}
here, the doWork
should be rewritten to not accept the Child
parameter.
avoid-high-cyclomatic-complexity. Warns when the cyclomatic complexity of a method or function exceeds the configured threshold.
Cyclomatic complexity is the number of linearly independent paths through a block of code. Consider keeping the cyclomatic complexity low as the more paths you have, the higher the overall complexity and the number of test cases that needs to be implemented.
For example,
void someComplexFunction() {
final d = c.isNotEmpty && true;
final e = c.isNotEmpty || false;
final f = c?.isNotEmpty;
final g = Object() ?? Object();
Object? h;
h ??= Object();
}
here, the cyclomatic complexity for this method is 14
and the best way to reduce it would be to split this method into 2 separate methods each doing their part of the work.
use-existing-variable. Warns when an expression can be replaced by an existing variable with the same initializer.
Reusing already declared variables helps avoid inconsistencies when only one of the repeated expressions is updated.
For example,
void fn(String value) {
final some = value.length.isOdd;
print(value.length.isOdd);
}
here, the second value.length.isOdd
can be replaced by the already declared variable some
resulting in simpler code.
avoid-complex-loop-conditions. Warns when a loop condition is too complex.
Keeping the loop conditions complex reduces readability. Consider moving some conditions out, or moving the entire condition into a method.
For example,
void fn(int maxRotationAttempts, bool hasSuccessfulResponse) {
for (var attempts = 0;
attempts < maxRotationAttempts && !hasSuccessfulResponse;
attempts++) {}
}
should be rewritten to
void fn(int maxRotationAttempts, bool hasSuccessfulResponse) {
if (!hasSuccessfulResponse) {
for (var attempts = 0; attempts < maxRotationAttempts; attempts++) {
...
}
}
}
avoid-non-empty-constructor-bodies. Suggests moving all initialization logic to a separate method instead of the constructor.
Use this rule if you have specific classes that require the initialization rule to be moved out to a separate initialization method.
For this rule to work only for specific classes, use the include config.
For example,
class SomeClass {
final String value;
SomeClass(this.value) {
log(this.value);
}
}
here, the rule will highlight the constructor as it has a non-empty body.
Bloc
avoid-empty-build-when. Warns when a BlocBuilder
or BlocConsumer
does not specify the buildWhen
condition.
Specifying buildWhen
helps avoid unnecessary rebuilds and improves overall performance.
For example,
BlocConsumer<BlocA, BlocAState>(
builder: (context, state) {
...
},
);
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
...
},
);
should be rewritten to
BlocConsumer<BlocA, BlocAState>(
buildWhen: (previous, current) {
...
},
builder: (context, state) {
...
},
);
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
...
},
builder: (context, state) {
...
},
);
GetX
avoid-mutable-rx-variables. Warns when GetX Rx primitives are declared as mutable variables.
Declaring Rx primitives as mutable variables can lead to memory leaks when the variable gets reassigned and should be avoided.
For example,
void fn() {
Rx<Null> rx = Rx<Null>(null);
RxList<String> list = <String>[].obs;
RxSet<String> set = <String>{}.obs;
RxMap<String> map = <String, String>{}.obs;
}
should be rewritten to
void fn() {
final rx = Rx<Null>(null);
final list = <String>[].obs;
final set = <String>{}.obs;
final map = <String, String>{}.obs;
}
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!