diff --git a/changelog_unreleased/javascript/16163.md b/changelog_unreleased/javascript/16163.md new file mode 100644 index 000000000..ef5b4bc8f --- /dev/null +++ b/changelog_unreleased/javascript/16163.md @@ -0,0 +1,32 @@ +#### Implement `objectWrap` config option (#16163 by @pauldraper, @sosukesuzuki) + +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. See [Multi-line objects](rationale.md#multi-line-objects) for more details. + +While this behavior continues to be the default, `--object-wrap=collapse` instead ignores whitespace when formatting object literals. + + +```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 (with `--object-wrapping=collapse`) +const obj1 = { name1: "value1", name2: "value2" }; + +const obj2 = { name1: "value1", name2: "value2" }; +``` diff --git a/docs/options.md b/docs/options.md index 68208bb4f..98bfd7479 100644 --- a/docs/options.md +++ b/docs/options.md @@ -163,6 +163,23 @@ Valid options: | ------- | ---------------------- | ------------------------ | | `true` | `--no-bracket-spacing` | `bracketSpacing: ` | +## Object Wrap + +_First available in v3.5.0_ + +Configure how Prettier wraps object literals when they could fit on one line or span multiple lines. + +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 readability, though it has some downsides. See [Multi-line objects](rationale.md#multi-line-objects). + +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 | CLI Override | API Override | +| ------------ | --------------------------------------------------- | --------------------------------------------------- | +| `"preserve"` | --object-wrap | objectWrap: "" | + ## 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). diff --git a/docs/rationale.md b/docs/rationale.md index fc0185eec..f4c774ec4 100644 --- a/docs/rationale.md +++ b/docs/rationale.md @@ -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 [`objectWrap`](options.md#object-wrap) option. + +**Tip:** If you have a multi-line object that you’d like to join up into a single line: ```js const user = { @@ -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 `{`: ```js diff --git a/src/common/common-options.evaluate.js b/src/common/common-options.evaluate.js index 33ec58d1c..3b68ebdae 100644 --- a/src/common/common-options.evaluate.js +++ b/src/common/common-options.evaluate.js @@ -9,6 +9,23 @@ const options = { description: "Print spaces between brackets.", oppositeDescription: "Do not print spaces between brackets.", }, + objectWrap: { + category: CATEGORY_COMMON, + type: "choice", + default: "preserve", + description: "How to wrap object literals.", + 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", diff --git a/src/index.d.ts b/src/index.d.ts index b7117cda6..4c54e0c06 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -341,6 +341,11 @@ export interface RequiredOptions extends doc.printer.Options { * @default true */ bracketSpacing: boolean; + /** + * How to wrap object literals. + * @default "preserve" + */ + objectWrap: "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). diff --git a/src/language-js/options.js b/src/language-js/options.js index f5927e35c..449f849d2 100644 --- a/src/language-js/options.js +++ b/src/language-js/options.js @@ -21,6 +21,7 @@ const options = { ], }, bracketSameLine: commonOptions.bracketSameLine, + objectWrap: commonOptions.objectWrap, bracketSpacing: commonOptions.bracketSpacing, jsxBracketSameLine: { category: CATEGORY_JAVASCRIPT, diff --git a/src/language-js/print/mapped-type.js b/src/language-js/print/mapped-type.js index 6e6c71483..d37f43d07 100644 --- a/src/language-js/print/mapped-type.js +++ b/src/language-js/print/mapped-type.js @@ -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.objectWrap === "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( [ diff --git a/src/language-js/print/object.js b/src/language-js/print/object.js index 3fec95877..cffedfe6a 100644 --- a/src/language-js/print/object.js +++ b/src/language-js/print/object.js @@ -94,6 +94,7 @@ function printObject(path, options, print) { property.value.type === "ArrayPattern"), )) || (node.type !== "ObjectPattern" && + options.objectWrap === "preserve" && propsAndLoc.length > 0 && hasNewlineInRange( options.originalText, diff --git a/tests/dts/unit/cases/parsers.ts b/tests/dts/unit/cases/parsers.ts index aecf628cb..aca7bfa19 100644 --- a/tests/dts/unit/cases/parsers.ts +++ b/tests/dts/unit/cases/parsers.ts @@ -20,6 +20,7 @@ const options: prettier.ParserOptions = { bracketSpacing: true, bracketSameLine: false, htmlWhitespaceSensitivity: "css", + objectWrap: "preserve", singleAttributePerLine: false, vueIndentScriptAndStyle: false, arrowParens: "always", diff --git a/tests/format/flow/object-multiline/__snapshots__/format.test.js.snap b/tests/format/flow/object-multiline/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..93967bafa --- /dev/null +++ b/tests/format/flow/object-multiline/__snapshots__/format.test.js.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`multiline.js - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; + +=====================================output===================================== +type A = { name1: "value1", name2: "value2" }; + +const a: A = { name1: "value1", name2: "value2" }; + +type B = { name1: "value1", name2: "value2" }; + +const b: B = { name1: "value1", name2: "value2" }; + +type C = { name1: "value1", name2: "value2" }; + +const c: C = { name1: "value1", name2: "value2" }; + +type D = { name1: "value1", name2: "value2" }; + +const d: D = { name1: "value1", name2: "value2" }; + +type E = { name1: "value1", name2: "value2" }; + +const e: E = { name1: "value1", name2: "value2" }; + +================================================================================ +`; + +exports[`multiline.js format 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; + +=====================================output===================================== +type A = { + name1: "value1", + name2: "value2", +}; + +const a: A = { + name1: "value1", + name2: "value2", +}; + +type B = { name1: "value1", name2: "value2" }; + +const b: B = { name1: "value1", name2: "value2" }; + +type C = { + name1: "value1", + name2: "value2", +}; + +const c: C = { + name1: "value1", + name2: "value2", +}; + +type D = { + name1: "value1", + name2: "value2", +}; + +const d: D = { + name1: "value1", + name2: "value2", +}; + +type E = { name1: "value1", name2: "value2" }; + +const e: E = { name1: "value1", name2: "value2" }; + +================================================================================ +`; diff --git a/tests/format/flow/object-multiline/format.test.js b/tests/format/flow/object-multiline/format.test.js new file mode 100644 index 000000000..cb97f9c14 --- /dev/null +++ b/tests/format/flow/object-multiline/format.test.js @@ -0,0 +1,2 @@ +runFormatTest(import.meta, ["flow"]); +runFormatTest(import.meta, ["flow"], { objectWrap: "collapse" }); diff --git a/tests/format/flow/object-multiline/multiline.js b/tests/format/flow/object-multiline/multiline.js new file mode 100644 index 000000000..7c0b86aa8 --- /dev/null +++ b/tests/format/flow/object-multiline/multiline.js @@ -0,0 +1,35 @@ +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; diff --git a/tests/format/js/object-multiline/format.spec.js b/tests/format/js/object-multiline/format.spec.js new file mode 100644 index 000000000..cee83268f --- /dev/null +++ b/tests/format/js/object-multiline/format.spec.js @@ -0,0 +1,2 @@ +runFormatTest(import.meta, ["babel"]); +runFormatTest(import.meta, ["babel"], { objectWrap: "collapse" }); diff --git a/tests/format/js/object-multiline/multiline.js b/tests/format/js/object-multiline/multiline.js new file mode 100644 index 000000000..24c93257c --- /dev/null +++ b/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', }; diff --git a/tests/format/json/json/__snapshots__/format.test.js.snap b/tests/format/json/json/__snapshots__/format.test.js.snap index 7e6d7a9ff..e58f586df 100644 --- a/tests/format/json/json/__snapshots__/format.test.js.snap +++ b/tests/format/json/json/__snapshots__/format.test.js.snap @@ -1,5 +1,28 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`array.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +[ + [ +1,null], + [1,null,], + [null,], + [0,], + [false,], + ['',] +] + +=====================================output===================================== +[[1, null], [1, null], [null], [0], [false], [""]] + +================================================================================ +`; + exports[`array.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -135,6 +158,21 @@ printWidth: 80 ================================================================================ `; +exports[`boolean.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +true + +=====================================output===================================== +true + +================================================================================ +`; + exports[`boolean.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -209,6 +247,66 @@ true ================================================================================ `; +exports[`json5.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +[ +{ + '//': 'JSON5 allow \`Infinity\` and \`NaN\`', + numbers: [ + Infinity, + -Infinity, + NaN, + ], + Infinity: NaN, + NaN: Infinity, + NaN: -Infinity, +}, +{ + '//': 'JSON5 numbers', + hexadecimal: 0xdecaf, + leadingDecimalPoint: .8675309, andTrailing: 8675309., + positiveSign: +1, +}, +{ + '//': 'JSON5 strings', +singleQuotes: 'I can use "double quotes" here', + lineBreaks: "Look, Mom! \\ +No \\\\n's!", +} +] + +=====================================output===================================== +[ + { + "//": "JSON5 allow \`Infinity\` and \`NaN\`", + "numbers": [Infinity, -Infinity, NaN], + "Infinity": NaN, + "NaN": Infinity, + "NaN": -Infinity + }, + { + "//": "JSON5 numbers", + "hexadecimal": 0xdecaf, + "leadingDecimalPoint": 0.8675309, + "andTrailing": 8675309, + "positiveSign": +1 + }, + { + "//": "JSON5 strings", + "singleQuotes": "I can use \\"double quotes\\" here", + "lineBreaks": "Look, Mom! \\ +No \\\\n's!" + } +] + +================================================================================ +`; + exports[`json5.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -511,6 +609,115 @@ No \\\\n's!", ================================================================================ `; +exports[`json6.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +[ +{'//': 'Keyword \`undefined\`', + "data": [ + {undefined: undefined}, + undefined, + [undefined, ] + ] + }, + +{'//': 'back-tick quoted strings', + "data": [ + \`\`, + \`foo\`, + \` + multiple-line +\`, + \`\\u{1F409}\\\`'"\\\${}\`, + {'as-object-value': \`foo\`} + ] + }, + +{'//': 'String escapes ', + "data": [ + '\\0', '\\xFF', '\\u00FF', \`\\u{1F409}\` + ] + }, + +{'//': 'Numbers', + "data": [ + 0o123, 0b101010, 1e5, 123_456, 0xDeeD_Beef, + 0123, + -Infinity, -NaN, + +1, -1, + +0o123, +0b101010, +1e5, +123_456, +0xDeeD_Beef, + -0o123, -0b101010, -1e5, -123_456, -0xDeeD_Beef, + ] + }, + +{'//': 'empty members', + data: [ + [,], + [1, , 2,,,,], + [1, , 2], + [1, , 2,] + ] + } +] + +=====================================output===================================== +[ + { + "//": "Keyword \`undefined\`", + "data": [{ "undefined": undefined }, undefined, [undefined]] + }, + + { + "//": "back-tick quoted strings", + "data": [ + \`\`, + \`foo\`, + \` + multiple-line +\`, + \`\\u{1F409}\\\`'"\\\${}\`, + { "as-object-value": \`foo\` } + ] + }, + + { "//": "String escapes ", "data": ["\\0", "\\xFF", "\\u00FF", \`\\u{1F409}\`] }, + + { + "//": "Numbers", + "data": [ + 0o123, + 0b101010, + 1e5, + 123_456, + 0xdeed_beef, + 0123, + -Infinity, + -NaN, + +1, + -1, + +0o123, + +0b101010, + +1e5, + +123_456, + +0xdeed_beef, + -0o123, + -0b101010, + -1e5, + -123_456, + -0xdeed_beef + ] + }, + + { "//": "empty members", "data": [[,], [1, , 2, , , ,], [1, , 2], [1, , 2]] } +] + +================================================================================ +`; + exports[`json6.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -1092,6 +1299,29 @@ printWidth: 80 ================================================================================ `; +exports[`key-value.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +{ + "string": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstring", + "stringstringstringstringstringstringstringstring": "stringstringstringstringstringstringstringstring", + "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstring": "string" +} + +=====================================output===================================== +{ + "string": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstring", + "stringstringstringstringstringstringstringstring": "stringstringstringstringstringstringstringstring", + "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstring": "string" +} + +================================================================================ +`; + exports[`key-value.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -1206,6 +1436,22 @@ printWidth: 80 ================================================================================ `; +exports[`multi-line.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +{"key1":[true,false,null],"key2":{"key3":[1,2,"3", +1e10,1e-3]}} + +=====================================output===================================== +{ "key1": [true, false, null], "key2": { "key3": [1, 2, "3", 1e10, 1e-3] } } + +================================================================================ +`; + exports[`multi-line.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -1300,6 +1546,21 @@ printWidth: 80 ================================================================================ `; +exports[`null.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +null + +=====================================output===================================== +null + +================================================================================ +`; + exports[`null.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -1374,6 +1635,21 @@ null ================================================================================ `; +exports[`number.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +0 + +=====================================output===================================== +0 + +================================================================================ +`; + exports[`number.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -1448,6 +1724,137 @@ printWidth: 80 ================================================================================ `; +exports[`pass1.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\\"", + "backslash": "\\\\", + "controls": "\\b\\f\\n\\r\\t", + "slash": "/ & \\/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "\`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}", + "quotes": "" \\u0022 %22 0x22 034 "", + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] + +=====================================output===================================== +[ + "JSON Test Pattern pass1", + { "object with 1 member": ["array with 1 element"] }, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.54321, + "e": 0.123456789e-12, + "E": 1.23456789e34, + "": 23456789012e66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\\"", + "backslash": "\\\\", + "controls": "\\b\\f\\n\\r\\t", + "slash": "/ & \\/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "\`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A", + "true": true, + "false": false, + "null": null, + "array": [], + "object": {}, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d ": [ + 1, 2, 3, + + 4, 5, 6, 7 + ], + "compact": [1, 2, 3, 4, 5, 6, 7], + "jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}", + "quotes": "" \\u0022 %22 0x22 034 "", + "\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" + }, + 0.5, + 98.6, + 99.44, + + 1066, + 1e1, + 0.1e1, + 1e-1, + 1, + 2, + 2, + "rosebud" +] + +================================================================================ +`; + exports[`pass1.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -2117,6 +2524,21 @@ printWidth: 80 ================================================================================ `; +exports[`positive-number.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== ++123 + +=====================================output===================================== ++123 + +================================================================================ +`; + exports[`positive-number.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -2191,6 +2613,67 @@ printWidth: 80 ================================================================================ `; +exports[`propertyKey.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +{ + a: '', + null: '', + true: '', + "string": "", + 0: '', + 1e2: '', + 1.0e+2: '', + .10e+2: '', + 1e-2: '', + .1e-2: '', + 0.1e+2: '', + 1.0: '', + 1.00000: '', + .1: '', + .100000: '', + 0.1: '', + 0.100000: '', + 999999999999999999999999999999: '', + .000000000000000000000000000001: '', + 0.000000000000000000000000000001: '', + 1e999999999999999999999999999999: '', + 1_2_3: '', +} + +=====================================output===================================== +{ + "a": "", + "null": "", + "true": "", + "string": "", + "0": "", + 1e2: "", + 1.0e2: "", + 0.1e2: "", + 1e-2: "", + 0.1e-2: "", + 0.1e2: "", + 1.0: "", + 1.0: "", + "0.1": "", + "0.1": "", + "0.1": "", + "0.1": "", + 999999999999999999999999999999: "", + 0.000000000000000000000000000001: "", + 0.000000000000000000000000000001: "", + 1e999999999999999999999999999999: "", + 1_2_3: "" +} + +================================================================================ +`; + exports[`propertyKey.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -2495,6 +2978,21 @@ printWidth: 80 ================================================================================ `; +exports[`single-line.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +{"key1":[true,false,null],"key2":{"key3":[1,2,"3",1e10,1e-3]}} + +=====================================output===================================== +{ "key1": [true, false, null], "key2": { "key3": [1, 2, "3", 1e10, 1e-3] } } + +================================================================================ +`; + exports[`single-line.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -2584,6 +3082,21 @@ printWidth: 80 ================================================================================ `; +exports[`single-quote.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +'hello' + +=====================================output===================================== +"hello" + +================================================================================ +`; + exports[`single-quote.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] @@ -2658,6 +3171,21 @@ printWidth: 80 ================================================================================ `; +exports[`string.json - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["json"] +printWidth: 80 + | printWidth +=====================================input====================================== +"string" + +=====================================output===================================== +"string" + +================================================================================ +`; + exports[`string.json - {"trailingComma":"all"} format 1`] = ` ====================================options===================================== parsers: ["json"] diff --git a/tests/format/json/json/format.test.js b/tests/format/json/json/format.test.js index 36ffcece6..b70891b68 100644 --- a/tests/format/json/json/format.test.js +++ b/tests/format/json/json/format.test.js @@ -1,3 +1,4 @@ +runFormatTest(import.meta, ["json"], { objectWrap: "collapse" }); runFormatTest(import.meta, ["json"], { trailingComma: "es5" }); runFormatTest(import.meta, ["json"], { trailingComma: "all" }); runFormatTest(import.meta, ["json5"], { trailingComma: "es5" }); diff --git a/tests/format/typescript/object-multiline/__snapshots__/format.test.js.snap b/tests/format/typescript/object-multiline/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..38955d19d --- /dev/null +++ b/tests/format/typescript/object-multiline/__snapshots__/format.test.js.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`multiline.ts - {"objectWrap":"collapse"} format 1`] = ` +====================================options===================================== +objectWrap: "collapse" +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; + +=====================================output===================================== +type A = { name1: "value1"; name2: "value2" }; + +const a: A = { name1: "value1", name2: "value2" }; + +type B = { name1: "value1"; name2: "value2" }; + +const b: B = { name1: "value1", name2: "value2" }; + +type C = { name1: "value1"; name2: "value2" }; + +const c: C = { name1: "value1", name2: "value2" }; + +type D = { name1: "value1"; name2: "value2" }; + +const d: D = { name1: "value1", name2: "value2" }; + +type E = { name1: "value1"; name2: "value2" }; + +const e: E = { name1: "value1", name2: "value2" }; + +================================================================================ +`; + +exports[`multiline.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; + +=====================================output===================================== +type A = { + name1: "value1"; + name2: "value2"; +}; + +const a: A = { + name1: "value1", + name2: "value2", +}; + +type B = { name1: "value1"; name2: "value2" }; + +const b: B = { name1: "value1", name2: "value2" }; + +type C = { + name1: "value1"; + name2: "value2"; +}; + +const c: C = { + name1: "value1", + name2: "value2", +}; + +type D = { + name1: "value1"; + name2: "value2"; +}; + +const d: D = { + name1: "value1", + name2: "value2", +}; + +type E = { name1: "value1"; name2: "value2" }; + +const e: E = { name1: "value1", name2: "value2" }; + +================================================================================ +`; diff --git a/tests/format/typescript/object-multiline/format.test.js b/tests/format/typescript/object-multiline/format.test.js new file mode 100644 index 000000000..81ab09ce4 --- /dev/null +++ b/tests/format/typescript/object-multiline/format.test.js @@ -0,0 +1,2 @@ +runFormatTest(import.meta, ["typescript"]); +runFormatTest(import.meta, ["typescript"], { objectWrap: "collapse" }); diff --git a/tests/format/typescript/object-multiline/multiline.ts b/tests/format/typescript/object-multiline/multiline.ts new file mode 100644 index 000000000..7c0b86aa8 --- /dev/null +++ b/tests/format/typescript/object-multiline/multiline.ts @@ -0,0 +1,35 @@ +type A = { + name1: 'value1', + name2: 'value2', +}; + +const a: A = { + name1: 'value1', + name2: 'value2', +}; + +type B = { name1: 'value1', + name2: 'value2', +}; + +const b: B = { name1: 'value1', + name2: 'value2', +}; + +type C = { + name1: 'value1', name2: 'value2', +}; + +const c: C = { + name1: 'value1', name2: 'value2', +}; + +type D = { + name1: 'value1', name2: 'value2', }; + +const d: D = { + name1: 'value1', name2: 'value2', }; + + type E = { name1: 'value1', name2: 'value2', }; + +const e: E = { name1: 'value1', name2: 'value2', }; diff --git a/tests/integration/__tests__/__snapshots__/early-exit.js.snap b/tests/integration/__tests__/__snapshots__/early-exit.js.snap index 73db20120..df4b67343 100644 --- a/tests/integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests/integration/__tests__/__snapshots__/early-exit.js.snap @@ -63,6 +63,9 @@ Format options: Defaults to css. --jsx-single-quote Use single quotes in JSX. Defaults to false. + --object-wrap + How to wrap object literals. + Defaults to preserve. --parser Which parser to use. --print-width The line length where Prettier will try wrap. @@ -231,6 +234,9 @@ Format options: Defaults to css. --jsx-single-quote Use single quotes in JSX. Defaults to false. + --object-wrap + How to wrap object literals. + Defaults to preserve. --parser Which parser to use. --print-width The line length where Prettier will try wrap. diff --git a/tests/integration/__tests__/__snapshots__/help-options.js.snap b/tests/integration/__tests__/__snapshots__/help-options.js.snap index e3f8580b3..f977b7e65 100644 --- a/tests/integration/__tests__/__snapshots__/help-options.js.snap +++ b/tests/integration/__tests__/__snapshots__/help-options.js.snap @@ -418,6 +418,23 @@ exports[`show detailed usage with --help no-semi (stdout) 1`] = ` exports[`show detailed usage with --help no-semi (write) 1`] = `[]`; +exports[`show detailed usage with --help object-wrap (stderr) 1`] = `""`; + +exports[`show detailed usage with --help object-wrap (stdout) 1`] = ` +"--object-wrap + + How to wrap object literals. + +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 object-wrap (write) 1`] = `[]`; + exports[`show detailed usage with --help parser (stderr) 1`] = `""`; exports[`show detailed usage with --help parser (stdout) 1`] = ` diff --git a/tests/integration/__tests__/__snapshots__/plugin-options-string.js.snap b/tests/integration/__tests__/__snapshots__/plugin-options-string.js.snap index b2f4773ca..592c82607 100644 --- a/tests/integration/__tests__/__snapshots__/plugin-options-string.js.snap +++ b/tests/integration/__tests__/__snapshots__/plugin-options-string.js.snap @@ -17,7 +17,7 @@ exports[`show external options with \`--help\` 1`] = ` - First value + Second value -@@ -27,16 +27,18 @@ +@@ -27,19 +27,21 @@ --experimental-operator-position Where to print operators when binary expressions wrap lines. Defaults to end. @@ -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. + --object-wrap + How to wrap object literals. + Defaults to preserve. - --parser + --parser Which parser to use. diff --git a/tests/integration/__tests__/__snapshots__/plugin-options.js.snap b/tests/integration/__tests__/__snapshots__/plugin-options.js.snap index 468004420..3f2e8148a 100644 --- a/tests/integration/__tests__/__snapshots__/plugin-options.js.snap +++ b/tests/integration/__tests__/__snapshots__/plugin-options.js.snap @@ -60,7 +60,7 @@ exports[`show external options with \`--help\` 1`] = ` - First value + Second value -@@ -27,16 +27,18 @@ +@@ -27,19 +27,21 @@ --experimental-operator-position Where to print operators when binary expressions wrap lines. Defaults to end. @@ -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. + --object-wrap + How to wrap object literals. + Defaults to preserve. - --parser + --parser Which parser to use. diff --git a/tests/integration/__tests__/__snapshots__/schema.js.snap b/tests/integration/__tests__/__snapshots__/schema.js.snap index b885b5faa..ca0815883 100644 --- a/tests/integration/__tests__/__snapshots__/schema.js.snap +++ b/tests/integration/__tests__/__snapshots__/schema.js.snap @@ -151,6 +151,24 @@ exports[`schema 1`] = ` "description": "Use single quotes in JSX.", "type": "boolean", }, + "objectWrap": { + "default": "preserve", + "description": "How to wrap object literals.", + "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": [ { diff --git a/tests/integration/__tests__/__snapshots__/support-info.js.snap b/tests/integration/__tests__/__snapshots__/support-info.js.snap index 057c81bb7..ce7b115ac 100644 --- a/tests/integration/__tests__/__snapshots__/support-info.js.snap +++ b/tests/integration/__tests__/__snapshots__/support-info.js.snap @@ -163,6 +163,14 @@ exports[`API getSupportInfo() 1`] = ` "default": false, "type": "boolean", }, + "objectWrap": { + "choices": [ + "preserve", + "collapse", + ], + "default": "preserve", + "type": "choice", + }, "parser": { "choices": [ "flow", @@ -927,6 +935,24 @@ exports[`CLI --support-info (stdout) 1`] = ` "pluginDefaults": {}, "type": "boolean" }, + { + "category": "Common", + "choices": [ + { + "description": "Keep as multi-line, if there is a newline between the opening brace and first property.", + "value": "preserve" + }, + { + "description": "Fit to a single line when possible.", + "value": "collapse" + } + ], + "default": "preserve", + "description": "How to wrap object literals.", + "name": "objectWrap", + "pluginDefaults": {}, + "type": "choice" + }, { "category": "Global", "choices": [ diff --git a/website/playground/Playground.js b/website/playground/Playground.js index 6571354ec..51ca8a24f 100644 --- a/website/playground/Playground.js +++ b/website/playground/Playground.js @@ -36,6 +36,7 @@ const ENABLED_OPTIONS = [ "singleQuote", "bracketSpacing", "jsxSingleQuote", + "objectWrap", "quoteProps", "arrowParens", "trailingComma",