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.global applies 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:

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:

  1. Update pubspec.yaml and CHANGELOG.md.
  2. Merge to main.
  3. Push a tag like v0.2.1.
  4. Let GitHub Actions publish the package.

One-time pub.flutter-io.cn setup is still required before the automated workflow can publish:

  1. Publish the first version manually.
  2. Configure GitHub Actions publishing for this package on pub.flutter-io.cn.
  3. Set the tag pattern to v{{version}}.
  4. Optionally require the pub.flutter-io.cn GitHub environment for manual approval.

Changelog

See CHANGELOG.md.

Libraries

main
mitch_analyzer_plugin
Public library for the Mitch analyzer plugin package.