Skip to main content

What’s new in DCM 1.22.0

· 8 min read

Cover

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

This release includes 15 new rules, DCM Teams Console improvements, ignore comment support for pubspec rules and the first version of our public roadmap.

info

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.

Let's go through the highlights of this release! (And you can find the full list of changes in our changelog)

Introducing Our Public Roadmap

We are excited to share the first version of our public roadmap.

It provides an overview of what is planned for the upcoming months (without going into too much detail and without describing smaller features like what rules will be added).

If it's something the community finds useful, we'll put more effort into improving it.

Let us know what you think!

Introducing Our New Video Series

In August we announced our new YouTube series: 🎉 Rules of the Week! 🎉.

Join us as we discuss various bugs and style issues in your Flutter code, share tips & show how DCM can help avoid them. 💻✨

https://youtu.be/tOniNvJ_40s

DCM Teams Console Updates

Multiple Email Logins

We are excited to announce that multiple login emails for the same account are now supported!

If you want your account to have multiple login emails, please reach out to our support.

New Modal to Reset CI/CD Key

Admin panel now supports resetting CI/CD keys. To reset your key, use the "Generate new CI key" button and then click "Generate new key".

Reset Key Modal

Ignore Comments for Pubspec Rules

With this release you can now ignore pubspec rules with # ignore: and # ignore_for_file: ignore comments!

name: some_package

dependencies:
some_dependency: ^1.0.0 # ignore: prefer-pinned-version-syntax
note

Code actions to add ignores are currently not available, so you need to add the ignore comments manually.

Rule updates

"avoid-unrelated-type-casts" and "avoid-unnecessary-type-casts" Update

avoid-unrelated-type-casts and avoid-unnecessary-type-casts now also support unrelated or unnecessary .cast invocations.

For example,

void main() {
<int>[].cast<String>();

final List<String?> myList = ['', null];
final newList = myList.cast<List<String>>().toList();
}

here, .cast<String>() and .cast<List<String>>() are unrelated to the type of the target collection and will throw at runtime.

New rules

Common

avoid-unnecessary-extends. Suggests removing unnecessary extends clauses that match default values for classes and type parameters.

In Dart, all classes by default extend Object and all type parameters extend Object?. Removing those extends clauses will not affect your code.

For example,

class Base {}

class Subclass extends Object {}

class Another extends Base {}

class WithTypeParam<T extends Object?, B extends Object> {}

here, Subclass explicitly extends Object and since it's the default behavior, this extend clause can be removed. Same for the T parameter.

avoid-assignments-as-conditions. Warns when an assignment is used inside a condition.

Assignments inside conditions can not only make readability worse, but also be a sign of a bug (then = is used instead of ==).

For example,

void fn(List<String> values) {
bool? flag;

if (flag ??= values.isEmpty) {}
if (flag = values.isEmpty) {}
}

here, to address the rule's warnings for both assignments, it's recommended to move them out of the condition and use flag instead.

avoid-type-casts. Warns about any usages of the as operator.

Incorrect type casts can throw an exception at runtime (which is usually undesired and not expected).

For example,

void fn() {
dynamic a = 3;
final s = a as String;

int b = 2;

final s = b as String;
if (b case String() as Object) {}
}

here, a as String and b as String will throw at runtime. To avoid this, it's recommended to use type checks (is operator) instead.

void fn(List<String> values) {
dynamic a = 3;
if (a is String) {
...
}
}

avoid-unused-assignment. Warns when an assignment is not used in the subsequent statements.

For example,

void fn1() {
var v = 'used';
doSomething(v);
v = 'unused';
}

void fn2(bool condition) {
var v = 'used';
if (condition) {
v = 'unused';
return;
}
doSomething(v);
}

here, both assignments to v after the declaration are actually not used and can be simply removed.

avoid-unnecessary-overrides. Warns when a parent declaration is overridden by a declaration without an implementation.

For example,

abstract class B {
String get field;

void foo();
}

abstract class A extends B {

String get field;


Future<void> foo();
}

mixin M on B {

String get field;
}

here, field and foo overrides add no implementation or additional logic and therefore can be removed without changing how the code behaves.

avoid-duplicate-constant-values. Warns when a class or enum declaration has several constants with the same primitive values.

For example,

enum MyEnum {
a('hi'),
b('hi'),
c('another');

final String value;

const MyEnum(this.value);
}

class RuleType {
final String value;

const RuleType._(this.value);

static const common = RuleType._('common');
static const flutter = RuleType._('common');
}

here, the string values passed to b and flutter constant match a and common (respectively). Having different constant values with the same primitive value is usually undesired and is a sign of a bug.

avoid-unnecessary-enum-arguments. Warns when a enum constant has an unnecessary empty argument list.

For example,

enum MyEnum {
alpha.named(),
beta.named(),
gama();

final String value;

const MyEnum.named() : value = '2';

const MyEnum() : value = '3';
}

here, parentheses in gama() have no use and can be removed

enum MyEnum {
alpha.named(),
beta.named(),
gama;

final String value;

const MyEnum.named() : value = '2';

const MyEnum() : value = '3';
}

prefer-contains. Suggests using .contains instead of .indexOf when checking for the presence of an element.

For example,

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

if (list.indexOf(1) == 2) {}
if (list.indexOf(1) == -1) {}
if (list.indexOf(1) != -1) {}
}

here, both == -1 and != -1 can be replaced with a contains instead.

avoid-unnecessary-constructor. Suggests removing an unnecessary empty constructor for enum and class declarations.

In Dart, classes and enums always have an implicit default constructor and declaring it explicitly is usually unnecessary.

For example,

class SomeClass {
final values = <int>{};

SomeClass();
}

enum MyEnum {
alpha,
beta,
gama;

const MyEnum();
}

here, SomeClass(); and const MyEnum(); can be simply removed.

avoid-implicitly-nullable-extension-types. Warns when an extension type declaration does not have the implements clause.

Not adding the implements clause (when applicable) implicitly makes the extension type nullable even when the representation type is not.

For example,

extension type A(Map json) {
B get b => json['b'] as B;
}

extension type B(Map json) {}

void main() {
var a = A({'b': {}, 'c': 'hello'});
a.b!;
}

here, since B has no implements clause, it's considered allowing both nullable and non-nullable values (even though Map is not marked as nullable). This leads to the analyzer not showing a warning for a.b! (for the ! being unnecessary) which is usually undesirable.

To avoid this, extension types with a non-nullable representation type should have the implements clause.

avoid-getter-prefix. Warns when a getter name starts from a banned prefix.

For example,

dart_code_metrics:
...
rules:
...
- avoid-getter-prefix:
prefix: '^get'
abstract class ModalRoute {
bool get getBarrierDismissible;
}

class Some implements ModalRoute {

bool get getBarrierDismissible => false;
}

here, getBarrierDismissible will be highlighted because it starts with the banned prefix.

avoid-assigning-to-static-field. Warns when an instance method assigns to a static field.

For example,

class Some {
static int value = 1;

void work() {
value = 2;
}
}

should be rewritten to

class Some {
static int value = 1;

static void work() {
value = 2;
}
}

avoid-unnecessary-enum-prefix. Suggests removing unnecessary enum prefixes.

For example,

enum MyEnum {
alpha(),
beta(),
gama();

const MyEnum();

int someFunction() => switch (this) {
MyEnum.alpha => 0,
beta => 1,
MyEnum.gama => 2,
};
}

here, since MyEnum.alpha is used inside the MyEnum declaration, the MyEnum prefix can be omitted.

enum MyEnum {
alpha(),
beta(),
gama();

const MyEnum();

int someFunction() => switch (this) {
alpha => 0,
beta => 1,
gama => 2,
};
}

avoid-non-final-exception-class-fields. Warns when an exception class declaration has non-final fields.

Mutating exception class fields is usually undesired and can lead to unexpected behavior.

For example,

class CaughtException implements Exception {
final Object exception;

final String? message;

StackTrace stackTrace;

CaughtException(Object exception, StackTrace stackTrace)
: this.withMessage(null, exception, stackTrace);

CaughtException.withMessage(this.message, this.exception, this.stackTrace);
}

should be rewritten to

class CaughtException implements Exception {
final Object exception;

final String? message;

final StackTrace stackTrace;

CaughtException(Object exception, StackTrace stackTrace)
: this.withMessage(null, exception, stackTrace);

CaughtException.withMessage(this.message, this.exception, this.stackTrace);
}

Pub

prefer-pinned-version-syntax. Warns when a dependency version is not specified as the exact version (e.g. 1.2.3).

Using exact version can help you avoid unexpected breaking changes in patch versions and have more control over which package version is used.

For example,

name: some_package
description: ...
version: 1.0.0

dependencies:
some_dependency: ^1.0.0

should be rewritten to

name: some_package
description: ...
version: 1.0.0

dependencies:
some_dependency: 1.0.0

What’s next

To learn more about upcoming features, check out our public roadmap.

And to learn more about upcoming videos and "Rules of the Week" content, subscribe to our Youtube Channel.

Sharing your feedback

If there is something you miss from DCM right now, want us to make something better, or have any general feedback - join our Discord server!