What’s new in DCM 1.36.0

Today we’re excited to announce the release of DCM 1.36.0!
This release includes 11 new rules; a new command to safely rename public code that can be private (with a CLI fix!); a new option for overriding DCM configuration when uploading results to Dashboards; improved error messages for failed uploads; improved detection of unused code and files; and more!
❗️ With the next release we plan to discontinue all DCM versions prior to 1.27.0. If you're still using one of those, consider upgrading to a newer version.
Let’s go through the highlights of this release (and the full list of changes is in our changelog)!
New Command for Unnecessarily Public Code
With this release, we are excited to present a new command for detecting public top-level declarations and class members (fields, methods, etc.) that are used only in the containing file (including parts) and can be made private.
This command helps you ensure that only relevant classes, state and behavior are accessible to external files reducing overall API surface.
To execute the command, run:
$ dcm check-unnecessarily-public-code lib # or dcm upc lib

Furthermore, you can automatically fix some of the issues reported by this command via dcm fix reducing time to addresses them to just few minutes.
To do so, set the --type option to unnecessarily-public-code --type=unnecessarily-public-code:
$ dcm fix --type=unnecessarily-public-code lib # or dcm f lib
The fix won't apply to fields as renaming fields without changing named parameters in constructors will break your code.
This issue will be addressed with Dart 3.12 once Dart gets private-named-parameters.
Overriding Configuration for Uploads
By default, dcm run --upload uses analysis_options.yaml files to detect available DCM configuration (same as other commands).
With this release, the command now supports a new option called --upload-options-path that allows overriding the configuration specifically for uploads helping you to have 2 different configurations: one for local and CI runs and another only for uploads.
Note that this configuration will apply to all analyzed packages.
For example,
dcm:
metrics:
cyclomatic-complexity:
threshold: 20
number-of-parameters:
threshold: 4
maximum-nesting-level:
threshold: 5
exclude:
metrics:
- test/**
rules:
- avoid-dynamic
- avoid-passing-async-when-sync-expected
- avoid-redundant-async
- avoid-unnecessary-type-assertions
- avoid-unnecessary-type-casts
- avoid-unrelated-type-assertions
dcm run lib --all --upload --project=PROJECT_KEY --email=LICENSE_EMAIL --upload-options-path="/root/dcm_shared_options.yaml"
will analyze and upload issues with the provided configuration.
The shared configuration file can have any name, but we recommend adding the dcm_ prefix which will enable autocompletion and validation for that file.
Detection of Unused Freezed and JsonSerializable
With this release, the check-unused-code command now detects Freezed and JsonSerializable classes that are only referenced in generated code helping you quickly locate and remove them (or add a proper reference if they should be actually used).
Improved Error Messages for Failed Uploads
To help you catch any configuration issues during the initial setup for Dashboards, a failure to upload the metadata will now show a detailed error message with useful links:
Failed to validate credentials. Please check that the passed global project key and email are correct.
You can access the global project key on the "Access" page https://dcm.dev/docs/getting-started/for-teams/license-keys/#access-global-project-key-.
The passed email must be your CI email https://dcm.dev/docs/getting-started/for-teams/license-keys/#access-cicd-credentials.
Project Tags in Automatic Projects Creation
This feature is available to Enterprise users only.
In DCM 1.33.0 we introduced a way to automatically generate DCM Dashboards projects based on the monorepo structure.
With this release, we are extending this feature to support automatically creating and assigning project tags. To do so, set the tags in the analysis_options.yaml file:
dcm:
project: my_monorepo_package
project-tags:
- some_tag
- another_tag
Those tags will be automatically assigned (or created and assigned) to your Dashboards project. Removing a tag from the list unassigns it from the project, but the tag itself will remain available. Keeping an empty list will not remove any tags from the existing projects.
New Rules
Discovering and Adding Rules from New Releases
Each DCM release introduces new rules. To explore all available rules and filter by version, use the dcm init lints-preview command:
dcm init lints-preview lib --rule-version=1.36.0 # Show rules added in 1.36.0
This command displays:
- Rule names and their violations in your codebase
- Estimated effort to fix all violations of each rule
- Whether a rule supports automatic fixes
You can also generate the output in different formats.
avoid-unassigned-local-variable
Warns when a local variable is used but has not yet received a value.
int? _value() {
int? val;
// LINT: This local variable is used but has not yet received a value.
// Try assigning a value or using 'null' directly.
print(val);
return val;
}
int? _another() {
int? val;
// LINT: This local variable is used but has not yet received a value.
// Try assigning a value or using 'null' directly.
return val;
}
To fix this issue, replace val with null or assign a value to it:
int? correct() {
int? val;
val = 1;
return val;
}
int? correct() {
final val = 1;
return val;
}
avoid-throw
Warns when the throw expression is used.
Use this rule if you prefer to avoid throwing exceptions or errors and instead rely on the result pattern. You can also scope this rule to only a part of your codebase by configuring exclude or include.
avoid-unnecessary-parentheses
Suggests removing unnecessary parentheses.
For example,
void fn(List<int> input, List<int>? another) async {
// LINT: Unnecessary parentheses. Try removing them.
final _ = [...(input)];
// LINT: Unnecessary parentheses. Try removing them.
final _ = (input.isEmpty) ? true : false;
final toolPath = another != null
// LINT: Unnecessary parentheses. Try removing them.
? input.isEmpty ? input : (await input)
// LINT: Unnecessary parentheses. Try removing them.
: (input);
}
bool f(int a) {
return (a.isEven); // LINT: Unnecessary parentheses. Try removing them.
}
can be simplified to
void fn(List<int> input, List<int>? another) async {
final _ = [...input];
final _ = input.isEmpty ? true : false;
final toolPath = another != null
? input.isEmpty ? input : await input
: input;
}
bool f(int a) {
return a.isEven;
}
This rule also comes with auto-fix.
prefer-non-nulls
Suggests using .nonNulls instead of .whereType for filtering out nullable values.
For example,
void fn() {
Iterable<String?> a = [];
// LINT: Prefer '.nonNulls' instead of '.whereType' for filtering out nullable values.
a.whereType<String>();
}
can be simplified to
void fn() {
Iterable<String?> a = [];
a.nonNulls;
}
This rule also comes with auto-fix.
avoid-disposing-late-fields
Warns when a late widget state field is disposed in dispose method.
Disposing a late field can lead to either an exception related to the field not being initialized or to an unnecessary initialization of the field followed by its immediate disposal. Consider declaring such fields as nullable instead of late.
For example,
class _ShinyWidgetState extends State<ShinyWidget> {
late final SomeDisposable _someDisposable;
void dispose() {
// LINT: Avoid calling 'dispose' on a late field. Try making it nullable instead.
_someDisposable.dispose();
super.dispose();
}
}
should be rewritten to
class _ShinyWidgetState extends State<ShinyWidget> {
SomeDisposable? _someDisposable;
void dispose() {
_someDisposable?.dispose();
super.dispose();
}
}
prefer-correct-static-icon-provider
Warns when a class with the @staticIconProvider annotation does not have IconData fields or any of those fields are not static.
All IconData fields must be static in order for tree shacking to work properly.
For example,
abstract final class Some {
// LINT: IconData fields must be static const for the @staticIconProvider to have any effect.
// Try removing the annotation or making this field static const.
final IconData icon = IconData(1);
}
// LINT: This class has no IconData fields which makes @staticIconProvider redundant.
// Try removing the annotation or adding IconData fields.
abstract final class Some {}
should be rewritten to
abstract final class Some {
static const IconData icon = IconData(1);
}
add-equatable-props
Warns when a class inherits Equatable but is missing the props getter.
For example,
// LINT: This class inherits Equatable, but is missing the 'props' getter. Try adding it.
final class Child extends Parent {
final int y;
Child({required this.y});
}
sealed class Parent with EquatableMixin {
List<Object?> get props => [];
Parent();
}
should be rewritten to
final class Child extends Parent {
final int y;
Child({required this.y});
List<Object?> get props => [...super.props, y];
}
sealed class Parent with EquatableMixin {
List<Object?> get props => [];
Parent();
}
use-then-answer
Warns when thenReturn is used with Future or Stream.
For example,
void main() {
// LINT: 'thenReturn' should not be used to return a Stream or Future.
// Use 'thenAnswer' instead.
when(() => expression).thenReturn(Future.value(1));
}
should be rewritten to
void main() {
when(() => expression).thenAnswer(Future.value(1));
when(() => expression).thenReturn(1);
}
pass-mock-object
Warns when verify, verifyNever, when or other similar invocations receive a regular object instead of a mock.
Such invocations must only be given a Mock object.
For example,
void main() {
test('some test', () {
final instance = SomeClass();
// LINT: This invocation must only be given a Mock object.
// Try passing an object that extends Mock.
when(() => instance.value);
// LINT: This invocation must only be given a Mock object.
// Try passing an object that extends Mock.
verify(() => instance.value);
// LINT: This invocation must only be given a Mock object.
// Try passing an object that extends Mock.
verifyZeroInteractions(instance);
});
}
class SomeClass {
String? value;
}
should be rewritten to
void main() {
test('some test', () {
final mock = _SomeMock();
when(() => mock.value);
verify(() => mock.value);
verifyZeroInteractions(mock);
});
}
class _SomeMock extends Mock implements SomeClass {}
avoid-implementation-in-mocks
Warns when a class that extends Mock has overridden members.
Instead of adding implementation to mock classes, try specifying expected values via when. Alternatively, consider using Fake instead of Mock.
For example,
class SomeClass extends Mock implements Other {
// LINT: Classes that extend Mock should not have any overridden members.
// Try moving the implementation to when or use Fake instead.
String getResult() => 'result';
}
should be rewritten to
void main() {
test('some test', () {
final mock = SomeClass();
when(() => mock.getResult()).thenReturn('result');
});
}
class SomeClass extends Mock implements Other {}
prefer-correct-any-matcher
Warns when any or captureAny have incorrect or misused named argument.
For example,
void main() {
test('some test', () {
final correct = Correct();
// LINT: This invocation stubs a positional argument and should not have the 'named' argument.
// Try removing this argument.
when(() => correct.work(any(named: 'named')));
// LINT: This invocation stubs a named argument and should have the 'named' argument.
// Try adding it.
when(() => correct.work(any(), namedValue: any()));
// LINT: The passed name of the argument does not match the actual argument name.
// Try updating the name.
when(() => correct.work(any(), namedValue: any(named: 'some')));
});
}
should be rewritten to
void main() {
test('some test', () {
final correct = Correct();
when(() => correct.work(any()));
when(() => correct.work(any(), namedValue: any(named: 'namedValue')));
});
}
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.





