skip to content

How do you make a CI job fail when Dart code is not formatted, using dart format, and what does the command change by default?

level: juniorimportance: must knowfreq 60%

answer

  1. overwrites files unless told otherwise
  2. -o none reports without writing
  3. --set-exit-if-changed returns 1
  4. page width 80 by default
  5. run dart pub get first

basics

~20 s

Run dart format -o none --set-exit-if-changed . in CI: it writes nothing and exits with code 1 if any file would change. Without flags, dart format rewrites files in place, wrapping to 80 columns and managing trailing commas.

solid answer

~40 s

By default `dart format <paths>` **overwrites** each Dart file with the formatted version, recursing into directories. For CI you want a check, not a rewrite: `-o none` (short for `--output none`) makes it report which files would change without touching them, and `--set-exit-if-changed` makes the process exit with code `1` when any change is needed and `0` otherwise, which fails the job. `-o show` or `-o json` print the formatted content instead. The formatter wraps to a page width of 80 unless configured, and in the current tall style it adds and removes trailing commas itself. Because it picks the style from each file's language version, the CI job must run `dart pub get` first so the `package_config.json` it reads exists.

code

bash · 2 lines
bash
dart pub get
dart format -o none --set-exit-if-changed .

go deeper

for a junior

Recall the check command: -o none to avoid writing and --set-exit-if-changed to exit 1 on unformatted code.

for a middle

Explain the output modes, the exit codes and why the package configuration must exist before formatting.

for a senior

Keep formatter results identical across machines by pinning the SDK and putting page width in analysis_options.yaml.

for a principal

Treat formatting as a non-negotiable automated gate so review time goes to design, and plan how SDK formatter changes land.

## What dart format does `dart format` is the Dart SDK's code formatter, the same one IDEs call on save. You pass it files or directories; a directory is processed **recursively**. Its output is deterministic: there are very few options, by design, so a team never argues about style. By default it: - **overwrites** each file in place with the formatted version; - wraps lines to **80 characters** (the page width) unless configured otherwise; - in the tall style used for language version 3.7 and later, **adds trailing commas** to argument and parameter lists that split across lines and **removes** them from ones that fit on one line; - normalises whitespace and may move comments across a comma. ## Output modes The `--output` option, short `-o`, controls where results go: | Flag | Effect | |---|---| | (none) | rewrite files in place | | `-o show` | print formatted contents, leave files untouched | | `-o json` | print formatted contents as JSON | | `-o none` | print nothing but the list of files that would change | ## Failing a CI job The second piece is the exit code. With `--set-exit-if-changed`, the command exits with **1** if any file would change and **0** if all are already formatted. Combined with `-o none`, that is a pure check: 1. `dart pub get` - so the formatter can find `.dart_tool/package_config.json`. 2. `dart format -o none --set-exit-if-changed .` 3. The CI step fails on exit code 1, and the log lists the offending files. Step 1 matters more than it looks. Since Dart 3.7 the formatter chooses between the old short style and the tall style **per file**, based on that file's language version, and it learns the language version from the package configuration. The Dart 3.7 release notes explicitly tell teams with CI format checks to run `dart pub get` first; without it, the check may format against the wrong assumptions and disagree with developers' machines. ## Keeping local and CI results identical - Use the **same SDK version** locally and in CI. The formatter ships with the SDK, and style fixes land in SDK releases; Dart 3.13, for example, changed several tall-style details for code at language version 3.13, including separating imports into sections. - Put any **page width** in `analysis_options.yaml` under `formatter: page_width:` rather than a command-line flag, so every developer and the CI job read the same value. - Enable **format on save** in the editor so the check rarely fires. ## Scenario: a new command-line package After `dart create my_tool`, the generated project already follows the package layout. A minimal CI workflow for it runs `dart pub get`, `dart format -o none --set-exit-if-changed .`, then the analyzer and tests. The format step is the cheapest of the three and the first to fail, which is why it usually runs first. ## Common mistakes - Running plain `dart format .` in CI, which rewrites files in the CI workspace and always passes. - Forgetting `--set-exit-if-changed`, so the job logs changes but still succeeds. - Skipping `dart pub get`, so the language version cannot be resolved. - Using `dart format --fix`, which was removed in Dart 3.7; fixes now belong to `dart fix`.

  • Why does a dart format check in CI need dart pub get to run first?
    The formatter picks the short or tall style per file from its language version, which it reads from the package configuration that `dart pub get` writes. Without it, the formatter cannot resolve the language version the way developers' machines do, and results can differ.
  • What exit code does dart format --set-exit-if-changed return when nothing needs formatting?
    It returns 0. It returns 1 only when at least one file would change, which is what lets a CI system treat the step as pass or fail.

saying these in an interview costs you the question

  • Runs plain dart format . in CI and expects it to fail
  • Thinks dart format only reports and never writes files by default
  • Believes --set-exit-if-changed alone stops files from being rewritten
  • Uses dart format --fix, which no longer exists
  • Skips dart pub get before the format check