Repository navigation
fix: don't break a member expression that fits from a stale line number with memberExpression.linePerExpression - #844
Merged
Conversation
…er with memberExpression.linePerExpression With `memberExpression.linePerExpression`, each `isMultipleLines` condition compares the line the member expression starts on with the line its last item starts on, which is resolved later. When the printer went back and printed the member expression again somewhere else, that line was from the earlier printing, so a member expression that fit could stay broken. The last item's line is now worked out again when it was on a later line, and moved when the member expression fit on one line at that column before. The first condition is also checked again after the last item, because when only the last item exceeded the line width only it moved to the next line.
…hen the first item isn't wider than the indent with memberExpression.linePerExpression Breaking after a first item that's no wider than the indent doesn't make the lines any shorter, so `foo.bar(...)` stays together on the first line before the remaining items go on their own lines, like without `memberExpression.linePerExpression`.
…emberExpression.linePerExpression The newline after the shebang was only expected, so it was written when the first statement's text was. The member expression's start line was resolved before that, on the shebang's line, which made a member expression that fits look like it was on multiple lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #686, fixes #715. Supersedes #829, using what dprint-core 0.74 added.
With
memberExpression.linePerExpression, eachisMultipleLinescondition compares the line the member expression starts on with the line its last item starts on, which is resolved later. The printer keeps resolved values when it goes back to an earlier point. So when the member expression was printed again somewhere else, a stale last-item line could say "multiple lines", which broke the expression, which then made it multiple lines. Expressions that fit were broken, and formatting again broke them further.#829 reset that line with the same rule as
InfoRegion: move it when the member expression starts at the same column, clear it otherwise. Redoing it withInfoRegionfixed the issues but kept both of #829's known problems, and the rule turned out to be wrong in both directions:x(<chain>);) stayed broken after moving down to the next line at the same column (<chain>,), where it fits.So this tracks the last item's line itself, with dprint-core's
actions,shift_line_numberandclear_info. When the member expression is printed again at a different position:As in #829, the first
isMultipleLinescondition is also re-evaluated after the last item. When only the last item exceeds the line width, only it moves to the next line, after the conditions were resolved.The first item stays on the first line when it's narrow
This also keeps the second item on the first line when the first item isn't wider than the indent. Breaking after it wouldn't make the lines any shorter, and it's what happens without
linePerExpression:It's measured from where the member expression starts, so it also applies in the middle of a line. With
indentWidth: 2,foois wider than the indent, so it still goes on its own line. This only changes output withlinePerExpression, when the first item is narrow.A member expression after a shebang
With
linePerExpression,console.log("1.0.0");after a#!line was broken across two lines. The newline after the shebang was only expected, so it was written when the first statement's text was. The member expression's start line was resolved before that, on the shebang's line. The newline is now written right away when a statement follows. The output with the default config is unchanged on the corpus.Testing
indentWidth: 4andindentWidth: 2.memberExpression.linePerExpression: true:maincli/tsc/00_typescript.jsWith the default config, which doesn't use this code, it's 317 and 0. The narrow first item changes the output of 1,179 of these files and doesn't change these numbers.
Known problem
Deeply nested member expressions in call arguments still take longer as they get deeper. #829's example at
lineWidth: 60,foo.bar(a0.b0(a1.b1(... a20.b20(z.y) ...))).end.more, takes about 6s for two formats vs under 0.1s onmain(about 0.1s at 10 levels deep). Each level whose arguments break moves the levels inside it to a new column, where they haven't been printed before, so they're worked out again.mainis fast here because it uses the stale lines, which gives the wrong output (a1and.b1(on separate lines) and needs more formats to settle. I didn't find this in real code: the corpus has no slow files.The printer finds out bottom-up that each level must be multi-line: it prints every level on one line, overflows deep inside, then flips the levels outward. Each time a level flips, the levels inside it move to a new column and have to be worked out again. I tried letting
InfoRegionkeep its line numbers at columns where it fit on one line before. That cut the work by about 60%, but it was still exponential and broke two existing specs, because how a region breaks also depends on what follows it on the line.Also not fixed here:
const value = foo.someService.someRepository.findAllItems;breaks after=and then changes on the next format. That happens onmaintoo, because the line breaks after=before it's known that the member expression will be multi-line.