-
Notifications
You must be signed in to change notification settings - Fork 121
FAQ
The formatter has a few goals, in order of descending priority:
-
Produce consistently formatted code. Consistent style improves readability because you aren't distracted by differences in style between different parts of a program. Consistency makes it easier to contribute to others' code because their style will already be familiar to you.
-
End debates about style issues in code reviews. This consumes an astonishingly large quantity of valuable engineering energy. Style debates are time-consuming, upset people, and rarely change anyone's mind. They make code reviews take longer and leave participants feeling bad.
-
Free users from having to think about and apply formatting. When writing code, you don't have to try to figure out the best way to split a line and then painstakingly add in the line breaks. When you do a global refactor that changes the length of some identifier, you don't have to go back and rewrap all of the lines. When you're in the zone, you can just pump out code and let the formatter tidy it up for you as you go.
-
Produce beautiful, readable output that helps users understand the code. We could solve all of the above goals with a formatter that just removed all whitespace, but that wouldn't be very human-friendly. So, finally, the formatter tries very hard to produce output that is not just consistent but readable to a human. It tries to use indentation and line breaks to highlight the structure and organization of the code.
In several cases, the formatter has pointed out bugs where the existing indentation was misleading and didn't represent what the code actually did. For example, automated formatting would have helped make Apple's "gotofail" security bug easier to notice:
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail; if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) goto fail;
The formatter would change this to:
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail; // <-- Now clearly not under the "if". if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) goto fail;
First of all, that's not a question. But, yes, sometimes you may dislike the output of the formatter. This may be a bug or it may be a deliberate stylistic choice of the formatter that you disagree with. The simplest way to find out is to file an issue.
Now that the formatter is pretty mature, it's more likely that the output is deliberate. If you still aren't happy with what it did to your code, the easiest thing you can do is tweak what you send it. While the formatter tries to make all code look great, there are trade-offs in some of the rules. In those cases, it leans towards making more common idioms look better.
If your code ends up looking bad, your code may be off the beaten path. Usually hoisting an expression up to a local variable or taking a big function expression out and making it a named function is all that's needed to get back to a happy place.
Line wrapping is one of the most important and most challenging things the formatter does. By default, it will try to keep every line of code 80 characters long or shorter. We've found this to be a good width that uses screen space well, plays nice with side-by-side diff tools, and avoids long and difficult to read lines.
However, some users strongly prefer different page widths. You can configure
this using an analysis_options.yaml
file. In that file,
add a top-level section like:
formatter:
page_width: 123
When you run dart format
, it looks for an analysis_options.yaml
file in the
directory where each formatted file is found. If not found there, it walks
parent directories until it finds one. This way, you can set the page width for
an entire collection of files with a single options file. Typically, you have an
analysis_options.yaml
file in the root directory of your pub package and it
applies to everything in that package.
If no analysis_options.yaml
is found, then dart format
defaults to 80
columns.
You can also configure the page within a file using a special marker comment that looks like this:
// dart format width=123
This comment must appear before any code in the file and must match that format
exactly. The width applies to the entire file and overrides the width set by any
surrounding analysis_options.yaml
file.
This feature is mainly for code generators that generate and immediately format
code but don't know about any surrounding analysis_options.yaml
that might be
configuring the page width. By inserting this comment in the generated code
before formatting, it ensures that the code generator's behavior matches the
behavior of dart format
.
End users should mostly use analysis_options.yaml
for configuring their
preferred page width, or do nothing and use the default page width of 80.
The formatter supports very few tweakable settings, by design. If you look up at the list of priorities above, you'll see configurability goes directly against the first two priorities, and halfway against the third (you have to think about it, but not apply it).
This may be surprising, but the goal of dart format
is not to automatically
make your code look the way you like. It's to make everyone's Dart code look
the same. The primary goal of dart format
is to improve the quality of the
Dart ecosystem. That transitively improves the lives of each Dart developer as
well—you get more code to reuse, more consistent code, it's easier to read and
contribute to each other's code, etc.
But it does that at the expense of individual preference. It requires you to buy
in to the idea that a consistent ecosystem is more valuable than anyone's
individual formatting preferences. Or, another way to say that is that no one's
individual formatting style is measurably better enough than what dart format
produces to compensate for the costs of inconsistency and having to
argue over what your team's house style should be.
If you don't buy that, that's OK. It just means dart format
probably isn't the
right tool for you.
For over a decade, the formatter supported one single opinioned style. That style was older than Flutter, older than many Dart language features, and older than almost all Dart code in the world today.
The way users write Dart code has changed significantly in those years. In particular, Flutter uses Dart as sort of a UI markup language, leading to large deeply nested declarative expressions. The formatter's original style optimized for the kind of imperative code that was more common in Dart's early history, but doesn't scale as well to deep tree-like expressions.
When support for trailing commas was later added to Dart, it wasn't clear how
they should be formatted. Up to that point, the formatter always placed the
closing )
of an argument list right after the last argument. It's silly to do
that if there's a trailing comma stuck in there, so the hacky compromise we
added at the time was that if you write a trailing comma, you get a different
formatting style:
// Without trailing comma:
someFunction(longArgument,
anotherArgument);
// With trailing comma:
someFunction(
longArgument,
anotherArgument,
);
This hack undermined the formatter's main goal of getting users out of the business of making tiny meaningless formatting choices. But it did let us observe which style users preferred in the wild. Over time (and with surveys and analysis of huge corpora of Dart to back it up), it became clear that most users prefer the latter style.
So we rewrote the formatter internally to support two styles, the original "short" style, and a new "tall" style that always formats like the latter argument list and adds and removes trailing commas on your behalf.
As of 2024, we are in the process of migrating the ecosystem over to that new
style. We use the Dart language version of the code being formatted to
determine which style you get. Code at Dart 3.6 or earlier continues to be
formatted using the old style so that users don't see their code formatting
spontaneously change under them. Code at Dart 3.7 or later gets the new style.
When you update your pubspec's SDK constraint to >=3.7
or later, you are also
opting in to the new style.
The goal is not to support both styles indefinitely. We still want a single ecosystem with a single consistent style. We are supporting both for some amount of time to ease the transition and let users migrate when it's a good time for them to do so. At some point in the future, we'll remove support for the old style completely and all code will be formatted using the tall style, regardless of language version.
You can rely on the formatter to not break your code or change its semantics. If it does, this is a critical bug and we'll fix it quickly. The formatter also has internal sanity checks to validate that its output doesn't corrupt the code. The formatter is used every day inside and outside of Google on millions of lines of Dart code. Most users have their IDE set to format every time they save a file, so the formatter is likely executed millions of times a day.
This means both that it is very heavily vetted and also that it doesn't change very often. The rules the formatter uses to determine the "best" way to split a line may change over time, mostly in complex cases. We don't promise that code produced by the formatter today will be identical to the same code run through a later version of the formatter. We do hope that you'll like the output of the later version more.
Since we are in the middle of transitioning to a new style, there will likely be more style rule changes for a while as we get feedback from users. But the new style has been heavily tested internally and externally, so we expect it to settle down fairly quickly.
I wrote a long article about how the formatter is implemented here. The new "tall" style implementation is architecturally different in some ways but that article should still give you the general flavor of how it works.
If your code is using the tall style, you can opt a region of code out of automated formatting by surrounding it in a pair of special marker comments:
main() {
this.isFormatted();
// dart format off
no + formatting
+
here;
// dart format on
formatting.isBackOnHere();
}
The comments must be exactly // dart format off
and // dart format on
. A
file may have multiple regions, but they can't overlap or nest. If you want to
opt the rest of a file out, you can use a single // dart format off
comment
without a closing one at the end.
Disabling formatting can be useful for highly structured data where a custom layout can help a reader understand the data, like large lists of numbers. But, in general, we recommend you use this feature sparingly. When you opt code out of automated formatting, you are opting back in to all of the headaches that manual formatting entails: arguing with teammates about style, needing to manually reformat it after applying automated refactoring, reduced readability for users who expect a different style, etc.
The formatter has two basic styles for formatting a function call:
// Fit everything on one line:
function(argument1, argument2, argument3);
// Split around every argument:
function(
argument1,
argument2,
argument3,
);
Those work fine in most cases. But you probably wouldn't want all of your tests to look like:
test(
"adds two numbers correctly",
() {
expect(1 + 2, equals(3));
},
);
Instead, you probably expect something like:
test("adds two numbers correctly", () {
expect(1 + 2, equals(3));
});
This "block-formatted" argument list style is so natural in many places that you may not even notice it:
argParser.addAll([
"--help",
"--mode",
"debug"
]);
Deciding which arguments should get this block formatting is one of the most subtle corners of the formatter's heuristics. Sometimes you run into code that seems like it really should use one style but the formatter picks the other. Often, you can get it back onto a happy path by reorganizing your code a bit. Perhaps hoist one of the arguments out to a separate local variable or break a long string literal in half.
If that doesn't help, file an issue with the code in question and it may be possible to tweak the rules. Otherwise, accept that the formatter is doing the best it can with its very limited understanding of your code.
Large collection literals are often used to define big chunks of structured data, like:
/// Maps ASCII character values to what kind of character they represent.
const characterTypes = const [
other, other, other, other, other, other, other, other,
other, white, white, other, other, white,
other, other, other, other, other, other, other, other,
other, other, other, other, other, other, other, other,
other, other, white,
punct, other, punct, punct, punct, punct, other,
brace, brace, punct, punct, comma, punct, punct, punct,
digit, digit, digit, digit, digit,
digit, digit, digit, digit, digit,
punct, punct, punct, punct, punct, punct, punct,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, brace, punct, brace, punct, alpha, other,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, brace, punct, brace, punct
];
The formatter doesn't know those newlines are meaningful, so it wipes it out to:
/// Maps ASCII character values to what kind of character they represent.
const characterTypes = const [
other,
other,
other,
// lots more ...
punct,
brace,
punct
];
In many cases, ignoring these newlines is a good thing. If you've removed a few items from a list, it's a win for the formatter to repack it into one line if it fits. But here it clearly loses useful information.
Fortunately, in most cases, structured collections like this have comments describing their structure:
const characterTypes = const [
other, other, other, other, other, other, other, other,
other, white, white, other, other, white,
other, other, other, other, other, other, other, other,
other, other, other, other, other, other, other, other,
other, other, white,
punct, other, punct, punct, punct, punct, other, // !"#$%&´
brace, brace, punct, punct, comma, punct, punct, punct, // ()*+,-./
digit, digit, digit, digit, digit, // 01234
digit, digit, digit, digit, digit, // 56789
punct, punct, punct, punct, punct, punct, punct, // :;<=>?@
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, // ABCDEFGH
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, brace, punct, brace, punct, alpha, other, // YZ[\]^_'
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, // abcdefgh
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha,
alpha, alpha, brace, punct, brace, punct // yz{|}~
];
In that case, the formatter is smart enough to recognize this and preserve your original newlines. So, if you have a collection that you have carefully split into lines, add at least one line comment somewhere inside it to get it to preserve all of the newlines in it.
If that's not sufficient (and you are using the tall style), you can opt the collection out of automated formatting and format it yourself.
Initially, the formatter had a simple, restricted charter: rewrite only the non-semantic whitespace of your program. Make absolutely no other changes to your code. This makes it more reliable to run the formatter automatically in things like presubmit scripts where a human does not vet the output.
With the tall style, we have slightly loosened that. It adds and removes trailing commas. It sometimes moves comments before or after commas. Trailing commas and comments aren't whitespace, but they aren't semantic either. The code means exactly the same regardless of what you do with them.
If we're willing to make those kinds of changes, why not other ones? There are two main reasons why:
First, the formatter has a goal of being reversible. If you run the formatter periodically while making a series of code changes, the formatter should never leave detritus in the code that indicates when you happened to format it. Some examples:
-
If we add curlies to the body of an
if
that doesn't fit on one line, do we remove them when later it does fit? What if the user prefers using curly braces on allif
s? If we don't remove them, then it means the formatter's behavior isn't reversible. Say you make anif
condition longer and format, it may add curlies. Then you change it back to the original shorter condition. If the formatter doesn't remove curly braces, you aren't back where you started even before the code changes you made are. -
If we split long string literals so that they fit in the line length, do we unsplit adjacent ones that would fit? What kind of string literal do we use when we split or unsplit? How do we handle escaped quotation marks that are affected by that choice? Are all of the things we might do here reversible? Likewise with re-wrapping comments.
Second, some seemingly simple code changes can have subtle failure modes:
-
If we alphabetize your imports, what happens to comments in the middle of them? What if it appears to be a commented out import? Do we sort it?
-
If we change the delimiter characters of your strings, what rules do we use to choose between
'
and"
? Minimum number of escapes needed? Based on the contents of the string?
These kinds of questions mean these changes should have a human validate the result. If it doesn't do what you want, you want to know. But the formatter is often run every single time you save a file. Users usually save after they've validated that the code is what they expect. They don't tend to read it again after saving.
Thus, the formatter still has a fairly restricted charter for the kinds of
changes it makes. For more aggressive automated changes, you want dart fix
.