prettier: Prettier does not respect existing method chain breaks

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.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 302
  • Comments: 178 (26 by maintainers)

Commits related to this issue

Most upvoted comments

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')

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.

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?

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?

I’m with all of those in favour of brining back line breaks.

From an e2e perspective in my organisation, I’m the only tester and as amazing as the rest of the dev team are, they need to be able to read my tests quickly if I’m off sick or on scheduled leave. Removing that line-break makes it harder for them to jump in and quickly make a change if needed, in my absence, which slows down production and ultimately puts more pressure on me when I return, because there is a backlog of updates to be made.

To the maintainers of the library, just bring back the old behaviour, as @murbanowicz suggested- newline-per-chained-call default it to false and let Prettier use the new behaviour, then for those of us who need it or want it, we can overwrite it.

It can’t really be that hard can it? You had it setup in v1. Or are you in favour of squabbling over what is right and wrong for months on end and causing a considerable number of us headaches.

@jomi-se Here’s a very escape-hatchy workaround for you:

Prettier 2.0.5 Playground link

--parser babel

Input:

moment() //
  .utc()
  .add(1, "year")
  .add(6, "months")
  .add(12, "hours")
  .toDate()

Output:

moment() //
  .utc()
  .add(1, "year")
  .add(6, "months")
  .add(12, "hours")
  .toDate();

/// 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 😃

I just experimented with adding prettier to my project for the first time, and was shocked when all of the nicely-formatted chains were crunched into one line. After reading the docs I was shocked again to discover there’s no option to control this behavior. It’s an immediate non-starter and completely at odds with our formatting standards for other languages.

In addition the idea that prettier is making such drastic changes between versions (with no way to preserve existing behavior), tells me that this is likely not the last time this will happen.

Very disappointing – I really hope you reconsider this.

I’d like to add 2 cents of my own experience with prettier’s printWidth and the way it doesn’t work.

There are, generally, two types of developers: the ones that care how the code is written and the ones that have come to accept the formatting in some kind of automatic way regardless if it makes sense or not. This is and has been the same for ages with IDEs like Eclipse or IntelliJ for Java and now prettier.

Me, personally, I care for the code I am writing. I always did. When I see code that looks like this it makes me want to puke (pardon the harsh language):

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

The only reason why the second piece is line-wrapped is that it exceeds the printWidth even though a fluent interface is supposed to be spelled like that. The formatting of the first line is just plain wrong and very much harmful for the reader. The rule of least surprise is violated here and is leaving the person reading the code wondering if there is a reason for things looking that way FOR NO APPARENT REASON.

Believe it or not I have read the entire conversation in this thread and comments such as the ones made by @thorn0 about not being interested in fixing this obvious misconception about prettier are just harmful to the project and (as a result) to the entire community. A lot of inexperienced developers will learn bad habbits from it (like not carrying about the code being written). This leads to people being totally messy when they lack the tooling that made them lazy in the first place. Comments like one less thing I have to worry about are a clear indication of it.

Remember: in software development, just like in war, there is no substitute for thinking. And as such if the tool knows better it’s a tool just not worth using. There is a difference between correcting obvious mistakes and forcibly pushing some insane style on the developer. And yes, in my professional opinion, after 30 years of writing code for a living, I do think that formatting that forgets user’s choice for line breaks in crucial situations such as these is the worst thing that can ever happen ever.

And last but not least: don’t make exceptions a rule (that’s about the // prettier-ignore thing)

We have hit this issue in our project and it makes us blocked from upgrading to Prettier 2.0 The configuration of the behaviour should be available. I would suggest something similar/inline to ESLint: newline-per-chained-call which is configurable in a nice way so it would meet all needs. This rule is really subjective and forcing all devs around the world using Prettier to follow exactly the same style which as can be seen is very controversial is not a good way to go.

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?

@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?

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.

Can’t there just be a prettierrc option for something like breakMethodChains

This is extremely annoying

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.

Hi dudes. Did this issue turn into an upcoming feature or what? Like @jfairley I also want to Prettier respects my hand made line breaks when chaining methods calls at my Cypress specs. Update me please. Is this already out there?

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

  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)?

Please fix, this is probably the most annoying part of prettier, more than the HTML formatting issues it has with white spaces.

For me, prettier works well, I use it with eslint prettier . --write && eslint . --fix.

After reading some of the comments above, I have to comment on the “tone” complaints - that’s a fallacy called “appeal to the tone”, also known as Tone policing, which “is an ad hominem (personal attack) and anti-debate tactic based on criticizing a person for expressing emotion. Tone policing detracts from the validity of a statement by attacking the tone in which it was presented rather than the message itself.

Please don’t do that and focus on what the person has said first and foremost, thank you.

As for the subject at hand, this simply has to be an option for the config, with the current behavior as the default. It really is that simple. That way, nothing changes unless some of us want to change it, and I for one really want to change it and break if >=2.

In case a chained method call doesn’t exceed the print-width,i understand that “chop down if long” won’t touch that statement, right? Would it leave these two statements like that?

Yes correct.

And, what happens if the two statements are already split into lines? Does it collapse the first one into one line, or does it leave it as it is?

It appears to do nothing at all unless the print width on the line is exceeded. This was honestly not what I expected when bringing this up, but I’m not sure if it’s that relevant here since Prettier already knows how to do this.

I suspect the current version has a bug since it does collapse the lines if I toggle: _'.' on a new line_, and reformat. It also collapses and wraps the lines back and forth within it's in-settings-example of the results.

Regardless, here’s a recap the different wrapping styles:

  1. Do not wrap: Method chain is not wrapped even if print width exceeds
  2. Wrap if long: Method chain wraps if print width exceeds, allows consecutive calls on a single line
  3. Chop if long: Method chains are wrapped one on each line if the print width is exceeded, otherwise kept
  4. Always wrap: Chained methods always wrap. (curiously this is the only option that is consistently applied)

In the context of this thread I believe only options 3 and 4 seem relevant. Options 3 is closest or same as how Prettier works now. Option 4 seems to be what most here wish. I also like the suggestion of a numeric configuration but to keep things simple, I’d be super happy with two options:

  • current behavior as the default
  • always break as an option.

image Pure lie 😆

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

Bumping this issue. Is there no way to get some kind of “escape hatch” to always split method chaining over multiple lines?

This is a common pattern of moment usage and the way it is formatted now is all over the place 😞

moment()
  .utc()
  .add(1, "year")
  .add(6, "months")
  .add(12, "hours")
  .toDate()

I think in certain cases having the possibility would be nice. We switched to v2.0.2 with our whole project. But the outcome is that your e2e tests suddenly have a totally different format, which make them less easy to read.

A small example:

before:

it('should login as superAdmin', () => {
  expect(
    appPage
      .loginAsSuperAdmin()
      .getHeader()
      .getHeaderProfileUserFullName()
  ).toBe(data.components.header.profile.user.fullName.superAdmin);
});

after:

it('should login as superAdmin', () => {
  expect(appPage.loginAsSuperAdmin().getHeader().getHeaderProfileUserFullName()).toBe(
    data.components.header.profile.user.fullName.superAdmin
  );
});

I always loved Prettier from day one, even though the formatting was not always to my liking but it is one thing less I have to worry about as a developer. But in this case I really think there are a few things to consider and maybe reconsider.

From the documentation:

Developers often use whitespace to break up long lines for readability. Prettier, on the other hand, strives to fit the most code into every line.

But I 😶 … … you said … 🤔 Wasn’t Prettier’s purpose to make code better readable …? 🙄

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 https://github.com/prettier/prettier/pull/4765#issuecomment-605457007 here 😃

Thanks again for your time

@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

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.

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
}

Yet another example that goes on my nerves:

const result = Object.values(this.values || {}).filter((param: Something) => param?.value)

that I’d normally format like this:

const result = Object
  .values(this.values || {})
  .filter((param: Something) => param?.value)

or to emphasize the source of values:

const result = Object.values(this.values || {})
  .filter((param: Something) => param?.value)

prettier literally forces me to format like that, which makes no sense whatsoever:

const result = Object.values(this.values || {}).filter(
  (param: Something) => param?.value
)

Yet another reason not to use prettier…

@epidemianThat solution comes with its own costs” - you know that’s a really bad excuse, right? It’s not about complexity, it’s about demand. If enough people decide to ditch prettier because they really hate the chain formatting, the option will be added to keep them / bring them back.

So, let’s not pretend like it’s about valid arguments, it’s not - it’s, as always, about argumentum ad populum and sadly nothing more.

I’m on the look for alternatives, because I simply cannot live with this chaining formatting, even at a cost of losing all other benefits - it really is that bad for me and there’s no discussion there, it’s a personal choice.

@jfairley I don’t think his issue should be left closed. A lot of people seem to support the suggestion, and I myself strongly believe that there should be an option for prettier to preserve existing line breaks. May I ask you to re-open it? I believe, if you’re not interested in seeing it in https://github.com/issues any longer, you can just hit the “Unsubscribe” button. But I would be more than happy if you and @thorn0 stayed in this discussion, it’s a shame it got heated this way…

@jfairley I compared method chains to other expressions because JCQuintas was annoyed that the way things are printed changes when the line gets long. My point was that that’s how Prettier works in general, with any kind of expression. So why is he not annoyed about other expressions, about the fact that Prettier changes their formatting when they’re modified? You’re however seem to argue about something different.

verb verb verb noun noun verb noun noun verb noun noun verb

Somehow we’re communicating now without putting every sentence on a new line. Yet the text is still readable. How is it possible? 🤔 whatisthesecrettothisphenomenoniwonder

Also the verb-noun metaphor looks really questionable to me because functions are values in JS.

I will concede that when I first started using prettier, I was a little upset that it was breaking all of my chained methods across multiple lines.

So you had a habit. Back then, would you write as much to defend it?

I actually prefer how much clearer it is.

Sounds like a new habit. Every change in Prettier is bound to conflict with someone’s habits.

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.

… it would be really awesome i think if Prettier could understand d3’s semantics and format chained method calls as the library intends, like:

d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .enter().append("p")
    .text(function(d) { return "I’m number " + d + "!"; });

What could Prettier do to support such indentation rules on d3?

Maybe allow library authors to define custom indentation semantics for their libraries, like saying ".enter() should increase indentaton by 1, .exit() should decrease it by 1` on something like that, such that Prettier or other formatters could read this and understand how the semantics of the library work and format code accordingly. Just to throw an idea out there.

This is not what Prettier was designed for.

I can imagine an autofixable eslint ruleset (plugin) enforcing this. Prettier is not the right tool for this, nor is it fair to request such niche feature from the team. Moreover, any such eslint plugin can work alongside Prettier - after Prettier has done it’s job.

Prettier was designed to produce a uniform machine-readable code, albeit unarguably ugly, but still at least uniformly ugly - and that’s something you can count on. No other tool can guarantee you a similar experience shared across the whole team, without endless bikeshedding. It’s a true Plug&Play experience.

Before Prettier, at one company, we held semi-weekly meetings just for discussions about code styleguides and we even maintained a shared repository for this. That’s a lot of resources used without any measurable output, just like tests or documentation, and no sensible company wants to spend extra resources just so that the developers can enjoy their time. After Prettier, the burden has moved from the company to the developer and it’s up to everyone how they deal with it internally. Some may burn out, some don’t.

For me: I have experienced so many instances where Prettier actually made the code less readable, comprehensible and maintainable, increasing the mental overhead, that I eventually gave up.

Do I hate what Prettier does? Yes. But did that stop me from using Prettier or leave the company where they enforced Prettier? Also yes & yes. Do I use eslint? Yes.

Either let Prettier in like Jesus, or move. You can’t fight it, otherwise you are in for real troubles and I speak about mental health and serious burnout.

I really love how Prettier has simplified my formatting overall, but now that I’m starting to leverage rxjs I’m finding its resulting in very inconsistent and unreadable code.

I honestly just want function parameters and arrays to be treated the exact same way as object literals:

1 - prefer single line (up to print width) if there is no newline after the opening parenthesis 2 - multiline if there is a newline after the opening parenthesis

I do agree the guidelines need to be altered. It seems as though concessions had to be made for object literals but the project maintainers do not want to apply those same concessions to function parameters. Some flexibility would go a long way to solving this, and the only price that would have to be paid would be slight git diffs if a developer explicitly puts in a newline after a parenthesis.

One thing i’m having trouble understanding is that, if the need for having each method call on a separate line is so strong, why not make each method call a separate statement?

Like:

// convert this:
domain.concept('Page').val('title', 'string').vals('widgets', 'Widget')
// into:
let page = domain.concept('Page')
let title = page.val('title', 'string')
title.vals('widgets', 'Widget')

(i don’t know about the semantics of this code, so i don’t know if those variable names make sense)

Prettier will not join different statements in the same line under any circumstances.

I personally feel like if a statement feels like it’s doing too much, even if it fits on 80 characters or whatever the line-width is, it makes sense to split it up into multiple statements, instead of dispersing the single statement into multiple lines via line breaks. It helps my human mind to process small simple statements, as well as step-by-step debuggers, that let you see the state of the world between each statements 😃


That being said however, i’m not against the idea of keeping those line breaks, even if the collapsed statement fits in one line. It’s clear that many people are trying to convey some meaning with those line breaks (though i don’t agree with the method of separation), so it makes sense for the formatter to keep them, and be consistent. E.g.,:

// in: two method calls on the same line when the rest are separated
something
  .with()
  .aLong().method()
  .chain()
// out: separate them consistently
something
  .with()
  .aLong()
  .method()
  .chain()

// in: the method chain starts in the same line as the receiver
something.with().aLong()
  .method().chain()
// out: collapse it into a single line to make all method calls equal
something.with().aLong().method().chain()

Of course if the chain would go over the line width, it would get broken into lines as it does now.

Prettier already has a similar logic for object literals, so it wouldn’t be an out-of-place behavior.

I personally wouldn’t like this behavior a whole lot, since i prefer not having to “hint” the formatter on what to do, but could live with it 😃

WDYT?

I started with prettier well over a year ago when it seemed like the original set of options was opinionated but stable. The same with other formatters and linters, as regarding the defaults you could take them of leave them. If I configured my prettier settings to X, I had expected that they’d behave in the same way down the track as the prettier standard, for lack of a better term, would remain intact.

With release of v2 I’ve been able to fix a couple of defaults-changes, which were unpleasant in my opinion (trailing commas, arrow function parentheses), but at least they were configurable. Now with this behavioural change I’m stuck with reverting to v1 to retain my code structure as my only option preventing entire restructurings of all of my projects.

To be clear I’m not trying to be snarky, but this was quite a shock and I’m definitely on the side of the bench that wishes this behaviour had been left alone.

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)

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

There is no one that is going to make a PR for this if it is only going to get rejected. The project owners need to make a final decision. The community representation, at least in this thread, overwhelming want this support. Examples laid out. Arguments made. Counter-points. It’s all been done. The community wants this, the ball is now in the repo owner’s hands to approve or reject. This needs to happen before ANY code is done.

there must be a reason why the old version is still so popular (comparing download count for the last 7 days): 1.19.1 => 8.379.228 2.5.1 => 4.539.628

I know it’s a long post. Hopefully it is beneficial to the discussion to layout the various cases for points for further discussion. In short:

  1. Follow the first line break and the break after each block
  2. Respect relative indents (0 or 1 tabWidth)
  3. preserve post first break / inlines
  4. Don’t indent after block
  5. Inlines exceeding printWidth become newlines
  6. Disallow indentation after lineLength < tabWidth
  7. Excessive chains (>= 5) are newlined

I appreciate the difficulty getting this right for every case, it may be more about avoiding disaster than guaranteeing you always get the same output, as there are times the developer knows best.

Case 1

const value = something.a()
.b()
.c();

becomes

const value = something.a().b().c();

Case 2

const value = something
.a()
.b()
.c();

becomes

const value = something
    .a()
    .b()
    .c();

Respect the first break


Case 3

const value = something
.a().b()
.c()
        .d();
.e();

becomes

const value = something
    .a().b()
    .c()
        .d()
    .e();

Respect relative indents (0 or 1 tabWidth) & preserve post first break / inlines


Case 4

const value = something.a((data) => {
    handle(data);
})
    .b()
    .c();

becomes

const value = something.a((data) => {
    handle(data);
})
.b()
.c();

Don’t indent after block


Case 5

const value = something.a((data) => {
    handle(data);
}).b()
    .c();

becomes

const value = something.a((data) => {
    handle(data);
}).b().c();

Respect first line break after block

Inlines exceeding printWidth become newlines

Case 6

s
    .a()
    .b();

becomes

s
.a()
.b();

Disallow indentation after lineLength < tabWidth

Case 7

const value = something.a().b().c().d().e();

becomes

const value = something
    .a()
    .b()
    .c()
    .d()
    .e();

Excessive chains (>= 5) are newlined

It’s really a deal-breaker for update to v2… Is there any plans to change/modify this behavior? Prettier becomes such opinionated that’s too hard to put up with it! Meh…

@thorn0 you’re not playing fair in this discussion IMO. We’re trying to say this is not only about preference but also quality & readability, but you discard our constructive arguments with dogmatic responses. I understand why @jfairley left. His arguments were very well constructed, and the verb / noun metaphor was perfect. Your response is quite demotivating when you imagine the time he spent crafting good arguments to help progress the discussion & the project. Same goes from previous discussion where you keep trying to narrow the debate to finding heuristics when it’s clear there is no way to find a solution with heuristics; examples given. When @RikJansen81 also pointed out the issue you’ve been linking was not a good rationale, you even evaded the response by saying you were leaving the discussion, rather than just re-read the issue to see the bug mentioned there. The original issue post you link litterally assert that line preserving is wanted by the majority, and that he experience some weird bug where lines sometimes wrap sometimes don’t. People wanted the feature because they thought it would be better. Now we have the feature, just ask feedback from those who switched their project… if a majority of real users of the new version prefers the new formatting in their real codebase (not short snippets easily manipulable), then I’ll be sorry for the noise, but I feel like the consensus around me is that it’s globally worse. (Event outside of github, with people I work with)

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.

@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.

But, how would Prettier know if a line-break was typed by user or not? How would it distinguish between code typed “manually” by a programmer from code that was generated or formatted by a tool (e.g. Prettier itself most likely)?

I’m very contempt with how JetBrain’s folks have managed to implement this:

image

I think if Prettier is going to add breaks to a chain, it should do all the chained methods at the same level, for consistency.

Right now, this output is possible, but shouldn’t be:

// input
something.concept('Howdy!').format('upper').reverse().upsideDown();

// output
something.concept('Howdy!').format('upper')
  .reverse()
  .upsideDown();

// preferred
something
  .concept('Howdy!')
  .format('upper')
  .reverse()
  .upsideDown();

And if chained methods already have breaks in them, Prettier doesn’t need to maximize the amount of code on a single line by collapsing them - just ensure each level is consistently formatted.

This request was opened almost three years ago. All the requests for this option are legitimate. Jesus, can you just add the option? I love how being “opinionated” means you can hold the “development high ground” and, while looking down, ignore the users of your tool. FFS…

This definitely needs to be configurable. If it cannot be configured to respect code author’s style, it is, sadly, not very helpful.

My vote is +1 for the preservation of intentional line breaks. So is there a clean, stable way to identify intentional line breaks?

My suggestion would be a line break in between the first and second parts of the chain:

// Intentional, format this to multiple lines
const intentional = moment()
  .utc().add(1, "day").toISOString()

// Unintentional, follow normal rules
const unintentional = moment().utc().add(1, "day")
  .toISOString()

The original code would need a concession to work properly:

// Instead of this, which does not signify intent:
cy.get(".ready")
  .should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)")

// Use this:
cy
  .get(".ready")
  .should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)")

To me, fluent API’s thrive at breaking apart larger, more complicated problems (frequently sequential) into smaller steps. The ability to scan those steps can be absolutely crucial to reading and understanding code written with that style. It’s an API style where formatting has a large impact on its usability, hence the passion behind this topic.

Thank you all for your dedication and passion!

@thorn0

This first section of my comment is purely hypothetical, but I’d argue writing code like your example isn’t something I’d accept as ok anyway.

foo(
  reallyLongArg(),
  omgSoManyParameters(),
  IShouldRefactorThis(),
  isThereSeriouslyAnotherOne()
);

… ought to be …

const arg1 = reallyLongArg();
const arg2 = omgSoManyParameters();
const arg3 = IShouldRefactorThis();
const arg4 = isThereSeriouslyAnotherOne();
foo(arg1, arg2, arg3, arg4);

because in reality some of those functions would have 1 or more arguments, as most standalone functions do have 1 or more arguments… Otherwise why would it be a function rather than a const? In the scenario that any of those functions have arguments, you clearly ought to have them pulled out into variables.


But that little pick aside, I do understand the argument you’re trying to make. My counter is that function arguments IRL are simple concepts like:

foo(thing1, thing2)

NOT:

foo(
  goGetThing1Given(A),
  goGetThing2Given(A, B)
)

The 2nd example is just sloppy programming.

So … given that function arguments consist of simple concepts –– or you may say an array of nouns –– it’s reasonable to lay them out horizontally across the screen, because they’re easily read and digested.

verb(noun, noun)

Chained methods, on the other hand, are not simple; they’re complex –– or a mix of verbs and nouns.

moment()
  .utc()
  .add(1, "year")
  .add(6, "months")
  .add(12, "hours")
  .toDate()

In this example, translated, you have:

verb()
  .verb()
  .verb(noun, noun)
  .verb(noun, noun)
  .verb(noun, noun)
  .verb()

Or to go even further by removing the notation:

verb
  verb
  verb noun noun
  verb noun noun
  verb noun noun
  verb

Without the pretty color coding of an IDE –– which we’re certainly much more reliant on when chained methods are all on the same line –– it’s much more difficult to digest when written as:

verb verb verb noun noun verb noun noun verb noun noun verb

Breaking based on the function of the word that you’re reading brings it more to a challenge in lexicology. Ask yourself, “why are we breaking in the place where we are?” I would argue that it’s all about readability. Sure as hell, the compiler doesn’t care, and I also doubt we care about file sizes (yes, line breaks introduce at least 3 more characters per break).


I will concede that when I first started using prettier, I was a little upset that it was breaking all of my chained methods across multiple lines.

Things like:

moment().utc().add(1, "day").toISOString();

became

moment()
  .utc()
  .add(1, "day")
  .toISOString();`

but I learned to live with it, and to my surprise, given the recent whiplash to the past (prettier v2), I realize that I actually prefer how much clearer it is. LOC be damned, chained methods broken is super clear, and if a codebase deciees to go with an automated “this way” or “that way” ruleset (prettier), I much prefer the line breaks.

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?

👍

I disagree with the initial assumption. I think it is a failure to understand the problem that leads to semi-usable semi-pissing-off solutions that force-format what should never be touched by prettier. I am still of the opinion, that the developer knows better.

Kind of related, this is a current typescript output for prettier

//capitalize first letter, replace "_x" with " X"
const localize = (s: string): string =>
	s
		.split("_")
		.map((str) => capitalize(str))
		.join(" ");

s hanging alone looks really odd.

this one issue was annoying enough to me that I trashed prettier entirely.

https://github.com/facebook/react-native/issues/26903#issuecomment-581074476

We really hope you reconsider this.

@ackvf What that piece of text is trying to say is that Prettier is a stupid computer program that needs to follow an algorithm, while humans can use their own judgement (for better or worse). The core algorithm Prettier uses is “try to fit roughly X characters per line, otherwise break” (but many times we break earlier of course). So it’s explaining how Prettier generally works – it’s not trying to justify cramming as many method chains in one line as possible. For that we have no good solution yet.

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…”

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.

Can you give some examples?

Two examples picked from this thread (hard wrap set at 80 columns):

// Chained method calls: Do not wrap
cy.get(".ready").should("have.text", "READY").should("have.css", "background-color", "rgb(136, 228, 229)");

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

// Chained method calls: Wrap if long
cy.get(".ready").should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)");

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

// Chained method calls: Chop down if long
cy.get(".ready")
  .should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)");

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

// Chained method calls: Always wrap
cy.get(".ready")
  .should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)");

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

// Chained method calls: Always wrap (align when multiline)
cy.get(".ready")
  .should("have.text", "READY")
  .should("have.css", "background-color", "rgb(136, 228, 229)");

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

However, I did notice “wrap if long” option did not produce the same output if the chained method calls were already each to their own line. This seemed to be regardless of unchecking “Keep line breaks on formatting”. Points lost on this but there doesn’t seem to be many on this thread who’d prefer the “wrap if long” option anyway (myself included).

Edit: chop down if long corrected. It seemed to also suffer from the above issue. I guess i’ve been happy with it since it never unwrapped anything I wrote 🙂. One reason for not noticing the issues is likely the fact I haven’t been able to use it’s formatting at work for the last 5 years.

Funny thing though. Objects are treated differently. Whether I write

const a = { prop: 10 };

or

const a = {
    prop: 10
};

prettier will not change the line breaks no matter the line length or fields count, which is great and exactly what I need.

However arrays, function arguments, and method chains don’t get the same treatment.

@ristomatti I think the amount of people asking for chained methods formatting tells you that the current default is not at all sensible.

I don’t know why people are so detirmend to ignore an undeniable fact that some of us are saying: “It is VERY HARD for me to read unbroken chained methods!

You can call me old or blind or even stupid for all I care - none of that will change that fact.

There really is no argument here other than “shall we make the development experience A LOT easier for so many people like myself, or tell them to f-off”…

By far the biggest reason for adopting Prettier is to stop all the ongoing debates over styles.

So not true! In every single project Prettier was used simply because devs were lazy as f… and just didn’t care how things are spelled. Having prettier format the file before saving makes them happier.

I am the kind of guy that does care and it pisses me off that I have to work in such an environment where the length of the line is more important than the meaning of what it expresses.

@epidemian I’m someone who decided to try out Prettier, and was ready to go ahead with it if it weren’t for this issue. The solution was to stick with 1.x, which is obviously a non-starter if you’re looking at integrating a tool into your development pipeline for the long-term.

I understand that Prettier is opinionated – however, when you build a user-base with one semantic and then make a non-configurable change that is essentially a complete reversal, there is going to be fallout. It erodes users’ confidence that care will be taken to avoid similar changes in the future. The maintainers are of course free to do what they wish, but nobody should really be surprised by the result.

@JCQuintas

Let’s apply your logic to other expressions. E.g. to function arguments:

foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis());

Add one more argument, and Prettier reformats the expression into:

foo(
  reallyLongArg(),
  omgSoManyParameters(),
  IShouldRefactorThis(),
  isThereSeriouslyAnotherOne()
);

Which is expected, right? So how are method chains different?

BTW, could you show examples of real-life code that Prettier formats in an unreadable way? Your string.replace example doesn’t look like real code.

I would really like if anyone here who wants to say something in support of preserving existing new lines first reflected on https://github.com/prettier/prettier/issues/2068#issuecomment-307247952 and realized how such situations defeat the whole point of Prettier. Thanks is advance.

@ackvf

but still at least uniformly ugly

Ugly is ugly, no matter how you want to frame it. If a tool works against the developer, it is a tool not worth using. Period.

I use Prettier despite certain drawbacks as it makes things simpler in general. What makes this a really bad change is that it reduced readability.

Using indents to convey meaning is not that common, and I personally prefer using closures to represent new concepts in DSLs. In addition this fixes a lot of poorly formatted code. So I’m not in favor of preserving arbitrary indents. I am however in favor of preserving chaining style as that is very often used by DSLs.

foo.bar.baz
->
foo.bar.baz

foo  .bar.baz
->
foo.bar.baz

foo
  .bar.baz
->
foo.bar.baz

foo
  .bar
  .baz
->
foo
  .bar
  .baz

foo
  .bar
    .baz
->
foo
  .bar
  .baz

foo.bar.baz.foo.bar.baz.foo.bar.baz.foo.bar.baz
->
foo
  .bar
  .baz
  .foo
  .bar
  .baz
  .foo
  .bar
  .baz
  .foo
  .bar
  .baz

This is not a breaking change for v1 or v2.

This might sound a bit like a tangent but, something that happened to me a couple of times using Prettier is that it made me rethink and eventually change my opinion on formatting-related things on which i felt pretty strongly before.

There were things, like formatting of function’s arguments, or using ' vs", which i used to care about a lot, but after seeing Prettier go through my code —and my coworkers’ code!— and making it all that much more consistent, i no longer think those things are really important, and i appreciate the freedom of not having to think about them. I still disagree with Prettier on some things from time to time. But those things became quite less important compared to having a consistent formatting style across a team, and across many many projects which use Prettier too! I think that things like naming of variables/functions and code structure are much more important to readability than personal formatting tweaks.

With that in mind, and maybe that’s not the case for you but, if you haven’t done so already, i would recommend giving current Prettier a try, for a little while at least, and especially trying out the “write without thinking of formatting, save -> formatted” flow. For me at least, this made a huge difference 🙂

Here’s an even better one 😃 Man I love the way Webpack Chain works 😃

config.module
  .rule('compile')
    .test(/\.js$/)
    .include
      .add('src')
      .add('test')
      .end()
    .use('babel')
      .loader('babel-loader')
      .options({
        presets: [
          ['@babel/preset-env', { modules: false }]
        ]
      });

And here’s what prettier did with it:

config.module
  .rule("compile")
  .test(/\.js$/)
  .include.add("src")
  .add("test")
  .end()
  .use("babel")
  .loader("babel-loader")
  .options({
    presets: [["@babel/preset-env", { modules: false }]],
  });
const result = new App()
  .login().with('johndoe', 'abc123')
  .goToShoppingCart()
  .selectFirstItem()
  .remove()

This is a hypothetical example, but in end-to-end tests it is very probable. Sure you can structure it differently but why? If you put that with() call in the next line it looses context. Nobody would know that it relates to the login().

Here’s another example, this time from Webpack Chain:

config
  // Interact with entry points
  .entry('index')
    .add('src/index.js')
    .end()
  // Modify output settings
  .output
    .path('dist')
    .filename('[name].bundle.js');

Of course, we’re not talking here about new lines but about indentation, quite different from what prettier would like.

White spaces give code meaning - be that new lines or spaces. That’s why I strongly believe a tool such as prettier that disregards user’s choice is just the wrong thing to use.

@epidemian See, here’s the thing:

Making any arbitrary decision regarding line breaks (maybe except for splitting things when the line length is exceeded) is going to fail in one form or another. The example you have outlined might actually be a depiction that some things are logically grouped, and therefore it makes perfect sense to put them in the same line. Even more, sometimes people do double or even deeper indentation of method chains (especially when using reactive programming!) and it is crucial for understanding the code!

As for introducing intermediate variables… well… Let’s put it this way: coming up with ways to satisfy a tool and no other reason (because those variables would definitely not add to code clarity) is wrong on so many levels!

As for me, just wanted to let you know guys that I have stopped using prettier and literally banned it in the projects I lead. It was just more trouble than it is worth. I’m getting along just fine with eslint’s hints and VSCode quick fixes bundled with husky that checks my code before I check it in.

@ackvf Might want to take a breath. I understand there is some frustrating behavior (it’s why I’m here), but insulting and yelling isn’t going to accomplish anything.

Minor note about your quote of my use of objectivity : The way you cut it out of context is misleading : I was saying that in some specific context, some pieces of code are made by their author to be read vertically (e.g. fluent apis when few context needed (so vertical space is not an issue), when code geometry and line similarity helps understanding it in once glance, etc), and for those cases, I highly doubt anyone would prefer to have it inlined beside having OCD bias toward code golfing. Thus, my initial request to support both.

regarding my ridiculous “interpretation” of your expect() example, think whatever you want; with your clarification, I keep understanding that post-refactoring, you feel the need to decompose your test into several statements for clarity, while you didn’t felt the need to do it before. I leave this fact to you analysis, can’t do much more.

Otherwise, you’re right, my tone was bad. I’ll stop here as I indeed seem to be contributing negatively to this discussion; The discussion is subtle, and I got annoyed some arguments. I’m sorry

@rvion IDK, i feel like what @thorn0 is actually the most constructive approach.

Ok, thorn0 is a contributor, I’m not; I’m grateful for this awesome tool, and I know formatting is a very sensitive topic BUT:

@epidemian Seriously ?

We say:

  • We would prefer to have both formatting automatically AND the ability to preserve line breaks because it makes our code that use fairly standard patterns globally worse and way less readable (tests frameworks, fluent apis, etc)

  • We think that in that specific case, line breaks convey enough SEMANTIC informations to be worth preserving because code is meant to be processed by humans this way and humans eyes work the same way, so we objectively know it’s now worse for anybody.

  • We show that no heuristic can detect cases where inlined code make no sense, only humans with their brain can because it depends the semantic of what we express (exemples with the verb / noun metaphor)

While:

  • he says preserving line breaks is a PITA for end-users (obviously wrong stated this way as all typescript+prettier devs I’ve worked with naturally insert newlines or join lines to optimize prettier output and has “format on save” enabled)
  • he justify his position by linking to an unrelated issue related to some unstable prettier behaviour that also happens says most devs want the power to preserve line breaks

and most importantly

  • he do not respond to our arguments but instead try to convince us it’s a matter of preference while there is obviously more to it that just preference (disregarding our arguments).

ALSO:

If Prettier did this, it would be another reason for adding a config option

NO, it’s already the case for objects and everyone I know is fine with it without options.

And if an option is needed or requested… why not adding one ? It would only impact those who want it, would be backward compatible, and it would not make the tool harder to use at all…

It would still be transparent for everybody, as we all lived happily until now without this option.

It would dilute the value of Prettier to the programmer, as they would now need to decide the formatting for these things. Or, if they don’t want to decide, they would inevitably have to live with inconsistenly formatted chained methods. It would also be an invitation to bikeshedding between people who prefer one output to the other, which is exactly the sort of pointless discussion that Prettier helps to avoid.

prettier authors themselves wrote in the project rationale : https://github.com/prettier/prettier/blob/76efb33e75365aceab508b0504f0e6f5ba2dd540/docs/rationale.md#multi-line-objects

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. This is the same strategy used by elm-format for multi-line records.

and… plot twist, we’re exaclty speaking about those cases where we need vertically aligned siblings in chained method.

(thanks @RikJansen81 for finding this)

so, NOTHING is diluted… again, look at objects. People are fine with those. Devs currently live with inconsistencies related to objects, and from what I see, those inconsistencies ARE A FEATURE so long as it is stable and readable. Code would be worse without.


@epidemian also, I’m sorry to be the bad guy here but for what I understand part of your previous messages seem to imply you agree with us for some parts, maybe without realising it;

📝 1

Before, the rules of splitting into multiple lines were super confusing, as they depended on the number of chained methods, so two things that looked very very similar, but differed only on one chained method more, got formatted completely differently.

NO; it’s mosly the opposite. NOW (with v2), lines that differs only in a few chars will suddenly be made into one line or many, when you add more members; BEFORE, it was consistent; the linked PR even suggests to add more heuristics and make the behaviour more cryptic.

📝 2

expect(
    wrapper
      .find('span')
      .children()
      .text()
).toBe('It renders!')

Which have now been collapsed to:

 expect(wrapper.find('span').children().text()).toBe('It renders!')

Even though the second code snippet is not great (it could really benefit from extracting the wrapper.find(‘span’).children().text() into a variable), I think it’s definitely better than the first one. The first one seems like the sort of thing a person would never write by their own.

❓ => To sum-up your position, you suggest to refactor perfectly readable code into something else with intermediary variable because with v2, your code is inlined and suddenly becomes unreadable ?

You then proceed to give an other refactoring example that has nothing to do with the V2: you could have already perform the refactoring with V1. If the code suddenly annoys you, it’s not because the code needs refactoring, it’s because code formatting suddenly made it less readable.

📝 3

Also, there is a problem with explaining something is better because some code example is better;

  • You can proove something with counter-examples (not all numbers are divisible by 2 because 3 is not)
  • You can NOT prove something by giving an example (all numbers are divisible by 2 because 4 is => absurd)

I think we ALL agree that inlining is nicer in some cases. This issue is about preserving intent when we SPECIFICALLY WANT to.

Then why not go with printWidth: 80 @hwalinga?

Yes @mvlabat, reopened. By closing the issue, I overreacted.

I believe strongly in contributing to community projects when I can, even though that’s usually not more than conversation. From this one, I’ll step away from the conversation. My thoughts have been expressed adequately. I trust that the group will come to a consensus that makes the most sense for prettier.

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.

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.

  • current behavior as the default
  • always break as an option.

@ristomatti This would be pure awesomness…

Yes it would. I’d also be fine with an option for n number of statements before breaking opposed to following the printWidth. For example setting three statements as the break parameter:

myObject.thisWill().notBreak()

vs

myObject
  .thisWill()
  .now()
  .break()

@epidemian Well, that request is not reasonable. Prettier should not have to track what linebreak was added by a human vs. by some tool… Same input should produce identical output.

I think (hope) that request was meant to say “For method chaining, existing newlines should not be erased.” - which I would also think is not good, btw.

IMO, prettier should break based on printWidth and/or chain length.

  1. default - what it does now
  2. break if chain length > X (where X can be 0, meaning always)

I’d like to meet those devs that have decided on some of the opnions encoded in prettier - like the method chain formatting based on line length. I’d like to meet them to give them some webpack chain configuration to read and extend 😄 That’d be a killer feature to convince them they’re wrong 😄

Prettier IS opinionated, and by that it means the opinion of the devs. That’s the thing about using opinionated tools, when you use those tools you’re also adopting their (the devs’) opinion.

In other words, if something is really hard for you to read, f-off…

How much longer do we talk about it? lul, This “issue” is over 2 years old now. Holy crap?! I am all for methodical, slower moves, but this is kind of ridiculous. Make a decision and move on.

Guys, I think it is obvious by now that Prettier’s creators just are not interested in solving it. Prettier is viewed as a tool that knows better. In some cases, when devs are very young, it actually is better! However, when you have any time under your belt as a programmer, you will notice its downsides and that is the time you should look for alternatives that maybe more tell you about things you did wrong and that will allow you to tell it what wrong actually looks like. Basically, prettier is a fantastic tool for learning but has no place in real-life projects. A proper linter and discipline when writing code is the way to go once you’ve realized the limitations of prettier.

Trust me, being pedant about your code isn’t that difficult once you learn how things should look like. Me, for example, when I look at code that has something wrong I can immediately spot it, it hurts my eyes. If I miss something, or if in the project some things are spelled differently, I have the linter to remind me about it.

@Andy2K11 I am struggling to understand the issue here? What do you mean by “how could we resolve this difference”?

There is one and only one logical solution: pick a default behaviour and add an optional config override for the other camp. Problem solved.

I honestly do not care which default is set, so long as I can override it if I need to. And in this case I really need to.

Thanks for providing these examples @padcom. They reveal an extra level of method chaining complexity that i was definitely not considering!

There’s a lot of intent and structure in those examples that is lost when applying a general rule of “one line per chained method, all indented at the same level”.

Of course, we’re not talking here about new lines but about indentation, quite different from what prettier would like.

Yep. And it seems like a really hard problem for Prettier to solve generally, because these “structures” are not present on the syntax itself, but on the semantics of each method, e.g. the .include() / .end() being a sort of “block” on the Webpack config.

I cannot really think of any way in which Prettier could handle those more complex examples keeping the indentation structure, besides foregoing automatic indentation of nested method calls altogether (well, maybe keeping basic rules like “indents must be a multiple of 2 spaces”) and letting that to the programmer. So now i understand why you’d prefer that 😅

I think it’s also unfortunate that a // prettier-ignore comment before one of these complex method chains makes Prettier ignore all formatting. For cases like this it would be nice if at least one could // prettier-ignore indentation or something like that, such that Prettier would still take care of stuff like .foo( badSpacing, 42+a*b,) inside the chained method calls but not of their indentation. I have no idea how much implementation complexity something like that would entail though 😕

@rvion the tone of your message puts me off of this conversation. There’s a couple concrete things I want to leave very clear:

[…] so we objectively know it’s now worse for anybody.

It’s not worse for me, if it wasn’t clear from my message “I’m really in favor of this change overall 😃”. Sorry for maybe not being “objective”, or having a different preference.

Part of Prettier’s value to me is to be able to just write code without worrying about formatting, and then having it formatted automatically. I don’t like having to sometimes tweak the line-breaks of small objects to make them collapse into a single line. I wouldn’t like having to do that to chained methods. And I certainly would not like to have discussions about those things on code reviews when it’s something that an automated tool could just do for us.

NO, it’s already the case for objects and everyone I know is fine with it without options.

“Everyone I know” might not include many people. Please see the already linked #2068 as an example.

NOW (with v2), lines that differs only in a few chars will suddenly be made into one line or many, when you add more members; BEFORE, it was consistent;

That’s the same rule that Prettier applies to everything: if it goes beyond print-width, try to break the lines somehow. Happens with assignments, binary operators, arrays, function calls, almost everything I can think of really :S

❓ => To sum-up your position, you suggest to refactor perfectly readable code into something else with intermediary variable because with v2, your code is inlined and suddenly becomes unreadable ?

This sort of tone… Isn’t it obvious that that is not what I meant? You could’ve asked for clarification, but no, better to just assume I meant something completely ridiculous I guess…

If it wasn’t clear, what I meant was that the first expect() example, breaking stuff into 6 lines, is something that I would never write by hand, and I find it extremely weird to read. The one-liner is more natural I think. But both would benefit from decomposing that statement a little bit and extracting at least the expect() argument into a variable.

And that’s something I personally care more about than formatting: good code structure. Breaking a complex statement into different lines is not decomposition, it’s just formatting; the statement remains just as complex. Breaking complex statements into simpler ones is more valuable. And BTW Prettier will alwas keep those different statements in different lines, so win-win.

I think that’s pretty much all I have to say about this issue. But please, check the tone of your future messages @rvion. This came off as very condescending:

[…] for what I understand part of your previous messages seem to imply you agree with us for some parts, maybe without realising it

One thing I want to add to the print-width configuration is that, for me it configures 2 things. The first one is: If a line exceeds x characters break it up. The second one is, if you can combine lines don’t exceed x. In my experience it would be nice if it can be configured to be two different numbers.

So, for me it would be break up lines if they are longer than 120 characters, but don’t create new lines that are longer than 80.

Even if people have 120 characters configured they most likely prefer that prettier doesn’t rewrite lines so that they are longer than 80 characters. Their only preference is that lines that are already longer than 80 characters but still shorter than 120 aren’t shorten.

This becomes especially apparent for people that want to guard their line length themselves and set print-width to 1000 of something. They just don’t want to see lines shortened, because they want to manage it themselves. However, if you do this with prettier it will try to combine method chains all in one chain.

I think @epidemian shows a good example for this where it is clear the the rewrite to 120 characters is not preferable if the line breaks were there before.

As a different data point, I’m quite happy with the formatting Prettier 2 did to our codebase. We had many statements like this in our tests:

      expect(
        wrapper
          .find('span')
          .children()
          .text()
      ).toBe('It renders!')

Which have now been collapsed to:

      expect(wrapper.find('span').children().text()).toBe('It renders!')

Even though the second code snippet is not great (it could really benefit from extracting the wrapper.find('span').children().text() into a variable), I think it’s definitely better than the first one. The first one seems like the sort of thing a person would never write by their own.

I also find the new formatting rules much more “sensical” (is that not a word?) and intuitive: if it fits within the print-width, it stays on one line, if not, it splits. Before, the rules of splitting into multiple lines were super confusing, as they depended on the number of chained methods, so two things that looked very very similar, but differed only on one chained method more, got formatted completely differently.

So, I’m really in favor of this change overall 😃

Now, I understand that sometimes we want multiple method calls to be separated, especially when we have these step-by-step sort of code. But to be honest, I’m not really convinced by the given examples. In most of them, I think the code would read much better if it were actually split into different statements, rather than a long chained one. For instance, the last given example

it('should login as superAdmin', () => {
  expect(
    appPage
      .loginAsSuperAdmin()
      .getHeader()
      .getHeaderProfileUserFullName()
  ).toBe(data.components.header.profile.user.fullName.superAdmin);
});

I think would be much easier to follow if split into simpler statements, by extracting some expressions into variables:

it("should login as superAdmin", () => {
  const headerName = appPage
    .loginAsSuperAdmin()
    .getHeader()
    .getHeaderProfileUserFullName();
  const adminName = data.components.header.profile.user.fullName.superAdmin;
  expect(headerName).toBe(adminName);
});

Note that this is the output with a print-width of 80. With a print-width of 120 the result is different, which I think is to be expected because with that option we’re basically saying “I’m OK with lines spanning 120 characters”, and it’s exactly what Prettier is doing.

(Personally I’m not OK with lines going into the 120-character territory. I think they become really hard to follow. But that’s what the print-width option is for 🙂)

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?

It’s because there is no good automatic rule here. It requires a setting and that breaks dogma. On Apr 10, 2024, at 9:15 PM, Avaranze @.***> wrote: 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.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

See https://prettier.io/docs/en/rationale.html#multi-line-objects:

The semi-manual formatting for object literals is in fact a workaround, not a feature. It was implemented only because at the time a good heuristic wasn’t found and an urgent fix was needed. However, as a general strategy, Prettier avoids non-reversible formatting like that, so the team is still looking for heuristics that would allow either to remove this behavior completely or at least to reduce the number of situations where it’s applied.

What does reversible mean? Once an object literal becomes multiline, Prettier won’t collapse it back. If in Prettier-formatted code, we add a property to an object literal, run Prettier, then change our mind, remove the added property, and then run Prettier again, we might end up with a formatting not identical to the initial one. This useless change might even get included in a commit, which is exactly the kind of situation Prettier was created to prevent.

However arrays, function arguments, and method chains don’t get the same treatment.

So, opinionated, but sensible in the case of object literals.

This conversation is turning rather non-constructive. Let’s please try to keep it constructive and try to look for a solution based on actual examples instead of arguing whether code formatters are a good idea at all or accusing voluntary open source maintainers of having ill intentions.

So, there’s something that i think would benefit from further clarification. The expected behavior described in this issue says:

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

And also quotes this message from another issue:

If the user write them on multiple lines => keep on multiple line

(Emphasis mine)

But, how would Prettier know if a line-break was typed by user or not? How would it distinguish between code typed “manually” by a programmer from code that was generated or formatted by a tool (e.g. Prettier itself most likely)?

AFAIK, Prettier doesn’t have that information. It just has the input code, and it does its best to format it.

Let’s think about a simple example. Suppose we have a page where we want to display the names of non-banned users, so we have:

// Snippet A
displayUserNames(users.filter(u => !u.isBanned()).map(u => u.fullName))

Some method chaining, but everything under the print-width. Prettier is OK with this and doesn’t re-format anything. Cool.

But then a strange requirement arrives: we’d like to add honorifics, like Mr. or Ms., to these names. So we change the code to:

displayUserNames(users.filter(u => !u.isBanned()).map(u => u.fullName).map(name => addHonorific(name)))

And this time Prettier formats it to break up those chained method calls:

displayUserNames(
  users
    .filter(u => !u.isBanned())
    .map(u => u.fullName)
    .map(name => addHonorific(name))
)

Which is good; that single-line statement was getting pretty crowded, and the breaking into different lines makes the code more readable. Notice though, that these lines breaks were not “manually inserted” by us the programmers, but instead were inserted by Prettier.

But, as you could probably imagine in this silly example, this automatic adding of honorifics was a terrible idea. Turns out honorifics are much more complicated than just adding Mr. or Ms., and inferring them just from names was basically impossible. So we scrap the “feature” by removing that last .map() call:

// Snippet B
displayUserNames(
  users
    .filter(u => !u.isBanned())
    .map(u => u.fullName)
)

But then, what should Prettier do with this snippet B?

  • If Prettier formats this back to a single line like in snippet A (as it currently does), then it wouldn’t be respecting those existing line-breaks in the input. But at the same time, those line-breaks were not inserted by a human programmer, but rather by a previous run of Prettier, so maybe it’s fine to disregard them?
  • If Prettier is OK with snippet B and leaves it unchanged, then it would be respecting the existing line-breaks in the input, but would not be respecting the original human-written code that was a single line. But most importantly: it would also mean that Prettier would be happy to accept both snippet A and snipped B as valid formats of the same code. In other words: it wouldn’t be doing its job of formatting the code into a canonical form, and thus it would be opening the gates for eternal bikeshedding between humans about which of the two valid formats should be used.

So i think this is a more complicated issue than it seems. And i cannot see a simple solution to “respect existing method chain breaks” without adding some metadata about who (a human or Prettier) edited which lines and how to Prettier’s input, but that also seems like another can of worms TBH.

But maybe i’m missing something and there’s a simple solution to this. What do you think Prettier should do in this example that could be generalized?

Prettier IS opinionated, and by that it means the opinion of the devs. That’s the thing about using opinionated tools, when you use those tools you’re also adopting their (the devs’) opinion.

I think the bottom line is that there can’t be a tool that is universal and knows best. Coding isn’t line production (even though a lot of companies would love to see it that way). It’s still creative work and as such can’t be done by machines (yet) - including formatting.

Why isn’t ignoring chained newlines a setting that can be turned on or off? Or a setting the enables/disables it (like if it’s enabled, work as it does now, but disabled means ignore it aside from indenting)?

I’ve been mulling this over, and I believe Prettier needs some new guiding principles.

That’s cool n all, but we’re definitely straying too far from the actual issue (method chain breaks), you can start a new discussion instead (given that this is exactly what you’re looking for).

I’ve been mulling this over, and I believe Prettier needs some new guiding principles. Or a fork and new library may be needed, as Prettier doesn’t deliver on its promises. The handling of the version change continues to be a headache.

  • No code should ever be unintentionally altered.
  • Prettier should not be applied unless there is a .prettierrc in the current or any parent folder. No command line flags should be able to override this.
  • .prettierrc should have one required property: “version”.
    • A higher version of Prettier should handle all lower versions. If this becomes impractical, create an extra folder legacy/${version} that contain older versions of the code transformer (code + config → altered code). Bundled with Rollup and frozen. The transformer code should have no side effects, so bundles will be compact and not have security issues. If a vulnerability is found however, legacy bundles may be dropped, effectively making one or more config-version noop.
    • If the version specified in .pretterrc is higher than the version of Prettier, an error should be thrown and formatting should not occur.
  • Major versions of the library should signify a new configuration version, and may not necessarily be breaking.
  • If a bug produces defective code, the fix should involve making the offending transformation no-op for a given config-version. May warrant a new config-version.
  • I would also propose a new test format, and require that all changes to the transformer is accompanied with tests.
Config: Same as .prettierrc, but with a "fileType" property.
----
Input source code
----
Expected result of transforming said source code

What I am proposing is very strict, but at this point I’m just throwing some ideas out there. What do you think?

And something like that?

function chain() {
    const object = {
      /** @prettier-indent off */
      inline: () => object,
      /** @prettier-indent +1 */
      start: () => object,
      /** @prettier-indent -1 */
      end: () => object,
      add: (v) => object
    }
    return object
}

chain()
  .inline().add(1).add(2).add(3).end()
  .start()
    .add(1)
    .end()
  .start()
    .add(2)
    .start()
      .add(3)
      .inline().add(4).add(5).end()
    .end()
     

@epidemian I have to be honest with you and say I really don’t get where you’re coming from with that idea. Had it been so obvious and simple, machines would write the code for us - not the other way around. For that reason alone, tools should assist, not replace the developer. In the end, there is no substitute for thinking, isn’t there?

@padcom,

In some cases, when devs are very young, it actually is better! However, when you have any time under your belt as a programmer, you will notice its downsides and that is the time you should look for alternatives that maybe more tell you about things you did wrong and that will allow you to tell it what wrong actually looks like. Basically, prettier is a fantastic tool for learning but has no place in real-life projects.

It almost looks like you’re trying to start a flame war here. Please consider that some experienced programmers might disagree with that, and might consider Prettier a useful tool, even if they have seen many winters and their faces are not as smooth are they once were. Also consider that Prettier might be successfully used in productive projects or many sized; definitely real-life ones!

Let’s try to keep this discussion constructive, and let’s aim for reaching a better situation, either by improving Prettier’s current behavior (like it was done on #7889), or by setting clearer boundaries of what Prettier should or should not do (which is what was being discussed lately), or something else entirely 😃

@Andy2K11 hmm yes, i think this might boil down to a difference in philosophy/perspective.

As i said, i would personally dislike it if Prettier didn’t do anything about those different method-chian indentation possibilites and left me to manually fiddle with them, opening the door for bikeshedding about the “correct” indent style. After all Prettier is “an opinionated code formatter”. But at the same time, it’s true that if for you and others having this possibility of manually indenting method-chains to different levels is a deal breaker, then yeah, Prettier is not suiting your needs, and i can see the why of these proposals.

@cope,

I am struggling to understand the issue here? There is one and only one logical solution: pick a default behaviour and add an optional config override for the other camp. Problem solved.

That solution comes with its own costs, and it’s not as trivial as it sounds. Adding a configuration options has many drawbacks —the most clear to me being that for each config option that is added, you need to consider how it interacts with all other options, which can quickly lead to a combinatorial explosion of complexity— but you can read more about that on Prettier’s philosophy on options. Or just check issue #40 to see that there’s quite a lot of support (maybe even a silent majority) for having Prettier be opinionated and not having too many options.

@epidemian Your points are well made, what it comes down to is a difference in philosophy:

  1. Absolute consistency and automaticity
  2. Workload reduction and readability

To expand on point 2, and @undergroundwires’ comment, readability isn’t about looking pretty so much as it is about conveying intent e.g. these items are logically grouped and separate from those other items.

Other than adding a configuration option, which is also something prettier tries to avoid, how could we resolve this difference of opinion? Being in the camp that prioritises readability, it’s a deal breaker, I won’t use a tool that overrides deliberate editing choices.

My only fallback in case 1 is to manually trigger a prettier format, then make the few corrections that bring back readability. This does still reduce workload 95% but does more harm to automatic formatting, which is also likely to diverge over time with code changes.

@Andy2K11 i think cases 1 & 2 are very reasonable.

Case 3 tho, i wouldn’t be happy if Prettier did nothing about this code:

const value = something
    .a().b()
    .c()
        .d()
    .e();

And if i understood the proposed rules correctly, it also wouldn’t do anything about the same statement being written as:

const value = something
    .a()
        .b()
            .c()
                .d()
                    .e();

Or some other creative way of indenting back and forth by 1 or 0 indents.

For me personally, some of the reasons i like Prettier are:

  1. automatic formatting,
  2. consistent formatting, and
  3. avoiding bikeshedding about formatting

But if Prettier behaved the way is proposed here, all 3 of these properties would be somewhat diminished for me.

About automatic formatting, i like being able to write code without paying much attention to formatting, copy-pasting and moving stuff around, and then doing ctrl+S and having everything correctly formatted. Not having automatic formatting of method chains’ indentation would mean that i’d have to fiddle around adding/removing indents to make the code look right.

On consistency, notice that if these two very different ways of indenting the same statement that i mentioned in snippets would be valid for Prettier, then inconsistencies on indentation are bound to arise on sizeable codebases, which is pretty sad considering it’s now completely avoidable.

And inconsistencies would inevitably lead to bikeshedding. I mean, if i’d propose the code on the first snippet on a PR i’d totally expect someone to ask why i indented the .d() call that way, and why i didn’t indent .b(), and why the hell is the automatic formatter not catching those.

Now, i understand that many people have voted this issue because they value preserving these manual method-chain line breaks and indentations. I just wanted to explain why this sort of goes against some things i (and maybe others) really value about Prettier.

Only if we completely fail to find a good heuristic should we reach for the last resort, preserving the original formatting.

Maybe it could be great if we could either:

  1. opt out of chain formatting altogether
  2. opt-in for (kind-f) the reverse of what you said: preserve the original formatting unless it breaks some rule (e.g. exceeds printWidth)
  3. setting for ‘break’, ‘inline’, ‘keep original’

I couldn’t agree more with @padcom .

At this point, my project is small enough so that I run prettier, then go through all diffs to revert all files where my chains have been messed up…

As my project grows, prettier is going to start doing more harm than good for me, which is a real shame.

@aprilmintacpineda

As you can see here, the printWidth is 70, yet the produced code is MORE than 70

https://prettier.io/docs/en/options.html#print-width

“It is not the hard upper allowed line length limit. It is a way to say to Prettier roughly how long you’d like lines to be. Prettier will make both shorter and longer lines, but generally strive to meet the specified printWidth.”

Feel free to open (search for) another issue if you think Prettier should work harder not to exceed the print width in this case.

causing an error with eslint’s max-len rule.

We recommend turning all rules that conflict with linters off.

https://prettier.io/docs/en/comparison.html https://prettier.io/docs/en/integrating-with-linters.html https://github.com/prettier/eslint-config-prettier

@aprilmintacpineda the Prettier playground has a button in the lower-right that you can use to copy the markdown that you can paste into these discussions. This would be much better than screenshots.

I’m also experiencing issues with v2 and chaining statements being reformatted to a single line. I had previously used the Prettier Now fork and I don’t recall how these were handled there.

Ideally, method chain breaks would occur up to 90 print width. I find 80 is far too restrictive and a waste of vertical real estate.

Prettier 2.1.1 Playground link

--parser babel
--print-width 90

Input:

tourRouter.route('/').get(getAllTours).post(createTour);

tourRouter.route('/:id').get(getTour).delete(deleteTour).patch(updateTour);

app.route('/api/v1/users').get(getAllUsers).post(createUser);

app
  .route('/api/v1/users/:id')
  .get(getUser)
  .patch(updateUser)
  .delete(deleteUser);


Output:

tourRouter.route("/").get(getAllTours).post(createTour);

tourRouter.route("/:id").get(getTour).delete(deleteTour).patch(updateTour);

app.route("/api/v1/users").get(getAllUsers).post(createUser);

app.route("/api/v1/users/:id").get(getUser).patch(updateUser).delete(deleteUser);

To be honest, having to set a small printWidth, because of a poorly implemented rule, and making it look as a good convention, instead of admitting that this well documented behavior is actually a bug is shameful.

https://prettier.io/docs/en/options.html#print-width

For readability we recommend against using more than 80 characters:

In code styleguides, maximum line length rules are often set to 100 or 120. However, when humans write code, they don’t strive to reach the maximum number of columns on every line. Developers often use whitespace to break up long lines for readability. In practice, the average line length often ends up well below the maximum.

Prettier, on the other hand, strives to fit the most code into every line. With the print width set to 120, prettier may produce overly compact, or otherwise undesirable code.

See the print width rationale for more information.

Please, focus especially on these sentence:

Developers often use whitespace to break up long lines for readability.

Prettier, on the other hand, strives to fit the most code into every line.

THIS DOESN’T EVEN MAKE SENSE

A developer wouldn’t ever do that unless they had a very good reason for it, so why does Prettier deliberately diverges from what is best for humans and intentionally formats the code to the opposite?

@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: https://github.com/prettier/prettier/issues/2068

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

@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?