Skip to main content

What’s new in DCM 1.28.0

· 10 min read

Cover

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

This release includes 11 new rules, general availability of DCM Dashboards, configurable estimated effort for rules and metrics, printable HTML reports, support for the old Dart formatter, and more! 🚀

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

DCM Dashboards (General Availability)

Two months ago we have announced closed beta of our new Dashboards feature.

Today, we’re thrilled to announce that DCM Dashboards are now generally available to all Teams and Enterprise users!

DCM Dashboards give you a complete view of your codebase quality and metrics trends across time. All rule violations and metrics results are tracked and visualized, helping teams better understand, prioritize, and act.

Cover

You’ll find the Dashboards in your Teams Console, no separate installation needed and it's bundled with Teams or Enterprise licenses.

A dedicated blog post with more details is coming soon, stay tuned.

Configurable Estimated Effort

You can now configure the estimated effort (in minutes) for each rule and each metric directly in your analysis_options.yaml. This helps you fine-tune your reports based on your team’s context.

analysis_options.yaml
dart_code_metrics:
rules:
- prefer-const-border-radius:
effort: 3
metrics:
cyclomatic-complexity:
effort: 5

These values are reflected in CLI, reports, and Dashboards, making prioritization easier for you and your team.

Printable HTML Reports

All HTML reports (including widgets and assets reports) now support printable styles.

This means you can easily save your reports as PDFs using your browser’s "Print to PDF" feature. A small tweak, but a big help for sharing, archiving, or compliance purposes.

Cover

Support for the Old Dart Formatter

You can now enable the old Dart formatter (pre-Dart 3.7) using a CLI option --old-formatter or a new setting in your IDE extension config (for the IDE formatting-related actions).

This is helpful if your project hasn’t migrated to the new formatter yet or if you prefer the old style.

New CLI Options

All Commands

All commands now support a new option --root-exclude that allows you setting excludes relative to the analyzed root folder (and the already available --exclude option is evaluated relative to each analyze package).

Run

Run command now has several new flags:

  • --all: Run all DCM commands in one go.
  • --preview-upload: Preview results before uploading to Dashboards.
  • --upload-date: Set a custom upload date (for historical tracking or custom workflows).
Usage: dcm run [arguments] <directories>

--all Run all dcm commands.
--analyze Run dcm analyze.
--metrics Run dcm calculate-metrics.
--analyze-widgets Run dcm analyze-widgets.
--analyze-assets Run dcm analyze-assets.
--code-duplication Run dcm check-code-duplication.
--unused-code Run dcm check-unused-code.
--unused-files Run dcm check-unused-files.
--unused-l10n Run dcm check-unused-l10n.
--dependencies Run dcm check-dependencies.
--parameters Run dcm check-parameters.
--exports-completeness Run dcm check-exports-completeness.


Upload:
--upload Upload the output to DCM Dashboards.
--preview-upload Preview the uploaded result.
--project The project key.
--upload-date="year-month-day" Override upload date to any previous date (for example, 2025-01-30).
--with-details Include detailed reports into the uploaded result.

We will release a new blog post to go deeper into Upload functionalities soon, stay tuned.

Updated JSON Reporters

All JSON reporters now include the number of analyzed files and folders, giving you better visibility into what was covered in your run.

{
"formatVersion":10,
"timestamp":"2025-04-15 20:05:58.000",
"summary":[
{
"title":"Total lint warning issues",
"value":3
},
{
"title":"Total lint style issues",
"value":35
},
{
"title":"Scanned folders",
"value":12
},
{
"title":"Scanned files",
"value":28
}
],
"analyzeResults":[]
}

New Rules

This release brings 11 new rules focused on improving code clarity, reducing common mistakes, and enforcing better conventions for both general Dart and Flutter-specific codebases. Let's explore in short but make sure to click on each rule and learn even more.

Common

avoid-recursive-tostring warns when a toString method calls itself recursively.

Recursive toString calls are never intentional and lead to runtime exceptions.

class First {
// LINT: Avoid recursive 'toString' calls.
String toString() => 'Something $this';
}

class Second {
// LINT: Avoid recursive 'toString' calls.
String toString() => 'Something ${this}';
}

class Third {
// LINT: Avoid recursive 'toString' calls.
String toString() => 'Something ${this.toString()}';
}

class Forth {
// LINT: Avoid recursive 'toString' calls.
String toString() => 'Something ' + this.toString();
}

avoid-enum-values-by-index warns when a enum value is accessed via Enum.values[index].

Accessing enum values by index is unstable and can lead to tricky bugs because the order of the enum values can change in the future (for example, when a new value is added).

enum SomeEnums { first, second }

void fn() {
// LINT: Avoid accessing enum values by index. Try using '.firstWhere' or '.byName' instead.
SomeEnums.values[0];
}

avoid-constant-assert-conditions warns when an assert statement has a constant condition.

Constant conditions in assert statements always evaluate to true or false and usually indicate a typo or a bug.

void fn() {
const value = 1;

// LINT: This condition is constant and will always evaluate to true or false.
// Try changing this condition or removing the assert.
assert(value > 1);

// LINT: This condition is constant and will always evaluate to true or false.
// Try changing this condition or removing the assert.
assert(true);
}

class SomeClass {
static const first = 1;

// LINT: This condition is constant and will always evaluate to true or false.
// Try changing this condition or removing the assert.
const SomeClass() : assert(first > 1);
}

avoid-nested-assignments warns when an assignment is used inside another expression.

Assigning to a variable inside another expression can be confusing and reduce readability. Consider moving the assignment out.

void fn() {
var userName = 'Someone';

// LINT: Avoid nested assignments. Try moving this assignment out.
final message = "Hello ${userName = 'Someone Else'}!";
}

avoid-unnecessary-compare-to suggests replacing .compareTo() with an equality check (when applicable).

void fn() {
// LINT: This '.compareTo()' invocation can be replaced with an equality check.
if (1.compareTo(2) == 0) {}
// LINT: This '.compareTo()' invocation can be replaced with an equality check.
if ('12'.compareTo('23') == 0) {}
}

prefer-digit-separators suggests using digit separators to improve readability of long numbers.

void fn() {
// LINT: Prefer adding digit separators to improve readability of this number.
final value = 1000000;

// LINT: Prefer adding digit separators to improve readability of this number.
final another = 10000234.23000;
}

You can also configure the minimum number of digits after which the number should have digit separators by setting min-length (default is 5).

analysis_option.yaml
dart_code_metrics:
rules:
- prefer-digit-separators:
min-length: 5

avoid-unnecessary-digit-separators suggests removing digit separators in short numbers to improve readability.

void fn() {
// LINT: Avoid adding digit separators to short numbers. Try removing them.
final value = 1_00;

// LINT: Avoid adding digit separators to short numbers. Try removing them.
final another = 1_0.23;
}

You can also configure the maximum number of digits by setting max-length (default is 5).

analysis_option.yaml
dart_code_metrics:
rules:
- avoid-unnecessary-digit-separators:
max-length: 5

avoid-inconsistent-digit-separators warns when the digit separators create inconsistent groups of digits.

Inconsistent groups are usually a sign of a typo and can significantly reduce readability.

void fn() {
// LINT: This number has inconsistent number of digits in separated groups.
// Try making the groups consistent.
final value = 1_00_000;

// LINT: This number has inconsistent number of digits in separated groups.
// Try making the groups consistent.
final value = 11_00_0;

// LINT: This number has inconsistent number of digits in separated groups.
// Try making the groups consistent.
final another = 1_00_00_000;

// LINT: This number has inconsistent number of digits in separated groups.
// Try making the groups consistent.
final another = 1_00.000_00_1;
}

Flutter

pass-existing-future-to-future-builder warns when a newly created future is passed to the FutureBuilder.

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

class SomeWidget extends Widget {

Widget build(BuildContext context) {
// LINT: Avoid passing a newly created future to FutureBuilder.
// Try moving the future to a variable and using it instead.
return FutureBuilder(
future: getValue(),
builder: ...,
);
}

Future<String> getValue() => Future.value('str');
}

pass-existing-stream-to-stream-builder warns when a newly created stream is passed to the StreamBuilder.

The stream must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the StreamBuilder. If the stream is created at the same time as the StreamBuilder, then every time the StreamBuilder's parent is rebuilt, the asynchronous task will be restarted.

class SomeWidget extends Widget {

Widget build(BuildContext context) {
// LINT: Avoid passing a newly created stream to StreamBuilder.
// Try moving the stream to a variable and using it instead.
return StreamBuilder(
stream: getValue(),
builder: ...,
);
}

Stream<String> getValue() => Stream.fromIterable(['1', '2', '3']);
}

prefer-compute-over-isolate-run suggests using compute(...) over Isolate.run(...). Isolate.run() does not support the web platform and if you are targeting it, it's recommended to use compute() to ensure your code works as expected.

void fn() {
// LINT: Prefer using 'compute()' instead of 'Isolate.run()' for web platform compatibility.
Isolate.run(...);
}

New Rule Configurations

ignore-errors for prefer-correct-throws

You can now ignore false-positive analysis errors when using this rule:

analysis_options.yaml
dart_code_metrics:
rules:
- prefer-correct-throws:
ignore-errors: true

ignored-instances & ignored-invocations

For avoid-passing-async-when-sync-expected, you can now ignore specific cases:

analysis_options.yaml
dart_code_metrics:
rules:
- avoid-passing-async-when-sync-expected:
ignored-instances:
- onPressed
ignored-invocations:
- runWith

VS Code & IntelliJ Updates

Both the VS Code extension and IntelliJ plugin received a set of improvements for working with metrics and rule configuration.

  • Autocompletion is now available when editing metric configuration values.

  • New code actions have been added to simplify editing your analysis_options.yaml:

    • Set or toggle the ignorable field for a rule.
    • Set or adjust the effort value.
    • Reset a metric threshold to its default value.
    • Enable the old Dart formatter via configuration.

These updates make it easier to fine-tune your setup directly from the IDE.

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.