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

Implement multilineObject config option #16163

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

pauldraper
Copy link

@pauldraper pauldraper commented Mar 25, 2024

Description

Implement multilineObject config option to control how multi-line JavaScript objects are formatted.

Choices

"preserve"

This is the default. Objects are kept on multiple lines if there is a newline between the opening brace and the first property key. (I.e. the existing behavior of Prettier.)

"collapse"

Objects ignore whitespace-only formatting from the source, and are collapsed to a single line when possible.

Examples

Input
const obj1 = {
  name1: "value1",
  name2: "value2",
};

const obj2 = {
  name1: "value1", name2: "value2",
};

const obj3 = {
  name1: "value1",
  name2: "value2", };

const obj4 = { name1: "value1",
  name2: "value2",
};

const obj5 = { name1: "value1", name2: "value2", };
"preserve"
const obj1 = {
  name1: "value1",
  name2: "value2",
};

const obj2 = {
  name1: "value1",
  name2: "value2",
};

const obj3 = {
  name1: "value1",
  name2: "value2",
};

const obj4 = { name1: "value1", name2: "value2" };

const obj5 = { name1: "value1", name2: "value2" };
"collapse"
const obj1 = { name1: "value1", name2: "value2" };

const obj2 = { name1: "value1", name2: "value2" };

const obj3 = { name1: "value1", name2: "value2" };

const obj4 = { name1: "value1", name2: "value2" };

const obj5 = { name1: "value1", name2: "value2" };

Motivation

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

https://prettier.io/docs/en/why-prettier

John submits a PR:

const buttonStyles = { background: "#45da09", borderRadius: "4px" };

Jessica submits a PR:

const buttonStyles = {
  background: "#45da09",
  borderRadius: "4px"
};

Prettier fails to prevent these two contributors from quibbling over the style. The project must publish a style guide to resolve the syntax debate of where and when to use multi-line objects. Moreover, to follow that guide for the single-line object cases, reviewers must now measure characters to see if the object can fit on a single-line without violating line length rules. (Which by itself is a complex task.)

This debate can be settled by using --multiline-object=collapse.

The pain of the above situation has been felt by many, many users who expect Prettier to solve this stylistic variation, and is a perennial source of consternation marked by complaints of "instability" and "inconsistency":

However, the original justifications for the semi-manual formatting still apply. Even though there are many who prefer to Prettier to be opinionated (including myself), it is a non-starter to impose whitespace agnostic object formatting on all Prettier users.

Thus, this change is the only path forward.

Aren't we not supposed to add options?

Normally yes, but this option fundamentally differs from the usual proposals.

1. This option makes Prettier more opinionated, not less.

Normally, options are eschewed because they contradict Prettier's purpose to be an opinionated formatter.

But today, the single-line vs multi-line is configured not project-by-project, not even file-by-file, but object-literal-by-object-literal. That's literally the most flexibility/least opinionated possible. This change allows users to reign in that flexibility.

Unlike other proposals for adding options, this does not change the variety of styles that Prettier outputs. It simply changes how those styles are configured (project-wide vs line-by-line).

2. The current behavior is "not a feature"

The semi-manual formatting for object literals is in fact a workaround, not a feature.

https://prettier.io/docs/en/rationale.html#%EF%B8%8F-a-note-on-formatting-reversibility

This option allows users to opt out of this non-feature workaround.

3. Non-reversibility is significantly undesirable

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.

https://prettier.io/docs/en/rationale.html#%EF%B8%8F-a-note-on-formatting-reversibility

This option allows users to make Prettier a reversible formatter.

4. Prettier maintainers have invited this change

#2068 (comment)

Checklist

  • 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/*/XXXX.md file following changelog_unreleased/TEMPLATE.md.
  • I’ve read the contributing guidelines.

Try the playground for this PR

Copy link

@jaydenseric jaydenseric left a comment

Choose a reason for hiding this comment

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

What about an option that's the opposite of the "collapse" option? This way objects could be forced to always be expanded on multiple lines. That approach would minimise Git diffs when someone adds a new property to an object, otherwise prettier might suddenly convert a previously single line object to multiple lines if it can't fit within the max line length anymore.

I'm not pushing for more configurability; just talking out loud to see what the best approach is. I would be happy with whatever the community lands on as the default.

@pauldraper
Copy link
Author

pauldraper commented Mar 25, 2024

@jaydenseric that hypothetical option would be "expand". It would not be without precedence (singleAttributePerLine, introduced version 2.6.0).

However, I do not believe this to be viable for short objects. Moreover, it makes more sense for object members to be consistent with array, function call, argument members. Which are placed on multiple lines only as necessary.

@pauldraper
Copy link
Author

Can I get some eyes on this?

@fisker @sosukesuzuki @alexander-akait

@fisker
Copy link
Member

fisker commented Apr 4, 2024

Personally, I like --multiline-object=collapse. 👍 from me.

@sosukesuzuki
Copy link
Member

I too am 👍 on this proposal. Ideally I don't want to add the option, but it seems like a good compromise. I will review it properly later.

Copy link
Member

@sosukesuzuki sosukesuzuki left a comment

Choose a reason for hiding this comment

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

The implementation looks good. However, additional tests are needed. This change also affects the type Foo = { field1: number }; syntax in TypeScript and Flow, so please add tests for it.
Also, look for existing test cases that may be relevant and enable the { multilineObjects: "collapse" } option. And please fix CI fail.


const obj2 = { name1: "value1", name2: "value2" };

// Prettier main
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Prettier main
// Prettier main (with `--multiline-object=collapse`)


Prettier has historically done semi-manual formatting of multi-line JavaScript object literals.

Namely, an object is kept on multiple lines if there is a newline prior to the first property, even if it could fit on a single line.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Namely, an object is kept on multiple lines if there is a newline prior to the first property, even if it could fit on a single line.
Namely, an object is kept on multiple lines if there is a newline prior to the first property, even if it could fit on a single line. See [Multi-line objects](rationale.md#multi-line-objects) for more details.

@fisker
Copy link
Member

fisker commented May 10, 2024

@pauldraper #16163 (review)

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.

None yet

4 participants