What’s new in DCM 1.7.0
Today we’re excited to announce the release of DCM 1.7.0!
Improved developer experience with DCM configuration, 25 new rules, 24 existing rules improved and more! 🚀
Let’s go on a quick journey together to explore all the new features!
Configuration updates
Previously, if the analysis_options.yaml
file was changed, only the package containing that file was reanalyzed.
Now DCM keeps track of how different configuration files depend on each other and changing the common config file results not only in the containing package reanalysis, but also in reanalysis of all dependant packages.
This mechanism also allows DCM to validate if a preset file can be resolved or not. If you've ever had problems with typos in preset filenames or missing presets in package dev dependencies, DCM can now highlight those issues as well!
Last but not least, DCM now checks for changes made to the analysis_options.yaml
and if they cannot affect the results of the analysis, the project won't be reanalyzed.
Support for environnement variables
When running a command on CI/CD instead of passing --ci-key
and --email
options you can now set DCM_CI_KEY
and DCM_EMAIL
env variables and they will be picked up by CLI.
New rules
Common
prefer-correct-future-return-type. Warns when a declaration that returns a Future
has an incorrect return type.
Object
, dynamic
, generic type, FutureOr
or a nullable Future
are considered incorrect and should be replaced by a Future
.
For example,
dynamic function() async => ...;
Object function() async => ...;
Future<int>? function() async => ...;
T function<T>() async => ...;
should be rewritten to
Future<dynamic> function() async => ...;
Future<Object> function() async => ...;
Future<int> function() async => ...;
Future<T> function<T>() async => ...;
prefer-correct-stream-return-type. Warns when a declaration that returns a Stream
has an incorrect return type.
Object
, dynamic
, generic type or a nullable Stream
are considered incorrect and should be replaced by a Stream
.
For example,
dynamic function() async* {
return ...;
}
Object function() async* {
return ...;
}
Stream<int>? function() async* {
return ...;
}
T function<T>() async* {
return ...;
}
should be rewritten to
Stream<dynamic> function() async* {
return ...;
}
Stream<Object> function() async* {
return ...;
}
Stream<int> function() async* {
return ...;
}
Stream<T> function<T>() async* {
return ...;
}
avoid-nested-futures. Warns when a Future
type contains another Future
.
Usually, nested Futures
highlight a problem in code design. Consider rewriting the code instead of nesting Futures
.
For example,
Future<Future<int>> function() async => ...;
should be rewritten to Future<int>
.
avoid-nested-streams-and-futures. Warns when a Stream
type contains a Future
or vice versa.
Usually, nested Streams
and Futures
highlight a problem in code design. Consider rewriting the code instead of nesting Streams
and Futures
.
For example,
Stream<Future<int>> function() async* {
...
}
should be rewritten to Stream<int>
.
avoid-accessing-other-classes-private-members. Warns when a private member of another class is used.
While Dart allows accessing private members of other classes in the same library, accessing them still violates encapsulation. Consider marking these members public or rewriting the code.
For example,
class SomeClass {
final String _value;
const SomeClass(this._value);
}
class OtherClass {
void work(SomeClass other) {
other._value;
}
}
here, accessing _value
inside the work
method violates SomeClass
encapsulation.
avoid-generics-shadowing. Warns when a generic type shadows and existing top-level (class, mixin, typedef or enum) declaration.
Shadowing existing declarations can be extremely confusing when it comes to parameters or variables annotated with a generic type that looks close to a class name.
For example,
class SomeClass {
void method<MyEnum>(MyEnum p) { ... }
AnotherClass anotherMethod<AnotherClass>() { ... }
}
class AnotherClass {}
enum MyEnum { first }
here, generic types MyEnum
and AnotherClass
share names with existing declarations which can be confusing. Consider to use more neutral names instead.
class SomeClass {
void method<T>(T p) {}
R anotherMethod<R>() {}
}
class AnotherClass {}
enum MyEnum { first }
avoid-uncaught-future-errors. Warns when an error from a Future
inside a try / catch block might not be caught.
If a Future
that results in an error is awaited only after an async gap (ex. another Future
is being awaited), the error will be caught by the global error handler as well as the outer try / catch block. This behavior is by design and might lead to unexpected errors being shown.
For example,
Future<void> asyncFunctionAssignFuture() async {
try {
final future = asyncFunctionError();
await Future.delayed(const Duration(seconds: 1));
await future;
} catch (e) {
print('caught in function: $e');
}
}
here, if the future
throws an error, it will be caught by the global handler.
prefer-parentheses-with-if-null. Warns when an if null (??) has a binary expression without parentheses.
Not adding parentheses might lead to unexpected execution order.
For example,
void main() {
final bool? nullableValue = false;
if (nullableValue ?? false || someOtherCondition()) {} // `someOtherCondition()` won't be executed
final bool? nullableValue = true;
if (nullableValue ?? false && someOtherCondition()) {} // `false && someOtherCondition()` won't be executed
}
here, right sides of both binary expressions won't be executed.
prefer-type-over-var. Warns when a variable is declared with the var
keyword instead of a type.
Although var
is shorter to write, the use of this keyword makes it difficult to understand the type of the declared variable.
For example,
class SomeClass {
void method() {
var variable = nullableMethod();
}
}
var topLevelVariable = nullableMethod();
String? nullableMethod() => null;
avoid-keywords-in-wildcard-pattern. Warns when a wildcard pattern has declaration keywords.
For example,
void fn() {
final animal = 'hello';
final value = switch (animal) {
final Object? _ => 'bad',
var _ => 'bad',
final _ => 'bad',
};
}
here, final
and var
keywords are unnecessary and can be removed without any consequences.
avoid-misused-wildcard-pattern. Warns when a wildcard pattern is used in the wrong context.
For example,
void fn() {
final animals = [];
if (animals.isEmpty case _) {}
if (animals.isEmpty case bool() && _) {}
}
when it comes to if-case, wildcard pattern has no meaningful effect.
avoid-mutating-parameters. Warns when a parameter's field or setter is reassigned.
Changing parameters increases the complexity for the reader of the code, as it makes it harder to keep track of possible side effects.
For example,
class SomeClass {
var flag = true;
set value(String value) {
...
}
}
void function(SomeClass some) {
some.flag = false;
some.value = 'hello';
}
here, fields of the SomeClass
parameter are being mutated inside of a function outside of the context of the class.
avoid-unnecessary-call. Warns when a .call()
invocation is unnecessary and can be simplified.
For example,
class SomeClass {
void call() {
...
}
}
final instance = SomeClass();
instance.call();
here, instance.call();
can be replaced with just instance();
.
no-equal-nested-conditions. Warns when an if statement contains another if statement with the same condition.
When it comes to nested if statements, it can be hard to track that checked conditions do not repeat (especially after refactoring).
For example,
if (value == 1) {
if (value == 1) {
...
}
}
if (value == 1) {
...
if (value == 1) {
...
}
}
avoid-negated-conditions. Warns when an if statement or conditional expression have a negated condition that can be inverted.
For example,
if (!flag) {
...
} else {
...
}
if (flag is! bool) {
...
} else {
...
}
final newValue = !flag ? value - 1 : value + 1;
prefer-correct-error-name. Ensures a consistent name for the error parameter in catch
, then
, catchError
, listen
and handleError
.
For example,
void function() async {
final future = Future.delayed(Duration(microseconds: 100), () => 6);
future.then((value) => print(value), onError: (Object? e) => print(e))
future.catchError((e) => print(e));
final stream = createStream();
stream.handleError((err) => print(err));
stream.listen((event) {}, onError: (err) {});
try {
await future;
} catch (err) {}
}
the rule helps you unify the name of the error parameter once and for all.
map-keys-ordering. Ensures a consistent alphabetical order for String
keys inside a map.
For example,
final map = {
'value': 'world',
'some': 1,
'hello': true,
};
should be updated to
final map = {
'hello': true,
'some': 1,
'value': 'world',
};
to keep alphabetical order.
avoid-unnecessary-futures. Warns when a return type of a declaration is unnecessary wrapped into a Future
.
For example,
Future<String> asyncValue() async => 'value';
class SomeClass {
Future<int> asyncMethod() async => 1;
}
both of those declarations can be rewritten to synchronous versions.
avoid-shadowed-extension-methods. Warns when an extension declares a method with the name that is already used by the extension target.
If the extension target already has a method with the same name, it requires additional hacks to call the method from the extension. Consider renaming the method instead.
For example,
extension Extension on String {
void split(String pattern) {}
}
here, this extension shadows an existing split
method that strings have.
avoid-barrel-files. Warns when a file is a barrel file.
Barrel files are files that only reexport declarations from other files (ex. all events or models an app has). Barrel files negatively affect analyzer's performance, so it's recommended to avoid them.
avoid-importing-entrypoint-exports. Warns when an entrypoint export is imported inside the library src
folder.
Package entrypoint exports are considered the package public API. Depending on this API in the implementation is fragile and can easily introduce cyclic imports.
Flutter
avoid-missing-image-alt. Warns when an Image
widget does not have a semanticLabel
.
Providing a label is important so that accessability tools to be able to read the image correctly and even if you do not plan to go all in on the accessability support, setting a label usually no extra effort and can be done when you create the widget.
For example,
void main() {
const Image(key: 'hello');
Image.asset(key: 'hello');
const FadeInImage(key: 'hello');
FadeInImage.assetNetwork(key: 'hello');
}
all the above images are missing a label property (semanticLabel
or imageSemanticLabel
) that must be provided.
avoid-unnecessary-overrides-in-state. Warns when a widget's State
has unnecessary overrides.
Standard rule for unnecessary overrides does not trigger if a declaration has different annotations (and for State
some of them are marked as @protected
). This rule does not have this limitation.
For example,
class _FooState extends State<SomeWidget> {
void dispose() {
super.dispose();
}
void initState() => super.initState();
}
both overridden declarations are unnecessary and can be removed without any consequences.
prefer-dedicated-media-query-methods. Warns when MediaQuery.of
or MediaQuery.maybeOf
are used over dedicated methods (ex. MediaQuery.sizeOf
).
New dedicated MediaQuery
methods became available only recently, but it's important to switch to them since using them reduces the number of unnecessary rebuilds.
For example,
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
...
}
if you want the widget to be updated only when the size changes, the code above will instead rebuild the widget every time any value that the MediaQuery
is tracking changes.
Intl
prefer-date-format. Warns when DateTime
values are formatted with toString
instead of DateFormat.format()
.
When it comes to internalization, countries might have their specific rules on how dates are displayed. To be able to take this rules into account without learning them, it's preferable to use DateFormat.format()
when it comes to formatting dates.
For example,
void main() {
final DateTime dateTimeValue = DateTime.now();
final dateTimeString = dateTimeValue.toString();
}
the correct version is to call DateFormat().format(dateTimeValue);
instead to get the locale-aware output.
Other rules that got updates
Rules below got several updates (including bug fixes, new options and new use-cases supported):
- format-comment
- avoid-dynamic
- avoid-explicit-type-declaration
- banned-usage
- prefer-correct-identifier-length
- no-equal-conditions
- avoid-redundant-async
- avoid-redundant-async-on-load
- check-if-not-closed-after-async-gap
- use-setstate-synchronously
- unnecessary-trailing-comma
- prefer-returning-conditional-expressions
- avoid-unrelated-type-assertions
- avoid-unrelated-type-casts
- avoid-declaring-call-method
- avoid-missing-enum-constant-in-map
- prefer-declaring-const-constructors
- avoid-throw-objects-without-tostring
- no-boolean-literal-compare
- prefer-immediate-return
- avoid-unsafe-collection-methods
- prefer-immediate-return
- avoid-explicit-pattern-field-name
- avoid-missed-calls
New quick fixes for existing rules 🔥
Rules below got quick fixes support (by dcm fix
, in the IDE or both):
- avoid-explicit-pattern-field-name
- avoid-missed-calls
- avoid-unused-generics
- avoid-redundant-pragma-inline
- prefer-visible-for-testing-on-members
- avoid-redundant-positional-field-name
- avoid-unnecessary-negations
- avoid-unsafe-collection-methods (only via IDE action)
- no-boolean-literal-compare
- prefer-conditional-expressions
- match-positional-field-names-on-assignment (only via IDE action)
- prefer-simpler-patterns-null-check
- prefer-wildcard-pattern
- no-equal-then-else
- format-comment
- prefer-last
- prefer-first
To improve the performance of heavy quick fixed, four existing rules (avoid-collapsible-if
, avoid-redundant-else
, arguments-ordering
and member-ordering
) got their quick fixed completely reworked. Now they are all calculated lazily only when a quick fix is selected in the IDE.
What’s next
Bringing the unused code check to the next level. What if the unused code check was available not only as a command, but was also integrated into the IDE providing real-time info on the unused parts? Let’s find out how this will change the overall experience.
Move performance improvements. We received a several reports about DCM performance issues on very large codebase. This is a serious problems that we also want to address. There is still some room to make DCM better.
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!