Skip to main content

What’s new in DCM 1.14.0

· 9 min read

Cover

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

This release includes a new command to find duplicate functions, methods and constructors, new built-in preset to avoid installing an additional package, 4 new rules, and more 🚀.

info

The recommended preset now has new rules from 1.13.0, so you may get new warnings once you update the dart-code-metrics-presets package dependency.

Now let's go through the highlights of this release 🚀.

Notable website updates

Features page

To simplify DCM features discoverability, the website now has a dedicated page with available features. This change also resulted in the removal of the "Core Concepts" docs page.

If you are still considering whether to use DCM or not, we hope this page helps you with that decision.

Ensuring DCM IDE extension is installed by your entire team

VS Code and IntelliJ extension docs now include a new section to help you ensure that your entire team has the DCM extension installed.

VS Code users can configure the "Workspace recommended extensions".

extensions.json
{
"recommendations": ["dcmdev.dcm-vscode-extension"]
}

And IntelliJ users can set up the list of "Required plugins".

Enabling inline warnings

Another extension docs update includes a new section on how to make IDE diagnostics appear as text at the end of the line.

For VS Code, consider installing the Error Lens extension.

For IntelliJ, the Inspection Lens plugin gives the same functionality.

warning

Make sure the installed third-party plugin is not malicious. We are not liable for any damage caused by third-party plugins.

New command "dcm check-code-duplication"

We are excited to introduce a new command to help you find duplicate functions, methods and constructors This command performs a structural check and is resistant to insignificant code changes.

For example,


void doWork() {
functionWithParameters(firstNamed: ..., secondNamed: ...);

someMethod(...);
}

void doAnotherWork() {
functionWithParameters(secondNamed: ..., firstNamed: ...);

shared.someMethod(...);
}

Even though these two functions have different names, different named arguments order and call the same function with a different import prefix, all these differences do not change the fact that this code is a duplication.

And this command will report these functions as such.

Here is an example of the CLI output:

Console output example

Configuration

There are several ways you can configure this command.

First, you can enable code comparison on a per-package level. To do so, just pass the --per-package CLI flag.

Using the --exclude-overrides flag, you can exclude methods with the @override annotation from the command output.

And last but not least, you can configure the minimum number of statements via --statements-threshold (default is 3).

info

Note that the --statements-threshold does not affect expression bodies and single return statements.

If you have any feedback regarding the new command, please consider posting it on our Discord.

This is the first iteration of the code duplication detection in DCM. We plan to introduce per-statement duplication detection later.

Presets update

To address feedback about the additional complexity with presets (related to installing an additional package), this release includes a new built-in preset called "recommended".

This preset matches the "recommended" preset in dart_code_metrics_presets, but is included into the tool.

Here is how you can enable it:

analysis_options.yaml
dart_code_metrics:
extends:
- recommended

One downside of this approach is that this preset is updated along with the executable, so you could potentially get new warnings after updating.

Keep this in mind if you decide to migrate to the built-in preset.

Trial license changes

The introduction of the built-in preset also made it possible to disable the "recommended rules by default" behavior for the trial license, since enabling rules now requires less effort.

Therefore, if you are still in the trial period and rely on this feature, you will need to migrate to the recommended preset described above.

Additionally, "dcm analyze" now suggests enabling rules for external packages that are added to your project and that DCM supports (for example, bloc) if they are not already enabled. We hope this helps with discovering applicable rules.

Reworked excludes for all commands

Prior to this release, if a package was excluded in the root analysis_options file (in the analyzer exclude section), all command would still analyze it and show output for it. But the IDE integration was not showing any issues for that package (as it should).

With this release we are brining this behavior towards the one the IDE has.

This means that if you open your project from the root and have a subpackage excluded in the root analysis_options, the commands won't show any output for this package unless you open its root directory (the one with the pubspec.yaml) and call the command from there.

Baseline improvements

Unified path separator

With this release, baseline now uses / as path separator for all platforms to avoid the problem with baseline generated on Windows not working on other platforms.

If your team generated baseline on the Windows device, you'll need to regenerate it after updating to 1.14.0.

New mode for IDE integration

Another baseline change allows you to show issues from baseline in your IDE, but keep them ignored when DCM is called from CLI or on CI/CD.

Both VS Code and IntelliJ extensions now have a new config option called Disable Baseline to enable this mode.

Preview improvements

Preview command now has a new option to exclude issues that are already in baseline to give you better perspective on the number of violation in your projects.

You can hide these issues with the --no-baseline-violations CLI flag.

Rule updates

Among other rule changes, there are two that we would like to take a closer look at.

First, the member-ordering rule got a new config option field-getter-setter that helps you group together fields, getters and setters with the same name.

This group has higher priority then individual groups (-fields, -getters or -setters) and only applies to class members if there are at least 2 of 3 declarations with the same name (e.g. a field AND a getter).

Additionally, if your class has multiple field-getter-setter groups, the field-getter-setter config will require to sort fields, getters and setter by their name (based on the order of the first declaration in the group).

For example,

class Correct {
final String someField;

const Correct(this.someField, this._forGetter, this._another);

final String _forGetter;
final String _another;

String get forGetter => _forGetter;
String get another => _another;
}

here, even though the field-getter-setter is the last, but the rule will still trigger, unless you group fields and getters by their name:

class Correct {
final String someField;

const Correct(this.someField, this._forGetter, this._another);

final String _forGetter;
String get forGetter => _forGetter;

final String _another;
String get another => _another;
}

Another notable change is that five existing rules (match-class-name-pattern, avoid-banned-annotations, banned-usage, avoid-banned-imports and avoid-banned-types) got a new config option called severity which allows you to override the main severity of the rule for each configuration entry separately.

For example,


dart_code_metrics:
rules:
- avoid-banned-imports:
severity: perf
entries:
- paths: ['some/folder/.*\.dart', 'another/folder/.*\.dart']
deny: ['package:flutter/material.dart']
message: 'Do not import Flutter Material Design library, we should not depend on it!'
severity: error

will mark all issues matching the config entry as errors.

New rules

Common

avoid-empty-test-groups. Warns when a test group does not have any test cases.

Test groups with no test cases can be accidentally introduced, for example during refactoring or when writing nested groups.

For example,

group('some group', () {
group('inner group', () {
setUp({
...
});
});
});

here, the inner group contains only the setUp invocation and no test cases.

avoid-contradictory-expressions. Warns when several conditions contradict each other.

The idea behind this rule is to help you quickly spot contradicting expressions in conditions which are usually a bug.

For example,

void fn() {
final num = 1;
final anotherNum = 2;

if (anotherNum == 3 && anotherNum == 4) {}
if (anotherNum < 4 && anotherNum > 4) {}
if (anotherNum == 2 && anotherNum != 2) {}
if (anotherNum == num && anotherNum != num) {}
}

here, all four if statements contain contradicting expressions that will never be evaluated to true.

avoid-excessive-expressions. Warns when a condition has excessive expressions.

For example,

void fn() {
final num = 1;
final anotherNum = 2;

if (anotherNum == 3 && anotherNum != 4) {}
if (num != null && num is int) {}
if (num == null || num is! int) {}
if (5 < anotherNum && anotherNum > 4) {}
}

here, anotherNum == 3 also means that the value is not equal 4. Therefore, the second condition is excessive and can be safely removed.

Next, num is int implicitly checks for the num value not to equal null which makes the first condition excessive. Same applies to num == null || num is! int.

And last but not least, anotherNum > 4 includes the > 5 range making the first condition excessive.

avoid-not-encodable-in-to-json. Warns when a toJson method references a non-encodable object.

Passing results of such toJson methods to jsonEncode will result in a runtime exception. If you for some reason write toJson methods manually, this rule is for you.

For example,

class SomeClass {
final String value;

const SomeClass(this.value);
}

class WithJson {
Map<String, Object?> toJson(Set<String> values) => {
'key': values,
'function': () => null,
'instance': SomeClass(''),
};
}

here, all three keys have non-encodable values.

To make this map encodable, it can be rewritten to:

class SomeClass {
final String value;

const SomeClass(this.value);

Map<String, Object?> toJson() => {'some': 'class'};
}

class WithJson {
Map<String, Object?> toJson(Set<String> values) => {
'key': values.toList(),
'instance': SomeClass(''),
};
}

What’s next

Documentation improvements. We plan to improve the overall state of the documentation, including a rewrite of the Getting Started section, introducing several user guides, as well as updating the example rules and adding more information about the problem behind each rule. We hope these changes will help us better explain DCM's features and help you get started with the product faster.

New command to analyze project structure. This command will help you visualize and analyze how different files / folders / packages depend on each other. Such analysis can help you create better boundaries between different modules of your apps and make the code more maintainable. On top of that, the command will allow validation of the project structure including cases when a file is expected to be present, but it as actually missing.

Sharing your feedback

If there is something you miss from DCM right now or want us to make something better or have any general feedback - join our Discord server!