What’s new in DCM 1.13.0
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 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.
To include similarity into the analyze-widgets
report, pass the --show-similarity
CLI flag.
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.
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:
After:
And added code actions for both removing the unused code and adding an ignore.
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).
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
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!