Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeScript] format TSAsExpression with same logic as BinaryExpression #7869

Merged
merged 17 commits into from Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions 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))

<!-- prettier-ignore -->
```ts
// Input
const varibale = foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType;

// Prettier stable
const varibale = foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType;

// Prettier master
const varibale =
foooooooooooooooooooooooooooooooooooooooooooooooooooo as SomeType;
```
100 changes: 61 additions & 39 deletions src/language-js/printer-estree.js
Expand Up @@ -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 =
Expand Down Expand Up @@ -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" &&
Expand All @@ -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));
}

Expand All @@ -662,7 +666,7 @@ function printPathNoParens(path, options, print, args) {
// </Foo>
// )

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);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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,
]);

Expand All @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/language-js/utils.js
Expand Up @@ -289,6 +289,7 @@ const binaryishNodeTypes = new Set([
"BinaryExpression",
"LogicalExpression",
"NGPipeExpression",
"TSAsExpression",
]);
function isBinaryish(node) {
return binaryishNodeTypes.has(node.type);
Expand Down
65 changes: 52 additions & 13 deletions 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
Expand All @@ -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<T> extends (RcTable.Column as React.ComponentClass<ColumnProps<T>,ColumnProps<T>,ColumnProps<T>,ColumnProps<T>>) {}
export const MobxTypedForm = class extends (Form as { new (): any }) {}
export abstract class MobxTypedForm1 extends (Form as { new (): any }) {}
Expand All @@ -35,22 +38,33 @@ const state = JSON.stringify({
(bValue as boolean) ? 0 : -1;
<boolean>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<T> extends (RcTable.Column as React.ComponentClass<
ColumnProps<T>,
ColumnProps<T>,
ColumnProps<T>,
ColumnProps<T>
>) {}
(scrollTop > visibilityHeight) as number;
export default class Column<T> extends (RcTable.Column as
React.ComponentClass<
ColumnProps<T>,
ColumnProps<T>,
ColumnProps<T>,
ColumnProps<T>
>) {}
export const MobxTypedForm = class extends (Form as { new (): any }) {};
export abstract class MobxTypedForm1 extends (Form as { new (): any }) {}
({} as {});
Expand All @@ -73,6 +87,31 @@ const state = JSON.stringify({
(bValue as boolean) ? 0 : -1;
<boolean>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
);

================================================================================
`;

Expand Down
29 changes: 0 additions & 29 deletions tests/typescript_as/as.js

This file was deleted.

40 changes: 40 additions & 0 deletions 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<T> extends (RcTable.Column as React.ComponentClass<ColumnProps<T>,ColumnProps<T>,ColumnProps<T>,ColumnProps<T>>) {}
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;
<boolean>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);