From c6e7c1966e38da28a770e9f03a01d312e34c5ef3 Mon Sep 17 00:00:00 2001 From: Kevin Donnelly Date: Mon, 7 May 2018 23:26:01 -0500 Subject: [PATCH] Improve formatting of TS casts with generics and unions (#4219) * Add tests showing existing behavior of various TS casts. * Improve formatting of TS casts with generics and unions. By adding a soft break between the cast and the expression. Also includes wrapping parentheses for clarity as suggested in #4171. Avoids changing behavior at all though if casting an array or object literal because those already have good behavior where the array or object literal breaks before the cast does so including them would just result in a pointless extra layer of parentheses that would add no clarity. * Update tests in prepation for updating long cast format. * Update so nested breaks inside type cast are indented. * Add tests where expression being cast is too long itself. * Update cast formatting to only sometimes break after cast. Specifically it will break after the cast if that makes the cast itself fit on a single line. If the cast itself won't fit on a single line then the expression being cast will be placed directly after the `>` at the end of the cast. --- src/language-js/printer-estree.js | 39 ++- .../__snapshots__/jsfmt.spec.js.snap | 246 ++++++++++++++++++ tests/typescript_cast/generic-cast.ts | 54 ++++ 3 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 tests/typescript_cast/generic-cast.ts diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 8f447704..1c16f678 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -487,13 +487,37 @@ function printPathNoParens(path, options, print, args) { " = ", path.call(print, "right") ]); - case "TSTypeAssertionExpression": - return concat([ - "<", - path.call(print, "typeAnnotation"), - ">", - path.call(print, "expression") + case "TSTypeAssertionExpression": { + const shouldBreakAfterCast = !( + n.expression.type === "ArrayExpression" || + n.expression.type === "ObjectExpression" + ); + + const castGroup = group( + concat([ + "<", + indent(concat([softline, path.call(print, "typeAnnotation")])), + softline, + ">" + ]) + ); + + const exprContents = concat([ + ifBreak("("), + indent(concat([softline, path.call(print, "expression")])), + softline, + ifBreak(")") ]); + + if (shouldBreakAfterCast) { + return conditionalGroup([ + concat([castGroup, path.call(print, "expression")]), + concat([castGroup, group(exprContents, { shouldBreak: true })]), + concat([castGroup, path.call(print, "expression")]) + ]); + } + return group(concat([castGroup, path.call(print, "expression")])); + } case "MemberExpression": { const parent = path.getParentNode(); let firstNonMemberParent; @@ -2506,6 +2530,7 @@ function printPathNoParens(path, options, print, args) { // | C const parent = path.getParentNode(); + const parentParent = path.getParentNode(1); // If there's a leading comment, the parent is doing the indentation const shouldIndent = @@ -2514,6 +2539,8 @@ function printPathNoParens(path, options, print, args) { parent.type !== "GenericTypeAnnotation" && parent.type !== "TSTypeReference" && !(parent.type === "FunctionTypeParam" && !parent.name) && + parentParent.type !== "TSTypeAssertionExpression" && + !( (parent.type === "TypeAlias" || parent.type === "VariableDeclarator") && diff --git a/tests/typescript_cast/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_cast/__snapshots__/jsfmt.spec.js.snap index e6caf614..a5e4244b 100644 --- a/tests/typescript_cast/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_cast/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,251 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`generic-cast.ts 1`] = ` +// https://github.com/prettier/prettier/issues/4171 +function y() { + + const fits = >fits(); + const fitsObjLiteral = >{ a: "test" }; + const fitsArrayLiteral = >["test", "test2"] + + const breakAfterCast = >someExistingConfigMap.mergeDeep(fallbackOpts); + + const stillTooLong = >someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong2 = | undefined>someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong3 = >someExistingConfigMap.mergeDeep(fallbackOptions.someMethodWithLongName(param1, param2)); + + const stillTooLong4 = | undefined>someExistingConfigMap.mergeDeep(fallbackOptions.someMethodWithLongName(param1, param2)); + + const testObjLiteral = >{ property1: "myPropertyVal" }; + + const testObjLiteral2 = >{ property1: "myPropertyVal" }; + + const testArrayLiteral = >["first", "second", "third"]; + + const testArrayLiteral2 = >["first", "second", "third"]; + + const insideFuncCall = myFunc(param1, >param2, param3) +} + +// https://github.com/prettier/prettier/issues/4168 +function x() { + const fits = | undefined>(permissions)[type]; + const fitsObjLiteral = | undefined>{ a: "test" }; + const fitsArrayLiteral = | undefined>["t1", "t2"]; + + const breakAfterCast = | undefined>(permissions)[receiverType]; + + const stillTooLong = | undefined | number | string | boolean>(permissions)[receiverType]; + + const stillTooLong2 = | undefined | number | string | boolean | null | never>(permissions)[receiverType]; + + const stillTooLong3 = | undefined>(permissions.someMethodWithLongName(parameter1, parameter2))[receiverTypeLongName]; + + const stillTooLong4 = | undefined | number | string | boolean | null | never>(permissions.someMethodWithLongName(parameter1, parameter2))[receiverTypeLongName]; + + const testObjLiteral = | undefined>{ prop1: "myPropVal" }; + + const testObjLiteral2 = | undefined | number | string | boolean | null | never | object>{ prop1: "myPropVal" }; + + const testArrayLiteral = | undefined>["first", "second", "third"]; + + const testArrayLiteral2 = | undefined | number | string | boolean | null | never | object>["first", "second", "third"]; + + const insideFuncCall = myFunc(param1, | undefined>param2, param3) +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// https://github.com/prettier/prettier/issues/4171 +function y() { + const fits = >fits(); + const fitsObjLiteral = >{ a: "test" }; + const fitsArrayLiteral = >["test", "test2"]; + + const breakAfterCast = >( + someExistingConfigMap.mergeDeep(fallbackOpts) + ); + + const stillTooLong = < + Immutable.Map< + string, + boolean, + number, + object, + null, + undefined, + any, + void, + never + > + >someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong2 = < + | Immutable.Map< + string, + boolean, + number, + object, + null, + undefined, + any, + void, + never + > + | undefined + >someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong3 = >( + someExistingConfigMap.mergeDeep( + fallbackOptions.someMethodWithLongName(param1, param2) + ) + ); + + const stillTooLong4 = < + | Immutable.Map< + string, + boolean, + number, + object, + null, + undefined, + any, + void, + never + > + | undefined + >someExistingConfigMap.mergeDeep( + fallbackOptions.someMethodWithLongName(param1, param2) + ); + + const testObjLiteral = >{ + property1: "myPropertyVal" + }; + + const testObjLiteral2 = < + Immutable.Map< + string, + any, + number, + boolean, + object, + null, + undefined, + never, + "extra long" + > + >{ property1: "myPropertyVal" }; + + const testArrayLiteral = >[ + "first", + "second", + "third" + ]; + + const testArrayLiteral2 = < + Immutable.Map< + string, + any, + number, + boolean, + object, + null, + undefined, + never, + "extra long" + > + >["first", "second", "third"]; + + const insideFuncCall = myFunc( + param1, + >param2, + param3 + ); +} + +// https://github.com/prettier/prettier/issues/4168 +function x() { + const fits = | undefined>(permissions)[type]; + const fitsObjLiteral = | undefined>{ a: "test" }; + const fitsArrayLiteral = | undefined>["t1", "t2"]; + + const breakAfterCast = | undefined>( + (permissions)[receiverType] + ); + + const stillTooLong = < + PermissionsChecker | undefined | number | string | boolean + >(permissions)[receiverType]; + + const stillTooLong2 = < + | PermissionsChecker + | undefined + | number + | string + | boolean + | null + | never + >(permissions)[receiverType]; + + const stillTooLong3 = | undefined>( + (permissions.someMethodWithLongName(parameter1, parameter2))[ + receiverTypeLongName + ] + ); + + const stillTooLong4 = < + | PermissionsChecker + | undefined + | number + | string + | boolean + | null + | never + >(permissions.someMethodWithLongName(parameter1, parameter2))[ + receiverTypeLongName + ]; + + const testObjLiteral = | undefined>{ + prop1: "myPropVal" + }; + + const testObjLiteral2 = < + | PermissionsChecker + | undefined + | number + | string + | boolean + | null + | never + | object + >{ prop1: "myPropVal" }; + + const testArrayLiteral = | undefined>[ + "first", + "second", + "third" + ]; + + const testArrayLiteral2 = < + | PermissionsChecker + | undefined + | number + | string + | boolean + | null + | never + | object + >["first", "second", "third"]; + + const insideFuncCall = myFunc( + param1, + | undefined>param2, + param3 + ); +} + +`; + exports[`hug-args.ts 1`] = ` postMessage( { diff --git a/tests/typescript_cast/generic-cast.ts b/tests/typescript_cast/generic-cast.ts new file mode 100644 index 00000000..8c523129 --- /dev/null +++ b/tests/typescript_cast/generic-cast.ts @@ -0,0 +1,54 @@ +// https://github.com/prettier/prettier/issues/4171 +function y() { + + const fits = >fits(); + const fitsObjLiteral = >{ a: "test" }; + const fitsArrayLiteral = >["test", "test2"] + + const breakAfterCast = >someExistingConfigMap.mergeDeep(fallbackOpts); + + const stillTooLong = >someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong2 = | undefined>someExistingConfigMap.mergeDeep(fallbackOptions); + + const stillTooLong3 = >someExistingConfigMap.mergeDeep(fallbackOptions.someMethodWithLongName(param1, param2)); + + const stillTooLong4 = | undefined>someExistingConfigMap.mergeDeep(fallbackOptions.someMethodWithLongName(param1, param2)); + + const testObjLiteral = >{ property1: "myPropertyVal" }; + + const testObjLiteral2 = >{ property1: "myPropertyVal" }; + + const testArrayLiteral = >["first", "second", "third"]; + + const testArrayLiteral2 = >["first", "second", "third"]; + + const insideFuncCall = myFunc(param1, >param2, param3) +} + +// https://github.com/prettier/prettier/issues/4168 +function x() { + const fits = | undefined>(permissions)[type]; + const fitsObjLiteral = | undefined>{ a: "test" }; + const fitsArrayLiteral = | undefined>["t1", "t2"]; + + const breakAfterCast = | undefined>(permissions)[receiverType]; + + const stillTooLong = | undefined | number | string | boolean>(permissions)[receiverType]; + + const stillTooLong2 = | undefined | number | string | boolean | null | never>(permissions)[receiverType]; + + const stillTooLong3 = | undefined>(permissions.someMethodWithLongName(parameter1, parameter2))[receiverTypeLongName]; + + const stillTooLong4 = | undefined | number | string | boolean | null | never>(permissions.someMethodWithLongName(parameter1, parameter2))[receiverTypeLongName]; + + const testObjLiteral = | undefined>{ prop1: "myPropVal" }; + + const testObjLiteral2 = | undefined | number | string | boolean | null | never | object>{ prop1: "myPropVal" }; + + const testArrayLiteral = | undefined>["first", "second", "third"]; + + const testArrayLiteral2 = | undefined | number | string | boolean | null | never | object>["first", "second", "third"]; + + const insideFuncCall = myFunc(param1, | undefined>param2, param3) +}