What’s new in DCM 1.11.0
Today we’re excited to announce the release of DCM 1.11.0!
New command to analyze the quality and usage of your widgets, 20 new rules (with 6 of them to help enforce the Flutter style guide 🔥), ~30 rules updated, redesigned HTML reports with, of course, dark mode support 🚀!
This release contains 3 breaking changes that you should be aware of: the avoid-unnecessary-late
rule has been renamed to avoid-unnecessary-local-late
, the check-exports-completeness
command now uses the --fatal-found
flag (instead of --fatal-unused
) and metrics global ignore has been renamed from type=metric
to type=metrics
.
Plus, in the next release we'll update the recommended preset to include new rules. If you use this preset, please be ready to address new warnings.
Now, let’s go on a quick journey together to explore all updates!
New "analyze-widgets" command
We are excited to present a new command that will help you measure the quality of your widgets as well as get a list of all used widgets, blocs and classes from context.read and context.watch, and a list of widgets that use a particular widget.
Here is an example of such s report:
The quality score is based on the number of public fields, the number of methods, the number of used widgets and several preconfigured metrics:
cyclomatic-complexity: 20
halstead-volume: 300
maintainability-index: 50
maximum-nesting-level: 5
number-of-parameters: 6
source-lines-of-code: 50
This configuration cannot be changed as we are still evaluating whether the set of selected metrics is actually useful to measure. Once this is resolved, all metric thresholds will be configurable.
The list of used widgets shows 3 types of widgets: local
, external
and flutter
.
Local widgets are widgets that you created in your project. You can navigate between reports by clicking on the widget name.
External widgets are widgets that come from pub packages. They are not analyzed by this command, but your can open their source code in VS Code.
Flutter widgets are widgets that come from Flutter itself. You can open their source code in VS Code, on GitHub or open the documentation link.
Next, the report shows 5 additional sections: "Used by", "Used inherited widgets", "Used blocs", "Classes from context.read" and "Classes from context.watch". Some of them will only appear if the widget has any relevant usages.
Last, the report provides widgets source code to help you validate the report data.
If you have any feedback about the command, please reach out on our Discord!
HTML report improvements
The HTML report got a significant redesign to match the UX of existing documentation pages (e.g. https://api.flutter.dev). We hope you'll like it!
Plus, it now supports dark theme!
We've also changed the sorting logic so that columns with violation in the metrics report are sorted correctly, and we've made the sort order constant between page navigations.
Metrics improvements
Metrics console reporter will now suggest to use the HTML reporter because the latter provides more detailed information on collected metrics.
You can now also use a new CLI flag "--open" to automatically open the generated HTML report.
Even though we do not recommend ignoring metrics, sometimes it might be useful to do so. That's why you can now use a // ignore: metrics
ignore to skip all metrics calculation on a particular method.
General command improvements
To simplify working with the list of available commands, dcm --help will now show 2 groups: regular commands and commands to manage the license.
Rule updates
The avoid-unnecessary-late
rule has been renamed to avoid-unnecessary-local-late
.
In addition to the 28 updated rules, this release also contains a major update to the prefer-moving-to-variable
rule. Now it will not only suggest to move the expression to a variable, but will also show existing variables that have the same initializer as the highlighted expression.
New rules
Common
avoid-inferrable-type-arguments. Warns when an inferrable type argument can be removed without affecting the code.
If you prefer to rely on type inference, this rule will help you remove all inferrable types from your codebase. No more <Widget>[ ... ]
and friends!
For example,
void main() {
withList(<num>[1]);
final List<int>? nullableList = null;
withList(nullableList ?? <num>[1]);
withList(nullableList == null ? <num>[1] : <num>[2]);
withGenericList(<String>['1']);
withList(withGenericList<num>([1]));
withList(onlyParam<String>('1'));
}
void withList(List<num> items) {}
List<T> withGenericList<T>(List<T> items) => items;
List<num> onlyParam<P>(P param) => [1];
here, all types inside <>
(e.g. num
and String
) in the main function can be safely removed.
This rule works for all inferrable contexts (e.g return statements, method invocations and collection literals).
prefer-unique-test-names. Warns when the test name is not unique within the same test suite.
A test with a non-unique name will be skipped when running from the IDE. Same goes for tests with non-unique names inside a for loop.
For example,
void main() {
test('some test', () {
...
});
test('some test', () {
...
});
for (final item in [1, 2, 3])
test('broken test', () {
...
});
}
should be rewritten to
void main() {
test('some test', () {
...
});
group('some group', () {
test('same test', () {
...
});
});
for (final item in [1, 2, 3])
test('good test $item', () {
...
});
}
prefer-null-aware-spread. Warns when a null check inside a collection literal can be replaced with a null-aware spread (...?)
.
For example,
void fn() {
final Set<String>? localSet = <String>{};
final collection = [
if (localSet != null) ...localSet,
...localSet != null ? localSet : <String>{},
...localSet ?? {},
];
}
should be rewritten to
void fn() {
final Set<String>? localSet = <String>{};
final collection = [
...?localSet,
...?localSet,
...?localSet,
];
}
avoid-duplicate-cascades. Warns when a cascade expression has duplicated cascades.
For example,
void fn() {
final value = SomeClass();
value
..field = '2'
..field = '1'
..another = '1';
value
..method(() => 1)
..method(() => 1)
..method(() => 2);
final list = [1, 2, 3];
list
..[1] = 2
..[1] = 3
..[2] = 2;
}
class SomeClass {
String? field;
String? another;
void method(Function callback) {}
}
here, value..field
and list..[1]
are assigned twice in a row as well as value..method()
is called twice with the same callback. These are probably mistakes.
prefer-specific-cases-first. Warns when a more specific switch case is placed after a more general one.
This can lead to the more specific case to never match.
For example,
void fn(SomeClass param) {
final value = switch (param) {
Sub() => 1,
Sub() when param.value.isEmpty => 2,
_ => 3,
};
switch (param) {
case Sub():
print(1);
case Sub() when param.value.isEmpty:
print(2);
case _:
print(3);
}
}
class SomeClass {
final String value;
const SomeClass(this.value);
}
class Sub extends SomeClass {
const Sub(super.value);
}
should be rewritten to
void fn(SomeClass param) {
final value = switch (param) {
Sub() when param.value.isEmpty => 2,
Sub() => 1,
_ => 3,
};
switch (param) {
case Sub() when param.value.isEmpty:
print(2);
case Sub():
print(1);
case _:
print(3);
}
}
class SomeClass {
final String value;
const SomeClass(this.value);
}
class Sub extends SomeClass {
const Sub(super.value);
}
avoid-duplicate-switch-case-conditions. Warns when several switch cases have the same condition.
For example,
void fn(SomeClass param) {
final value = switch (param) {
Sub() when param.value.isEmpty => 1,
Sub() when param.value.isEmpty => 2,
_ => 3,
};
}
class SomeClass {
final String value;
const SomeClass(this.value);
}
class Sub extends SomeClass {
const Sub(super.value);
}
here, even both cases with Sub()
have the same condition which is a sign of a mistake.
match-lib-folder-structure. Warns when the file path in the test folder does not match the implementation file path in the lib folder.
For example, if you have two files package/lib/src/my_file.dart
and package/test/some_folder/my_file_test.dart
then the folder structure in the test folder does not match the one in the lib folder.
So the correct structure for this test file is package/test/src/my_file_test.dart
.
avoid-missing-test-files. Configure a list of files that should have a corresponding test file.
This rule assumes your test folder structure matches your lib folder structure.
prefer-both-inlining-annotations. Warns when a @pragma('vm:prefer-inline')
annotation does not have a corresponding @pragma('dart2js:tryInline')
annotation.
For example,
('vm:prefer-inline')
Future<String> bad() {
return Future.value('hello');
}
should be rewritten to
('dart2js:tryInline')
('vm:prefer-inline')
Future<void> good() async {
return Future.value('hello');
}
avoid-collection-mutating-methods. Warns when a mutating method is called on a collection.
By default this rule triggers on every method that has a void return type, as well as for methods in the methods config option.
For example,
void main {
final list = [1, 2, 3];
list.remove(1);
list.insert(0, 4);
list.addAll([1, 2, 3]);
list.removeAt(0);
list.shuffle();
}
here, instead of mutating the list, a new list should be created
void main {
final list = [1, 2, 3];
final newList = [...list, ...[1, 2, 3]];
}
prefer-explicit-type-arguments. Warns when a method that accepts type arguments has no arguments and no type arguments passed.
This rule can help improve readability of your code when it's not clear what value hides behind context.read()
calls.
For example,
class _MyAppState extends State<MyApp> {
late final A a;
late final B b;
void initState() {
super.initState();
a = context.read();
b = context.read();
}
Widget build(BuildContext context) {
return Widget();
}
}
here, the rule will required to provide type annotations to both context.read
calls so that the type is explicitly visible
class _MyAppState extends State<MyApp> {
late final A a;
late final B b;
void initState() {
super.initState();
a = context.read<A>();
b = context.read<B>();
}
Widget build(BuildContext context) {
return Widget();
}
}
avoid-unnecessary-super. Warns when a constructor has an unnecessary super invocation.
For example,
class Base {}
class Subclass extends Base {
const Subclass() : super();
}
here, the super
is unnecessary and can be removed.
avoid-banned-annotations. Configure annotations that you want to ban.
For example,
class Person {
Person({
required this.value,
this.another,
});
final int value;
final int? another;
void method() {}
}
here, if you configure visibleForTesting
as banned, the rule will trigger on both annotations.
prefer-explicit-function-type. Warns when a Function type does not specify the return type and arguments.
For example,
class SomeWidget {
final Function onTap;
const SomeWidget(this.onTap);
}
should be rewritten to
class SomeWidget {
final void Function() onTap;
const SomeWidget(this.onTap);
}
prefer-correct-setter-parameter-name. Warns when the setter parameter name does not match the configured one.
This rule is from the Flutter style guide.
For example,
class Some {
String? _value;
set prop(String someValue) {
_value = someValue;
}
}
here, the rule expects the parameter name to be value
class Some {
String? _value;
set prop(String value) {
_value = value;
}
}
prefer-prefixed-global-constants. Warns when a global constant does not start with a configured prefix.
This rule is from the Flutter style guide.
For example,
const String _nothing = '1';
const String public = '1';
const int _sValue = 1;
here, the rule expects names to start with k
.
prefer-correct-callback-field-name. Warns when a field with a Function type does not matched the configured name or a field with a non-Function type matches the configured name.
This rule is from the Flutter style guide.
For example,
class SomeWidget {
final String onSome;
final Function doWork;
const SomeWidget(this.onSome, this.doWork);
}
here, the rule expects that any non-Function type filed does not start with on
whereas all fields with Function type start with on
class SomeWidget {
final String value;
final Function onTap;
const SomeWidget(this.value, this.onTap);
}
prefer-typedefs-for-callbacks. Warns when a Function type is declared not as a typedef.
This rule is from the Flutter style guide.
For example,
class SomeWidget {
final void Function() onEnterButton;
const SomeWidget(
this.onEnterButton,
);
void another({
void Function()? onRemoved,
}) {}
}
should be rewritten to
class SomeWidget {
final VoidCallback onEnterButton;
const SomeWidget(
this.onEnterButton,
);
void another({
VoidCallback? onRemoved,
}) {}
}
prefer-correct-handler-name. Warns when the callback handler name does not matched the configured one.
This rule is from the Flutter style guide.
For example,
SomeClassWithCallbacks(
someWork,
onSecond: someWork,
onThird: () => someWork(),
onForth: () async => await someAsyncWork(),
);
here, the rule expects all callbacks to start with handle
SomeClassWithCallbacks(
_handleWork,
onSecond: _handleWork,
onThird: widget.onWork,
onForth: _handleWork,
);
prefer-addition-subtraction-assignments. Warns when ++
or --
is used instead of +=
or -=
.
This rule is from the Flutter style guide.
For example,
void main() {
int value = 1;
value++;
value--;
++value;
--value;
}
should be rewritten to
void main() {
int value = 1;
value += 1;
value -= 1;
}
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!