diff --git a/changelog_unreleased/typescript/pr-7869.md b/changelog_unreleased/typescript/pr-7869.md new file mode 100644 index 000000000000..a242c4d324a8 --- /dev/null +++ b/changelog_unreleased/typescript/pr-7869.md @@ -0,0 +1,14 @@ +#### Wrap TSAsExpression ([#7869](https://github.com/prettier/prettier/pull/7869) by [@sosukesuzuki](https://github.com/sosukesuzuki)) + + +```ts +// Input +const varibale = foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType; + +// Prettier stable +const varibale = foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType; + +// Prettier master +const varibale = + foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType; +``` diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 583c21e39ed0..0468af2bba66 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -553,7 +553,9 @@ function printPathNoParens(path, options, print, args) { ); case "BinaryExpression": case "LogicalExpression": - case "NGPipeExpression": { + case "NGPipeExpression": + case "TSAsExpression": { + const { rightNodeName, operator } = getBinaryishNodeNames(n); const parent = path.getParentNode(); const parentParent = path.getParentNode(1); const isInsideParenthesis = @@ -609,12 +611,12 @@ function printPathNoParens(path, options, print, args) { // Avoid indenting sub-expressions in some cases where the first sub-expression is already // indented accordingly. We should indent sub-expressions where the first case isn't indented. - const shouldNotIndent = + let shouldNotIndent = parent.type === "ReturnStatement" || parent.type === "ThrowStatement" || (parent.type === "JSXExpressionContainer" && parentParent.type === "JSXAttribute") || - (n.operator !== "|" && parent.type === "JsExpressionRoot") || + (operator !== "|" && parent.type === "JsExpressionRoot") || (n.type !== "NGPipeExpression" && ((parent.type === "NGRoot" && options.parser === "__ng_binding") || (parent.type === "NGMicrosyntaxExpression" && @@ -629,23 +631,25 @@ function printPathNoParens(path, options, print, args) { parentParent.type !== "OptionalCallExpression") || parent.type === "TemplateLiteral"; - const shouldIndentIfInlining = - parent.type === "AssignmentExpression" || - parent.type === "VariableDeclarator" || - parent.type === "ClassProperty" || - parent.type === "TSAbstractClassProperty" || - parent.type === "ClassPrivateProperty" || - parent.type === "ObjectProperty" || - parent.type === "Property"; - - const samePrecedenceSubExpression = - isBinaryish(n.left) && shouldFlatten(n.operator, n.left.operator); + if (!shouldNotIndent) { + if (shouldInlineLogicalExpression(n)) { + const samePrecedenceSubExpression = + isBinaryish(n.left) && shouldFlatten(operator, n.left.operator); + shouldNotIndent = !samePrecedenceSubExpression; + } else { + const shouldIndentIfInlining = + parent.type === "AssignmentExpression" || + parent.type === "VariableDeclarator" || + parent.type === "ClassProperty" || + parent.type === "TSAbstractClassProperty" || + parent.type === "ClassPrivateProperty" || + parent.type === "ObjectProperty" || + parent.type === "Property"; + shouldNotIndent = shouldIndentIfInlining; + } + } - if ( - shouldNotIndent || - (shouldInlineLogicalExpression(n) && !samePrecedenceSubExpression) || - (!shouldInlineLogicalExpression(n) && shouldIndentIfInlining) - ) { + if (shouldNotIndent) { return group(concat(parts)); } @@ -662,7 +666,7 @@ function printPathNoParens(path, options, print, args) { // // ) - const hasJSX = isJSXNode(n.right); + const hasJSX = isJSXNode(n[rightNodeName]); const rest = concat(hasJSX ? parts.slice(1, -1) : parts.slice(1)); const groupId = Symbol("logicalChain-" + ++uid); @@ -2489,7 +2493,6 @@ function printPathNoParens(path, options, print, args) { n.expressions[i].type === "OptionalMemberExpression" || n.expressions[i].type === "ConditionalExpression" || n.expressions[i].type === "SequenceExpression" || - n.expressions[i].type === "TSAsExpression" || isBinaryish(n.expressions[i]) ) { printed = concat([indent(concat([softline, printed])), softline]); @@ -3190,12 +3193,6 @@ function printPathNoParens(path, options, print, args) { return "unknown"; case "TSVoidKeyword": return "void"; - case "TSAsExpression": - return concat([ - path.call(print, "expression"), - " as ", - path.call(print, "typeAnnotation"), - ]); case "TSArrayType": return concat([path.call(print, "elementType"), "[]"]); case "TSPropertySignature": { @@ -5695,6 +5692,20 @@ function shouldInlineLogicalExpression(node) { return false; } +function getBinaryishNodeNames(node) { + const leftNodeName = node.type === "TSAsExpression" ? "expression" : "left"; + const rightNodeName = + node.type === "TSAsExpression" ? "typeAnnotation" : "right"; + const operator = + node.type === "NGPipeExpression" + ? "|" + : node.type === "TSAsExpression" + ? "as" + : node.operator; + + return { leftNodeName, rightNodeName, operator }; +} + // For binary expressions to be consistent, we need to group // subsequent operators with the same precedence level under a single // group. Otherwise they will be nested such that some of them break @@ -5713,8 +5724,12 @@ function printBinaryishExpressions( let parts = []; const node = path.getValue(); - // We treat BinaryExpression and LogicalExpression nodes the same. + // We treat BinaryExpression, LogicalExpression, NGPipeExpression and TSAsExpression the same. if (isBinaryish(node)) { + const { leftNodeName, rightNodeName, operator } = getBinaryishNodeNames( + node + ); + // Put all operators with the same precedence level in the same // group. The reason we only need to do this with the `left` // expression is because given an expression like `1 + 2 - 3`, it @@ -5724,7 +5739,11 @@ function printBinaryishExpressions( // precedence level and should be treated as a separate group, so // print them normally. (This doesn't hold for the `**` operator, // which is unique in that it is right-associative.) - if (shouldFlatten(node.operator, node.left.operator)) { + if ( + node.type === "NGPipeExpression" || + (node.type !== "TSAsExpression" && + shouldFlatten(operator, node[leftNodeName].operator)) + ) { // Flatten them out by recursively calling this function. parts = parts.concat( path.call( @@ -5736,21 +5755,24 @@ function printBinaryishExpressions( /* isNested */ true, isInsideParenthesis ), - "left" + leftNodeName ) ); } else { - parts.push(path.call(print, "left")); + parts.push(path.call(print, leftNodeName)); } const shouldInline = shouldInlineLogicalExpression(node); const lineBeforeOperator = - (node.operator === "|>" || + (operator === "|>" || node.type === "NGPipeExpression" || - (node.operator === "|" && options.parser === "__vue_expression")) && - !hasLeadingOwnLineComment(options.originalText, node.right, options); + (operator === "|" && options.parser === "__vue_expression")) && + !hasLeadingOwnLineComment( + options.originalText, + node[rightNodeName], + options + ); - const operator = node.type === "NGPipeExpression" ? "|" : node.operator; const rightSuffix = node.type === "NGPipeExpression" && node.arguments.length !== 0 ? group( @@ -5770,12 +5792,12 @@ function printBinaryishExpressions( : ""; const right = shouldInline - ? concat([operator, " ", path.call(print, "right"), rightSuffix]) + ? concat([operator, " ", path.call(print, rightNodeName), rightSuffix]) : concat([ lineBeforeOperator ? softline : "", operator, lineBeforeOperator ? " " : line, - path.call(print, "right"), + path.call(print, rightNodeName), rightSuffix, ]); @@ -5785,8 +5807,8 @@ function printBinaryishExpressions( const shouldGroup = !(isInsideParenthesis && node.type === "LogicalExpression") && parent.type !== node.type && - node.left.type !== node.type && - node.right.type !== node.type; + node[leftNodeName].type !== node.type && + node[rightNodeName].type !== node.type; parts.push(" ", shouldGroup ? group(right) : right); diff --git a/src/language-js/utils.js b/src/language-js/utils.js index 8c485a366010..33a20c756bb4 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -289,6 +289,7 @@ const binaryishNodeTypes = new Set([ "BinaryExpression", "LogicalExpression", "NGPipeExpression", + "TSAsExpression", ]); function isBinaryish(node) { return binaryishNodeTypes.has(node.type); diff --git a/tests/typescript_as/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_as/__snapshots__/jsfmt.spec.js.snap index a90a79d45674..7a9682cea1cd 100644 --- a/tests/typescript_as/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_as/__snapshots__/jsfmt.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`as.js 1`] = ` +exports[`as.ts 1`] = ` ====================================options===================================== parsers: ["typescript"] printWidth: 80 @@ -9,9 +9,12 @@ printWidth: 80 const name = (description as DescriptionObject).name || (description as string); this.isTabActionBar((e.target || e.srcElement) as HTMLElement); (originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as InjectionError; -'current' in (props.pagination as Object) -start + (yearSelectTotal as number) -scrollTop > (visibilityHeight as number) +'current' in (props.pagination as Object); +('current' in props.pagination) as Object; +start + (yearSelectTotal as number); +(start + yearSelectTotal) as number; +scrollTop > (visibilityHeight as number); +(scrollTop > visibilityHeight) as number; export default class Column extends (RcTable.Column as React.ComponentClass,ColumnProps,ColumnProps,ColumnProps>) {} export const MobxTypedForm = class extends (Form as { new (): any }) {} export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} @@ -35,22 +38,33 @@ const state = JSON.stringify({ (bValue as boolean) ? 0 : -1; bValue ? 0 : -1; +const value1 = thisIsAReallyReallyReallyReallyReallyLongIdentifier as SomeInterface; +const value2 = thisIsAnIdentifier as thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; +const value3 = thisIsAReallyLongIdentifier as (SomeInterface | SomeOtherInterface); +const value4 = thisIsAReallyLongIdentifier as { prop1: string, prop2: number, prop3: number }[]; +const value5 = thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyLongIdentifier as [string, number]; + +const iter1 = createIterator(this.controller, child, this.tag as SyncFunctionComponent); +const iter2 = createIterator(self.controller, child, self.tag as SyncFunctionComponent); =====================================output===================================== const name = (description as DescriptionObject).name || (description as string); this.isTabActionBar((e.target || e.srcElement) as HTMLElement); -(originalError - ? wrappedError(errMsg, originalError) - : Error(errMsg)) as InjectionError; +(originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as + InjectionError; "current" in (props.pagination as Object); +("current" in props.pagination) as Object; start + (yearSelectTotal as number); +(start + yearSelectTotal) as number; scrollTop > (visibilityHeight as number); -export default class Column extends (RcTable.Column as React.ComponentClass< - ColumnProps, - ColumnProps, - ColumnProps, - ColumnProps ->) {} +(scrollTop > visibilityHeight) as number; +export default class Column extends (RcTable.Column as + React.ComponentClass< + ColumnProps, + ColumnProps, + ColumnProps, + ColumnProps + >) {} export const MobxTypedForm = class extends (Form as { new (): any }) {}; export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} ({} as {}); @@ -73,6 +87,31 @@ const state = JSON.stringify({ (bValue as boolean) ? 0 : -1; bValue ? 0 : -1; +const value1 = + thisIsAReallyReallyReallyReallyReallyLongIdentifier as SomeInterface; +const value2 = + thisIsAnIdentifier as + thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; +const value3 = + thisIsAReallyLongIdentifier as SomeInterface | SomeOtherInterface; +const value4 = + thisIsAReallyLongIdentifier as + { prop1: string; prop2: number; prop3: number }[]; +const value5 = + thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyLongIdentifier as + [string, number]; + +const iter1 = createIterator( + this.controller, + child, + this.tag as SyncFunctionComponent +); +const iter2 = createIterator( + self.controller, + child, + self.tag as SyncFunctionComponent +); + ================================================================================ `; diff --git a/tests/typescript_as/as.js b/tests/typescript_as/as.js deleted file mode 100644 index 596bdd8d2c92..000000000000 --- a/tests/typescript_as/as.js +++ /dev/null @@ -1,29 +0,0 @@ -const name = (description as DescriptionObject).name || (description as string); -this.isTabActionBar((e.target || e.srcElement) as HTMLElement); -(originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as InjectionError; -'current' in (props.pagination as Object) -start + (yearSelectTotal as number) -scrollTop > (visibilityHeight as number) -export default class Column extends (RcTable.Column as React.ComponentClass,ColumnProps,ColumnProps,ColumnProps>) {} -export const MobxTypedForm = class extends (Form as { new (): any }) {} -export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} -({}) as {}; -function*g() { - const test = (yield 'foo') as number; -} -async function g1() { - const test = (await 'foo') as number; -} -({}) as X; -() => ({}) as X; -const state = JSON.stringify({ - next: window.location.href, - nonce, -} as State); - -(foo.bar as Baz) = [bar]; -(foo.bar as any)++; - -(bValue as boolean) ? 0 : -1; -bValue ? 0 : -1; - diff --git a/tests/typescript_as/as.ts b/tests/typescript_as/as.ts new file mode 100644 index 000000000000..cfdaced54745 --- /dev/null +++ b/tests/typescript_as/as.ts @@ -0,0 +1,40 @@ +const name = (description as DescriptionObject).name || (description as string); +this.isTabActionBar((e.target || e.srcElement) as HTMLElement); +(originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as InjectionError; +'current' in (props.pagination as Object); +('current' in props.pagination) as Object; +start + (yearSelectTotal as number); +(start + yearSelectTotal) as number; +scrollTop > (visibilityHeight as number); +(scrollTop > visibilityHeight) as number; +export default class Column extends (RcTable.Column as React.ComponentClass,ColumnProps,ColumnProps,ColumnProps>) {} +export const MobxTypedForm = class extends (Form as { new (): any }) {} +export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} +({}) as {}; +function*g() { + const test = (yield 'foo') as number; +} +async function g1() { + const test = (await 'foo') as number; +} +({}) as X; +() => ({}) as X; +const state = JSON.stringify({ + next: window.location.href, + nonce, +} as State); + +(foo.bar as Baz) = [bar]; +(foo.bar as any)++; + +(bValue as boolean) ? 0 : -1; +bValue ? 0 : -1; + +const value1 = thisIsAReallyReallyReallyReallyReallyLongIdentifier as SomeInterface; +const value2 = thisIsAnIdentifier as thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; +const value3 = thisIsAReallyLongIdentifier as (SomeInterface | SomeOtherInterface); +const value4 = thisIsAReallyLongIdentifier as { prop1: string, prop2: number, prop3: number }[]; +const value5 = thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyLongIdentifier as [string, number]; + +const iter1 = createIterator(this.controller, child, this.tag as SyncFunctionComponent); +const iter2 = createIterator(self.controller, child, self.tag as SyncFunctionComponent); diff --git a/tests/typescript_template_literals/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_template_literals/__snapshots__/jsfmt.spec.js.snap index abb2f54740fc..69c933d06a6b 100644 --- a/tests/typescript_template_literals/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_template_literals/__snapshots__/jsfmt.spec.js.snap @@ -25,7 +25,8 @@ const b = \`\${ }\`; const b = \`\${ (veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongFoo + - veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar) as veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBaz + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBar) as + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongBaz }\`; ================================================================================ diff --git a/tests/typescript_union/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_union/__snapshots__/jsfmt.spec.js.snap index 33ccf96dbb43..ca216209fe06 100644 --- a/tests/typescript_union/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_union/__snapshots__/jsfmt.spec.js.snap @@ -379,10 +379,8 @@ type State = { | { discriminant: "BAZ"; baz: any } ); -const foo1 = [abc, def, ghi, jkl, mno, pqr, stu, vwx, yz] as ( - | string - | undefined -)[]; +const foo1 = + [abc, def, ghi, jkl, mno, pqr, stu, vwx, yz] as (string | undefined)[]; const foo2: ( | AAAAAAAAAAAAAAAAAAAAAA