From d91a28ef81e7efea38f8494b781706fb494c76e9 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 11 Jan 2017 15:15:12 -0800 Subject: [PATCH] Wrap Stateless JSX Arrow Functions and Assignment in Parens (fixes part of #73) --- src/printer.js | 140 ++++++++++-------- .../__snapshots__/jsfmt.spec.js.snap | 6 +- .../__snapshots__/jsfmt.spec.js.snap | 6 +- .../prettier/__snapshots__/jsfmt.spec.js.snap | 127 ++++++++++++++-- tests/prettier/jsx-multiline-assign.js | 19 +++ tests/prettier/jsx-stateless-arrow-fn.js | 17 +++ 6 files changed, 233 insertions(+), 82 deletions(-) create mode 100644 tests/prettier/jsx-multiline-assign.js create mode 100644 tests/prettier/jsx-stateless-arrow-fn.js diff --git a/src/printer.js b/src/printer.js index 4f9b1e1a2..0e023a271 100644 --- a/src/printer.js +++ b/src/printer.js @@ -343,12 +343,20 @@ function genericPrintNoParens(path, options, print) { parts.push(" =>"); + const body = path.call(print, "body"); + const collapsed = concat([ concat(parts), " ", body ]); + + if (n.body.type === "JSXElement") { + return group(collapsed); + } + return conditionalGroup([ - concat([ concat(parts), " ", path.call(print, "body") ]), - concat([ concat(parts), - indent(options.tabWidth, - concat([line, path.call(print, "body")]))]) - ]) + collapsed, + concat([ + concat(parts), + indent(options.tabWidth, concat([ line, body ])), + ]), + ]); case "MethodDefinition": if (n.static) { parts.push("static "); @@ -553,22 +561,8 @@ function genericPrintNoParens(path, options, print) { case "ReturnStatement": parts.push("return"); - var arg = path.call(print, "argument"); - if (n.argument) { - if ( - namedTypes.JSXElement && namedTypes.JSXElement.check(n.argument) && - hasHardLine(arg) - ) { - parts.push( - " (", - indent(options.tabWidth, concat([ hardline, arg ])), - hardline, - ")" - ); - } else { - parts.push(" ", arg); - } + parts.push(" ", path.call(print, "argument")); } parts.push(";"); @@ -1091,50 +1085,7 @@ function genericPrintNoParens(path, options, print) { "}" ])); case "JSXElement": - var openingLines = path.call(print, "openingElement"); - - if (n.openingElement.selfClosing) { - assert.ok(!n.closingElement); - - return openingLines; - } - - var children = []; - - path.map( - function(childPath) { - var child = childPath.getValue(); - - if ( - namedTypes.Literal.check(child) && typeof child.value === "string" - ) { - if (/\S/.test(child.value)) { - const beginBreak = child.value.match(/^\s*\n/); - const endBreak = child.value.match(/\n\s*$/); - - children.push( - beginBreak ? hardline : "", - child.value.replace(/^\s+|\s+$/g, ""), - endBreak ? hardline : "" - ); - } else if (/\n/.test(child.value)) { - children.push(hardline); - } - } else { - children.push(print(childPath)); - } - }, - "children" - ); - - var mostChildren = children.slice(0, -1); - var closingLines = path.call(print, "closingElement"); - return concat([ - openingLines, - indent(options.tabWidth, concat(mostChildren)), - util.getLast(children) || "", - closingLines - ]); + return printJSXElement(path, options, print); case "JSXOpeningElement": return group( concat([ @@ -2053,6 +2004,67 @@ function printMemberChain(node, options, print) { } } +function printJSXElement(path, options, print) { + const n = path.getValue(); + var openingLines = path.call(print, "openingElement"); + + let elem; + if (n.openingElement.selfClosing) { + assert.ok(!n.closingElement); + + elem = openingLines; + } else { + var children = []; + + path.map( + function(childPath) { + var child = childPath.getValue(); + + if ( + namedTypes.Literal.check(child) && typeof child.value === "string" + ) { + if (/\S/.test(child.value)) { + const beginBreak = child.value.match(/^\s*\n/); + const endBreak = child.value.match(/\n\s*$/); + + children.push( + beginBreak ? hardline : "", + child.value.replace(/^\s+|\s+$/g, ""), + endBreak ? hardline : "" + ); + } else if (/\n/.test(child.value)) { + children.push(hardline); + } + } else { + children.push(print(childPath)); + } + }, + "children" + ); + + var mostChildren = children.slice(0, -1); + var closingLines = path.call(print, "closingElement"); + elem = concat([ + openingLines, + indent(options.tabWidth, concat(mostChildren)), + util.getLast(children) || "", + closingLines + ]); + } + + const parent = path.getParentNode(); + if (!parent || parent.type === "JSXElement" || parent.type === "ExpressionStatement") { + return elem; + } + + return multilineGroup(concat([ + ifBreak("("), + indent(options.tabWidth, concat([ softline, elem ])), + softline, + ifBreak(")"), + ])); +} + function adjustClause(clause, options, forceSpace) { if (isCurlyBracket(clause) || forceSpace) { return concat([ " ", clause ]); diff --git a/tests/jsx_intrinsics.builtin/__snapshots__/jsfmt.spec.js.snap b/tests/jsx_intrinsics.builtin/__snapshots__/jsfmt.spec.js.snap index 919599d66..c791e96be 100644 --- a/tests/jsx_intrinsics.builtin/__snapshots__/jsfmt.spec.js.snap +++ b/tests/jsx_intrinsics.builtin/__snapshots__/jsfmt.spec.js.snap @@ -39,9 +39,9 @@ var c: React.Element =
; // different attributes. var d: React.Element<{ doesntmatch: string }> =
; // No error as long as expectations are consistent, though. -var e: React.Element<{ - not_a_real_attr: string -}> =
; +var e: React.Element<{ not_a_real_attr: string }> = ( +
+); " `; diff --git a/tests/jsx_intrinsics.custom/__snapshots__/jsfmt.spec.js.snap b/tests/jsx_intrinsics.custom/__snapshots__/jsfmt.spec.js.snap index 34d3db059..5d6966fe0 100644 --- a/tests/jsx_intrinsics.custom/__snapshots__/jsfmt.spec.js.snap +++ b/tests/jsx_intrinsics.custom/__snapshots__/jsfmt.spec.js.snap @@ -32,9 +32,9 @@ var b: React.Element<{ prop1: string }> = ;
; // Error: (\`id\` prop) number ~> string var c: React.Element<{ id: string }> =
; -var d: React.Element<{ - id: number -}> =
; // Error: Props<{id:string}> ~> Props<{id:number}> +var d: React.Element<{ id: number }> = ( +
+); // Error: Props<{id:string}> ~> Props<{id:number}> " `; diff --git a/tests/prettier/__snapshots__/jsfmt.spec.js.snap b/tests/prettier/__snapshots__/jsfmt.spec.js.snap index 494775736..dfa2c53cb 100644 --- a/tests/prettier/__snapshots__/jsfmt.spec.js.snap +++ b/tests/prettier/__snapshots__/jsfmt.spec.js.snap @@ -13,18 +13,6 @@ function fn() { " `; -exports[`test method-chain.js 1`] = ` -"method().then(x => x) - [\"abc\"](x => x) - [abc](x => x); -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -method() - .then(x => x) - [\"abc\"](x => x) - [abc](x => x); -" -`; - exports[`test exports.js 1`] = ` "export { value1, value2 as value2_renamed, value3, value4 as value4_renamed, value5 } from \"exports\"; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -38,6 +26,121 @@ export { " `; +exports[`test jsx-multiline-assign.js 1`] = ` +"const comp1 = ( +
+ Keep the wrapping parens. +
+); + +const comp2 =
+ Create wrapping parens. +
; + +comp2A =
+ Create wrapping parens. +
; + +const comp3 =
Bump to next line without parens
; + +const comp4 =
Create wrapping parens and indent all the things.
; + +const comp5 =
Keep it on one line.
; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const comp1 = ( +
+ Keep the wrapping parens. +
+); + +const comp2 = ( +
+ Create wrapping parens. +
+); + +comp2A = ( +
+ Create wrapping parens. +
+); + +const comp3 = ( +
Bump to next line without parens
+); + +const comp4 = ( +
Create wrapping parens and indentall the things.
+); + +const comp5 =
Keep it on one line.
; +" +`; + +exports[`test jsx-stateless-arrow-fn.js 1`] = ` +"const render1 = ({ styles }) => ( +
+ Keep the wrapping parens. Put each key on its own line. +
+); + +const render2 = ({ styles }) =>
+ Create wrapping parens. +
; + +const render3 = ({ styles }) =>
Bump to next line without parens
; + +const render4 = ({ styles }) =>
Create wrapping parens and indent all the things.
; + +const render5 = ({ styles }) =>
Keep it on one line.
; + +const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) => this.someLongCallWithParams(aaaaaa, bbbbbbb).anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const render1 = ({ styles }) => ( +
+ Keep the wrapping parens. Put each key on its own line. +
+); + +const render2 = ({ styles }) => ( +
+ Create wrapping parens. +
+); + +const render3 = ({ styles }) => ( +
Bump to next line without parens
+); + +const render4 = ({ styles }) => ( +
Create wrapping parens and indentall the things.
+); + +const render5 = ({ styles }) =>
Keep it on one line.
; + +const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) => + this + .someLongCallWithParams(aaaaaa, bbbbbbb) + .anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd); +" +`; + +exports[`test method-chain.js 1`] = ` +"method().then(x => x) + [\"abc\"](x => x) + [abc](x => x); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +method() + .then(x => x) + [\"abc\"](x => x) + [abc](x => x); +" +`; + exports[`test optional-type-name.js 1`] = ` "type Foo = (any) => string diff --git a/tests/prettier/jsx-multiline-assign.js b/tests/prettier/jsx-multiline-assign.js new file mode 100644 index 000000000..7c3649a6e --- /dev/null +++ b/tests/prettier/jsx-multiline-assign.js @@ -0,0 +1,19 @@ +const comp1 = ( +
+ Keep the wrapping parens. +
+); + +const comp2 =
+ Create wrapping parens. +
; + +comp2A =
+ Create wrapping parens. +
; + +const comp3 =
Bump to next line without parens
; + +const comp4 =
Create wrapping parens and indent all the things.
; + +const comp5 =
Keep it on one line.
; diff --git a/tests/prettier/jsx-stateless-arrow-fn.js b/tests/prettier/jsx-stateless-arrow-fn.js new file mode 100644 index 000000000..859c0aeef --- /dev/null +++ b/tests/prettier/jsx-stateless-arrow-fn.js @@ -0,0 +1,17 @@ +const render1 = ({ styles }) => ( +
+ Keep the wrapping parens. Put each key on its own line. +
+); + +const render2 = ({ styles }) =>
+ Create wrapping parens. +
; + +const render3 = ({ styles }) =>
Bump to next line without parens
; + +const render4 = ({ styles }) =>
Create wrapping parens and indent all the things.
; + +const render5 = ({ styles }) =>
Keep it on one line.
; + +const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) => this.someLongCallWithParams(aaaaaa, bbbbbbb).anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd)