Skip to main content

What’s new in DCM 1.31.0

· 14 min read
Majid Hajian
Developer Advocate

Cover

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.

DCM MCP Server

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.

.cursor/mcp.json
{
"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.

Memory Consumption ChartsMemory Consumption Charts

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).

Issues Overview

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,

analysis_options.yaml
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:

lints_preview_example.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:

CSV example

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

analysis_options.yaml
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.