Watch the video (15 min)
On this page26 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Two Tools, Two Jobs
  5. 2. dart format — Before and After
  6. 3. Formatting a Whole Project
  7. 4. Page Width
  8. 5. Trailing Commas in the Tall Style
  9. 6. Checking Formatting in CI
  10. 7. dart analyze — Reading the Output
  11. 8. Errors, Warnings and Infos
  12. 9. Fixing Each Issue
  13. 10. analysis_options.yaml
  14. 11. Strict Language Modes
  15. 12. Automatic Fixes with dart fix
  16. 13. // ignore: Comments
  17. 14. A Real-World Example
  18. Code Examples
  19. Common Beginner Mistakes
  20. Best Practices
  21. Mini Challenge
  22. Practice Questions
  23. Practice Answers
  24. Quick Revision
  25. Key Takeaways
  26. What's Next?

Lecture 114 — Dart Formatter & Analyzer

Category19 — Dart Developer Tools
Lecture114 of 140
Estimated video length15 min
TargetDart 3.12.2
LevelAdvanced

Two programmers, two layouts, and bugs that only show up when the program crashes. dart format gives every file one layout, and dart analyze finds mistakes before you run.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every output and error message in this lecture is real. Each one was produced by running the commands shown inside a project called class_report. Only the timings, long folder paths (shortened to <project>) and crash stack traces (trimmed) will differ on your machine.


Prerequisites

Students should already understand, from earlier lectures:

  • creating a project with dart create and running it with dart run (Lectures 002 and 113)
  • pubspec.yaml, dev_dependencies and the lints package (Lecture 104)
  • jsonDecode and why its result is dynamic (Lecture 108)
  • dynamic, as casts, and generic types such as List<int> (Lectures 011 and 072)

Connection With Previous Lectures

In Lecture 113 we toured the dart command. Two of its commands keep coming up: Lecture 003 promised to explain dart format, and since Lecture 014 we have quoted dart analyze warnings without explaining them. Meanwhile, this kind of program passes every check — then crashes:

output
Unhandled exception:
type 'String' is not a subtype of type 'double'

The JSON held "1500.50" — text, not a number — and nothing warned us. Dart can catch this before the program runs, if we ask it to.


Learning Objectives

By the end of this lecture you will be able to:

  • Format files and whole projects with dart format
  • Explain how page width and trailing commas shape the formatter's layout
  • Check formatting in CI with --output=none --set-exit-if-changed
  • Read every part of a dart analyze line and fix errors, warnings and infos
  • Configure analysis_options.yaml with package:lints and the strict language modes
  • Use a strict mode to turn a runtime crash into an analysis error
  • Preview and apply automatic fixes with dart fix
  • Use // ignore: comments only when they are justified

1. Two Tools, Two Jobs

The formatter changes how code looks — spaces, line breaks, indentation — never what it does. The analyzer reads what code means and reports mistakes without running it.

Analogy — the formatter is a typist who retypes your notes neatly; the analyzer is a teacher who reads them and marks mistakes in red. You want both, and they never argue.


2. dart format — Before and After

Ali wrote this in a hurry:

dart
void main(){
var marks={'Ali':72,'Sara':95,'Zayan':58};
  for(var name in marks.keys){
      if(marks[name]!>=60){print('$name passed');}
    else{print('$name failed');}
  }
}
output
$ dart format bin/messy.dart
Formatted bin/messy.dart
Formatted 1 file (1 changed) in 0.00 seconds.
dart
void main() {
  var marks = {'Ali': 72, 'Sara': 95, 'Zayan': 58};
  for (var name in marks.keys) {
    if (marks[name]! >= 60) {
      print('$name passed');
    } else {
      print('$name failed');
    }
  }
}

Two-space indentation, spaces around operators, one statement per line — the same layout in every Dart project.


3. Formatting a Whole Project

Pass . to format every Dart file under the current folder. Run it again, and nothing changes:

output
$ dart format .
Formatted bin/greet.dart
Formatted 2 files (1 changed) in 0.00 seconds.
$ dart format .
Formatted 2 files (0 changed) in 0.00 seconds.

Add -o show to print the formatted code instead of saving it.

The formatter only works on code that parses. With a missing ), it refuses and exits with code 65:

output
$ dart format bin/broken.dart
Could not format because the source could not be parsed:

line 3, column 1 of bin/broken.dart: Expected to find ')'.
  ╷
3 │ }
  │ ^
  ╵

4. Page Width

The formatter tries to keep lines within 80 characters. When a line is longer, it splits it — here, the parameter list:

dart
int calculateFee({
  required int tuition,
  required int transport,
  required int books,
}) => tuition + transport + books;

With dart format --page-width 100, the same code fits on wider lines:

dart
int calculateFee({required int tuition, required int transport, required int books}) =>
    tuition + transport + books;

Rather than typing the flag, put the width in analysis_options.yaml (Step 10), so the team and the editor share it:

yaml
formatter:
  page_width: 100

5. Trailing Commas in the Tall Style

Since Dart 3.7 the formatter uses the tall style: it decides whether to split, then adds or removes the trailing comma to match. Your own commas no longer force a layout:

dart
void main() {
  printReport('Ali', 72, 'B',);
  printReport('Zayan, the youngest student in the class', 58, 'C - needs to practise more');
}
dart
void main() {
  printReport('Ali', 72, 'B');
  printReport(
    'Zayan, the youngest student in the class',
    58,
    'C - needs to practise more',
  );
}

The short call lost its comma; the long one was split and gained one. To make a trailing comma always force a split (the old behavior), set:

yaml
formatter:
  trailing_commas: preserve

With preserve, printReport('Ali', 72, 'B',); stays split, one argument per line.


6. Checking Formatting in CI

A CI server checks every change to a project automatically. It must not rewrite files, only answer "is this formatted?":

output
$ dart format --output=none --set-exit-if-changed .
Changed bin/a.dart
Changed bin/b.dart
Formatted 3 files (2 changed) in 0.00 seconds.
$ echo $?
1

--output=none discards the result, so no file changes. --set-exit-if-changed exits with 1 when anything would change, failing the check; a formatted project exits with 0.


7. dart analyze — Reading the Output

Sara runs the analyzer on her project:

dart
import 'dart:math';

void main() {
  var Total = 0;
  var marks = [72, 95, 58];
  int count = 'three';
  for (var mark in marks) {
    Total = Total + mark;
  }
  print('Average: ${Total / marks.length}');
}
output
$ dart analyze
Analyzing class_report...

  error - bin/report.dart:6:15 - A value of type 'String' can't be assigned to a variable of type 'int'. Try changing the type of the variable, or casting the right-hand type to 'int'. - invalid_assignment
warning - bin/report.dart:1:8 - Unused import: 'dart:math'. Try removing the import directive. - unused_import
warning - bin/report.dart:6:7 - The value of the local variable 'count' isn't used. Try removing the variable or using it. - unused_local_variable
   info - bin/report.dart:4:7 - The variable name 'Total' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - non_constant_identifier_names

4 issues found.

Every line has five parts: the severity, the place (file:line:column), the problem, a suggestion starting with "Try", and the diagnostic code, which you can look up on dart.dev.


8. Errors, Warnings and Infos

SeverityMeaningRuns?Exit code
errorThe code is invalidNo3
warningValid, but almost certainly a mistakeYes2
infoStyle or a better way to write itYes0

The exit code reports the worst severity found. Warnings fail by default; add --fatal-infos to make infos fail too (exit code 1):

output
$ dart analyze
Analyzing class_report...

   info - bin/report.dart:2:7 - The variable name 'Total' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - non_constant_identifier_names

1 issue found.
$ echo $?
0

dart analyze --fatal-infos prints the same report, but echo $? then shows 1.


9. Fixing Each Issue

Fix from the top: errors first, then warnings, then infos.

  • invalid_assignment — count was never needed, so delete the line.
  • unused_import — nothing uses dart:math; delete the import.
  • unused_local_variable — gone with the count line.
  • non_constant_identifier_names — rename Total to total.
dart
void main() {
  var total = 0;
  var marks = [72, 95, 58];
  for (var mark in marks) {
    total = total + mark;
  }
  print('Average: ${total / marks.length}');
}
output
$ dart analyze
Analyzing class_report...
No issues found!

10. analysis_options.yaml

dart create puts this file at the root of every project. Its first line pulls in the recommended rules from the lints package you met in Lecture 104:

yaml
include: package:lints/recommended.yaml

analyzer:
  exclude:
    - lib/generated/**
  errors:
    unused_local_variable: error

exclude skips generated code. errors changes a diagnostic's severity (here, to error) or turns it off with ignore. Choosing and adding individual lint rules is the topic of Lecture 117 — Linting & Effective Dart.


11. Strict Language Modes

Three settings make the analyzer stricter about types. They go under analyzer: language::

yaml
analyzer:
  language:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true
ModeReports
strict-castsA dynamic value used where a specific type is expected, without as
strict-inferencePlaces where Dart cannot infer a type and silently picks dynamic
strict-raw-typesA generic type such as List written without <...>

Here is the bug from the Connection section:

dart
import 'dart:convert';

void main() {
  var account = jsonDecode('{"owner": "Sara", "balance": "1500.50"}');
  double balance = account['balance'];
  print('${account['owner']}: ${balance + 100}');
}

With default settings: No issues found! — then the crash. With strict-casts: true:

output
  error - bin/bank.dart:5:20 - A value of type 'dynamic' can't be assigned to a variable of type 'double'. Try changing the type of the variable, or casting the right-hand type to 'double'. - invalid_assignment

The analyzer forces us to say what we expect, which makes us notice the value is text:

dart
var balance = double.parse(account['balance'] as String);
output
Sara: 1600.5

12. Automatic Fixes with dart fix

Many diagnostics have a known fix. dart fix --dry-run lists them without changing anything:

dart
class Student {
  final String name;
  final int marks;

  Student(this.name, this.marks);

  String get summary => '${this.name}: ${marks}';
}

void main() {
  var students = new List<Student>.empty(growable: true);
  students.add(Student('Ali', 72));
  students.add(Student('Sara', 95));
  var lookup = new Map<String, int>();
  for (var s in students) {
    lookup[s.name] = s.marks;
    print('${s.summary}');
  }
  print(lookup);
}
output
$ dart fix --dry-run
Computing fixes in class_report (dry run)...

6 proposed fixes in 1 file.

bin/marks.dart
  prefer_collection_literals - 1 fix
  unnecessary_brace_in_string_interps - 2 fixes
  unnecessary_new - 1 fix
  unnecessary_string_interpolations - 1 fix
  unnecessary_this - 1 fix

To fix an individual diagnostic, run one of:
  dart fix --apply --code=prefer_collection_literals
  ...
To fix all diagnostics, run:
  dart fix --apply
output
$ dart fix --apply
Computing fixes in class_report...
Applying fixes...
...
6 fixes made in 1 file.

The getter now reads '$name: $marks' and the map is <String, int>{}. Errors are never fixed for you — they need a human decision.


13. // ignore: Comments

An // ignore: comment silences one diagnostic on the next line (or the same line). // ignore_for_file: silences it in the whole file:

dart
void main() {
  // The school database names this column student_ID.
  // ignore: non_constant_identifier_names
  var student_ID = 'S-101';
  print(student_ID);
}

It is justified when the cause is outside your control — a name fixed by another system, a generated file you must not edit — and the reason is written above it. Hiding a warning you don't understand is not justified.


14. A Real-World Example

Ali checks his project the way a CI server would, before sharing it:

dart
import 'dart:convert';

void main(){
  var text = '{"Ali": 72, "Sara": 95, "Zayan": 58}';
  var data = jsonDecode(text) as Map<String, dynamic>;
  var passed = new List<String>.empty(growable: true);
  for (var entry in data.entries) {
    int mark = entry.value;
    if (mark >= 60) passed.add('${entry.key}');
  }
  print('Passed: ${passed.join(', ')}');
}
output
$ dart format --output=none --set-exit-if-changed . && dart analyze --fatal-infos
Changed bin/class_report.dart
Formatted 1 file (1 changed) in 0.00 seconds.

The format check failed, so && stopped. He formats, then analyzes with strict modes on:

output
$ dart format .
$ dart analyze --fatal-infos
  error - bin/class_report.dart:8:16 - A value of type 'dynamic' can't be assigned to a variable of type 'int'. ... - invalid_assignment
   info - bin/class_report.dart:6:16 - Unnecessary 'new' keyword. ... - unnecessary_new
   info - bin/class_report.dart:9:32 - Unnecessary use of string interpolation. ... - unnecessary_string_interpolations

dart fix --apply removes both infos. The error is his to fix: var mark = entry.value as int;.

output
$ dart format --output=none --set-exit-if-changed . && dart analyze --fatal-infos
Formatted 1 file (0 changed) in 0.00 seconds.
Analyzing class_report...
No issues found!
$ dart run bin/class_report.dart
Passed: Ali, Sara

Code Examples

All examples run inside class_report, with analysis_options.yaml containing only the include line unless the example says otherwise.

Example 1 — Showing Without Saving

dart
void main()  {  var name='Sara';print( 'Hello, $name' ) ;}
output
$ dart format -o show bin/greet.dart
void main() {
  var name = 'Sara';
  print('Hello, $name');
}
Formatted 1 file (1 changed) in 0.00 seconds.

Example 2 — A Long List Splits

dart
void main() {
  var marks = [72, 95, 58,];
  var students = ['Ali', 'Sara', 'Zayan', 'Hina', 'Omar', 'Fatima', 'Bilal', 'Ayesha'];
  print(marks);
  print(students);
}

After dart format:

dart
void main() {
  var marks = [72, 95, 58];
  var students = [
    'Ali',
    'Sara',
    'Zayan',
    'Hina',
    'Omar',
    'Fatima',
    'Bilal',
    'Ayesha',
  ];
  print(marks);
  print(students);
}

Example 3 — Preserving Trailing Commas

Same input, dart format --trailing-commas preserve. Now marks splits too:

dart
  var marks = [
    72,
    95,
    58,
  ];

Example 4 — Analyzing One File

output
$ dart analyze bin/shop.dart
Analyzing shop.dart...

warning - shop.dart:3:7 - The value of the local variable 'discountRate' isn't used. Try removing the variable or using it. - unused_local_variable

1 issue found.

Given one file, the analyzer shows paths relative to it.

Example 5 — Fixing One Code at a Time

dart
void main() {
  var cart = new Map<String, int>();
  cart['Pen'] = 50;
  cart['Bag'] = 1500;
  print('Items: ${cart.length}');
}
output
$ dart fix --apply --code=unnecessary_new
...
bin/cart.dart
  unnecessary_new - 1 fix

1 fix made in 1 file.

The line becomes var cart = Map<String, int>();. A second dart fix --apply then applies prefer_collection_literals: var cart = <String, int>{};.

Example 6 — Nothing Left to Fix

output
$ dart fix --dry-run
Computing fixes in class_report (dry run)...
Nothing to fix!

Example 7 — Student Marks

dart
void main(){
var marks={'Ali':72,'Sara':95,'Zayan':58};
var total=0;
for(var mark in marks.values){total+=mark;}
var average=total/marks.length;
print('Average: ${average.toStringAsFixed(1)}');
if(average>=70)print('Class result: good');
}

After dart format, dart analyze and dart run:

dart
void main() {
  var marks = {'Ali': 72, 'Sara': 95, 'Zayan': 58};
  var total = 0;
  for (var mark in marks.values) {
    total += mark;
  }
  var average = total / marks.length;
  print('Average: ${average.toStringAsFixed(1)}');
  if (average >= 70) print('Class result: good');
}
output
No issues found!
Average: 75.0
Class result: good

Example 8 — Shopping Total

dart
void main() {
  var prices = [500, 50, 1500];
  var discountRate = 0.1;
  var total = 0;
  for (var price in prices) {
    total += price;
  }
  print('Total: $total');
}

It prints Total: 2050. Example 4's warning found a real bug — the discount was never applied:

dart
  print('Total: ${total * (1 - discountRate)}');
output
Total: 1845.0

Example 9 — Bank Balance with strict-casts

With strict-casts: true, this fails analysis instead of crashing at runtime:

dart
import 'dart:convert';

void main() {
  var account = jsonDecode('{"owner": "Sara", "balance": "1500.50"}');
  var balance = double.parse(account['balance'] as String);
  print('${account['owner']}: ${balance + 100}');
}
output
Sara: 1600.5

Example 10 — Temperatures with strict-inference

dart
void main() {
  var readings = [];
  readings.add(29.5);
  readings.add('31.0');
  readings.add(30.5);
  var sum = 0.0;
  for (var t in readings) {
    sum += t;
  }
  print('Average: ${sum / readings.length}');
}

By default: No issues found!, then a crash — type 'String' is not a subtype of type 'num'. With strict-inference: true:

output
warning - bin/temps.dart:2:18 - The type argument(s) of 'List' can't be inferred. Use explicit type argument(s) for 'List'. - inference_failure_on_collection_literal

With var readings = <double>[];, adding '31.0' becomes an error. Using 31.0 prints Average: 30.333333333333332.


Common Beginner Mistakes

1. Ignoring an error

dart
void main() {
  // ignore: invalid_assignment
  // int marks = 'seventy'; // ✗
}

dart analyze goes quiet, but dart run still refuses:

output
bin/marks.dart:3:15: Error: A value of type 'String' can't be assigned to a variable of type 'int'.

Ignore comments only silence the analyzer. Fix errors instead.

2. Putting language: at the top level

yaml
include: package:lints/recommended.yaml

language: # ✗ must be inside analyzer:
  strict-casts: true

No message — the setting is silently unused, and Step 11's bug slips through. Indent it under analyzer:. A misspelling like strict_casts does get a warning: The option 'strict_casts' isn't supported by 'language'.

3. Misspelling the include

output
warning - analysis_options.yaml:1:10 - The URI 'package:lint/recommended.yaml' included in '<project>/analysis_options.yaml' can't be found when analyzing '<project>'. - include_file_not_found

The package is lints, and it must be in dev_dependencies.

4. Forgetting to format after dart fix

Removing an import can leave an empty first line. Run dart format . after fixing.


Best Practices

  • Run dart format . before every commit; never format by hand.
  • Store page_width and trailing_commas in analysis_options.yaml, not in your memory.
  • Keep the project at "No issues found!" so a new issue stands out.
  • Turn on strict-casts, strict-inference and strict-raw-types in new projects.
  • In CI, use dart format --output=none --set-exit-if-changed . and dart analyze --fatal-infos.
  • Write an // ignore: only with a comment explaining why; never ignore an error.

Mini Challenge

Zayan's piggy-bank program works, but it is one long line with an unused import. Using only dart format, dart analyze and dart fix, clean it until the analyzer reports no issues.

dart
import 'dart:math';
void main(){var owner='Zayan';var balance=150;balance=balance+50;balance=balance-30;print('Owner: ${owner}');print('Balance: $balance');}

Try it yourself before opening the solution.

Show solution
output
$ dart format .
$ dart analyze
warning - bin/piggy.dart:1:8 - Unused import: 'dart:math'. Try removing the import directive. - unused_import
   info - bin/piggy.dart:8:17 - Unnecessary braces in a string interpolation. Try removing the braces. - unnecessary_brace_in_string_interps

2 issues found.
$ dart fix --apply
bin/piggy.dart
  unnecessary_brace_in_string_interps - 1 fix
  unused_import - 1 fix

2 fixes made in 1 file.
$ dart format .
$ dart analyze
No issues found!
dart
void main() {
  var owner = 'Zayan';
  var balance = 150;
  balance = balance + 50;
  balance = balance - 30;
  print('Owner: $owner');
  print('Balance: $balance');
}
output
Owner: Zayan
Balance: 170

dart format only changes layout, so it cannot remove the import; dart fix can. The second dart format removes the empty line the deleted import left behind.


Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Which command formats every Dart file in the current folder?

  2. Format this by hand the way dart format would, then run it: void main(){var city='Karachi';print(city);}

  3. Name the five parts of this line:

    output
    warning - bin/shop.dart:3:7 - The value of the local variable 'discountRate' isn't used. Try removing the variable or using it. - unused_local_variable

Application

  1. Write one command for CI that fails if any file needs formatting, without changing files.

  2. Write an analysis_options.yaml that includes the recommended lints, sets the page width to 100, and turns on strict-casts.

  3. dart analyze warns that bonus is unused. Fix the bug so it prints Total: 245:

    dart
    void main() {
      var marks = [80, 75, 85];
      var bonus = 5;
      var total = 0;
      for (var mark in marks) {
        total += mark;
      }
      print('Total: $total');
    }
  4. dart fix --dry-run lists unnecessary_this - 1 fix among others. Which command applies only that fix?

  5. In the default tall style, what does dart format turn print(add(1, 2,)); into?

Challenge

  1. With strict-raw-types: true, what does dart analyze report for List marks = [72, 95];, and how do you fix it?

  2. A teammate wrote this, and dart analyze says No issues found!. What happens on dart run, and how should it really be fixed so it prints 20?

    dart
    void main() {
      // ignore: invalid_assignment
      int age = '20';
      print(age);
    }

Practice Answers

Question 1 — Solution

output
$ dart format .
Formatted 2 files (0 changed) in 0.00 seconds.

Question 2 — Solution

dart
void main() {
  var city = 'Karachi';
  print(city);
}
output
Karachi

Question 3 — Solution

Severity warning; place bin/shop.dart, line 3, column 7; the problem ("...isn't used."); the suggestion ("Try removing..."); code unused_local_variable.

Question 4 — Solution

output
$ dart format --output=none --set-exit-if-changed .

It exits with 1 when a file would change, and 0 otherwise.

Question 5 — Solution

yaml
include: package:lints/recommended.yaml

analyzer:
  language:
    strict-casts: true

formatter:
  page_width: 100

Question 6 — Solution

dart
void main() {
  var marks = [80, 75, 85];
  var bonus = 5;
  var total = 0;
  for (var mark in marks) {
    total += mark;
  }
  print('Total: ${total + bonus}');
}
output
Total: 245

Question 7 — Solution

output
$ dart fix --apply --code=unnecessary_this

Question 8 — Solution

print(add(1, 2)); — the call fits on one line, so the formatter removes the trailing comma.

Question 9 — Solution

output
warning - bin/q.dart:2:3 - The generic type 'List<dynamic>' should have explicit type arguments but doesn't. Use explicit type arguments for 'List<dynamic>'. - strict_raw_type

Write the type argument: List<int> marks = [72, 95]; (or var marks = [72, 95];, which infers List<int>).

Question 10 — Solution

The comment silences only the analyzer; the compiler still rejects the program:

output
bin/age.dart:3:13: Error: A value of type 'String' can't be assigned to a variable of type 'int'.

Remove the comment and convert the text properly:

dart
void main() {
  var age = int.parse('20');
  print(age);
}
output
20

Quick Revision

  1. Which tool changes layout, and which reports mistakes?
  2. What is the default page width, and where do you change it for a whole project?
  3. In the tall style, when does the formatter add a trailing comma?
  4. What do --output=none and --set-exit-if-changed each do?
  5. Which severity stops a program from running?
  6. What does strict-casts report?
  7. Why can't // ignore: make invalid code run?

Key Takeaways

  • dart format fixes layout only; it never changes behavior and needs code that parses.
  • Lines aim for 80 characters; set page_width under formatter: to change it.
  • The tall style adds or removes trailing commas itself; trailing_commas: preserve keeps yours.
  • CI checks formatting with --output=none --set-exit-if-changed and analysis with --fatal-infos.
  • Analyzer lines show severity, place, problem, suggestion and code; fix errors first.
  • analysis_options.yaml includes package:lints and can exclude files or change severities.
  • strict-casts, strict-inference and strict-raw-types turn hidden dynamic bugs into analysis issues.
  • dart fix applies known fixes; // ignore: is for causes outside your control, never errors.

What's Next?

Lecture 115 — Debugging Dart. The analyzer catches mistakes in how code is written, but not in what it does: a wrong formula passes every check. When a program runs and gives the wrong answer, you need to stop it mid-run and look inside. Next we will use breakpoints, stepping and variable inspection to find bugs the analyzer can't see.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.