mitch_analyzer_plugin
mitch_analyzer_plugin is a security-minded Dart analyzer plugin that adds
custom warnings, quick fixes, and architectural checks for Dart and Flutter
projects.
It is built on top of the Dart 3.10+ analyzer plugin API and works in IDEs, in
dart analyze, and in flutter analyze.
Installation
Enable the published plugin in the analysis_options.yaml file located at the
root of your package:
plugins:
mitch_analyzer_plugin: ^0.2.1
When developing the plugin locally, use a path instead:
plugins:
mitch_analyzer_plugin:
path: ../mitch_analyzer_plugin
After updating the plugins: section, restart the Dart Analysis Server so the
new plugin configuration is picked up.
Configuration
Disable specific diagnostics
Plugin diagnostics are enabled by default. You can selectively disable a rule
under the plugin diagnostics section:
plugins:
mitch_analyzer_plugin:
path: ../mitch_analyzer_plugin
diagnostics:
avoid_print_usage: false
Exclude paths
You can skip plugin diagnostics for globbed paths:
plugins:
mitch_analyzer_plugin:
path: ../mitch_analyzer_plugin
exclude:
global:
- tool/**
avoid_substring:
- legacy/**
- !legacy/allowlisted/**
Notes:
exclude.globalapplies to every rule.exclude.<rule_name>applies to one rule.!re-includes a path after a broader exclusion.- Legacy
exclude.*remains supported as a global alias.
Ignore a single occurrence
// ignore: mitch_analyzer_plugin/avoid_print_usage
print('debug');
Ignore an entire file
// ignore_for_file: mitch_analyzer_plugin/avoid_print_usage
Rule Catalog
| Rule | Summary | Quick fix | Scope |
|---|---|---|---|
avoid_conditions_with_boolean_literals |
Flags boolean literals in conditions and boolean operators. | no | Dart / Flutter |
avoid_dynamic |
Flags dynamic declarations and type arguments. |
yes | Dart / Flutter |
avoid_empty_spread |
Flags spreads of empty collection literals. | no | Dart / Flutter |
avoid_explicit_type_declaration |
Flags redundant local type annotations with stable inference. | no | Dart / Flutter |
avoid_explicit_pattern_field_name |
Flags redundant explicit field names in patterns. | no | Dart / Flutter |
avoid_inferrable_type_arguments |
Flags explicit type arguments that inference can recover. | no | Dart / Flutter |
avoid_instantiating_in_value_provider |
Flags new instances passed directly to Provider.value. |
no | Flutter / provider-like patterns |
avoid_late_keyword |
Flags late variables and fields. |
no | Dart / Flutter |
avoid_non_null_assertion |
Flags postfix ! null assertions. |
no | Dart / Flutter |
avoid_non_nullable_provider_context_access |
Flags non-nullable context.read/watch/select provider generics. |
yes | Flutter / provider-like patterns |
avoid_print_usage |
Flags print and debugPrint calls. |
yes | Dart / Flutter |
avoid_substring |
Flags direct String.substring() usage. |
no | Dart / Flutter |
avoid_type_casts |
Flags as casts in local code. |
no | Dart / Flutter |
avoid_unnecessary_overrides |
Flags trivial overrides that only forward to super. |
no | Dart / Flutter |
avoid_unused_assignment |
Flags overwritten local writes with no read in between. | no | Dart / Flutter |
avoid_unused_generics |
Flags unused generic type parameters in local declarations. | no | Dart / Flutter |
avoid_unused_instances |
Flags discarded instance creation expressions. | no | Dart / Flutter |
avoid_unused_parameters |
Flags unused parameters in bounded private and local scopes. | no | Dart / Flutter |
avoid_unsafe_collection_methods |
Flags unsafe indexed, getter, pattern, and *Where collection access. |
no | Dart / Flutter |
avoid_unsafe_reduce |
Flags reduce calls without a preceding emptiness guard. |
no | Dart / Flutter |
Example Projects
This repository includes two showcase projects:
example/dart_demo: demonstrates the non-Flutter rules.example/flutter_demo: demonstrates the shared Dart / Flutter rules plus the provider-oriented Flutter rules in a runnable app.
To validate both examples from the repository root:
dart test test/example_validation_test.dart
Rule Reference
avoid_dynamic
Why:
Avoids erasing type information with dynamic.
DON'T:
dynamic value = fetchValue();
List<dynamic> values = [];
dynamic parse(dynamic input) => input;
DO:
Object? value = fetchValue();
List<Object?> values = [];
Object? parse(Object? input) => input;
Quick fix:
Replaces dynamic with Object?.
avoid_empty_spread
Why: Empty spreads add noise without changing the resulting collection literal.
DON'T:
final values = [...[]];
final uniqueValues = {...<int>{}};
final entries = {...<String, int>{}};
DO:
final values = <Object?>[];
final uniqueValues = <int>{1, 2, 3};
final entries = <String, int>{'answer': 42};
Quick fix: None.
avoid_explicit_type_declaration
Why: Explicit local variable types are redundant when a stable initializer already spells out the same readable type.
DON'T:
String name = 'mitch';
List<int> values = <int>[1, 2, 3];
DO:
final name = 'mitch';
final values = <int>[1, 2, 3];
Quick fix: None.
avoid_explicit_pattern_field_name
Why: Pattern shorthand keeps destructuring fields smaller when the local variable already matches the field name.
DON'T:
if (value case Box(width: var width)) {
print(width);
}
DO:
if (value case Box(:var width)) {
print(width);
}
Quick fix: None.
avoid_conditions_with_boolean_literals
Why: Boolean literals inside conditions usually mean dead or redundant logic.
DON'T:
if (true) {
run();
}
while (enabled && true) {
tick();
}
DO:
run();
while (enabled) {
tick();
}
Quick fix: None.
avoid_inferrable_type_arguments
Why: Explicit type arguments add noise when local arguments or literal elements already force the same inferred type.
DON'T:
final value = identity<int>(1);
final numbers = <int>[1, 2, 3];
DO:
final value = identity(1);
final numbers = [1, 2, 3];
Quick fix: None.
avoid_instantiating_in_value_provider
Why:
Provider.value and related constructors should reuse an existing instance.
DON'T:
Provider<MyService>.value(value: MyService());
ChangeNotifierProvider<MyNotifier>.value(value: MyNotifier());
DO:
final service = MyService();
Provider<MyService>.value(value: service);
final notifier = MyNotifier();
ChangeNotifierProvider<MyNotifier>.value(value: notifier);
Quick fix: None.
avoid_late_keyword
Why:
late shifts initialization errors to runtime.
DON'T:
late String token = '';
class Session {
late int retries = 0;
}
DO:
String token = '';
class Session {
int retries = 0;
}
Quick fix: None.
avoid_non_null_assertion
Why: Postfix null assertions can throw at runtime and hide missing null handling.
DON'T:
final name = user.name!;
DO:
final name = user.name ?? 'unknown';
Quick fix: None.
avoid_non_nullable_provider_context_access
Why:
Using non-nullable provider generics with context.read/watch/select hides
missing provider wiring until runtime.
DON'T:
context.read<MyService>();
context.watch<MyService>();
context.select<MyService, int>((service) => service.count);
DO:
context.read<MyService?>();
context.watch<MyService?>();
context.select<MyService?, int>((service) => service?.count ?? 0);
Quick fix: Makes the provider generic type nullable when a safe transformation is available.
avoid_print_usage
Why:
print() and debugPrint() bypass structured logging.
DON'T:
print('loading');
debugPrint('request failed');
DO:
Logger().info('loading');
Logger().info('request failed');
Quick fix:
Replaces print and debugPrint with Logger().info(...).
avoid_substring
Why:
String.substring() is often brittle and obscures intent.
DON'T:
final code = value.substring(0, 3);
DO:
final isPrefix = value.startsWith('ABC');
final updated = value.replaceRange(0, 3, 'ABC');
Quick fix: None.
avoid_type_casts
Why:
as casts defer type safety to runtime and often hide missing local checks.
DON'T:
final user = value as User;
DO:
if (value is User) {
return value;
}
return null;
Quick fix: None.
avoid_unnecessary_overrides
Why:
Trivial overrides duplicate inherited behavior without adding local value.
This plugin only reports method overrides whose entire body is one super
method call forwarding the same parameters in the same shape.
DON'T:
class Base {
String format(String value) => value;
}
class Derived extends Base {
@override
String format(String value) => super.format(value);
}
DO:
class Base {
String format(String value) => value;
}
class Derived extends Base {}
Quick fix: None.
avoid_unused_assignment
Why:
Overwritten local writes hide dead work. This rule intentionally stays inside
straight-line block segments and skips branches, loops, switch, try, and
closure-captured variables.
DON'T:
void update() {
var retryCount = 0;
retryCount = 1;
retryCount = 2;
print(retryCount);
}
DO:
void update() {
var retryCount = 2;
print(retryCount);
}
Quick fix: None.
avoid_unused_generics
Why: Unused generic parameters make local APIs look more general than they are.
DON'T:
void parse<T, Format>(T value) {
print(value);
}
DO:
void parse<T>(T value) {
print(value);
}
Quick fix: None.
avoid_unused_instances
Why: Creating an object and discarding it immediately usually means dead work or a missing use-site. This rule only reports bare instance creation statements.
DON'T:
void warmUp() {
Session();
}
DO:
void warmUp() {
final session = Session();
consume(session);
}
Quick fix: None.
avoid_unused_parameters
Why: Unused parameters add noise to local APIs and hide dead signature surface. This plugin intentionally limits the rule to private methods, private top-level functions, and local functions when no override or callback contract is detected in the current unit.
DON'T:
void _parse(String source, int unusedRetryCount) {
if (source.isEmpty) return;
}
DO:
void _parse(String source) {
if (source.isEmpty) return;
}
Quick fix: None.
avoid_unsafe_collection_methods
Why:
Unsafe collection access can throw RangeError or StateError at runtime.
This rule covers unsafe getters, iterable [], object-pattern getter access,
and *Where calls without orElse. Map[] stays allowed, reduce stays in
avoid_unsafe_reduce, and flow-sensitive safe-index proofs are intentionally
out of scope.
DON'T:
final first = values.first;
final second = values[1];
final selected = values.firstWhere((item) => item.isActive);
if (value case List(:final first)) {
print(first);
}
DO:
final first = values.firstOrNull;
final second = values.elementAtOrNull(1);
final selected = values.firstWhereOrNull((item) => item.isActive);
if (scores case List(:final firstOrNull)) {
print(firstOrNull);
}
Quick fix: None.
avoid_unsafe_reduce
Why:
reduce throws on empty iterables unless local code proves the iterable is not
empty first.
DON'T:
int sum(Iterable<int> values) {
return values.reduce((left, right) => left + right);
}
DO:
int sum(Iterable<int> values) {
if (values.isEmpty) {
return 0;
}
return values.reduce((left, right) => left + right);
}
Quick fix: None.
Per-rule Docs
Each rule also has a dedicated markdown page under rules/ with rationale, config snippets, and focused examples.
Development
Common commands from the repository root:
dart pub get
dart format --output=none --set-exit-if-changed .
dart analyze
dart test
dart test test/example_validation_test.dart
dart test test/repository_conventions_test.dart
dart run bin/benchmark_analyze.dart --iterations=5 --target=.
Publishing
This repository includes:
- a validation workflow at
.github/workflows/validate.yml - a release workflow at
.github/workflows/publish.yml
Expected release flow:
- Update
pubspec.yamlandCHANGELOG.md. - Merge to
main. - Push a tag like
v0.2.1. - Let GitHub Actions publish the package.
One-time pub.flutter-io.cn setup is still required before the automated workflow can publish:
- Publish the first version manually.
- Configure GitHub Actions publishing for this package on pub.flutter-io.cn.
- Set the tag pattern to
v{{version}}. - Optionally require the
pub.flutter-io.cnGitHub environment for manual approval.
Changelog
See CHANGELOG.md.
Libraries
- main
- mitch_analyzer_plugin
- Public library for the Mitch analyzer plugin package.