Irbisa · Cheatsheet

Lints, Analyzer & Code Review

analysis_options, lint rulesets, dart fix, dart format, custom_lint, annotations, PR review

Middle Developer · 21 questions

  1. 01

    What is analysis_options.yaml, and what can you configure in it?

    analysis_options.yaml sits at the package root and configures the Dart analyzer: which ruleset you inherit, which extra lints run, how severe each diagnostic is, which files are skipped, and the formatter's page width.

    include: package:flutter_lints/flutter.yaml analyzer:  exclude:    - "**/*.g.dart"    - "**/*.freezed.dart"…
  2. 02

    What is the difference between dart analyze, dart fix and dart format?

    dart analyze reports problems without touching the code, dart fix applies the automated fixes the analyzer knows for those problems, and dart format rewrites whitespace and line breaks into the one official style.

    dart analyze                  # report only; infos do not faildart analyze --fatal-infos    # what CI should runflutter analyze               # infos are already fatal here dart fix --dry-run            # preview automated fixes, grouped by ruledart fix --apply --code=prefer_const_constructors…
  3. 03

    With flutter_lints enabled, what does flutter analyze report for this widget, and how do you fix each issue?

    class Greeting extends StatelessWidget {  Greeting({this.name});  final String? name;   @override  Widget build(BuildContext context) {    print('build $name');    return Column(children: [      Container(height: 16, child: const Text('Hi')),      Container(child: Text(name ?? 'guest')),      const Icon(Icons.star, color: Color(0xFF00)),    ]);  }}

    Six infos: a missing key, a constructor that could be const, a print, a Container that is only a sized box, a Container that does nothing, and a colour with too few hex digits.

  4. 04

    What is the difference between lints, flutter_lints and very_good_analysis, and how do you choose one?

    All three are packages with a ready-made ruleset you include: lints is the Dart team's baseline, flutter_lints adds Flutter rules on top and comes with flutter create, and very_good_analysis is a much stricter third-party set.

    # Flutter app default, generated by flutter createinclude: package:flutter_lints/flutter.yaml # Pure Dart package:   include: package:lints/recommended.yaml# Strict third-party:  include: package:very_good_analysis/analysis_options.yaml…
  5. 05

    How do you change a lint's severity, and which rules are worth promoting to error?

    Under analyzer: errors: you map any diagnostic code to error, warning, info or ignore.

    include: package:flutter_lints/flutter.yaml analyzer:  errors:    use_build_context_synchronously: error   # crash after an await    unawaited_futures: error                 # error with no catch to reach…
  6. 06

    Which Flutter lints catch real bugs rather than style, and what does each one prevent?

    A small group: lints that catch comparisons that are always false, broken equality, futures nobody awaits, BuildContext used after the widget is gone, and secrets printed to the device log.

    // Always false, and it compilesfinal ids = <int>[1, 2, 3];const raw = '2';final found = ids.contains(raw); // collection_methods_unrelated_typefinal same = raw == 2; // unrelated_type_equality_checksfinal fixed = ids.contains(int.parse(raw)); // right…
  7. 07

    Find the bug: on a 404 the profile screen stays on the spinner and the console shows an unhandled error.

    enum ApiFailure { notFound, server } Future<User> fetchUser(http.Client client, Uri url) async {  final res = await client.get(url);  if (res.statusCode == 404) throw ApiFailure.notFound;  if (res.statusCode != 200) throw ApiFailure.server;  return User.fromJson(jsonDecode(res.body) as Map<String, dynamic>);} Future<void> loadProfile(Uri url) async {  try {    profile.value = ProfileLoaded(await fetchUser(client, url));  } on Exception catch (e) {    profile.value = ProfileError(e);  }}

    ApiFailure is a plain enum, not an Exception, so on Exception catch does not match it.

  8. 08

    What is the difference between // ignore:, // ignore_for_file: and analyzer: exclude:, and which do you accept in a PR?

    // ignore: silences one rule on one line, // ignore_for_file: silences it for the whole file including code written later, and exclude: drops files from analysis entirely, errors included.

    // wrong in hand-written code: every lint in this file, including future ones// ignore_for_file: type=lint // right: one rule, one line, and a reason the reviewer can check// ignore: avoid_dynamic_calls -- payload shape is validated in decode()String title(dynamic json) => json['title'] as String;…
  9. 09

    What do @override, @immutable, @protected, @visibleForTesting and @mustCallSuper do, and what actually enforces them?

    They are metadata the analyzer reads: @override comes from dart:core, the others from package:meta, which Flutter's foundation re-exports.

    import 'package:flutter/foundation.dart'; // re-exports package:meta annotations @immutableclass Filter {  const Filter(this.tags);  final List<String> tags; // passes must_be_immutable, yet tags.add() works…
  10. 10

    Find the bug: DevTools shows one more ChatViewModel in memory each time the user opens and closes a chat.

    abstract class ViewModel {  final _subs = <StreamSubscription<Object?>>[];  void watch<T>(Stream<T> s, void Function(T) f) => _subs.add(s.listen(f));  Future<void> dispose() => Future.wait([for (final s in _subs) s.cancel()]);} class ChatViewModel extends ViewModel {  ChatViewModel(ChatRepository repo) {    watch(repo.messages(), messages.add);  }  final messages = <Message>[];  Timer? _typing;   @override  Future<void> dispose() async => _typing?.cancel();}

    ChatViewModel.dispose overrides the base method without calling super.dispose(), so the subscriptions made by watch are never cancelled.

  11. 11

    How do you rename a public method in a package other apps depend on without breaking their builds?

    Add the new name, keep the old one as a forwarding member marked @Deprecated('Use X instead'), and delete it only in the next major version.

    class UserApi {  // The new name holds the only implementation.  User parseUser(String body) => User.fromJson(jsonDecode(body) as Map<String, dynamic>);   @Deprecated('Use parseUser instead. Will be removed in 3.0.0.')  User decodeUser(String body) => parseUser(body); // forwards, never copies…
  12. 12

    How do you keep formatting consistent across a team, and what changed in dart format with Dart 3.7?

    Let the tool decide: everyone runs dart format, CI fails on any diff, and nobody argues about layout in review.

    # analysis_options.yaml, read by the IDE, the CLI and CI:#   formatter:#     page_width: 100#     trailing_commas: preserve   # Dart 3.8+: keep commas as written # CI gate: exit 1 if any file would change…
  13. 13

    What belongs in a pre-commit hook and what belongs in CI, and why can't the hook be your quality gate?

    CI is the gate and the hook is a convenience: hooks live in .git/hooks, which is never cloned, and git commit --no-verify skips them by design.

    # lefthook.yml: committed; `lefthook install` wires .git/hooks after a clonepre-commit:  commands:    format:      glob: "*.dart"      run: dart format {staged_files}…
  14. 14

    dart analyze fails in CI on code your IDE shows as clean. It is the same analyzer, so what differs?

    The tool is the same; its inputs differ.

  15. 15

    How do you find unused code, dependencies and assets in a Flutter project, and what can no tool detect?

    The analyzer only proves things inside one library: unused imports, private members and dead branches.

  16. 16

    This State passes the analyzer and its widget test. What would you flag in code review?

    class _OrderPageState extends State<OrderPage> {  final _note = TextEditingController();  Order? _order;  @override  void initState() {    super.initState();    _load();  }  Future<void> _load() async {    final order = await OrderRepository().fetch(widget.orderId);    setState(() => _order = order);  }  @override  Widget build(BuildContext context) =>      Column(children: [Text(_order?.title ?? 'Loading'), TextField(controller: _note)]);}

    Five problems no lint catches: the controller is never disposed, a failed fetch is never handled, setState can run after the widget is gone, the repository is built inside the widget, and a new orderId is ignored.

  17. 17

    Half of a PR was written by an AI assistant. What do you check before approving it, beyond what flutter analyze and the tests catch?

    Hold it to the same bar as any PR, aimed at where generated code goes wrong: unvetted dependencies, code that ignores the project's conventions, silenced errors and security checks, and tests that pass whatever the code does.

    // All three pass `flutter analyze` with flutter_lints; none should be approved. HttpClient trustingClient() => HttpClient()  ..badCertificateCallback = (cert, host, port) => true; // accepts any certificate Future<void> onSave(OrderRepository repo, Order order) async {…
  18. 18

    How do you run code review on a Flutter team: what do you look for beyond the analyzer, and how do you phrase comments?

    Let tools own formatting and lints, and spend human attention on what they cannot see: state and lifecycle, failure paths, layer boundaries and tests.

  19. 19

    How do you turn a recurring review comment into a custom lint rule, and how do you make it run in CI?

    Write an analyzer plugin that walks the resolved AST and reports a diagnostic.

    // Rules package, built on custom_lint_builder.// CI must run it separately: dart run custom_lintPluginBase createPlugin() => _HouseLints(); class _HouseLints extends PluginBase {  @override…
  20. 20

    A reviewer wants CI to fail when a file passes 300 lines or a function's cyclomatic complexity passes 10. Would you agree?

    Use metrics as a signal on the pull request, not as a hard gate: a number tells you which code is worth reading, not whether it is good, and a hard threshold gets gamed.

  21. 21

    Enabling a strict ruleset in a 200,000-line app reports 4,000 issues. How do you land it without one unreviewable diff?

    Turn it on in stages: apply dart fix first, keep the remaining rules visible but non-blocking for old code and blocking for code the PR touches, and burn the backlog down rule by rule in small PRs.

    # 1. The mechanical share, in its own commit, with no manual editsdart fix --dry-rundart fix --apply                      # or --code=<rule> to keep it reviewablegit commit -am "chore: dart fix --apply (machine-generated)" # 2. Rank what is left; in --format=machine field 3 is the diagnostic code…