What’s new in DCM 1.32.0
With a little delay, we are excited to announce the release of DCM 1.32.0!
This release includes 12 new rules, 21 new quick fix, optimized memory consumption for remaining DCM commands, improved support for pubspec rules across all DCM features, new metrics config to pass multiple thresholds for different paths, new "unset" metric threshold, new role for accessing only a selected number of projects in DCM Dashboards and improved UX for adding new seats to your subscription, and more!
Let’s go through the highlights of this release (and the full list of changes is in our changelog)!
Dashboard Improvements
New Role and External Users
To help you granularly manage access to your DCM projects, this release includes a new "View Selected" role for which you can select a list of projects a particular user or developer can access:
We've also removed the "Dashboards access" toggle and made the UX for managing developers and users more similar.
Additionally, you can now invite external users (users with an email address that does not match your account domain address), but their role is limited to "View Selected" only:
Improved Baseline
To reduce confusion between regular and baselined issues, cards with the active baseline state now have several distinctive elements:
Updated Billing Page and Seat Selection
The Billing page also got a few improvements.
First, it shows more details about the current plan and the numbers of added developers, users and projects:
When it comes to adding new seats to your subscription, the modal dialog now includes more information about the cost of each seat and the billing interval.
Metrics Improvements
If you have any feedback or feature requests for current metrics, any metric-related features or new metrics, please let us know on our Discord or via email [email protected].
We plan to ship a lot of changes and improvements (including documentation updates, new metrics and more) in the upcoming 2 releases (1.33 and 1.34), so your feedback can help us make metrics even better!
Multiple Threshold
With this release we are introducing a new configuration for all metrics to accept multiple thresholds for different paths:
dcm:
metrics:
cyclomatic-complexity:
threshold: 20
entries:
- threshold: 10
paths:
- '.*some_folder.*'
- '.*some_file.dart'
- threshold: 15
paths:
- '.*another_folder'
For the example above, all files and folders that match .*some_folder.*
or .*some_file.dart
will have the cyclomatic complexity threshold equal to 10
, the ones that match .*another_folder
- 15
, and for all other files the threshold will be 20
.
Configuring the main threshold is required as that threshold will be used for file that are not covered by the entries
config (if no threshold is set, the default threshold will be used instead).
We hope this change will make metrics usage even more convenient especially for cases when you want one set of thresholds for your UI widgets and another for business logic.
"unset" Threshold
On top of the multiple thresholds, you can now set a metric threshold to "unset" to avoid getting any violations.
dcm:
metrics:
cyclomatic-complexity:
threshold: unset
or for multiple thresholds:
dcm:
metrics:
cyclomatic-complexity:
threshold: 20
entries:
- threshold: 10
paths:
- '.*some_folder.*'
- '.*some_file.dart'
- threshold: unset
paths:
- '.*another_folder'
This threshold can be helpful if you want to simply collect metric values (for example, when uploading to DCM Dashboards).
"maximum-nesting-level" Updates
The maximum-nesting-level
metric now covers additional cases (such as multiline list, map and set literals, if and for collection elements and switch cases without a block body).
For example, prior to this release, the code below had a nesting level of 1 (which is clearly not the case).
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(),
Text(),
const SizedBox(height: 8),
Row(
children: [
for (var i = 0; i < 5; i++) ...[
const SizedBox(width: 4),
]
],
),
],
),
),
Now, it has a nesting level of 5 (with 3 list literals and 1 collection for element).
Improved HTML Reports
The metrics HTML report got a few UX improvements as well.
For example, the single file shows a summary for each metrics on the left (same as for other pages) and each summary now includes a threshold.
Plus, you can now open the file in VS Code with just one click by clicking "Open in VS Code".
Memory Usage Improvements (part 2)
If you have checked out our previous release, there was a separate section dedicated to memory optimizations.
Unfortunately, that release didn't include a fix for all commands (and check-parameters
and analyze-widgets
were left without an optimization).
Now, all commands support the optimization and you should see the results of it when running dcm run --all .
Better Support for Pubspec Rules
With this release, all pubspec rules now have better integration with all other DCM features.
For example, you can now apply quick fixes and add # ignore_for_file:
and # ignore:
comments:
Plus, they are now fully supported in the analysis_options.yaml
config (including autocompletion, code actions and configuration validation):
And last but not least, pubspec rules are now shown in the results of dcm init lints-preview
and are applied with built-in presets!
Improved Detection of Pub Cache and Flutter SDK
If you are using tools like asdf
, with the release we are happy to announce an improved support for automatic Flutter SDK detection and the overall improvement of our SDK detection logic.
Now, if the tool fails to locate the SDK in your PATH, here is the list of all places it checks to locate the SDK (in that particular order):
--sdk-path
CLI option (if present)- the
DCM_SDK_PATH
env variable (if present) - the
FLUTTER_ROOT
env variable (if present) fvm
configuration.dart_tool/package_config.json
which flutter
/where flutter
(depending on the platform)
We hope this change reduces the need of passing --sdk-path
manually even more!
Additionally, we've improved the detection of external code (the tool treats all code from pub.dev packages as external) and it now correctly picks up the standard PUB_CACHE
environment variable recognized by pub.
New Rules
Common
avoid-unremovable-callbacks-in-listeners
Warns when an addListener
invocation receives a callback that cannot be unregistered.
Since no function expression (() => ... or () { ... })
is equal to any other function expression, passing it to addListener
creates a callback that cannot be unregistered via removeListener
and thus leads to a memory leak.
Same applies to extension method tear-offs as they are never equal to each other (including methods with the same signature).
class SomeClass {
final _someListener = Listener();
void work() {
// LINT: Avoid passing function expressions as callbacks since they are never equal to each other and can't be unregistered.
_someListener.addListener(() {});
// LINT: Avoid passing function expressions as callbacks since they are never equal to each other and can't be unregistered
_someListener.addListener(() => null);
}
}
To fix that, assign the function expression to a field or variable and use that field or variable in both addListener
and removeListener
.
You can also configure this rule to pass additional methods (for example, if you have a custom method that adds a listener).
avoid-constant-switches
Warns when a switch expression or statement has a constant expression.
Constant expressions in switches always evaluate to the same value and usually indicate a typo or a bug.
void fn() {
// LINT: This expression is constant and will always evaluate to the same value.
// Try changing this expression.
final value = switch (Some._val) {
...
};
// LINT: This expression is constant and will always evaluate to the same value.
// Try changing this expression.
switch (_another) {
...
};
}
abstract final class Some {
static const _val = '1';
}
const _another = 10;
To fix this issue, try either using a different value or replacing the switch statement/expression with the branch that matches the constant value.
avoid-constant-conditions
Warns when both sides of a binary expression are constant.
Constant conditions always evaluate to true
or false
and usually indicate a typo or a bug.
void fn() {
// LINT: This condition is constant and will always evaluate to 'false'.
// Try changing this condition.
if (_another == 11) {
print(value);
}
// LINT: This condition is constant and will always evaluate to 'true'.
// Try changing this condition.
if (Some._val == '1') {
print('hello');
} else {
print('hi');
}
}
abstract final class Some {
static const _val = '1';
}
const _another = 10;
avoid-unused-local-variable
Warns when a local variable is not referenced and can be removed or renamed to _
.
void fn() {
var a = 1;
// LINT: This local variable is not used. Try removing the variable or using it.
var b = a;
print(a);
}
void withRecord() {
// LINT: This local variable is not used. Try removing the variable or using it.
final (a, b) = (1, 2);
print(a);
}
match-base-class-default-value
Warns when the default value of a parameter in an overridden method does not match the default value of the base class parameter.
Changing the default value of a parameter can lead to subtle bugs when some subclasses of the base class have different behavior due to different default values. Additionally, this rule helps spot changes in the base class default values that have not been propagated to subclasses.
class A {
void work({bool defaultValue = false}) {
...
}
}
class B implements A {
// LINT: This default value does not match the default value of the base class parameter.
// Try changing it to match the base class default value.
void work({bool defaultValue = true}) {
...
}
}
This rule comes with an IDE fix.
prefer-test-structure
Warns when a test is not separated in three sections: arrange
, act
, assert
.
Using those sections in your tests helps keep the same structure for all tests and ensures that tests are not doing multiple rounds of arrange and act.
For example,
void main() {
test('bad unit test', () {
// act
final a = 1; // LINT: This test is missing an arrange section before this act section. Try refactoring this test.
final b = 2;
// arrange
final c = a + 1;
// assert
expect(b, c); // LINT: This test is missing an act section before this assert section. Try refactoring this test.
});
}
is expected to be
void main() {
test('good unit test', () {
// arrange
final a = 1;
final b = 2;
// act
final c = a + 1;
// assert
expect(b, c);
});
}
Equatable
prefer-equatable-mixin
Warns when a class extends Equatable
instead of mixing in the EquatableMixin
.
There is no upside in extending Equatable
, but using the mixin allows you to also extend another class while keeping all equatable features.
// LINT: The class should mix in 'EquatableMixin' instead of extending 'Equatable'.
class Person extends Equatable {
const Person(this.name);
final String name;
List<Object> get props => [name];
}
This rule comes with an IDE fix.
Pub
dependencies-ordering
Suggests sorting pubspec dependencies in alphabetical order (A to Z).
Works for dependencies
, dev_dependencies
and dependency_overrides
pubspec section.
For example,
name: some_package
description: ...
version: 1.0.0
dependencies:
xml: '>=5.3.0 <7.0.0'
yaml: 3.1.0+1
test: 1.22.2 # LINT: Dependencies are not sorted alphabetically. Try sorting them from A to Z.
is expected to be
name: some_package
description: ...
version: 1.0.0
dependencies:
test: 1.22.2
xml: '>=5.3.0 <7.0.0'
yaml: 3.1.0+1
Additionally, this rule supports a config option called flutter
to separately configure the order of flutter-related dependencies.
For example,
dcm:
pubspec-rules:
- dependencies-ordering:
flutter: first
will require flutter
dependencies be first regardless of the alphabetical order.
name: some_package
description: ...
version: 1.0.0
dependencies:
flutter:
sdk: flutter
collection: ^1.18.0
xml: '>=5.3.0 <7.0.0'
yaml: 3.1.0+1
This rule comes with an IDE fix.
newline-before-pubspec-entry
Enforces a blank line between the configured pubspec entries.
For example,
dcm:
pubspec-rules:
- newline-before-pubspec-entry:
entries:
- environment
- dependencies
- dev_dependencies
- flutter
- dependencies:collection
will highligh the following cases
name: some_package
description: ...
version: 1.0.0
environment: # LINT: Missing a blank line before this entry. Try adding it.
sdk: '>=3.6.0 <4.0.0'
flutter: '>=3.27.1'
dependencies: # LINT: Missing a blank line before this entry. Try adding it.
flutter:
sdk: flutter
collection: ^1.18.0 # LINT: Missing a blank line before this entry. Try adding it.
meta: ^1.12.0
ordered_set: ^8.0.0
vector_math: ^2.1.4
dev_dependencies: # LINT: Missing a blank line before this entry. Try adding it.
benchmark_harness: ^2.3.1
until they are formatted to
name: some_package
description: ...
version: 1.0.0
environment:
sdk: '>=3.6.0 <4.0.0'
flutter: '>=3.27.1'
dependencies:
flutter:
sdk: flutter
collection: ^1.18.0
meta: ^1.12.0
ordered_set: ^8.0.0
vector_math: ^2.1.4
dev_dependencies:
benchmark_harness: ^2.3.1
This rule comes with an IDE fix.
pubspec-ordering
Suggests sorting pubspec entries in the configured order.
For example,
version: 1.0.0
description: ... # LINT: 'description' should be before 'version'
name: some_package # LINT: 'name' should be before 'description'
is expected to be
name: some_package
description: ...
version: 1.0.0
This rule comes with an IDE fix.
add-resolution-workspace
Warns when a package is missing the resolution: workspace
entry.
If you are using pub workspaces, this rule helps to ensure that all subpackages are included into the workspace.
name: some_package # LINT: Add 'resolution: workspace' to include the package into your pub workspace.
description: ...
version: 1.0.0
This rule comes with an IDE fix.
prefer-correct-topics
Warns when the topics
section has incorrect entries.
You can find all requirements for this section on the official website.
topics:
- Network # LINT: The topic name should only use lowercase alphanumeric characters or hyphens and must start with a lowercase alphabet character
- A # LINT: The topic name should have between 2 and 32 characters
What’s next
To learn more about upcoming features, keep an eye on our public roadmap.
And to learn more about our 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! We’d love to hear from you.