Skip to content

Commit

Permalink
Implement multilineObject config option
Browse files Browse the repository at this point in the history
Fixes #2068
  • Loading branch information
pauldraper committed Mar 25, 2024
1 parent d970dee commit 7c3754c
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 11 deletions.
33 changes: 33 additions & 0 deletions changelog_unreleased/javascript/16163.md
@@ -0,0 +1,33 @@
#### Implement multilineObject config option (#16163 by @pauldraper)

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

Namely, it keeps objects on multiple lines if there is a newline prior to the first property, even if it could fit the object on a single line.

Using `--multiline-object=collapse` causes Prettier to instead format object literals independent of whitespace.

<!-- prettier-ignore -->
```js
// Input
const obj1 = {
name1: "value1",
name2: "value2",
};

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

// Prettier stable
const obj1 = {
name1: "value1",
name2: "value2",
};

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

// Prettier main
const obj1 = { name1: "value1", name2: "value2" };

const obj2 = { name1: "value1", name2: "value2" };
```
17 changes: 17 additions & 0 deletions docs/options.md
Expand Up @@ -152,6 +152,23 @@ Valid options:
| ------- | ---------------------- | ------------------------ |
| `true` | `--no-bracket-spacing` | `bracketSpacing: <bool>` |

## Multi-line Object

_First available in v3.3.0_

Allow multi-line objects to collapse to a single line.

By default, Prettier formats objects as multi-line if there is a newline prior to the first property. Authors can use this heuristic to contextually improve reability, though it has some downsides. See [Multi-line objects](rationale.md#multi-line-objects).

Valid options:

- `"collapse"` - Fit to a single line when possible.
- `"preserve"` - Keep as multi-line, if there is a newline between the opening brace and first property.

| Default | CLI Override | API Override |
| ------------ | -------------------------------------------------------- | -------------------------------------------------------- |
| `"preserve"` | <code>--multiline-object <preserve&#124;collapse></code> | <code>multilineObject: "<preserve&#124;collapse>"</code> |

## Bracket Line

Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being alone on the next line (does not apply to self closing elements).
Expand Down
8 changes: 5 additions & 3 deletions docs/rationale.md
Expand Up @@ -29,9 +29,11 @@ It turns out that empty lines are very hard to automatically generate. The appro

### Multi-line objects

By default, Prettier’s printing algorithm prints expressions on a single line if they fit. Objects are used for a lot of different things in JavaScript, though, and sometimes it really helps readability if they stay multiline. See [object lists], [nested configs], [stylesheets] and [keyed methods], for example. We haven’t been able to find a good rule for all those cases, so Prettier instead keeps objects multiline if there’s a newline between the `{` and the first key in the original source code. A consequence of this is that long singleline objects are automatically expanded, but short multiline objects are never collapsed.
By default, Prettier’s printing algorithm prints expressions on a single line if they fit. Objects are used for a lot of different things in JavaScript, though, and sometimes it really helps readability if they stay multiline. See [object lists], [nested configs], [stylesheets] and [keyed methods], for example. We haven’t been able to find a good rule for all those cases, so by default Prettier keeps objects multi-line if there’s a newline between the `{` and the first key in the original source code. Consequently, long single-line objects are automatically expanded, but short multi-line objects are never collapsed.

**Tip:** If you have a multiline object that you’d like to join up into a single line:
You can disable this conditional behavior with the [multilineObject](options.md#multi-line-object) option.

**Tip:** If you have a multi-line object that you’d like to join up into a single line:

```js
const user = {
Expand All @@ -55,7 +57,7 @@ const user = { name: "John Doe",
const user = { name: "John Doe", age: 30 };
```

And if you’d like to go multiline again, add in a newline after `{`:
And if you’d like to go multi-line again, add in a newline after `{`:

<!-- prettier-ignore -->
```js
Expand Down
17 changes: 17 additions & 0 deletions src/common/common-options.evaluate.js
Expand Up @@ -9,6 +9,23 @@ const options = {
description: "Print spaces between brackets.",
oppositeDescription: "Do not print spaces between brackets.",
},
multilineObject: {
category: CATEGORY_COMMON,
type: "choice",
default: "preserve",
description: "Allow multi-line objects to collapse to a single line.",
choices: [
{
value: "preserve",
description:
"Keep as multi-line, if there is a newline between the opening brace and first property.",
},
{
value: "collapse",
description: "Fit to a single line when possible.",
},
],
},
singleQuote: {
category: CATEGORY_COMMON,
type: "boolean",
Expand Down
5 changes: 5 additions & 0 deletions src/index.d.ts
Expand Up @@ -341,6 +341,11 @@ export interface RequiredOptions extends doc.printer.Options {
* @default true
*/
bracketSpacing: boolean;
/**
* Allow multi-line objects to collapse to a single line.
* @default "preserve"
*/
multilineObject: "preserve" | "collapse";
/**
* Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being
* alone on the next line (does not apply to self closing elements).
Expand Down
1 change: 1 addition & 0 deletions src/language-js/options.js
Expand Up @@ -21,6 +21,7 @@ const options = {
],
},
bracketSameLine: commonOptions.bracketSameLine,
multilineObject: commonOptions.multilineObject,
bracketSpacing: commonOptions.bracketSpacing,
jsxBracketSameLine: {
category: CATEGORY_JAVASCRIPT,
Expand Down
14 changes: 8 additions & 6 deletions src/language-js/print/mapped-type.js
Expand Up @@ -55,12 +55,14 @@ function printTypeScriptMappedTypeModifier(tokenNode, keyword) {
function printTypescriptMappedType(path, options, print) {
const { node } = path;
// Break after `{` like `printObject`
const shouldBreak = hasNewlineInRange(
options.originalText,
locStart(node),
// Ideally, this should be the next token after `{`, but there is no node starts with it.
locStart(node.typeParameter),
);
const shouldBreak =
options.multilineObject === "preserve" &&
hasNewlineInRange(
options.originalText,
locStart(node),
// Ideally, this should be the next token after `{`, but there is no node starts with it.
locStart(node.typeParameter),
);

return group(
[
Expand Down
1 change: 1 addition & 0 deletions src/language-js/print/object.js
Expand Up @@ -93,6 +93,7 @@ function printObject(path, options, print) {
property.value.type === "ArrayPattern"),
)) ||
(node.type !== "ObjectPattern" &&
options.multilineObject === "preserve" &&
propsAndLoc.length > 0 &&
hasNewlineInRange(
options.originalText,
Expand Down
87 changes: 87 additions & 0 deletions tests/format/js/object-multiline/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,87 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`multiline.js - {"multilineObject":"collapse"} format 1`] = `
====================================options=====================================
multilineObject: "collapse"
parsers: ["babel", "flow"]
printWidth: 80
| printWidth
=====================================input======================================
const a = {
name1: 'value1',
name2: 'value2',
};
const b = { name1: 'value1',
name2: 'value2',
};
const c = {
name1: 'value1', name2: 'value2',
};
const d = {
name1: 'value1', name2: 'value2', };
const e = { name1: 'value1', name2: 'value2', };
=====================================output=====================================
const a = { name1: "value1", name2: "value2" };
const b = { name1: "value1", name2: "value2" };
const c = { name1: "value1", name2: "value2" };
const d = { name1: "value1", name2: "value2" };
const e = { name1: "value1", name2: "value2" };
================================================================================
`;

exports[`multiline.js format 1`] = `
====================================options=====================================
parsers: ["babel", "flow", "typescript"]
printWidth: 80
| printWidth
=====================================input======================================
const a = {
name1: 'value1',
name2: 'value2',
};
const b = { name1: 'value1',
name2: 'value2',
};
const c = {
name1: 'value1', name2: 'value2',
};
const d = {
name1: 'value1', name2: 'value2', };
const e = { name1: 'value1', name2: 'value2', };
=====================================output=====================================
const a = {
name1: "value1",
name2: "value2",
};
const b = { name1: "value1", name2: "value2" };
const c = {
name1: "value1",
name2: "value2",
};
const d = {
name1: "value1",
name2: "value2",
};
const e = { name1: "value1", name2: "value2" };
================================================================================
`;
2 changes: 2 additions & 0 deletions tests/format/js/object-multiline/jsfmt.spec.js
@@ -0,0 +1,2 @@
runFormatTest(import.meta, ["babel", "flow", "typescript"]);
runFormatTest(import.meta, ["babel", "flow"], { multilineObject: "collapse" });
17 changes: 17 additions & 0 deletions tests/format/js/object-multiline/multiline.js
@@ -0,0 +1,17 @@
const a = {
name1: 'value1',
name2: 'value2',
};

const b = { name1: 'value1',
name2: 'value2',
};

const c = {
name1: 'value1', name2: 'value2',
};

const d = {
name1: 'value1', name2: 'value2', };

const e = { name1: 'value1', name2: 'value2', };
6 changes: 6 additions & 0 deletions tests/integration/__tests__/__snapshots__/early-exit.js.snap
Expand Up @@ -60,6 +60,9 @@ Format options:
Defaults to css.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--multiline-object <preserve|collapse>
Allow multi-line objects to collapse to a single line.
Defaults to preserve.
--parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc>
Which parser to use.
--print-width <int> The line length where Prettier will try wrap.
Expand Down Expand Up @@ -225,6 +228,9 @@ Format options:
Defaults to css.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--multiline-object <preserve|collapse>
Allow multi-line objects to collapse to a single line.
Defaults to preserve.
--parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc>
Which parser to use.
--print-width <int> The line length where Prettier will try wrap.
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/__tests__/__snapshots__/help-options.js.snap
Expand Up @@ -331,6 +331,23 @@ Default: log"
exports[`show detailed usage with --help log-level (write) 1`] = `[]`;
exports[`show detailed usage with --help multiline-object (stderr) 1`] = `""`;
exports[`show detailed usage with --help multiline-object (stdout) 1`] = `
"--multiline-object <preserve|collapse>
Allow multi-line objects to collapse to a single line.
Valid options:
preserve Keep as multi-line, if there is a newline between the opening brace and first property.
collapse Fit to a single line when possible.
Default: preserve"
`;
exports[`show detailed usage with --help multiline-object (write) 1`] = `[]`;
exports[`show detailed usage with --help no-bracket-spacing (stderr) 1`] = `""`;
exports[`show detailed usage with --help no-bracket-spacing (stdout) 1`] = `
Expand Down
Expand Up @@ -17,7 +17,7 @@ exports[`show external options with \`--help\` 1`] = `
- First value
+ Second value
@@ -24,16 +24,18 @@
@@ -24,19 +24,21 @@
--end-of-line <lf|crlf|cr|auto>
Which end of line characters to apply.
Defaults to lf.
Expand All @@ -30,6 +30,9 @@ exports[`show external options with \`--help\` 1`] = `
Defaults to css.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--multiline-object <preserve|collapse>
Allow multi-line objects to collapse to a single line.
Defaults to preserve.
- --parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc>
+ --parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc|foo-parser>
Which parser to use.
Expand Down
Expand Up @@ -60,7 +60,7 @@ exports[`show external options with \`--help\` 1`] = `
- First value
+ Second value
@@ -24,16 +24,18 @@
@@ -24,19 +24,21 @@
--end-of-line <lf|crlf|cr|auto>
Which end of line characters to apply.
Defaults to lf.
Expand All @@ -73,6 +73,9 @@ exports[`show external options with \`--help\` 1`] = `
Defaults to css.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--multiline-object <preserve|collapse>
Allow multi-line objects to collapse to a single line.
Defaults to preserve.
- --parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc>
+ --parser <flow|babel|babel-flow|babel-ts|typescript|acorn|espree|meriyah|css|less|scss|json|json5|jsonc|json-stringify|graphql|markdown|mdx|vue|yaml|glimmer|html|angular|lwc|foo-parser>
Which parser to use.
Expand Down
18 changes: 18 additions & 0 deletions tests/integration/__tests__/__snapshots__/schema.js.snap
Expand Up @@ -133,6 +133,24 @@ exports[`schema 1`] = `
"description": "Use single quotes in JSX.",
"type": "boolean",
},
"multilineObject": {
"default": "preserve",
"description": "Allow multi-line objects to collapse to a single line.",
"oneOf": [
{
"description": "Keep as multi-line, if there is a newline between the opening brace and first property.",
"enum": [
"preserve",
],
},
{
"description": "Fit to a single line when possible.",
"enum": [
"collapse",
],
},
],
},
"parser": {
"anyOf": [
{
Expand Down

0 comments on commit 7c3754c

Please sign in to comment.