Skip to main content

What’s new in DCM 1.7.0

· 11 min read

Cover

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!