Skip to main content

What’s new in DCM 1.21.0

· 9 min read

Cover

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 🚀.

warning

❗️ "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).

Rule Tags

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:

  1. Assignments to a field inside a constructor
  2. 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.

note

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;
}
note

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).

note

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.

info

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!