What’s new in DCM 1.21.0
Today we’re excited to announce the release of DCM 1.21.0!
This release includes 9 new rules, a reworked "banned-usage" rule, a new reporter format for SonarQube, a new "Guides" section in the documentation, and general UX improvements 🚀.
❗️ "banned-usage" now works only for usages (e.g., method invocations or property accesses); use "avoid-banned-names" to ban declaration names
❗️ "ban-name" has been removed; use "avoid-banned-names" instead
Let's go through the highlights of this release! (And you can find the full list of changes in our changelog)
Documentation Updates
Rule Tags
To make it easier to work with the list of available rules (and taking into account the number of available rules), all rules now have tags (sometimes several).
For example, the arguments-ordering
rule has the consistency
and pedantic
tags because it is a style rule and is relatively strict, and the avoid-accessing-other-classes-private-members
rule has the correctness
and maintainability
tags because it helps prevent errors in the moment, but also helps enforce boundaries, which affects long-term maintainability.
New "Guides" Section
Another documentation update is that we now have a dedicated section for guides (finally)! 🚀
There are currently only two guides: "Integrating DCM Into an Existing Project" and "Handing Over Teams License to Another Company", but we are working on adding more.
So expect more guides soon!
New CI/CD integration (Azure DevOps)
And last but not least, here is our new, ready-to-use guide on leveraging DCM in your Azure DevOps pipeline.
SonarQube Output Format
We are excited to introduce a new reporter type called sonar
!
This reporter prints SonarQube's generic format for external issues. Use --reporter=sonar
to enable it.
Here is the output example:
{
"rules": [
{
"cleanCodeAttribute": "LOGICAL",
"description": "To learn more, visit the documentation https://dcm.dev/docs/rules/common/prefer-match-file-name",
"engineId": "dcm",
"id": "prefer-match-file-name",
"impacts": [
{
"severity": "MEDIUM",
"softwareQuality": "RELIABILITY"
}
],
"name": "prefer-match-file-name"
},
{
"cleanCodeAttribute": "LOGICAL",
"description": "To learn more, visit the documentation https://dcm.dev/docs/rules/flutter/proper-super-calls",
"engineId": "dcm",
"id": "proper-super-calls",
"impacts": [
{
"severity": "MEDIUM",
"softwareQuality": "RELIABILITY"
}
],
"name": "proper-super-calls"
},
],
"issues": [
{
"primaryLocation": {
"filePath": "lib/main.dart",
"message": "The first class name does not match the file name. Try renaming it.",
"textRange": {
"endColumn": 12,
"endLine": 12,
"startColumn": 7,
"startLine": 12
}
},
"ruleId": "prefer-match-file-name"
},
{
"primaryLocation": {
"filePath": "lib/main.dart",
"message": "This 'super' call must come first.",
"textRange": {
"endColumn": 23,
"endLine": 51,
"startColumn": 5,
"startLine": 51
}
},
"ruleId": "proper-super-calls"
}
]
}
If you use both DCM and SonarQube, with --reporter=sonar
, you can upload the output of any DCM command to Sonar and keep all issues in one place! 🚀
"integration_test" And "test_driver" Folders Support for Rules
With this release, all DCM rules for tests (e.g., avoid-empty-test-groups
and avoid-misused-test-matchers
) also include "integration_test" and "test_driver" folders (on top of the "test") folder.
You can still enable the old behavior by explicitly setting
dart_code_metrics:
rules:
- avoid-empty-test-groups:
include:
- test/**
New Hint For Analyze Structure
The analyze-structure
command now prints a hint on how the "dot" format can be visualized:
// To visualize the output, use one of the "dot" format visualization tools https://dcm.dev/docs/cli/analysis/analyze-structure/#output-format.
This hint is added as a comment so copying the full output of the command to any visualizer will not break the parsing.
Check Parameters Improvements
As we continue to improve the check-parameters
command, we fixed two recently discovered edge cases in this release:
- Assignments to a field inside a constructor
- Constructors with constructor field initializers.
class AnotherClass {
AnotherClass({required this.id});
AnotherClass.fromJson(Map<String, dynamic> json) : id = json['id'] as int?; // constructor field initializer
final int? id;
}
class AssignableClass {
AssignableClass({required this.id});
AssignableClass.fromJson(Map<String, dynamic> json) {
id = json['id'] as int?; // field assignment
}
int? id;
}
both cases are now correctly picked up by the command (and treated as parameters).
Rule updates
"banned-usage" Update
Prior to this release, the banned-usage
rule was responsible for two separate cases: usages and names. And having the same configuration apply to both is sometimes undesirable.
That's why, in this release, we moved the part responsible for banning declaration names (variables, classes, methods, etc.) to a new rule called avoid-banned-names
. It supports almost the same configuration and works only for your code (and not the code imported from outside, e.g., Flutter) since only your code can declare new classes, variables, etc., in the analyzed scope.
The old ban-name
rule has been removed, consider using avoid-banned-names
instead.
"avoid-unnecessary-super" Update
The avoid-unnecessary-super
now supports additional cases when an invocation has an unnecessary super
prefix.
For example,
class B extends A {
void work() {
super.work();
super.another();
}
}
class A {
void work() {}
void another() {}
}
here, adding the super
prefix to the super.another()
invocation is unnecessary since B
does not override another
.
New rules
Common
prefer-boolean-prefixes. Suggests to prefix boolean variables, methods, fields and parameters with one of the configured prefixes.
For example,
class Some {
final bool test;
final bool isnotcorrect;
final bool _private;
bool someRandom() => false;
}
here, all declarations with the bool
type do not comply with the list of allowed prefixes, so the example should be rewritten to
class Some {
final bool hasTest;
final bool isCorrect;
final bool _isPrivate;
bool isCorrectMethod() => true;
}
You can configure the list of prefixes and exclude some declarations (e.g., fields or parameters).
avoid-banned-names. Warns against using banned names (e.g. a variable or class name).
This rule replaces the deprecated and now removed "ban-name".
For example,
dart_code_metrics:
...
rules:
...
- avoid-banned-names:
entries:
- name: strangeMethod
- name: StrangeClass
- name: strangeName
void func() {
var strangeName = 42;
}
void strangeName() {}
class StrangeClass {
late var strangeName;
bool strangeMethod() {}
}
here, with the given config, the rule will highlight all declarations with the banned names (e.g., StrangeClass
and strangeName
(both the method and the variable)).
avoid-misused-set-literals. Warns when a set literal is used in a wrong place.
In some cases (especially, when the return type is void
), it's easy to accidentally add extra {}
to the function body converting the returned value to a set.
For example,
void fn() {
wrong(() => {1});
wrong(() async => {1});
}
void wrong(void Function() p) {}
class _State<T> extends State<T> {
String value;
Widget build() {
return MyWidget(
onPressed: () => {
setState(() {
value = '2';
})
},
);
}
}
here, since both wrong
invocations have =>
and {}
the return type is changed from int
to Set<int>
which is usually a mistake (some other languages (e.g. JS) allow => {}
to be a valid function body).
As for the onPressed
, it also has an unnecessary set literal which can be simply removed.
dispose-class-fields. Warns when a field is not disposed in a class's dispose
method.
Not disposing fields that have a dispose
, close
or cancel
methods may lead to memory leaks and should be avoided.
This rule will not trigger if the class does not declare any of the configured methods.
If the class declares multiple methods (e.g. dispose
and close
), then the rule will use the first one from the configuration list.
For example,
class SomeDisposable implements Disposable {
void dispose() {}
}
class SomeClass {
final _someDisposable = SomeDisposable();
final _anotherDisposable = SomeDisposable();
void dispose() {
_someDisposable.dispose();
}
}
here, _anotherDisposable
is not disposed and should be added to the dispose
method
class SomeDisposable implements Disposable {
void dispose() {}
}
class SomeClass {
final _someDisposable = SomeDisposable();
final _anotherDisposable = SomeDisposable();
void dispose() {
_someDisposable.dispose();
_anotherDisposable.dispose();
}
}
avoid-suspicious-super-overrides. Warns when a getter overrides a field that is already passed to the super constructor.
For example,
sealed class DependencyIssue {
final String issueType;
const DependencyIssue({
required this.issueType,
});
}
class First extends DependencyIssue {
final String packageName;
String get issueType => 'hi';
const First({
required this.packageName,
}) : super(issueType: 'promotion');
}
here, the issueType
getter overrides the value passed to the super constructor ('promotion'), but does not use super.issuesType
.
newline-before-method. Enforces a blank line before a method declaration.
For example,
class Wrong {
void fn() {}
void another() {}
void anotherFn() {}
}
should be rewritten to
class Correct {
void fn() {}
void another() {}
void anotherFn() {}
}
Easy Localization
avoid-missing-tr. Warns when the tr
extension method is not called on localization keys.
Not calling tr
will result in localization strings not being displayed.
For example,
void fn() {
print(LocaleKeys.title);
Text(LocaleKeys.title);
}
should be rewritten to
void fn() {
print(LocaleKeys.title.tr());
// or
print(LocaleKeys.title.plural());
Text(LocaleKeys.title).tr();
// or
Text(LocaleKeys.title).plural();
}
Firebase Analytics
incorrect-firebase-event-name. Warns when the event name does not comply with Firebase limitations.
Firebase requires the event name to contain 1 to 40 alphanumeric characters or underscores and start with an alphabetic character.
For example,
void fn() {
_analytics.logEvent(name: '');
_analytics.logEvent(name: '😅');
_analytics.logEvent(name: '_');
}
here, all event names will not be accepted by Firebase Analytics.
incorrect-firebase-parameter-name. Warns when the parameter name does not comply with Firebase limitations.
Firebase requires the parameter name to contain 1 to 40 alphanumeric characters or underscores and start with an alphabetic character.
For example,
void fn() {
_analytics.logEvent(name: 'some_name', parameters: {
'some-name': 'str',
'': 'str',
});
}
here, all parameter names will not be accepted by Firebase Analytics.
What’s next
Baseline for all commands. To further simplify DCM integration into existing projects, we want to expand baseline support from just "dcm analyze" to all commands that can produce analysis issues ("dcm check-unused-code", "dcm check-unused-files" and other).
Support for all commands in DCM GitHub action. One of the ideas behind "dcm run" was to integrate it into DCM GitHub action so that it's not limited to just "dcm analyze". Expanding the number of supported commands will make the action even better.
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!