What’s new in DCM for Teams 1.2.0
Today we’re excited to announce the release of DCM for Teams 1.2.0! In this version we focused on improving dcm fix
, cleaned up false-positives in the unused code check, added 22 new rules, fixed false-positives in existing rules and more!
Let’s go on a quick journey together to explore all the new features!
Removing anti-patterns
Anti-patterns were added to DCM long time ago and even with the idea looking great, something always felt off. So with this release we removed existing anti-patterns long-parameter-list
and long-method
and instead added two new rules avoid-long-parameter-list
and avoid-long-functions
as a replacement.
This transition helps avoid extra calculations, since anti-patterns were taking metrics config to work and calculating metrics was a requirement for the anti-patterns to be shown in the IDE. Moving towards rules also allows to add configuration making those rules more flexible (like, having an option to skip optional parameters for avoid-long-parameter-list
).
Removing anti-patterns is a first step towards reworking metrics and we hope that this change will make the overall tool usage more convenient.
Analysis options improved support
Considering the number of rules DCM provides, sometimes it might be hard to understand what configuration a rule has, what it actually does and how to change its severity or completely disable it.
With this release we're happy to introduce several improvements in this direction:
-
If rule is selected in the analysis_options.yaml, new code actions appear: they allow you to quickly configure a rule, change its severity, open the documentation or disable it.
-
When analysis_options.yaml file is opened in the IDE, all rules that have additional config are now marked with ⚙️ (this feature is only available for VS Code users).
Here is an example:
DCM fix updates
Most of our attention in this release was to make dcm fix
more useful and it now supports even more rules:
- avoid-border-all
- avoid-duplicate-exports
- avoid-explicit-type-declarations
- avoid-late-keyword
- avoid-redundant-else
- avoid-self-assignment
- avoid-unnecessary-conditionals
- avoid-unused-parameters
- binary-expression-operand-order
- double-literal-format
- newline-before-return
- prefer-const-border-radius
- prefer-declaring-const-constructor
- prefer-iterable-of
- prefer-return-await
- proper-super-calls
- unnecessary-trailing-comma
- arguments-ordering
Additionally, the command API was extended with these new options:
-
--include-rules
- allows you to add rules that are not listed in your config, but you want to apply their fixes as well -
--exclude-rules
- allows you to exclude rules listed in your config from fixes calculation -
--only-rules
- allows you to run calculate fixes only for the given list of rules -
--dry-run
- allows you to see the proposed changes without applying them -
--apply-to
- allows you to run fixes only for the listed files
For example, --dry-run
will output changes, coloring the deleted characters red and added characters green.
And last, but not least, dcm fix
now also supports removing detected unused-code 🚀!
This can be achieved via --type
option which supports lints (the default) and unused-code values.
Here is an example PR where code is removed by this command option.
Extract to a new file assist
We also experimenting with other directions on how to help you manage the codebase. In this release a new refactoring assist is added as a first step in this direction. This assist allows you to extract a class in new file with just a few clicks!
Performance improvements
Performance improvements were not left aside too: in this release we improved the overall time for calculating rules results and their quick fixes. We also fixed a problem with quick fixes not showing up.
New rules
Common
avoid-shadowing. Warns when a declaration name shadows another declaration with the same name in the current scope. Shadowing an existing variable / parameter / field can lead to unexpected results, especially if the types of both declaration (shadowed and new one) match.
For example,
class SomeClass {
final String content;
...
void method() {
final content = ...;
}
}
If we declare a variable with the name content
, but the class already has a field with the same name, we might end up using the variable instead of the field without noticing. This problem is easy to notice in short methods / classes, but in the real world code it's different.
This rules is also very flexible, allowing you to exclude fields, parameters or static members from the scope.
prefer-return-await. Warns when a Future
is returned from a try / catch block without an await
. An error thrown by the Future won't be catch by the catch block which rarely an expected behavior, but also very easy to miss in code review.
For example,
Future<String> report(Iterable<String> records) async {
try {
return anotherAsyncMethod();
} catch(e) {
print(e);
}
}
If anotherAsyncMethod
throws an error, the catch block will never handle it unless the method invocation is awaited.
unnecessary-trailing-comma. Checks for unnecessary trailing commas for arguments, parameters, enum values and collections. Removing those commas results in dart format
producing code that fits on one line.
For example,
void function(
String first,
String second,
) {
return;
}
can be reformated to a more readable one-liner:
void function(String first, String second) {
return;
}
avoid-duplicate-named-imports. Warns when a file has duplicate imports which differ only in name prefix. Sometimes when a named import is used, the IDE can suggest to import from the same file, but without a named prefix. This results in the file having two imports: one named and one regular.
For example,
import 'dart:io' as io;
import 'dart:io';
Flutter
proper-super-calls. Checks that super
calls in the initState
and dispose
methods are called in the correct order. It's pretty easy to call super methods too early or too late which might result in strange bugs that are really hard to spot.
For example,
class _MyHomePageState<T> extends State<MyHomePage> {
int _counter = 0;
void initState() {
someWork();
super.initState();
}
void dispose() {
super.dispose();
someWork();
}
}
In initState
the super
call should be the first statement inside the method body. And as for the dispose
, the super
call should be the last.
add-copy-with. Warns when a class that matches the config does not declare a copyWith
method.
For example,
class SomeState {
final String value;
const SomeState({required this.value});
}
If the rule was configured to trigger on classes named SomeState
, then a warning will show up. This rule is useful for cases when you regularly forget to add copyWith
.
Bloc
avoid-bloc-public-methods. Warns when a Bloc
has public methods except the overridden ones. According to the docs it's considered a bad practice if a bloc has public methods.
For example,
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
void changeSate(int newState) {
state = newState;
}
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
void _listenToChange() {}
}
The changeSate
should be removed and replaced with an event if the state needs to be updated.
avoid-passing-bloc-to-bloc. Warns when a Bloc
depends on another Bloc
. You can also refer to this article.
For example,
class BadBloc extends Bloc<CounterEvent, int> {
final OtherBloc otherBloc;
late final StreamSubscription otherBlocSubscription;
BadBloc(this.otherBloc) : super(0) {
otherBlocSubscription = otherBloc.stream.listen((state) {
add(Change(1));
});
}
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
}
In this case the Bloc dependency will be highlighted.
prefer-multi-bloc-provider. Warns when a BlocProvider
/ BlocListener
/ RepositoryProvider
can be replace with a Multi
version. Replacing with multi provider improves the readability and reduces the widget tree nesting level.
For example,
final provider = BlocProvider<BlocA>(
create: (context) => BlocA(),
child: BlocProvider<BlocB>(
create: (context) => BlocB(),
child: BlocProvider<BlocC>(
create: (context) => BlocC(),
child: Widget(),
),
),
);
can be replaced with MultiBlocProvider
and transformed into:
final multiProvider = MultiBlocProvider(
providers: [
BlocProvider<BlocA>(create: (context) => BlocA()),
BlocProvider<BlocB>(create: (context) => BlocB()),
BlocProvider<BlocC>(create: (context) => BlocC()),
],
child: Widget(),
);
prefer-correct-bloc-provider. Warns when a Bloc
is provided not with a BlocProvider
. If you use both bloc
and provider
packages it becomes easy to accidentally use a wrong Provider
for your Blocs
.
For example,
final provider = Provider<BlocA>(
create: (context) => BlocA(),
child: Widget(),
);
In this case BlocProvider
should be used instead of a regular Provider
:
final blocProvider = BlocProvider<BlocA>(
create: (context) => BlocA(),
child: Widget(),
);
avoid-cubits. Warns when a Cubit
is used. It's recommended to avoid Cubits if you want to scale your codebase, since Cubits might look like a good way to stat, but they scale badly.
For example,
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
}
the rule will trigger on any cubit usage.
Equatable
extend-equatable. Warns when a class that matches the config does not extend Equatable
.
For example,
class SomePerson {
const SomePerson(this.name);
final String name;
}
If the rule was configured to trigger on classes named SomePerson
, then a warning will show up. This rule is useful for cases when you regularly forget to extend Equatable
.
Pubspec
This release also contains a new type of rules - pubspec rules. They should be listed in a special config section named pubspec-rules
.
banned-dependencies. Warns when a pubspec contains a banned dependency. It's pretty common to have some public libraries replaced by private ones. This rules helps enforce this policy.
prefer-publish-to-none. Warns when a pubspec file does not have publish_to: none
config. Explicitly adding publish_to: none
can help avoid accidental publishing, especially if you manage both public and private packages.
prefer-correct-package-name. Warns when a package name does not match the config. If you have a naming convention for packages, this rule will help you enforce it.
prefer-semver-version. Warns when a package version does not match the semantic versioning approach.
avoid-dependency-overrides. Warns when a pubspec file has the dependency_overrides
section. Overriding dependencies might lead to unexpected behavior, so it's recommended to avoid it.
prefer-caret-version-syntax. Warns when a dependency version is listed not with the caret syntax.
avoid-any-version. Warns when a dependency version is set as any
.
prefer-correct-screenshots. Warns when a screenshots section has incorrect entries. You can find all requirements toward this section on the official website.
What’s next
Reworking metrics and reporting. Metrics is one of the first features we implemented and it’s time to rethink and rework some of its parts. Waited long for an ability to ignore a separate metric violation? Want to see the metrics in real-time? Want to have more usable reports? In the future releases all these issues will be addressed.
Baseline support. Some feedback we received was about adding DCM to an existing codebase and that this process is not easy. We want to address this problem by introducing support for baseline when you will be able to ignore existing code or have a specific config that automatically ignores all existing issues allowing you to partially enable rules for the new code.
Bringing the unused code check to the next level. Even though we have a working prototype that allows to see unused code in real-time, the memory cost of this feature is too high. This feature is postponed until we rework how AST is parsed and stored in memory.
Sharing your feedback
If there is something you miss from DCM right now or want us to make something better or have any general feedback - there are several ways to share: you can join our Discord server or send us feedback directly from our website.