Skip to content
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

Prettier does not respect existing method chain breaks #7884

Open
jfairley opened this issue Mar 26, 2020 · 184 comments
Open

Prettier does not respect existing method chain breaks #7884

jfairley opened this issue Mar 26, 2020 · 184 comments
Labels

Comments

@jfairley
Copy link

jfairley commented Mar 26, 2020

referencing: Improved method chain breaking heuristic

I understand the motivation behind the recent change, and fwiw, I agree with your solution in 2.0 with one exception. I don't think that code expressing chained methods across multiple lines should be automatically collapsed by prettier.

Through each issue linked (#3197, #4765, #1565 and #4125) I didn't interpret that the community wanted chained methods collapsed; they simply wanted inline chained methods to remain inline.

In the highest rated comment from #4125, one of @pvdlg's suggestions is If the user write them on multiple lines => keep on multiple line. In this comment, I think @pvdlg nailed it, and that's exactly what I'm looking for too.

Prettier 2.0.2
Playground link

# Options (if any):
--print-width=120

Input:

    cy.get(".ready")
      .should("have.text", "READY")
      .should("have.css", "background-color", "rgb(136, 228, 229)");
    cy.get(".pending")
      .should("have.text", "PENDING")
      .should("have.css", "background-color", "rgb(253, 212, 90)");
    cy.get(".live")
      .should("have.text", "LIVE")
      .should("have.css", "background-color", "rgb(175, 221, 255)");
    cy.get(".draft")
      .should("have.text", "DRAFT")
      .should("have.css", "background-color", "rgb(255, 181, 181)");
    cy.get(".scheduled")
      .should("have.text", "SCHEDULED")
      .should("have.css", "background-color", "rgb(222, 222, 222)");

Output:

  cy.get(".ready").should("have.text", "READY").should("have.css", "background-color", "rgb(136, 228, 229)");
  cy.get(".pending").should("have.text", "PENDING").should("have.css", "background-color", "rgb(253, 212, 90)");
  cy.get(".live").should("have.text", "LIVE").should("have.css", "background-color", "rgb(175, 221, 255)");
  cy.get(".draft").should("have.text", "DRAFT").should("have.css", "background-color", "rgb(255, 181, 181)");
  cy.get(".scheduled").should("have.text", "SCHEDULED").should("have.css", "background-color", "rgb(222, 222, 222)");

Expected behavior:

For method chaining, manually inserted newlines should not be erased.

@j-f1
Copy link
Member

j-f1 commented Mar 27, 2020

Kinda related to #4765

@j-f1 j-f1 added the lang:javascript Issues affecting JS label Mar 27, 2020
@thorn0
Copy link
Member

thorn0 commented Mar 27, 2020

Conceptually, Prettier doesn't preserve the existing formatting, it removes it. 🔥🔥🔥 There are a few exceptions to this (object literals, method decorators, and some cases in JSX), but those are cases where a better trade-off couldn't be found at all. I'm not convinced that method chains are such a stalemate case.

If the lines Prettier generates are too long and unreadable, the general advice is to reduce printWidth. You were warned in the docs:

With the print width set to 120, prettier may produce overly compact, or otherwise undesirable code.

But if we're sure that we want to change the current output, the first thing we should do is try to tweak the heuristic. E.g. we can add a new rule: if a chain contains a call with >= 3 args, always split it onto multiple lines. Only if we completely fail to find a good heuristic should we reach for the last resort, preserving the original formatting.

What is a good heuristic though? If it yields good results say in 95% of the cases, is it good enough? Does it matter if its results are subpar when printWidth is set to 120?

@j-f1
Copy link
Member

j-f1 commented Mar 27, 2020

if a chain contains a call with >= 3 args, always split it onto multiple lines

That’s what we had before. It didn’t work very well for simple expressions like Math.random().toString().split('.'). This sounds like a good idea!

If it yields good results say in 95% of the cases, is it good enough?

Arguably, no, since Prettier is widely used and many people will run into the 5% every day.

Does it matter if its results are subpar when printWidth is set to 120?

👍

@thorn0
Copy link
Member

thorn0 commented Mar 27, 2020

That’s what we had before. It didn’t work very well for simple expressions like Math.random().toString().split('.').

No, that's not the same thing as "if a chain contains a call with >= 3 args..."

@rvion
Copy link

rvion commented Mar 28, 2020

big +1 on the request.
Previous discussions seemd to imply that pre-existing line breaks should have been preserved.
That appears not to be the case.

IMO: it's now as annoying as before for a different set of use-cases, notably fluent APIs.

As a library author with dozen builder types libs with fluent apis, hundreds of files are now beeing reformated in a non consistent / weird and not readable way.

I now need to go and edit all those files to add comments in the middle of each chain to ensure lines are broken correctly.

Is there a way to keep using the previous heuristic ?

here is an example I'm editing right now :

domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain.concept('Widget').val('title', 'string').val('color', 'Color')

here is what it used to be

domain
    .concept('Page')
    .val('title', 'string')
    .vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain // <-- I now manually add a comment here
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')

@rvion
Copy link

rvion commented Mar 28, 2020

@thorn0

There are a few exceptions to this (object literals, method decorators, and some cases in JSX), but those are cases where a better trade-off couldn't be found at all. I'm not convinced that method chains are such a stalemate case.

I tend to think they do under the same circumstances

=> in some cases: alignment convey a "big" semantic information.

this is especially true in fluent apis / builder-like apis

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

@rvion I strongly believe preserving manual formatting dilutes the value of Prettier. We all are used to the current semi-automatic formatting of object literals, but in fact it's a PITA. Let's improve the heuristic. I wrote some additional proposals in #7889. What are your thoughts on them?

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

@rvion

As a library author with dozen builder types libs with fluent apis, hundreds of files are now beeing reformated in a non consistent / weird and not readable way.

BTW, this sounds like a great source of information for improving the heuristic! What do those weirdly formatted chains have in common structurally?

@rvion
Copy link

rvion commented Mar 28, 2020

Hi @thorn0 , thanks for your answers !

@rvion I strongly believe preserving manual formatting dilutes the value of Prettier.

I have no strong opinion on this but tends to disagree.
This is IMO part of what makes prettier pragmatic and efficient.

  • Sometimes code is way more readable on multiple lines because several keys convey information when seen together. So they need to be on separate lines.
  • Sometimes, seeing the big picture is more important and object litterals should take as little space as possible to help see the big picture.

Prettier found a very nice in-between IMO.

We all are used to the current semi-automatic formatting of object literals, but in fact it's a PITA.

For prettier devs, I believe you it is a PITA.
For me, it isn't at all :)

Let's improve the heuristic. I wrote some additional proposals in #7889. What are your thoughts on them?

It may help some cases, but not most fluent APIs / Builder I can think of.

I tried the same example as shown above in the playground for your PR and this is the result

image

BTW, this sounds like a great source of information for improving the heuristic! What do those weirdly formatted chains have in common structurally?

Good question;
Here is what I can say after thinking a short bit about this:

  • fluent apis end up being list of function calls with few to no arguments (usually 0, 1 or 2) but it vary a lot.
  • number of chained calls vary a lot too. it really depends the usage & library, but lots of usage imply a short number or calls (from 1->5/6)
  • I really can't see a simple way to disambiguate things 🤔

=> so I really don't see lots of ways to deal with this beside preserving indentation.

this being said, I'm used to adding empty comments to force indentation when I really want to, and the benefit of prettier offsets this very minor annoyance by a HGUE margin, so it's not the end of the world for me. It's just slightly worse.


I can also say that I've written a fair amount of typescript over the years.

Sometimes, Prettier euristics is not perfect, but it's usually great, and I usually don't care about it.

When I do care , I always find a way to force things either with comments, or with //prettier-ignore }

(for instance, from time to time, Igetters spanning on 3 lines even when they fit in a few chars. For those cases, and when I have lots of them but want to see the whole file at once, I add a few //prettier-ignore at the end of the line like so:
get foo() { return this.bar.baz } // prettier-ignore )

but that's about it. It's the first time I felt the need to come report how this change affected me in a negative way.

@rvion
Copy link

rvion commented Mar 28, 2020

For now, I'll just type [START,/,/,enter,.,METHOD_CHAIN]

and will just loose 3 keystrokes in the process.

The drawbacks are :

  • It's a bit annoying for existing codebases
  • It's harder for me to enforce this in my team
  • a few additional keystrokes

@RikJansen81
Copy link

RikJansen81 commented Mar 28, 2020

Conceptually, Prettier doesn't preserve the existing formatting, it removes it.

By doing so, I believe you're destroying a very important piece of information: intent.

No (flexible) heuristic will be able to transform

s.split('').map(Number).sort()

into

s.split('').map(Number).sort()

(i.e. don't transform at all)

while also transforming

domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')

into

domain
    .concept('Page')
    .val('title', 'string')
    .vals('widgets', 'Widget')

Both snippets have the same-chain length, and comparable argument-complexity and text-width.

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

@RikJansen81

By doing so, I believe you're destroying a very important piece of information: intent

How can we distinguish between a valuable intentional line break and a random line break that doesn't convey any important information and was inserted, for example, by Prettier's previous run because the line used to be long?

Both snippets have the same-chain length, and comparable argument-complexity and text-width

I'm not sure about that. The total amount of arguments is different. 2 vs 5. The first chain includes only calls with 0 or 1 argument. It's definitely something we can work with.

@rvion Why object literals are a PITA: #2068

@RikJansen81
Copy link

RikJansen81 commented Mar 28, 2020

I agree, it's not an easy problem to solve.

I'm not sure about that. The total amount of arguments is different. 2 vs 5. The first chain includes only calls with 0 or 1 argument. It's definitely something we can work with.

Sure, that's why I included the flexible part. The example could just as well have been:

s.split(';').splice.(2, 5).map(Number).sort()

and

domain
    .concept('Page')
    .val('title')
    .vals()

I guess, no matter what combinations of statistics you'll utilize, it will always create a false dichotomy.

How can we distinguish between a valuable intentional line break and a random line break that doesn't convey any important information and was inserted, for example, by Prettier's previous run because the line used to be long?

That would be pretty difficult. But I would argue that devising a heuristic for solving the command-chain problem would be at least as difficult.

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

I really don't understand why it's a problem if

domain
    .concept('Page')
    .val('title')
    .vals()

gets reformatted into

domain.concept('Page').val('title').vals();

I mean I see the problem in the original post in this issue, but here? I expect that the answer is along the lines of "other domain.concept chains are multiline, so this one should be too", but is it really a good reason? Would code really become less readable or anyhow else worse if you stop caring that much about those line breaks and let this reformatting happen?

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

I would really like if anyone here who wants to say something in support of preserving existing new lines first reflected on #2068 (comment) and realized how such situations defeat the whole point of Prettier. Thanks is advance.

@rvion
Copy link

rvion commented Mar 28, 2020

How can we distinguish between a valuable intentional line break and a random line break that doesn't convey any important information and was inserted

we can't, and that's exactly why I would like Prettier to preserve my line breaks there.
It's one of those cases where intent matters, like for Objects.

@rvion Why object literals are a PITA: #2068

To be honest, that's a very bad reason why it's a PITA and I'm totally unconvinced.
the post states that:

  • "the majority of people seem to want this level of control"
  • There is a bug in prettier that formats differently in some CI env and it annoys someone

Sure, why not adding a flag to remove all whitespace related preserving... but I'd rather solve the bug and leave the features instead if "the majority of people seem to want this level of control" 🤔

IMO: if someone has problem with CI because of some tooling, and decide to worsen the code to please the CI, there is a problem going on.

I really don't understand why it's a problem if ... gets reformatted into ...

I do; for the same reason users want the power to choose if objects are multiline or not.
My example may not be clear, but in the example below, I'm define a schema Page with two fields:

this is quite visible here, and easy to read:

domain
    .concept('Page')
    .val('title', 'string')
    .vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain // <-- I now manually add a comment here
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')

But in the below example, it's way less clear.

domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain.concept('Widget').val('title', 'string').val('color', 'Color')

It's even worse in lots of cases where I have

  • several definitions (e.g. 20/30) in the same file, or
  • several builder directives / fluent api nested:
    - it used to be very easy to use indentation to parse things: now it's not

as @RikJansen81 said, it's about preserving intent (& clear readability) conveyed by formatting, while still not caring about mundane formating mostly unrelated to intent.

In my current project (~500 files) where I use several fluent APIs / "builder" classes, I can assure you my code seems globally worse now after applying the formatting.

I think I have a pragmatic aproach to line breaks. I only care when it boost readability a lot. In this case, I really feel the current default is globally slightly worse. But again, I can fix it easilly by adding comments, so it's not a major problem.


I redirected people from #4765 (comment) here :)

Thanks again for your time

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

@rvion

Prettier is well past that phase when a GitHub issue or a Twitter poll can be used to understand what the majority of users wants.

As for #2068, preserving new lines is a destructive suggestion because it provokes merge conflicts and preserving at least as much noise as "valuable line breaks". It's not about some CI. Where did you read that?

Again, let's try and think of heuristic improvements. How about special-casing expression statements somehow?

@rvion
Copy link

rvion commented Mar 28, 2020

indeed, nothing about CI, I made a shortcut in my mind, sorry, but it doesnt change much IMO.

You redirected me to #2068 :

in #2068 , I read in the original post

  • majority of people seem to want this [ability to preserve newlines] level of control since we haven’t found a super good heuristic for printing those things

  • some people prefer a 100% uniform printing [...] even if it sometimes results in “uglier” code

  • I'm running into some weird issues where lines wrap and then don't unwrap

  • have[ing] a strict option that always prints out the same so long as the AST is the same [...] would greatly help with some of my merge conflicts

so basically, what I read is :

  • majority of users want some kind of line preserving
  • there is a bug in prettier
  • because of this bug, some merging (= operation related to collaboration / unrelated to code quality) is made harder
  • could we have an option that mitigates the bug even if it decreasing code quality.

Again, I don't see at all why it makes line preserving a PITA for end-users.
I personally still think current behaviour offers a nice tradeoff.


Again, let's try and think of heuristic improvements. How about special-casing expression statements somehow?

I understand you want to go back to thinking about heuristic improvements. but I think it misses the point that there are cases where no heuristics can do anything. Focusing on improving heuristics seems to be narrowing the debate right off the bat.

IMO, It's a matter of: does presentation has enough semantic information to be worth preserving in some cases. My opinion is yes, and improving heuristics doesn't seem to help me as @RikJansen81
showed.

(sorry for the noise if this was clear already. I'll continue watching the thread but won't add more informations unless requested. Thanks for you time)

@thorn0
Copy link
Member

thorn0 commented Mar 28, 2020

@rvion

there is a bug in prettier
because of this bug, some merging (= operation related to collaboration / unrelated to code quality) is made harder

Not a bug. The line break preserving behavior. Resolving merge conflicts is not unrelated to code quality.

there are cases where no heuristics can do anything

Prettier has prettier-ignore for those cases. Or it can be solved by adding comments like you do already.

Another problem with object literals is that users have to know to remove the new line before the first property for an object literal to collapse, but of course many of them don't know about that because it's counter-intuitive and because they naively think the formatting is fully automated. Prettier should remain a simple automatic tool, not a semi-automatic one with esoteric tricks the user has to be aware about.

@gms1
Copy link

gms1 commented Mar 29, 2020

there are many style guides and linter rules (eslint, tslint,..) that are recommending or even forcing a new line per chained call, so it is obvious that there is a not inconsiderable number of users who want these line breaks to be preserved and obviously no heuristic improvement would help.
What about a new option that preserves these line breaks?

@syabro
Copy link

syabro commented Mar 29, 2020

/// yay
str
	.split()
	.join()
domain
    .concept('Page')
    .val('title', 'string')
    .vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
/// meh
str.split().join()
domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo')
    .val('bar', 'Bar')
domain.concept('Widget').val('title', 'string').val('color', 'Color')

The reason why I personally think it's unconvinient it's because it's not stable.
Human brain tends to group similar looking object into a groups. So if we speak about "readability" and easier code blocks recognition I would defenitely vote for the first variant.

And also from perspective "one line - one instruction" and in our case "one function call" sounds sane to me.

So for for me

  1. Prefer user formatting
  2. Split all if chaining > 2
  3. Current situation mess :)

thorn0 added a commit to thorn0/prettier that referenced this issue Mar 29, 2020
@thorn0
Copy link
Member

thorn0 commented Mar 29, 2020

@syabro @gms1 Your thoughts on #7889?

@RikJansen81
Copy link

RikJansen81 commented Mar 30, 2020

I would really like if anyone here who wants to say something in support of preserving existing new lines first reflected on #2068 (comment) and realized how such situations defeat the whole point of Prettier. Thanks is advance.

Just as @rvion , I fail to see the relevance of that issue to ours. Firstly because I have a hard time understanding the issue described there as the problem is nowhere clearly stated in a reproducible fashion. The best I can make of it is that the commenter is stating that the same input produces different output.

  • If this is all under the same configuration, that would mean Prettier is not deterministic which would be an obvious bug. Later-on it was suggested Prettier is not idempotent, which I believe is a very undesirable property (and probably also a bug).
  • If this is under different configuration, that would be expected and I see no issue.

Either way, why is this an illustration that preserving line breaks is a PITA?

As for #2068, preserving new lines is a destructive suggestion because it provokes merge conflicts.

If the intent of the code changed or it the new formatting conveys the semantics of the code in a different/clearer way, I would say that should provoke a merge conflict. Code is written for coders and there is a good reason we are not directly coding in ASTs.

Of course, I do understand this can open up a whole new can of worms, because discussions will arise of what formatting will convey intent in the best manner, which seems like a type of issue Prettier tries to solve, but I feel these types of discussions (regarding intent) are one abstraction level higher than the ones Prettier should try to solve.

That issue did lead me to this section in the rationale:

Multi-line objects
It is tempting to collapse an object to a single line if it fits, but there are times when it is better for sibling/cousin keys to stay vertically aligned—see object lists, nested configs, stylesheets, and keyed methods. To avoid unfavorable collapsing, prettier simply formats any object as multi-line if it appears as such in the original source code.

It seems that our request aligns very will this rationale.

How can we distinguish between a valuable intentional line break and a random line break that doesn't convey any important information and was inserted, for example, by Prettier's previous run because the line used to be long?

I would also like to come back on this quote, because I fail to see why Prettier would need to solve that problem at all:

  • If you don't respect line-breaks (current situation), all line-breaks would be the ones enforced by Prettier. Successive runs of prettier will not change that (if Prettier is idempotent). If the content of the lines change, new line-breaks might occur, which is expected.
  • If you do respect line-breaks (requested by this issue), all line-breaks are either intensional by the developer or "radomly" inserted by Prettier. Either way, the developer can have the final say by either leaving the code as-is, or manually breaking it differently in a way that respects the constraints. Whatever choice he makes, the developer is giving his final consent to the formatting, making sure it aligns with his intent. After such modifications, successive runs of Prettier will not transform that code, as the constraints are met (again, assuming Prettier is idempotent). The only situation you will miss are accidental line breaks by the developer (as it the current case with object literals), but I may hope developers check their code before commits.

Again, let's try and think of heuristic improvements. How about special-casing expression statements somehow?

What is exactly meant with expression statement, I see a lot of different definition of this term online.

@thorn0
Copy link
Member

thorn0 commented Mar 30, 2020

Sorry, I really have no time for the discussion. If anyone has interesting counter-examples for the heuristic proposed in #7889, please write them here or there.

@keithgabryelski

This comment has been minimized.

@JCQuintas
Copy link

I see the reasoning behind what prompted the heuristics change, but on a daily basis the new heuristics are pretty annoying to use. I want my formatting to be consistent, not all over the place, which is the result of the current heuristics.

// 79 characters
string
  .replace(regex, '')
  .replace(regex2, ',')
  .replace(regex3, '%')
  .split(',')

// 80+ characters
string
  .replace(regex, '')
  .replace(regex2, ',')
  .replace(regex3, '%')
  .split(',')
  .map(mapFunc)

the code above annoyingly turns into

// 79 characters
string.replace(regex, '').replace(regex2, ',').replace(regex3, '%').split(',')

// 80+ characters
string
  .replace(regex, '')
  .replace(regex2, ',')
  .replace(regex3, '%')
  .split(',')
  .map(mapFunc)

They do pretty much the same thing, except the 80+ example has one more method, I want to be able to easily read what they are doing and understand that they are both similar with one small change, but since they aren't formatted the same it is harder to understand it at a glance.

I believe for method chain breaks manual multi-line should be kept.

@dustin-rcg
Copy link

dustin-rcg commented Mar 28, 2023

It would help if I could have a comment at the end of the line that I want to force wrapping, and Prettier would leave it alone, but it doesn't:

Original:

const FROM_NATIVES = Cases //
  .when((x): x is boolean => typeof x === 'boolean', bool)
  .when((x): x is number => typeof x === 'number', number)
  .when((x): x is string => typeof x === 'string', string);

Prettier says:

const FROM_NATIVES = Cases.when((x): x is boolean => typeof x === 'boolean', bool) //
  .when((x): x is number => typeof x === 'number', number)
  .when((x): x is string => typeof x === 'string', string);

Isn't this a bug? I should be able to comment on the line and not have Prettier insert another line's code there that may not pertain to the comment.

Apparently this was possible in the past but has gotten even less user-friendly. I feel like there should be a post-processor specifically for handling chaining.

@ristomatti
Copy link

ristomatti commented Mar 28, 2023

A project I'm keeping an eye on: https://dprint.dev/. It's got some way to go but it does support this use case.

There's a playground at https://dprint.dev/playground/#code/Q/language/typescript. With these settings it breaks chained method calls if you include a new line at any point in the chain:

{
  "preferSingleLine": false,
  "memberExpression.linePerExpression": true
}

@dustin-rcg
Copy link

dustin-rcg commented Mar 28, 2023

Another thought from my previous example is some degree of optimization heuristics. If I change the printWidth to 80, I get

const FROM_NATIVES = Cases.when( // My Comment about this line
  (x): x is boolean => typeof x === "boolean",
  bool
)
  .when((x): x is number => typeof x === "number", number)
  .when((x): x is string => typeof x === "string", string);

To me, if moving the chained member to the preceding line results in breaking the parameters over multiple lines -- resulting in more total lines of code -- than moving the chained member to the next line, then I prefer to move it to the next line.

To do that, the formatter has to use some degree of backtracking. The formatter keeps the printWidth constraint fixed, and computes the LOC for the current member on the current line having to be broken over lines due to that constraint. Then it also considers the LOC for the current member starting on a new line. The option resulting in the least LOC wins. It may be sufficient to do that in a greedy way considering only the current chained member. You could constrain the backtrack depth to limit backtracking in nested member chains.

From there, you get into longer backtracking breadth to determine whether the chain meets sufficient criteria to be split into one member per line. Such as, how many members were forced onto a new line by the greedy compact algorithm I just described, and then breaking the entire chain onto newlines if it exceeds the threshold. You can set that to 0 to always break, or to say -1 to disable and keep the greedy compact algorithm result.

Lastly, for the edge case where you only want to break member chains onto newlines in certain cases, it would be nice to have a simple heuristic (assuming the trailing line comment bug is fixed) like if the chain is forced onto a newline by a trailing line comment immediately before the first member access, then the entire chain is broken into newlines.
I don't think it would be very common to have a trailing line comment in that position otherwise.

@akutruff
Copy link

akutruff commented Apr 25, 2023

Just ran into this myself when trying to use trpc and zod together. All that fluency is now inconsistently indented. There appear to be some people who want to control chaining line breaks and others that don't. Using printwidth is basically figuring out a way to use an option without breaking the "no options" philosophy. Printwidth has a purpose and it's not to control fluent interface formatting.

The fact of the matter is that not one size fits all as code formatting is now based on semantics. There are so many declarative APIs that beg for different indentation.

@cope
Copy link

cope commented Apr 25, 2023

@akutruff That has been the main question for... well, years now.

@av-2024
Copy link

av-2024 commented Jun 30, 2023

Newlines can convey meaning or grouping.

@lpbonomi
Copy link

Chaining in different lines makes it easier for debugging, why isn't this an option?

@Sh1ken
Copy link

Sh1ken commented Dec 19, 2023

Is this still not an option?

vishnu-meera added a commit to vishnu-meera/prettier that referenced this issue Dec 20, 2023
@iiNomad23
Copy link

iiNomad23 commented Dec 27, 2023

Is there no way to add an option to ignore method chaining below the configured printWidth?

@undergroundwires
Copy link

undergroundwires commented Jan 3, 2024

  1. This is the sole reason I cannot introduce prettier into my code bases. What are the cons of having ability to disable it? What is the logical argumentation?
  2. Is there anything more in the community we can do rather than bumping this, which has become like signing a petition that's ignored without clear response by not closing the issue? There are already PRs resolving this.
  3. If maintainers choose to ignore instead of having an official conclusion, is there any fork/plugin or workaround with this in Vue ecosystem (where dprint is not really supported)?

@stefanzero
Copy link

If I only have 2 chained functions, but I will want to force a wrap on each chained function, I just add a "no-op" call to map:

.map(x => x)

@currentcreative
Copy link

It seems like Prettier stopped doing this (?). I just did a crazy Promise chain example and it did not put all the .then() on one line like I would have expected.

@padcom
Copy link

padcom commented Feb 26, 2024

@currentcreative That's probably because it didn't fit in one line. Try making a shorter one that would, better yet, do a different indentation on the fluent interface to see if prettier still knows better.

@kejedi
Copy link

kejedi commented Mar 8, 2024

4 years and still no config setting to simply ignore method chain linebreaks

i love prettier but this makes me big sad

would they even accept a PR for this? or should i not waste my time

@joewolschon
Copy link

+1 on big sad

@av-2024
Copy link

av-2024 commented Apr 11, 2024

They seem really committed to ignoring this problem. Luckily, alternatives are popping up and gaining mindshare.

Imagine bleeding your userbase because you don't want to add a single option that is both sensible and popular.

@akutruff
Copy link

akutruff commented Apr 11, 2024 via email

@Yikes2000
Copy link

It would help if I could have a comment at the end of the line that I want to force wrapping, and Prettier would leave it alone, but it doesn't:

Original:

const FROM_NATIVES = Cases //
  .when((x): x is boolean => typeof x === 'boolean', bool)
  .when((x): x is number => typeof x === 'number', number)
  .when((x): x is string => typeof x === 'string', string);

End-of-line "//" implemented, albeit a different meaning:
https://www.npmjs.com/package/@yikes2000/prettier-plugin-merge-preserve

I could implement preservation of existing method chains, e.g.
Input:

domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
domain
    .concept('Widget')
        .val('title', 'string')
    .val('color', 'Color')
  .val('foo', 'Foo').val('bar', 'Bar')

Output:

domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
domain
    .concept('Widget')
    .val('title', 'string')
    .val('color', 'Color')
    .val('foo', 'Foo').val('bar', 'Bar')

Preserve and align the chained dot method calls on separate lines.

Would that help?

@Yikes2000
Copy link

Implemented '--preserve-dot-chain' option for preserving method chain breaks:

        cy.get("something")
          .style().isBold()
          .value().matches('abc')
          .hasAttribute({
              name: "explanation",
              xyz: true
          })
          .done();

https://www.npmjs.com/package/@yikes2000/prettier-plugin-merge-preserve
Enjoy!

@silviogutierrez
Copy link

@Yikes2000 : do you think the plugin architecture could support fixing this? #2724

@ristomatti
Copy link

@Yikes2000 I've yet to try this but hats off already for the "rolling up your sleeves" attitude towards solving this!

@Yikes2000
Copy link

@Yikes2000 : do you think the plugin architecture could support fixing this? #2724

That issue already has a fork solution: https://github.com/qpwo/prettier-no-jsx-parens

Seems reliable. Did it not work?

@silviogutierrez
Copy link

@Yikes2000 : it does work, but I'd love a plugin form so that Prettier can be kept up to date instead of having to merge changes downstream.

@Yikes2000
Copy link

Yikes2000 commented May 2, 2024

@silviogutierrez : The methodology would likely work for #2724.

Hurdles:

  • It isn't technically "preserving lines", so a new plugin project is needed
  • I don't code in React/JSX -- would need assistance with field test
  • Not applicable to my job, limited free time :-(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests