Skip to content
23 changes: 23 additions & 0 deletions changelog_unreleased/javascript/7111.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#### Break lines before binary operators (#7111 by @btmills)

This is implemented behind the `--experimental-operator-position <start|end>` flag.

When binary expressions wrap lines, `start` prints the operators at the start of new lines. Placing binary operators at the beginning of wrapped lines can make the operators more prominent and easier to scan.

<!-- prettier-ignore -->
```jsx
// Input
var a = Math.random() * (yRange * (1 - minVerticalFraction)) + minVerticalFraction * yRange - offset;

// Default behavior
var a =
Math.random() * (yRange * (1 - minVerticalFraction)) +
minVerticalFraction * yRange -
offset;

// `experimentalOperatorPosition: true`
var a =
Math.random() * (yRange * (1 - minVerticalFraction))
+ minVerticalFraction * yRange
- offset;
```
11 changes: 11 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ Valid options:
| ------- | -------------------------- | ------------------------------- |
| `false` | `--experimental-ternaries` | `experimentalTernaries: <bool>` |

## Experimental Operator Position
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again, I welcome feedback on wording here. I used the Experimental Ternaries section above as a starting point, though I omitted the line about "before it becomes the default behavior" because I don't know whether that's the intent for this flag.


Valid options:

- `"start"` - When binary expressions wrap lines, print operators at the start of new lines.
- `"end"` - Default behavior; when binary expressions wrap lines, print operators at the end of previous lines.

| Default | CLI Override | API Override |
| ------- | -------------------------------------------------------------- | ------------------------------------------------------------- |
| `"end"` | <code>--experimental-operator-position <start&#124;end></code> | <code>experimentalOperatorPosition: "<start&#124;end>"</code> |

## Print Width

Specify the line length that the printer will wrap on.
Expand Down
16 changes: 16 additions & 0 deletions src/language-js/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ const options = {
oppositeDescription:
"Do not print semicolons, except at the beginning of lines which may need them.",
},
experimentalOperatorPosition: {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The options defined in this file appeared to follow alphabetical order with a few deviations, so I put this next to and alphabetically before experimentalTernaries.

category: CATEGORY_JAVASCRIPT,
type: "choice",
default: "end",
description: "Where to print operators when binary expressions wrap lines.",
choices: [
{
value: "start",
description: "Print operators at the start of new lines.",
},
{
value: "end",
description: "Print operators at the end of previous lines.",
},
],
},
experimentalTernaries: {
category: CATEGORY_JAVASCRIPT,
type: "boolean",
Expand Down
59 changes: 46 additions & 13 deletions src/language-js/print/binaryish.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
line,
softline,
} from "../../document/builders.js";
import { DOC_TYPE_FILL, DOC_TYPE_GROUP } from "../../document/constants.js";
import { cleanDoc } from "../../document/utils.js";
import {
DOC_TYPE_ARRAY,
DOC_TYPE_FILL,
DOC_TYPE_GROUP,
DOC_TYPE_LABEL,
} from "../../document/constants.js";
import { cleanDoc, getDocType } from "../../document/utils.js";
import { printComments } from "../../main/comments/print.js";
import {
CommentCheckFlags,
Expand All @@ -23,6 +28,7 @@ import {
isObjectProperty,
shouldFlatten,
} from "../utils/index.js";
import isTypeCastComment from "../utils/is-type-cast-comment.js";

/** @import {Doc} from "../../document/builders.js" */

Expand Down Expand Up @@ -231,6 +237,14 @@ function printBinaryishExpressions(
node.type === "NGPipeExpression" ||
isVueFilterSequenceExpression(path, options)) &&
!hasLeadingOwnLineComment(options.originalText, node.right);
const hasTypeCastComment = hasComment(
node.right,
CommentCheckFlags.Leading,
isTypeCastComment,
);
const commentBeforeOperator =
!hasTypeCastComment &&
hasLeadingOwnLineComment(options.originalText, node.right);

const operator = node.type === "NGPipeExpression" ? "|" : node.operator;
const rightSuffix =
Expand Down Expand Up @@ -267,13 +281,28 @@ function printBinaryishExpressions(
"right",
)
: print("right");
right = [
lineBeforeOperator ? line : "",
operator,
lineBeforeOperator ? " " : line,
rightContent,
rightSuffix,
];
if (options.experimentalOperatorPosition === "start") {
let comment = "";
if (commentBeforeOperator) {
switch (getDocType(rightContent)) {
case DOC_TYPE_ARRAY:
comment = rightContent.splice(0, 1)[0];
break;
case DOC_TYPE_LABEL:
comment = rightContent.contents.splice(0, 1)[0];
break;
}
}
right = [line, comment, operator, " ", rightContent, rightSuffix];
} else {
right = [
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This assignment to right is just an indentation change from moving it into the else branch. Hide whitespace to remove this from the diff.

lineBeforeOperator ? line : "",
operator,
lineBeforeOperator ? " " : line,
rightContent,
rightSuffix,
];
}
}

// If there's only a single binary expression, we want to create a group
Expand All @@ -289,11 +318,15 @@ function printBinaryishExpressions(
parent.type !== node.type &&
node.left.type !== node.type &&
node.right.type !== node.type);
if (shouldGroup) {
right = group(right, { shouldBreak });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This line came from the inline expression shouldGroup ? group(right, { shouldBreak }) : right. Now the possibly-grouped right is used by both branches' .push() calls below.

}

parts.push(
lineBeforeOperator ? "" : " ",
shouldGroup ? group(right, { shouldBreak }) : right,
);
if (options.experimentalOperatorPosition === "start") {
parts.push(shouldInline || commentBeforeOperator ? " " : "", right);
} else {
parts.push(lineBeforeOperator ? "" : " ", right);
}

// The root comments are already printed, but we need to manually print
// the other ones since we don't call the normal print on BinaryExpression,
Expand Down
3 changes: 3 additions & 0 deletions src/language-js/print/type-annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ function printIntersectionType(path, options, print) {

// If no object is involved, go to the next line if it breaks
if (!previousIsObjectType && !currentIsObjectType) {
if (options.experimentalOperatorPosition === "start") {
return indent([line, "& ", doc]);
}
return indent([" &", line, doc]);
}

Expand Down
Loading