Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS: Break lines before binary operators (fixes #3806) #7111

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

btmills
Copy link
Contributor

@btmills btmills commented Dec 9, 2019

On Friday, @evilebottnawi requested a PR to resolve #3806 in v2.0 by breaking lines, where necessary, before binary operators rather than after them, putting the operators at the beginning of each line.

Previously, there was special case logic to break lines before pipe operators but after all other operators. I removed the special case handling so that line breaks occur before all operators.

I left the implementation, tests, and changelog as separate commits to help ease review. Happy to squash or make any other changes as necessary to help get this in!

  • I’ve added tests to confirm my change works.
  • (If changing the API or CLI) I’ve documented the changes I’ve made (in the docs/ directory)
  • (If the change is user-facing) I’ve added my changes to changelog_unreleased/*/pr-XXXX.md file following changelog_unreleased/TEMPLATE.md.
  • I’ve read the contributing guidelines.

Try the playground for this PR

@alexander-akait
Copy link
Member

/cc @prettier/core will be great if you look at this

@thorn0
Copy link
Member

thorn0 commented Dec 9, 2019

I thought there was a consensus that 2.0 would be focused on deprecating old Node versions etc. Updating to 2.0 should not lead to massive code reformatting. Otherwise too many people might prefer to keep using 1.x.

@BrianHVB
Copy link

BrianHVB commented Dec 9, 2019

The main 2.0 thread(s) have mainly talked about Node versions, but there have also been many comments on not introducing changes that there is disagreement or lack of consensus on.

But there seems to be a very strong consensus on the proposed fix to #3806. The only argument offered against the change is that it would be breaking and shouldn't be implemented in a minor version. However, for the change itself, there is widespread agreement both in the issue thread and in the JavaScript community.

AirBnB Issue: airbnb/javascript#1281
AirBnB Style Guidelines: https://github.com/airbnb/javascript#comparison--nested-ternaries

StandardJS (based on ESLint, widely used): https://standardjs.com/rules.html

@thorn0
Copy link
Member

thorn0 commented Dec 10, 2019

@BrianHVB You completely miss the point of why the ex-2.0 was renamed to 3.0. I'm not saying that there is no consensus about this change, only that first things first.

@thorn0 thorn0 modified the milestones: 2.0, 3.0 Jan 9, 2020
@thorn0 thorn0 force-pushed the next branch 2 times, most recently from e16f758 to 7d88fb8 Compare January 10, 2020 00:39
@fisker
Copy link
Member

fisker commented Jan 31, 2020

Anyone considered break line both before and after?

const isEventTarget = value != null
  && 
    typeof value.addEventListener === "function"
  && 
    typeof value.removeEventListener === "function"
  && 
    typeof value.dispatchEvent === "function"
  && 
    typeof value.dispatchEvent === "function"
  -
    Also_Nice_With_A_Shorter_Operator_Inside
  ?
    SOME_OTHER_EXPRESSION
  ??
    SOME_OTHER_EXPRESSION
  instanceof
    Even_Longer_Operator

@btmills btmills force-pushed the issue3806 branch 3 times, most recently from ba9d939 to 300b900 Compare February 3, 2020 06:00
@btmills
Copy link
Contributor Author

btmills commented Feb 3, 2020

I've rebased this change on next to resolve merge conflicts, added tests verifying that this change only moves binary operators to the beginning of lines while leaving assignment operators on their previous lines, and corrected the changelog in 300b900.

@VincentLanglet
Copy link

@btmills There is still a conflict

Comment on lines 16 to 19
(isAnyValueSelected
&& node.getAttribute(radioAttr.toLowerCase()) === radioValue)
|| (!isAnyValueSelected && values[a].default === true)
|| a === 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to also change this case where the multiline operators are nested? When I encounter such a case in my code, I usually refactor it so that prettier doesn't make it ugly like that. If I was writing it manually I would write something like:

  (
    isAnyValueSelected
    && node.getAttribute(radioAttr.toLowerCase()) === radioValue
  )
  || (
    !isAnyValueSelected 
    && values[a].default === true
  )
  || a === 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to keep this change as small as possible to improve the odds the team decides to merge it. Based on the volume of 👍s on #3806, putting binary operators at the beginning of lines has pretty widespread support. It helps that the logic change to do that exclusively deletes code. I'm concerned that expanding the scope could reduce the chance it gets merged at all.

Copy link

@phaux phaux Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I didn't mean this comment to show as a "review" of this PR. It was just a random question 😅 This needs a separate issue.

@btmills
Copy link
Contributor Author

btmills commented Mar 2, 2020

@btmills There is still a conflict

@VincentLanglet I'm not surprised one of the snapshots has been updated since I last rebased. Rebasing the branch takes several minutes, so I haven't been actively monitoring this PR to keep it up to date. If the team indicates they're interested in merging, I'll certainly do another rebase as soon as possible!

@thorn0 thorn0 closed this Mar 22, 2020
@thorn0 thorn0 reopened this Mar 22, 2020
@thorn0 thorn0 changed the base branch from next to master March 22, 2020 21:11
@btmills btmills force-pushed the issue3806 branch 2 times, most recently from f5f93ae to 8723d6b Compare March 22, 2020 23:12
@btmills
Copy link
Contributor Author

btmills commented Mar 23, 2020

I’ve rebased this on top of master now that 2.0 has been released.

@tattali
Copy link

tattali commented Jul 13, 2020

Hi, it looks like the conflicting files are just snapshots and this for years.
Are there any plans to merge this pull request into a milestone?

There has been a lot of discussion around this PR I think it is a quite important one.

Thank you!

@btmills btmills force-pushed the issue3806 branch 2 times, most recently from b2d88d9 to a2f6eb6 Compare November 22, 2020 23:07
@btmills
Copy link
Contributor Author

btmills commented Nov 22, 2020

Rebased after the 2.2.0 release:

  • printBinaryishExpression() was extracted from src/language-js/printer-estree.js to src/language-js/print/binaryish.js, but the logic didn't change, so I just had to move the diff.
  • The changelog format appears to have changed since the last rebase, so that's now 7111.md instead of pr-7111.md.
  • No tests needed to be updated since the last rebase.

A demo install via prettier@npm:@btmills/prettier@^2.2.0 with yarn and npm v7 correctly adds node_modules/.bin/prettier. I don't have an npm v6 install handy right now to see if it's been fixed there.

Base automatically changed from master to main January 23, 2021 17:13
@alex12058
Copy link

alex12058 commented Feb 12, 2022

This does not seem to handle comments correctly.

Input:

const logical_expression =
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /* trailing comment */
  /* leading comment */ && bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
  // line comment
  && cccccccccccccccccccccccc

Output:

const logical_expression =
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /* trailing comment */
  && /* leading comment */ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb &&
  // line comment
  cccccccccccccccccccccccc;

Leading comments are placed after operators (which is fine) and operators do not appear at the start of the line when there is a line comment.

@alex12058
Copy link

alex12058 commented Feb 12, 2022

In src/language-js/print/binaryish.js if you replace this:

   right = [
      lineBeforeOperator ? line : "",
      operator,
      lineBeforeOperator ? " " : line,
      rightContent,
      rightSuffix,
    ];

with this:

    // Place leading own line comments before the operator.
    const comment = commentBeforeOperator ? rightContent.splice(0, 1)[0] : '';
    right = [
      line,
      comment,
      operator,
      " ",
      rightContent,
      rightSuffix,
    ];

it will extract leading own line comments from the rightContent and place them before the operator. commentBeforeOperator is the inversion of lineBeforeOperator.

Before changing operator positions, these tests demonstrate the current
state.
Previously, there was special case logic to break lines before pipe
operators but after all other operators. By removing the special case,
line breaks, where necesary, will be placed before all operators.
@btmills
Copy link
Contributor Author

btmills commented Apr 13, 2022

Thank you for catching that bug, @alex12058, and thank you even more for suggesting the fix! I was able to use it with a modification to avoid clobbering type annotation comments.

I rebased this on latest main.

@alex12058
Copy link

alex12058 commented Apr 14, 2022

@btmills Good catch on the type annotation comments.

I found a potential formatting issue with type annotation comments that appear in multline binary operators.

// Input
var a =  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar || /** @type {string} */
    (c);

// Output
var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */
  (c);

// Expected
var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */ (c);

playground link

What do you think the output should be?


Edit: I think the current output if is fine as it respects the users preference to place the comment on it's own line or inline.

// Input
var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */ (c);

var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */
  (c);

// Output
var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */ (c);

var a =
  veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVar
  || /** @type {string} */
  (c);

@btmills
Copy link
Contributor Author

btmills commented Apr 23, 2022

@alex12058 I agree with your edit. That seems just far enough away as to be unrelated, so I'm glad this change doesn't affect behavior there either way.

@chrisbobbe chrisbobbe mentioned this pull request Jul 20, 2022
23 tasks
@JanJakes
Copy link

JanJakes commented Sep 8, 2022

Seeing Release v3 being planned, I'd like to ask whether this is considered for v3. I see it has the 3.0 milestone, but at the same time, it's not on the release checklist. It seems to be the most upvoted PR, issue (second, but almost no downvotes), and comment on the v3 issue. It seems to be non-controversial issue with a broad consensus. Thanks for any information!

@MarkAckert MarkAckert mentioned this pull request Nov 29, 2022
15 tasks
@fisker fisker added status:needs discussion Issues needing discussion and a decision to be made before action can be taken lang:javascript Issues affecting JS and removed status:needs discussion Issues needing discussion and a decision to be made before action can be taken lang:javascript Issues affecting JS labels Jan 10, 2023
@fisker fisker modified the milestones: 3.0, 4.0 Feb 8, 2023
@fisker
Copy link
Member

fisker commented Feb 8, 2023

I'm going to postpone this, since we haven't reached a consensus.

@bsell93
Copy link

bsell93 commented Feb 8, 2023

@fisker can you explain what you mean?

From what I can see there is a consensus it was just a matter of deciding which release to put it in (v2 or v3):
#7111 (comment)

@sosukesuzuki
Copy link
Member

sosukesuzuki commented Mar 19, 2023

This is a major formatting change, so I think we should hide this feature in an experimental flag(Please see #14527 about experimental flags). Then it could be shipped in v3 as an experimental feature.

@btmills
Copy link
Contributor Author

btmills commented Apr 8, 2023

I'd love to see this merged in, even if it's only experimental initially! Since it sounds like that's likely to happen after v3, I'll wait until that's out and rebase this branch again.

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

Successfully merging this pull request may close these issues.

Placing operators at the beginning of lines