gofumpt: break long lines when it's appropriate to do so
Howdie, love your work, and just stumbled across this which also looks awesome š
I use prettier with JavaScript, and cargo fmt
with Rust, and they both automatically (and consistently) break up long lines
Example func
:
func somethingTooLong(ctx context.Context, longArgument string, anotherOne func(time.Duration) bool) (int, error) {
// foo
}
func somethingTooLong(
ctx context.Context,
longArgument string,
anotherOne func(time.Duration) bool
) (int, error) {
// foo
}
gofmt
doesnāt do this, but it also doesnāt undo it where Iāve done this manually, so at least this particular example is gofmt
-compatible
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 74
- Comments: 57 (18 by maintainers)
Commits related to this issue
- format: break long lines if it's easy to do so Note that this feature is guarded behind GOFUMPT_SPLIT_LONG_LINES=on, as this feature is still experimental and needs more testing and tweaking before i... — committed to mvdan/gofumpt by mvdan 3 years ago
- format: break long lines if it's easy to do so Note that this feature is guarded behind GOFUMPT_SPLIT_LONG_LINES=on, as this feature is still experimental and needs more testing and tweaking before i... — committed to mvdan/gofumpt by mvdan 3 years ago
- format: break long lines if it's easy to do so Note that this feature is guarded behind GOFUMPT_SPLIT_LONG_LINES=on, as this feature is still experimental and needs more testing and tweaking before i... — committed to mvdan/gofumpt by mvdan 3 years ago
Iāve merged the long-lived PR into master. Note that the feature is not enabled by default just yet, because I want to get some early feedback first.
If you would like to help:
Then run it with the special env var:
If you find a bug or a regression, or a case where a line was split when it clearly shouldnāt have been, please leave a comment here. This issue will remain open until the feature is enabled by default. Thank you!
FWIW personally I think that looks ugly, and itās easy to miss arguments; when I reformat code like that, I either choose all args on one line or all args on separate lines.
Iād be interested to see how the different approaches end up looking when run on real code.
The irony is that Effective Go is formatted with a consistent line length, presumably for readability reasons.
Itās not just about punch cards - typographers have been thinking about this stuff for the past 100 years or so.
Thanks for the interest! This is indeed part of my grand plan š
Iāve left this feature out of the initial implementation, simply because itās one of the tricky ones. A number of decisions must be taken:
gofmt
, Iād very much rather not add a flag.The closest existing tool is https://github.com/walle/lll, which counts runes (characters), and defaults to a maximum of 80.
rustfmt
seems to enforce a configurable limit, defaulting to 100: https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_widthMy current thinking is to start small and conservative. That is, use a simple large limit like 120 runes, and only add newlines in non-controversial places, like between expressions in an expression list (such as your example). If weāre happy with that, we can look at more complex cases, like splitting up strings and comments, or dealing with nested expressions.
I think counting runes is the only sane default we can use. Counting bytes is just far too basic. Measuring width via https://godoc.org/golang.org/x/text/width would work, but only for monospace fonts that happen to agree with that package.
gofmt
aligns comments, so it must also measure the length of lines, for which it uses https://golang.org/pkg/text/tabwriter/:So I think itās fair to follow gofmt here, for the sake of simplicity and consistency.
Also cc @rogpeppe, who has thought about breaking super long lines for a while.
This sounds like a good guideline to me. Anything in between is ugly and harder to read.
Yes, please no options.
It is either in or not. Adding options is a whole new can of worms that Go does not need.
This is awesome @mvdan thank you š ā¤ļø Iāll post this here so not to pollute the PR with discussions.
Is the way func definitions and calls are split final? If not, do you have an idea of how youād like them to look like?
Currently
becomes (1)
Is this an acceptably readable result?
Possible alternatives would be I guess something like (2)
or (3)
My personal preference is the last one (3), but I understand not many people like that.
ps. Same thing with the func definitions, though I agree that returns should be kept together.
Kubernetes does this nicely for func definitions which I really like.
(source)
Sorry, but as I commented in another issue, itās unlikely that gofumpt will ever have any options of its own - just like gofmt doesnāt have any formatting optins.
Another example, this time in
rustfmt
(Rust is like Go in thatif
conditions do not require parens):becomes:
From Effective Go: āGo has no line length limit. Donāt worry about overflowing a punched card. If a line feels too long, wrap it and indent with an extra tab.ā https://golang.org/doc/effective_go.html#formatting
Just my two cents, I donāt think this should be part of gofumpt. Formatters should be there for when there is an obvious, idiomatic modification that can be made to source code, not for style preference.
Also not sure if this is the time or place but donāt understand why something like this would not be configurable, especially as part of something like golangci-lint. For example, if I love all the adjustments made by gofumpt except one (like this one), I have to stop using the tool entirely.
I agree with @geoah , 3 is my preference as well.
Thatās true in a way, but itās impossible to avoid this separation. I canāt take over gofmt or attempt to replace it entirely. If anyone wants to experiment with a stricter gofmt (but backwards compatible, like @jokeyrhyme said), it has to be a separate project.
So I canāt get rid of the
gofmt
vsgofumpt
separation. I can, however, prevent more dimensions to be added in the form of extra options.I already touched on this topic, but to repeat my thinking - their stance is generally that line length limits are silly, I agree. They do not say that line length does not matter. If that were the case, most idiomatic Go code wouldnāt typically fit within 80-120 columns or so. Significantly longer lines are definitely not considered idiomatic in general.
Again, thereās noone forcing you to use gofumpt. If gofmt is enough for you, thatās absolutely fine. Iām always looking for more input to fine-tune gofumpt to be as good as possible, but the lack of options and the backwards compatibility are pretty much non-negotiable. The moment this tool loses either, it would stop being useful as a companion to gofmt.ā
@samlof assuming youāre talking about preventing prettier from joining lines, I donāt think you should worry about that, because gofumpt wonāt join short lines into regular-sized ones. This issue is only about splitting very long lines as long as they are easy to split into more reasonable lines.
for now using https://github.com/segmentio/golines as an alternative.
I agree with @jokeyrhyme, basically.
@kylechadha I also used to think that line length shouldnāt matter. And I definitely agree that a hard limit on line length is a terrible idea. Somewhere in between, humans make a judgement call, and Iām trying to encode that into a heuristic. I think the current state of the PR is already a pretty good approximation, but I would need to finish it up and furiously test it on large amounts of āidiomaticā Go code.
As for the configurability - you quote the Go docs, so I should mention that one big design decision from gofmt is to not make formatting configurable. Once you do that, different projects will use different, incompatible styles. I definitely want to follow the same rule of thumb here, so this will not be configurable. If itās annoying or incorrect, then we should fix or remove the feature entirely, not expect the user to turn it off.
This is very subjective, and can be labor intensive to do consistently, and also a time-waster in code reviews, which is part of the value of an opinionated line-break tool
This is a delicate balancing act that the prettier folks have struggled with as well
Options tend to increase adoption, but also increase code complexity, so itās sort of up to each community / author to decide whether adoption is more important than simplicity / reliability
Please remember that the first iteration of this feature needs to be very conservative. It seems to me like forcing arguments to all be on separate lines is exactly the opposite of conservative. Letās go step by step.
If a conservative heuristic goes well and people seem to like it, we can push it further in the future. For example, by requiring that multi-line parameter and argument lists have each element in a separate line. We already enforce that rule for composite literals.
I guess I could be convinced that such a stricter format would be a good thing, but I donāt see it clearly right now, and I work with a lot of code that would be heavily modified by such a rule. Hence why I strongly prefer to leave it for a future incremental step.
https://github.com/golang/go/issues/11915 outlines more or less my concerns above. I see this project as a way to experiment with these features.
https://github.com/golang/go/issues/8146 is more interesting. It turns out that
gofmt
does break up long func lines, if they are ācomplexā enough:Looks like
headerSize
andbodySize
are just approximations of the number of characters it will take to print each bit of code:So, in a way,
gofmt
already enforces certain line length limits. I think people are happy with 100 for top-level function declarations, since thereās zero indentation and thereās little reason to make them long. So this tells us that we definitely want at least 100 as a global limit.in vscode it can be set in settings.json with:
In the past Iāve written Dart with dartfmt. I definitely miss the auto-line wrapping when writing Go. Especially the amount of noise, that not having auto-line wrapping, adds to MR discussions. Cheers for working on this!
Regarding wrapping long argument and collection lists, dartfmtās approach is to wrap these differently depending on the presence of a trailing comma (been a while since I wrote any, so hopefully my memory is correct).
So this:
Becomes:
And without the trailing comma:
Becomes:
Seems to work quite well in practise. Hopefully Goās simpler syntax will make this easier than implementing āThe Hardest Program Iāve Ever Writtenā. See: https://journal.stuffwithstuff.com/2015/09/08/the-hardest-program-ive-ever-written/
And: https://github.com/dart-lang/dart_style
I think thereās an effort to keep the output of
gofumpt
compatible withgofmt
, that is:gofmt
will takegofumpt
-formatted code and not detect anything it needs to changeItās in the README.md:
Anyone working on this?
Thanks all for the comments. This is definitely something I want to try; itās just a matter of finding the time to properly write it and test it.
On the other hand, forcing this behavior by default could mean that gofumpt would be less of a drop-in replacement, if the line-breaking is too agressive. I personally never write lines over 100-120 characters long, but sometimes I do go over 80 when declaring functions or writing comments. Iād have to experiment with large amounts of Go code.
I also think gofumpt should make it clear what itās optimizing the codeās presentation for: editors, diffs, just the
$PAGER
, etc. Counting graphemes would optimize for most graphical editors (and a few console-based ones); counting cell width would optimize for pretty diffs.I agree that following upstream is the best approach for the project, so I suppose the scope could be clarified to āoptimizing display for simple diffs or pagers, assuming all code points have equal widthā. That being said, Iād like to see support for code points with different widths.
Some of the people who worked on https://github.com/psf/black have had lots of discussions on line-wrapping (stemming from a decision to break from PEP-8ās 79/80-col recommendation) and may be able to share thoughts.
If anyone else is looking for a decent solution, golines is an excellent option.
Iāve been trying to keep the env var on at all times, and Iāve been finding itās a tiny bit too aggressive - especially on indented code. For example, it did this change:
This definitely feels wrong. The indented line does reach to column 110 if we count tabs as 8 columns, but the non-indented line is just 86 characters, and we end up with two tiny lines of 41 and 44 non-indented characters.
The new format is not wrong per se, but I lean towards the old format, and I donāt think thereās anything wrong with it. I think it falls under āgoes over the limit, but splitting the line isnāt betterā.
Iāll probably tweak the heuristic to be a bit less aggressive on indented lines.
Yes, that definitely sounds like a bug in master. Please file a separate issue, and Iāll hide these comments since theyāre off-topic for this feature/PR.
I put together a rough first cut of this feature in https://github.com/mvdan/gofumpt/pull/70. Please try it out and provide feedback.
To repeat the design choices here, at least for the initial implementation:
The heuristic isnāt documented, but you can get a rough idea of how it behaves by looking at the test cases added in the PR.
Looking forward to testing this feature!
Having that feature would be greatest impact on the go fmt ecosystem, to be honest, it is a lot of frustration to manually format Go code after working with Java for some time where code formatting is very powerful, for example: https://github.com/palantir/palantir-java-format https://github.com/google/google-java-format
Sorry, I donāt follow.
The plan is to try with a large limit like 120 first, which would apply to all files equally. Conservatively, it would likely ignore lines that canāt be split into shorter ones.
+1 for multiline function definitions and calls.
I understand that you donāt like options, I assume that includes different versions of the tool? Maybe something behind a build tag and people who really donāt want this can build the tool with something like an
!lll
build tag?