-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flutter style mode for dartfmt #442
Comments
I wonder how extensible dartfmt is. Is there something we can do, without forking it and doing major re-engineering? |
Our style guide prohibits multi-line This follows Google's Java style guide which doesn't allow ifs without curlies at all. We loosen that to allow single-line ones, which are really handy for things like parameter validation. I've seen a number of style guides for various languages that prohibit multi-line
Do you mean this for all blocks, or just ones used in
I didn't follow this one. Can you give me some examples?
In your example here, the formatter will do what you want (minus the alignment) if you split the string yourself. It's not that it's too eager to fit into 80 characters, it's that you gave it something it couldn't fit into 80 in any nicer way. The formatter can't split string literals for lots of reasons that I can get into if you're curious. The main one is that it isn't easily reversible.
In general, I don't think most Google style guides encourage alignment. I don't dislike it, but it is quite hard to implement well, I think. I wouldn't rule out the formatter supporting it, but it's an open question as to if it's actually feasible to implement in an automated, efficient way. I've never had the time to give it a try.
If your code follows the Flutter style and does fit on one line, then why would the formatter add any splits? Can you give an example?
Interesting. Empty blocks are not very common in Dart and when they do occur, they generally are in places where I don't think it's that easy to mistake it for a map. We probably could change this, though I don't know what our users would think. Is this a big deal for you folks? It feels edge casey to me. If I were dropped on to a team that had one style or the other for this, I would probably just go with the flow and adopt the prevailing style.
This is ugly because multi-line (Whether multi-line
Yeah, this is a nasty edge case. Again, the behavior isn't arbitrary. (Despite the temptation, dartfmt does not use The general rules in play here are:
some(deeply(nested(function(argument,
another))), waitWhichFunctionDoesThisBelongTo?) There are lots of other cases where this general rule avoids weird ambiguous splits and indentation.
In some cases, these two rules interact in the above way where you get more indentation and line breaks than you expect. You probably expect: The output is helpful in some ways: it makes it visually clear that the assert(heroes[tag]
.keys
.where((Key key) => mostValuableKeys.contains(key))
.length <= 1); In this case it would be OK, but there are other cases where allowing that would push the assert(heroes[tag]
.keys
.where((Key key) => mostValuableKeys.contains(key))
.someMuchLongerMethod(with, a, whole, bunch, of, arguments) <= 1);
Do you do the same thing for optional positional parameters? This, to me, is in the bucket of things where either style is fine but being consistent matters most. Do you think it's a compelling advantage to have spaces here? (Personally, I think the Dart syntax for optional parameters is so hopelessly ugly that it's impossible to format it gracefully, but dartfmt does the best it can.)
You do that for all wrapped lines? That's different from Google's C++, Java, and JavaScript style guides. Don't you worry about confusing code like: someFunction(very, long,
parameter, list) {
body(mixes, in, with, the, wrapped, parameters);
} or: if (some(long(condition(
that, wraps) {
body(mixes, in, with, the, wrapped, parameters);
} Or, I suppose, you have the even worse: if (some(long(condition(
that, wraps)
body(mixes, in, with, the, wrapped, parameters);
Yeah, I agree that's not great. For what it's worth, we could change this in dartfmt without changing either of the rules you mention. We would just switch to formatting optional parameter lists as if they were collections. |
The formatter would avoid the 'goto fail' problem, because it would make it obvious that the second 'goto' wasn't in the "if" block. I'd much rather risk that, than risk the much more dangerous (IMHO) case of early returns being essentially invisible because they're hidden on the right end of a line in a dense block (in much the same way that you describe the The "one-line blocks don't get {}, multiline blocks do" rule is, specifically:
Where I wrote "symmetric punctuation" I meant "symmetric whitespace", my apologies. Whatever spaces (whether single spaces, newlines, etc) appear after an open bracket (of any kind), the same sequence of whitespace must appear before the matching close bracket (relative to the current indent level). Thus this: foo(bar(
baz()
)) ...or: foo(
bar(baz())
) ...or: foo(
bar(
baz()
)
) ...as opposed to: foo(bar(
baz())) I hope that clears that up. In the example regarding "too eager", it was actually derived from code where what I wanted was a single 81 character line, rather than two lines. The formatter was more worried about minimising the average line length than about avoiding a line break after the assignment operator. (But I don't want to set the line limit to 81, because generally it should be 80.) Regarding aligning expressions, I agree that it might be hard. I just don't want the formatter to make the alignment worse; if it's already aligned, then it should be left alone. I agree that it's hard to know whether it's "already aligned" or not. For the => splitting case, if the code doesn't fit on one line, then the => should be replaced by { }. The void detach() { }
void acceptGesture(int pointer) { }
void test() { setState(() { }); } ...etc. Typically either empty blocks in virtual methods that don't do anything in the class that introduces them, or calls to a method that takes a callback but where the call is really only wanted for the side-effect (so the callback is empty). If this was the only issue, we'd just change our style. Re loops, I think it's telling that what we call a "one-line for loop", you call a "multiline for loop", and what you call a "one-line for loop", we call a style guide violation. :-P The control flow condition should never be on the same line as the control flow body, according to our style guide. It's just too easy to miss the control flow when you're scanning the code. Regarding spaces around positional arguments, we don't currently do this but we have so few of them that it's hard to say if that's a conscious decision. We have named arguments all over the place and not having the space around the first and last but having the space after the comma separating them looks really weird to me. I don't think it's that ugly once you style it like we do. I do agree it's ugly if you style it the way that dartfmt does. ;-) Your three examples would wrap like this in our style, which avoids the problems you mention: someFunction(
very,
long,
parameter,
list
) {
body(mixes, in, with, the, wrapped, parameters);
}
if (some(long(condition(
that, wraps
)))) {
body(mixes, in, with, the, wrapped, parameters);
} There are many examples of this in our code, because we have lots and lots of really long argument lists and lots and lots of nested calls in bodies. Anyway. My goal here wasn't to argue about style. I just wanted to let you know why we're not currently using dartfmt. |
Good point.
Yes, it does, thanks. I think applying that rule even for
The formatter only makes whitespace changes, for lots of reasons, so it can't do this fix-up. (There are lots of other simple AST changes like this it could make if it did.) So for stuff like this, it produces the best output it can and relies on the human to realize that the output doesn't match the style guide and adjust the actual code in a way that let's the formatter do its job. A similar example is really long identifiers or string literals. The formatter can't force those to fit inside the line length, so it relies on you to help it out.
I do like that having a space makes code like this clearer: takesTwoCallbacks(() {}, () => {}); // {} mean very different things here! I don't know how the userbase would think about this change, but I filed a separate bug to track it: #445. I need to see what kind of impact it has but I think it's a reasonable change.
I personally use blank lines to make that clear. Thanks for the details! |
I'm curious about the "no code transformation" rule, but I've filed #452 and #453 about that.
We have a lot of functions that have a lot of early returns dotted around, but are otherwise relatively short and closely related to nearby code (e.g. a field, getter, and setter, where the setter has two early returns). Since they're related to each other we group them together without blank lines between them. If the function had blank lines, it would be really weird, a bit like having blank lines in the middle of a paragraph. |
@munificent What do you mean with this? Do you mean the code in the dart_style package? Or existing code formatted with dartfmt? I would personally love symmetric punctuation. |
This one. It would affect millions of lines of code. Worse, it would change them to be different from hundreds of millions of other lines of code written in C++, Java, and JS using style guides that don't line up |
Closing this out since I don't think I'll be taking action on the remaining couple of tasks. The others are all done now. |
There's a number of places where dartfmt formats Flutter code in ways that we discourage in our style guide.
The dart formatter puts if statement bodies on the same line as if statement expressions (dartfmt doesn't like 'if' flow control statements without blocks (FIXED) #448)
The dart formatter does not implement our "one-line blocks don't get {}, multiline blocks do" rule.(Convert between "if () ..." and "if () { ... }" based on line length (WONTFIX) #453) (won't fix)The dart formatter does not implement our "symmetric punctuation" rule. In Flutter code, the punctuation that follows an open bracket ("(", "{", "[", "<") must be the same as the punctuation that precedes the matching close bracket (")", "}", "]", ">").
The dart formatter is too eager to split lines early to fit lines into 80 characters.(allow lines to go over 80 chars when the alternative is worse (WONTFIX) #450) (won't fix)The dart formatter loses alignment that matters for showing code symmetry. (Vertical alignment #508)
The dart formatter is willing to wrap lines that use(Convert between => and { return ...; } based on line length (WONTFIX) #452) (won't fix)=>
. Flutter style says that a line that the arguments and the expression of a=>
declaration must all be on the same line.The dart formatter uses(Put a space inside empty blocks { } #445) (won't fix){}
for empty blocks rather than{ }
. We use{}
to mean "empty Map".Sometimes, the dart formatter just wraps the code arbitrarily in ugly ways.
Constructor named arguments aren't separated from their
{}
punctuation by spaces.Wrapped lines are indented 4 by dartfmt, 2 by Flutter style.(fixed for argument lists with trailing commas, won't fix for other cases)Combining some of the above, you end up with things like this code (#447):
...being formatted to:
Notice the 4 space indents and the asymmetric whitespace around brackets.
Style guide: https://github.com/flutter/engine/blob/master/sky/specs/style-guide.md
The text was updated successfully, but these errors were encountered: