Skip to main content

What’s new in DCM 1.13.0

· 11 min read

Cover

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

This release includes similar widgets report, improved unused code IDE integration and a new mode, 8 new rules, unused files in IDE, performance improvements, and more 🚀.

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

Similar widgets report

We're excited to announce a new feature for the dcm analyze-widgets command called similarity report.

Similarity report helps you find similar widgets in your codebase and then decide whether this similarity is acceptable or whether widgets should be refactored. While not all similarity is bad, reducing the number of duplicate code can help you not only make it easier to maintain, but also avoid potential visual or behavioral differences when you change one of the widgets.

Here is how similarity is displayed in the HTML report:

Similarity report example

Similarity report compares widget trees and shows the list of similar widgets, the percentage of the widget build method tree that matches another widget and a list of the matching used widgets.

You can also configure the similarity threshold (default is 0.2) which basically means that a pair of widgets are considered similar if their subtrees match more than the configured threshold.

To configure the similarity threshold, pass the --threshold CLI option.

The report also highlights the matching subtree.

Highlighted subtree example

To include similarity into the analyze-widgets report, pass the --show-similarity CLI flag.

note

DCM analyzes only the part of the widget tree that is statically available meaning that fields with the Widget type are not included.

As for the conditional usages (e.g. condition ? Widget1 : Widget2) and local variables, the command calculates and includes them all as potential branches of the widget tree.

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

Improved detection of available widgets

analyze-widget got another important update regarding what is considered a unique id for a widget in the report.

Prior to this release, the widget name was a unique key, but as we now know, some widget names can be repeated within a project. To resolve this issue, non-unique widgets are now marked with a (N) suffix in the report.

Unique names example

Unused code improvements

IDE integration updates

This release brings a lot of improvements for the unused code IDE integration! 🚀

First, we fixed a ton of different issues regarding the performance and general DX. Please refer to the latest changelog entry if you want to learn more about them.

As for the visible changes, we've updated the highlighted area to make unused-code issues less annoying.

Before:

Before

After:

After

And added code actions for both removing the unused code and adding an ignore.

Unused code actions

This integration is still in "Beta", but we are getting closer to marking it as stable.

You can enable unused-code in IDE in DCM VS Code extension / IntelliJ plugin settings.

New config option

To simplify managing excludes for both IDE and CLI, this release introduces a new config entry to analysis_options.yaml called unused-code-exclude.

It works the same way as other exclude entries (e.g. exclude or rules-exclude), but only for unused code.

New mode

Up to this point, the unused code check has excluded any overridden or abstract members, since there are many ways to use them through a parent class or interface.

With this release, we're excited to introduce a new experimental mode for the unused code check that includes overridden elements that don't come from external packages into the analysis.

Let's tale a loot at this example:

class SomeClass {
void fn() { ... }
}

class AnotherClass extends SomeClass {

void fn() { ... }
}

Without the new mode, both fn declaration are considered used even if there is 0 actual references.

If you enable the new mode and you code does not have references for all declaration of a particular member (in our case, fn) in the class hierarchy, the command will now report such declarations as unused.

To enable the new mode, pass the '--no-exclude-overridden' flag to CLI.

Unused files

New IDE integration

We're excited to say that another command got the IDE integration. This time it's the unused files check!

Highlighted area is intentionally reduced to the first token in an unused file to make the warning less annoying during development (when some newly created files are not yet referenced by other files).

note

Unused files integration has the same limitations as the unused code integration (supports only the monorepo mode) and requires the unused code to be enabled.

Improved detection of files used only in tests

If you run the check-unused-files command from the root of your project and you have a file in lib that is referenced only by files in test, the previous versions of the command would not report the file in lib as unused.

Now, you'll get a new warning message for such files.

⚠ used only in tests 
/path/to/file.dart
note

The command won't trigger for files in test that are used by other files in test.

Command improvements

Improved exports analyzer

If you run any command that has a monorepo mode, that command uses the exports analyzer under the hood to first calculate the code that is publicly available to exclude it from the analysis if the monorepo mode is disabled.

In this release, we have completely rewritten the exports analyzer, significantly reducing the calculation time for dependant commands and for check-exports-completeness 🔥.

Monorepo flag updates

To improve the discoverability of the monorepo mode, any command run without the --monorepo flag will also show a hint about this mode:

By default, this command does not report unused code in the package's
public API. To disable this behavior, pass the "--monorepo" flag

We also changed the description of this flag in the commands help section to help you better understand what this mode actually does.

In the upcoming releases we plan to introduce an automatic monorepo mode detection and a new config entry to the analysis_options.yaml that would alow you to have the monorepo mode enabled not per a command invocation, but per-package.

Windows path separator issues

Last but not least, another issue worth mentioning is that some commands and 4 rules that use absolute paths to determine whether a file belongs to external files or a specific directory did not properly respect the Windows path separator (\).

If you have a Windows device, consider updating to 1.13.0 as soon as possible.

New rules

Flutter

avoid-undisposed-instances. Yet another rule to reduce the number of memory leaks in your app is here!

This one warns about an instance that is not assigned to a variable, but is expected to be disposed.

Given that it's usually the responsibility of the creating side to dispose objects, passing such instances often means a memory leak.

For example,

TextSpan(
...
recognizer: LongPressGestureRecognizer()
..onLongPress = _handlePress,
),

here, if you open the documentation for TextSpan, it clearly states that an InlineSpan object does not manage the lifetime of the gesture recognizer. Meaning that the code that owns the recognizer object must call dispose when the InlineSpan object is no longer used.

So the correct version is:

class _SomeState extends State<Some> {
late LongPressGestureRecognizer _longPressRecognizer;


void initState() {
super.initState();
_longPressRecognizer = LongPressGestureRecognizer()
..onLongPress = _handlePress;
}


void dispose() {
_longPressRecognizer.dispose();
super.dispose();
}


Widget build(BuildContext context) {
...
TextSpan(
...
recognizer: _longPressRecognizer,
),
...
}
}

If you are looking for other rules that address the memory leak issue, check out dispose-field and dispose-providers.

Common

avoid-duplicate-collection-elements. Warns when a collection has duplicate collection elements.

Often a duplicate element is either redundant (e.g. when it comes to sets) or may introduce a bug (e.g. a different element should've been added instead).

For example,

void fn() {
final list = <String>[...];

final anotherList = [
...list,
...list,
if (list.isNotEmpty) 'value',
if (list.isNotEmpty) 'value',
];

final map = {
...{
'key': 'value',
},
...{
'key': 'value',
},
};
}

here, adding the same element twice via a spread operator is rarely a desired outcome, that's why the rule will highlight both spreads. On top of that, the condition in if(...) is also a duplicate, so the rule will highlight it as well.

avoid-duplicate-map-keys. Warns when a map has duplicate keys.

The idea behind this rule is to help you avoid accidental duplicate keys not only for regular map entries, but also for spreads, nested spreads and conditional entries.

For example,

void fn() {
final map = {
'key': 'value',
'key': 'value',
...{'key': 'value'},
if (...) 'key': 'value',
};

final override = {
if (...) 'key': 'value',
'key': 'value',
};
}

here, for the first map the rule will highlight all entries after the first 'key': 'value'. For the second map the rule's message will a bit different. Instead of highlighting it as a duplicate, it will state that the second entry unconditionally overrides the first one which in most cases is a bug.

move-variable-outside-iteration. Warns when a variable does not depend on the outer loop and can be moved out. This can be very helpful if you want to avoid unnecessary declarations of variables that can be declared only once.

For example,

void fn() {
final list = [1, 2, 3];

for (final element in list) {
final reversed = list.reversed;

...
}
}

here, the reversed variable does not depend on the for loop and therefore the example can be rewritten to

void fn() {
final list = [1, 2, 3];
final reversed = list.reversed;

for (final element in list) {
...
}
}

avoid-nullable-tostring. Warns when the toString method is called on a nullable value. Calling toString on a nullable value can result in an unexpected output (e.g. 'null' instead of just null).

For example,

void fn(String? nullableValue) {
print(nullableValue.toString());
}

should be rewritten to

void fn(String? nullableValue) {
print(nullableValue?.toString());
}

avoid-duplicate-initializers. Warns when a final variable has the same initializer as another variable in scope. Such variables can either be successfully removed or were supposed to have a different initializer.

For example,

void fn(SomeClass param) {
final myValue = param.value;
final anotherValue = param.value;
}

here, both variables have exactly the same initializer and the rule will highlight the second one.

avoid-unused-after-null-check. Warns when a variable is checked for non-nullable value, but is not used within the condition or then branch.

For example,

void fn(String value) {
final String? myNullableValue = 'hello';
if (myNullableValue != null) {
print(value);
}
}

here, the myNullableValue variable is checked for not being null, but the block the uses value instead. Either the check is redundant or the value should be replaced with myNullableValue.

void fn(String value) {
final String? myNullableValue = 'hello';
if (myNullableValue != null) {
print(myNullableValue);
}
}

avoid-unassigned-stream-subscriptions. Warns when a stream subscription is not assigned to a variable. Not assigning a stream subscription to a variable and not calling the cancel method when the subscription is not longer needed can lead to a memory leak.

For example,

final stream = Stream.fromIterable([1, 2, 3]);

void fn() {
stream.listen((event) {
print(event);
});

...
}

if the stream object lives outside the scope of where the .listen method was called, the subscription can stay active even when the scope with .listen get destroyed.

To avoid such cases, cancel the subscription once it's no longer needed

final stream = Stream.fromIterable([1, 2, 3]);

void fn() {
final savedSubscription = stream.listen((event) {
print(event);
});

...

savedSubscription.cancel();
}

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!