Skip to main content

What’s new in DCM 1.12.0

· 9 min read

Cover

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

New command to run multiple DCM checks at once, 7 new rules (3 for FlutterHooks), custom analysis_options files support, performance improvements (with ~58 fully rewritten rules and fixed freezes of the IntelliJ plugin) and more 🚀.

info

This release contains 1 breaking change that you should be aware of: the avoid-unassigned-late-fields-keyword rule has been renamed to avoid-unassigned-late-fields.

Plus, the recommended preset has been updated so you might get new warnings once you update the dart-code-metrics-presets package dependency.

Now, let’s go on a quick journey together to explore all updates!

New command "dcm run"

We are excited to introduce a new command to help you run multiple DCM checks with the merged output 🚀! And as a bonus, this command is faster than running multiple DCM checks separately.

Here is the list of the commands supported:

  • analyze
  • analyze-widgets
  • calculate-metrics
  • check-unused-code
  • check-unused-files
  • check-unused-l10n
  • check-dependencies
  • check-unnecessary-nullable
  • check-exports-completeness

with the following output formats:

  • console
  • json
  • codeclimate
  • gitlab

For example, here is an output for calling dcm run --analyze --unused-code lib:

dcm run output example

This command is designed to be used on CI/CD and it does not require --no-congratulate flag since it has no progress indication and does not have any output on a successful run.

In the upcoming releases we also plan to support this command by our GitHib Action and optimize it even more so that you get even faster CI/CD checks!

Command improvements

With this release we cleaned up some existing inconsistencies between different commands.

For example, we unified exit codes: now, exit code 2 means that the command produced some analysis issues that are considered fatal and exit code 1 means an error with the command itself.

Next, all command that has a JSON report format now support the "--json-path" CLI option to produce a JSON file with the output while also showing results in the selected output format (e.g. console). Previously this flag was supported only by analyze and calculate-metrics commands.

As for the analyze-widgets command, it now supports more widgets (e.g. widgets that transitively extend Stateless and Stateful widgets). Additionally, it now has a new "fatal-level" CLI option that causes the command to complete with the exit code 2 when a low-quality widget is found.

Custom analysis options files support

Previously, only files named analysis_options.yaml were tracked by the analysis server and since DCM gradually reanalyses affected files when the config changes, validates rule names and shows configuration icon, all other files (e.g. analysis_options.1.1.0.yaml or dcm.yaml) were not properly picked up by the server.

However, with this release any file that matches one of the following patterns:

  • starts with analysis_options (e.g. analysis_options.1.1.0.yaml)
  • starts with dcm (e.g. dcm_config.yaml or dcm.yaml)
  • placed inside a folder called dcm (e.g. dcm/config.yaml)

and is referenced by any analysis_options.yaml gets picked up by the analysis server.

Performance improvements

First things first, Intellij / AS plugin freezes have been finally fixed. If you were affected by this problem, please make sure to update to at least DCM IntelliJ plugin 1.9.0. We are very happy to finally fix this and hope that it significantly improves the UX.

Next, we rewrote around 58 rules to reduce their analysis time. This also resulted in supporting more complex cases for several existing rules. For example, avoid-unnecessary-setstate is now capable of reporting transitive setState usages within the same widget.

Last but not least, the DCM Analysis Server will no longer send empty diagnostic lists if the previous result was the same. For large codebases, this reduces the overall load on the IDE since a file change could potentially generate hundreds of notifications with empty diagnostics.

GitHub Action updates

dcm-action also got a small update: you can now enable the pull_request_comment_on_fail option to get a PR comment only when a DCM check fails.

Note that if you enable this option, to disable the old behavior you need to set pull_request_comment to false.

VS Code updates

To meet our users' expectations, DCM VS Code extension now reuses the "dart.analysisExcludedFolders" and "dart.onlyAnalyzeProjectsWithOpenFiles" config options that you can set in the Dart extension.

We hope that this change helps you get consistent behavior for both extensions and reduce the memory consumption when it comes to large codebases.

IntelliJ updates

To help you deal with large lists of diagnostics, Intellij plugin now has a new filter menu.

DCM Problems View

Don't forget to update to the latest plugin version (1.10.0) to see it in action! 🔥

Filters for rules page

Speaking of filters, the rules page on our website also got them!

You can now filter the rules by category, version, severity, configurability and fixes.

Website Filters

You can even share a link with the applied filters with your colleagues! Try it yourself: https://dcm.dev/docs/rules/?categories=flutter&severities=performance.

Rule updates

warning

The avoid-unassigned-late-fields-keyword rule has been renamed to avoid-unassigned-late-fields.

New rules

Common

prefer-switch-with-enums. Suggests to use a switch statement or expression instead of conditionals with multiple enum values.

When it comes to enum values, the analyzer warns you when a new enum value is not covered by a switch case. This feature is not available for if statements and conditional expressions.

Therefore using switches where possible can reduce the number of potential bugs.

For example,

void fn(MyEnum value) {
if (value == MyEnum.first) {
print(value);
} else if (value == MyEnum.second) {
print(value);
} else if (value == MyEnum.third) {
print(value);
}

if (value == MyEnum.first || value == MyEnum.second) {
} else {}
}

enum MyEnum {
first,
second,
third,
}

here, the rule will suggest to replace both if statements with switch statements. And if MyEnum gets a new value, the analyzer will highlight both new switch statements to help you correctly handle the new value.

avoid-misused-test-matchers. Warns when an incorrect matcher or literal is used to verify the target expression.

When it comes to expect, expectLater and test matchers, their API is quite dynamic which leads to a lot of potential errors with matchers that can only be observed at runtime.

This rule is designed to help you avoid such errors even before a test case is run.

For example,

const _someNumber = 1;
const _someString = '1';
const _someList = [1, 2, 3];

void fn() {
expect(_someString, isList);
expect({1}, isList);
expect(_someNumber, isEmpty);
expect(_someNumber, isNull);
expect(_someNumber, isNotNull);
expect(_someNumber, hasLength(1));
expect(_someString, equals(<int>[]));
expect(_someString, allOf(isZero, isA<num>()));
}

here, none of these test assertions make sense, so the rule will highlight them all.

This rule supports both matchers and literal values (e.g. null). It also checks that the type in isA<T> is related to the target expression's type.

prefer-any-or-every. Warns when a collection's call chain can be rewritten to use .any() or .every() instead of .isEmpty or .isNotEmpty.

Using .any() or .every() helps to abort the calculation when the condition is true (or false) the first time, resulting in more performant code.

For example,

void fn() {
final collection = [1, 2, 3, 4, 5];

collection
.where((item) => item.isEven)
.isNotEmpty;

collection
.where((item) => item.isEven)
.isEmpty;
}

should be rewritten to

void fn() {
final collection = [1, 2, 3, 4, 5];

collection.any((item) => item.isEven);

collection.every((item) => !item.isEven);
}

avoid-duplicate-test-assertions. Warns when a test case has multiple test assertions with the same expression and expected value.

For example,

void withEmpty() {
final array = [1, 2, 3];

expect(array, isNotEmpty);
expect(array, isNotEmpty);
}

here, second expect(array, isNotEmpty); is unnecessary since the test case already checks the array for being not empty. It might be easy to spot the problem in this example, but if the test case body is large enough, it becomes really hard to notice.

Flutter Hooks

prefer-use-prefix. Suggests renaming hooks to start with use.

For example,

String myCustomHook() {
useMemoized(() {
...
}, []);
}

class MyWidget extends HookWidget {
void _myPrivateHook() {
useMemoized(() {
...
}, keys);
}
}

here, both method and function declarations violate the common agreement on hook names and therefore should be rewritten to

String useCustomHook() {
useMemoized(() {
...
}, []);
}

class MyWidget extends HookWidget {
void _usePrivateHook() {
useMemoized(() {
...
}, keys);
}
}

avoid-conditional-hooks. Warns when hooks inside the build method or other hooks are called conditionally.

Hooks are obtained from their index and therefore should be called unconditionally.

For example,

void useOtherFunction(bool condition) {
final b = condition ? useMemoized(() {}) : useMemoized(() {});
}

class MyWidget extends HookWidget {

Widget build() {
var condition = true;
if (condition) {
useMemoized(() {});
}

...
}
}

here, the rule will highlight all useMemoized invocations.

avoid-hooks-outside-build. Warns when a hook is used outside the build method, other hooks or HookBuilder.

For example,

class MyWidget extends HookWidget {
void myCallback() {
useMemoized(() => null);
}


Widget build(BuildContext context) {
return FloatingActionButton(onPressed: () {
useMemoized(() {});
});
}
}

here, the hook call in the onPressed callback should be moved out from the callback and as for the myCallback, it should be either renamed to be a hook or refactored the same way as onPressed.

What’s next

New commands. Reworking metric HTML reports unblocked other commands that need an HTML report as well. We hope to add them in the upcoming release to make DCM cover even more use-cases.

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.

"unused code in the IDE" feature improvements. There are plenty areas of improvements (for example, this feature only supports the "monorepo" mode for now, can be a bit annoying when you just created a new class and so on). That's why we are releasing it as "Beta" (and disabled by default) and will work on improving it in the upcoming releases.

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!