What’s new in DCM 1.31.0
Today we’re excited to announce the release of DCM 1.31.0!
This release includes 13 new rules, optimized memory consumption for some DCM commands, DCM MCP Server integration, baseline support for check-parameters
, more rules in Free and Starter plans, new output formats for dcm init lints-preview
and dcm init metrics-preview
, and more!
Let’s go through the highlights of this release! (And the full list of changes is in our changelog)
Pricing Plans Updates
Before we move to new features and changes, we are happy to say that this release adds more rules to Free and Starter plans! The Free plan now includes 20 additional rules which makes it in total 100 unique rules, and the Starter plan adds 27 more which makes it 240 unique rules in total.
And we’ve also removed the LOC limit from the analyze
command, so you can run full-project analysis without the LOC restrictions. Enjoy!
AI Tooling Integration Using DCM MCP Server
With this release we’re introducing DCM MCP Server support which is making it possible to integrate DCM directly into any client that supports the Model Context Protocol (MCP). This essentially makes your workflow smarter and tightly interested with AI toolings.
DCM MCP server is particularly very helpful when you are using Agent mode in different AI tooling which can help you automatically detect code quality issues with DCM and get them fix or find a solution for them.
Below is an example of that in GitHub Copilot in VsCode:
Once enabled, the DCM MCP server can:
- Analyze and fix errors in your project's code.
- Apply fixes for unused code, unused files and dependency issues
- Format code.
- Generate a baseline.
- Generate a preview for lint rules and metrics.
- Calculate metrics.
- Run code quality checks (detection of unused code, unused files, parameter issues, etc.).
- Analyze your Flutter widgets and image assets.
Starting the server is simple:
{
"mcpServers": {
"dcm": {
"command": "dcm",
"args": ["start-mcp-server"]
}
}
}
If your client does not support setting project roots (like Cursor), you can pass the --force-roots-fallback
flag to enable root management tools.
{
"mcpServers": {
"dcm": {
"command": "dcm",
"args": ["start-mcp-server", "--force-roots-fallback", "--client=cursor"]
}
}
}
To install and run DCM MCP server with your favorite editor and choice such as Gemini CLI, VS Code, IntelliJ, Android Studio or Cursor, you can follow our MCP server documentation.
Stay tuned to learn how you can leverage DCM intelligently with your AI assisted to make your workflow even smarter and more efficient to achieve high quality code in our upcoming blog post and videos.
Memory Usage Improvements
We’ve significantly optimized the memory consumption of several commands:
dcm check-unused-code
dcm check-unused-files
dcm check-exports-completeness
dcm check-code-duplication
dcm check-unused-l10n
In our benchmarks on the Flame repository, memory usage dropped from ~4GB to ~350MB when running these checks from the repository root.


This improvement applies both to running these commands individually and when they are part of a dcm run
execution.
Dashboards Updates
All charts now display how the number changed compared to the previous interval (for the issues overview the default interval is 7 days).
You can also generate a new project key directly from the dashboard and to make it easier for daily use, the sorting order is now remembered per project (and for the projects overview) and is saved between sessions.
Analyze Improvements
With this release, all lint rules support a new configuration option called message
which allows you to set an additional message to any rule's default message.
For example,
dart_code_metrics:
rules:
- avoid-non-null-assertion:
message: 'Use our custom function "safeNonNull" instead.'
will show
class Test {
String? field;
void method() {
// LINT: Avoid non-null assertions. Try rewriting the code without it.
// Use our custom function "safeNonNull" instead.
field!.contains('other');
}
}
The command now also supports 3 new config options which were previously available only in dcm fix
:
--include-rules
to include additional rules not listed in your config.--exclude-rules
to skip certain rules even if they are in your config.--only-rules
to analyze just the rules you specify.
Metrics and Lints Preview Improvements
The dcm init preview
&& dcm init lints-preview
commands are now more versatile.
The --format
option supports two new aliases --output-format
and --reporter
for consistency with other commands.
We’ve also added support for JSON and CSV output formats, making it easier to integrate with external tools.
To get the JSON output, run dcm init lints-preview . --format=json
:
{
"date": "2025-08-14 13:59:01.216016Z",
"version": "1.31.0",
"contexts": {
"/absolute/path/to/context": {
"add-copy-with": {
"id": "add-copy-with",
"category": "flutter",
"violations": 0,
"totalEffort": "0m",
"documentation": "https://dcm.dev/docs/rules/flutter/add-copy-with",
"severity": "warning",
"autoFixable": false,
"tags": [
"correctness",
"maintainability",
"requires-config"
],
"version": "1.2.0"
},
"always-remove-getx-listener": {
"id": "always-remove-getx-listener",
"category": "getx",
"violations": 0,
"totalEffort": "0m",
"documentation": "https://dcm.dev/docs/rules/getx/always-remove-getx-listener",
"severity": "warning",
"autoFixable": false,
"tags": [
"correctness",
"memory-leak"
],
"version": "1.18.0"
},
}
}
}
And for CSV, run dcm init lints-preview . --format=csv
:
Check Parameters Improvements
Baseline Support
We are excited to say that the baseline now also supports the check-parameters
command.
To baseline parameter issues, call the baseline command with the new --parameters
flag: dcm init baseline . --parameters
.
New Mode
Up to this point, the parameters check has excluded any overridden or abstract members, since there are many ways to call them through a parent class or interface.
With this release, we're excited to introduce a new mode (the same one our check-unused-code
command has) that includes overridden members (that don't come from external packages) into the analysis.
Let's take a look at this example:
class SomeClass {
void fn(String? nullable) { ... }
}
class AnotherClass extends SomeClass {
void fn(String? nullable) { ... }
}
If you enable the new mode and, for example, both fn
declarations are called with non-nullable values, they both will be reported by the command. Without the new mode, both fn
declaration will be skipped.
To enable the new mode, pass the --no-exclude-overridden
flag.
New Flags
To simplify the command's integration and adoption, it now supports 3 new flags to disable default checks:
--no-show-always-passed
--no-show-unnecessary-nullable
--no-show-never-passed
New Rules
DCM 1.31.0 brings 13 new rules for Dart, Flutter and Riverpod.
Common
avoid-unnecessary-local-variable
. Warns when a local variable is never referenced directly or is only used as an initializer for another variable.
void fn() {
var a = 1;
var b = a;
print(b);
var c = 2;
var d = c;
print(c);
}
This rule also comes with auto-fix.
avoid-unnecessary-late-fields
. Warns when a late final
field is assigned in all constructors.
In these cases, the late
keyword has no effect because the field is always initialized during object creation. Keeping late
only adds noise and can confuse readers.
class SomeClass {
late final String clientId;
SomeClass({required this.clientId});
}
class SomeOtherClass {
late final int anotherField;
SomeOtherClass() : anotherField = 1;
}
The rule comes with auto-fix, so the late
keyword can be removed safely.
avoid-unnecessary-nullable-parameters
. Warns when a private function or method declares a nullable parameter even though it never receives a nullable value.
Unnecessary nullability adds redundant checks (if (param == null)
) and complicates the code and making the parameter non-nullable simplifies logic and avoids useless branches.
class Some {
void work() {
_fn();
}
void _fn([String? nullable]) {
if (nullable == null) {
// handle nullable case
} else {
// handle regular case
}
}
}
With auto-fix, the ?
is removed automatically.
avoid-never-passed-parameters
. Warns when a private function or method declares an optional parameter that is never actually passed a value.
Such parameters are usually a leftover from refactoring or incomplete code. They add noise and can mislead developers into thinking the function supports more use cases than it really does.
class Some {
void work() {
_fn();
}
void _fn([String? nullable]) {
// handle logic without using nullable
}
}
You reduce unnecessary branching and make function signatures more accurate if you clean up never passed parameters.
prefer-returning-condition
. Suggests returning the condition directly instead of using an if
statement followed by return true
and return false
. Such patterns are redundant and make the code unnecessarily verbose.
void fn(bool condition, bool anotherCondition) {
if (condition) {
return true;
}
return false;
}
void fn(bool condition, bool anotherCondition) {
if (condition) {
return anotherCondition;
}
return false;
}
The rule comes with auto-fix, so it can simplify these cases automatically.
avoid-wildcard-cases-with-sealed-classes
. Warns when a switch
on a sealed class contains a wildcard (_
) case.
Using wildcards prevents the analyzer from reminding you to handle newly added subclasses, which can lead to missed logic.
void someFn(Vehicle value) {
final result = switch (value) {
Car() => 1,
_ => 2,
};
}
sealed class Vehicle {}
class Car extends Vehicle {}
class Truck extends Vehicle {}
By enumerating all subclasses instead of using _
, the analyzer will prompt you whenever new subclasses are introduced which ultimately keeps your code more maintainable.
Flutter
prefer-constrained-box-over-container
. This rule suggests using a ConstrainedBox
instead of a Container
when the only argument you pass is constraints
. A Container
is a more general-purpose widget, but if all you need are constraints, ConstrainedBox
is the more direct and efficient choice.
class SomeWidget {
Widget build() {
Container(constraints: BoxConstraints(), child: Widget());
}
}
Since this rule has auto-fix, DCM can automatically replace unnecessary Container
usages with ConstrainedBox
.
prefer-void-callback
. Suggests using the VoidCallback
typedef instead of explicitly writing void Function()
.
VoidCallback
is shorter, and the preferred convention in Flutter codebases.
void fn(List<void Function()> _callbacks) {
final void Function()? onPressed = enabled ? () {} : null;
void Function()? onTap;
final List<void Function()> callbacks = _callbacks;
for (final void Function() callback in callbacks) {
callback();
}
}
This rule has auto-fix which means DCM can automatically replace void Function()
with VoidCallback
.
prefer-async-callback
. Suggests using the AsyncCallback
typedef instead of explicitly writing Future<void> Function()
.
AsyncCallback
is more concise, improves readability, and is the recommended style in Flutter codebases.
void fn(List<Future<void> Function()> _callbacks) {
// LINT: Prefer using AsyncCallback instead of Future<void> Function().
final Future<void> Function()? onPressed = enabled ? () {} : null;
Future<void> Function()? onTap;
final List<Future<void> Function()> callbacks = _callbacks;
for (final Future<void> Function() callback in callbacks) {
callback();
}
}
Since this rule has auto-fix, DCM can automatically replace Future<void> Function()
with AsyncCallback
.
Riverpod
prefer-immutable-provider-arguments
. When working with Riverpod, one of the most common pitfalls is passing mutable or non-comparable values as provider arguments. This is where the rule prefer-immutable-provider-arguments
comes into play.
This rule warns when a provider’s argument does not have a consistent ==
. In practice, that means if you pass a value that doesn’t support stable equality checks, Riverpod can’t know whether the “new” argument is logically the same as the old one. As a result, you can run into subtle issues like:
- Unnecessary rebuilds – your widget rebuilds even if nothing meaningful changed.
- Lost caching – Riverpod thinks you’re asking for “new” data every time.
- Stale data – providers don’t reuse results across identical inputs.
The rule helps you catch this early, pushing you toward safer, more predictable provider usage.
Here’s a snippet that triggers this lint:
ref.watch(someProvider(SomeClassWithoutEquals()));
ref.watch(someProvider([42]));
ref.watch(someProvider(() { ... }));
avoid-public-notifier-properties
. Warns when a Notifier
/AsyncNotifier
exposes public state outside the state
property (e.g., extra getters/fields).
Doing so splits the source of truth, confuses rebuild semantics, and makes state transitions harder to track. Model all public data through state
instead which will be keeping updates and subscriptions predictable.
class MyNotifier extends Notifier<int> {
int get _privateGetter => 0;
int get publicGetter => _privateGetter;
int build() => 0;
}
avoid-ref-inside-state-dispose
. Warns when ref
is used inside the dispose
method of a ConsumerState
.
At disposal, providers may already be disposed, and accessing them can lead to unexpected errors or inconsistent behavior.
class _SomeState extends ConsumerState<SomeWidget> {
void dispose() {
ref.read(provider).doSomething();
super.dispose();
}
}
Cleanup logic should avoid using ref
directly and instead rely on other safe teardown patterns.
avoid-nullable-async-value-pattern
. Warns when a potentially nullable AsyncValue
is matched in a pattern using :final value?
without also checking hasValue: true
.
void fn() {
switch (...) {
case AsyncValue<int?>(:final value?):
print(value);
case AsyncError<int?>(:final value?):
print(value);
case AsyncLoading<int?>(:final value?):
print(value);
}
}
Without the hasValue
guard, you risk handling null
values unsafely inside your pattern match.
Improved Rules
This release improves 32 existing rules, reducing false positives and covering more edge cases. For the full list of improvements, see the 1.31.0 changelog.
member-ordering
With this release, the member-ordering
rule supports several new modifiers:
external-
(for all members)operator-
(for methods only)initialized-
(for fields only)abstract-
(for fields and methods)redirecting-
(for constructors only)
For example, with the following config
dart_code_metrics:
rules:
- member-ordering:
order:
- final-initialized-fields
- fields
- constructors
- redirecting-constructors
the following code
class SomeClass {
final String value;
final initializedValue = <String, String>{'some' : 'value'};
const factory SomeClass.load() = SomeOtherClass.load;
const SomeClass(this.value);
}
with have initializedValue
and the default constructor highlighted as initialized fields are expected to be placed before all other fields and constructors are expected to be placed before redirecting constructors.
Additionally, we've removed the order requirements for any configuration entry and you can write public-static-fields
or static-public-fields
(or any other modifier) in any order.
dispose-fields
dispose-fields
, dispose-class-fields
and dispose-getx-fields
now support transitive dispose()
calls.
For example,
class _ShinyWidgetState extends State<ShinyWidget> {
final _someDisposable = SomeDisposable();
final _anotherDisposable = SomeDisposable()
void transitiveDispose() {
_anotherDisposable.dispose();
}
void dispose() {
_someDisposable.dispose();
transitiveDispose();
super.dispose();
}
}
now will not trigger any warning.
What’s next
To learn more about upcoming features, keep an eye on our public roadmap. We have exciting plans to expand the DCM Dashboards, introduce additional rule configuration options, and continue improving the performance of all DCM commands.
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.