Skip to content

Commit

Permalink
Support TS4.9 satisfies operator (prettier#13764)
Browse files Browse the repository at this point in the history
* Print `satisfies` operators

* Add tests

* Add changelog

* Add `hug-args` tests

* Add more tests for TSAsExpression

* Add tests for argument expansion

* Add tests for template literal

* Add tests for ternary

* Add tests for export default satisfies

* Add tests for no lookahead tokens

* Add more tests for needs parens 1

* Add more tests for needs-parens 2

* Add more tests for needs parens 3

* Add tests for needs parens 4

* Add comment

* Add more tests for needs parens 5

* Add more tests for needs parens 6

* Fix test cases

* Fix snapshots

* Fix unstable tests

* No parens for mixed

* Merge printing logic

* More reuse `isTSTypeExpression`
  • Loading branch information
sosukesuzuki authored and medikoo committed Feb 5, 2024
1 parent 76bfccf commit 509b999
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 37 deletions.
16 changes: 16 additions & 0 deletions changelog_unreleased/typescript/13764.md
@@ -0,0 +1,16 @@
#### Support TypeScript 4.9 (#13764 by @sosukesuzuki)

Support [TypeScript 4.9](https://devblogs.microsoft.com/typescript/announcing-typescript-4-9) features!

##### [`satisfies` operator](https://devblogs.microsoft.com/typescript/announcing-typescript-4-9-beta/#the-satisfies-operator)

Supported by only `babel-ts` parser.

<!-- prettier-ignore -->
```tsx
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;
```
14 changes: 11 additions & 3 deletions src/language-js/needs-parens.js
Expand Up @@ -290,9 +290,13 @@ function needsParens(path, options) {
case "TSSatisfiesExpression":
case "LogicalExpression":
switch (parent.type) {
case "TSSatisfiesExpression":
case "TSAsExpression":
// example: foo as unknown as Bar
case "TSSatisfiesExpression":
// examples:
// foo as unknown as Bar
// foo satisfies unknown satisfies Bar
// foo satisfies unknown as Bar
// foo as unknown satisfies Bar
return !isTSTypeExpression(node);

case "ConditionalExpression":
Expand Down Expand Up @@ -327,7 +331,11 @@ function needsParens(path, options) {
case "AssignmentPattern":
return (
key === "left" &&
(node.type === "TSTypeAssertion" || isTSTypeExpression(node))
(node.type === "TSTypeAssertion" ||
// babel-parser cannot parse `satisfies` operator in left side of assignment
// https://github.com/babel/babel/issues/15095
// TODO: Add tests after the bug is fixed
isTSTypeExpression(node))
);

case "LogicalExpression":
Expand Down
6 changes: 3 additions & 3 deletions src/language-js/print/call-arguments.js
Expand Up @@ -13,13 +13,13 @@ import {
isCallExpression,
isStringLiteral,
isObjectProperty,
isTSTypeExpression,
getCallArgumentSelector,
isSimpleCallArgument,
isBinaryish,
isRegExpLiteral,
isSimpleType,
isCallLikeExpression,
isTSTypeExpression,
} from "../utils/index.js";

import {
Expand Down Expand Up @@ -217,7 +217,7 @@ function couldExpandArg(arg, arrowChainRecursion = false) {
(arg.type === "ArrayExpression" &&
(arg.elements.length > 0 || hasComment(arg))) ||
(arg.type === "TSTypeAssertion" && couldExpandArg(arg.expression)) ||
(arg.type === "TSAsExpression" && couldExpandArg(arg.expression)) ||
(isTSTypeExpression(arg) && couldExpandArg(arg.expression)) ||
arg.type === "FunctionExpression" ||
(arg.type === "ArrowFunctionExpression" &&
// we want to avoid breaking inside composite return types but not simple keywords
Expand Down Expand Up @@ -317,7 +317,7 @@ function isHopefullyShortCallArgument(node) {
return isHopefullyShortCallArgument(node.expression);
}

if (node.type === "TSAsExpression") {
if (isTSTypeExpression(node)) {
let { typeAnnotation } = node;
if (typeAnnotation.type === "TSArrayType") {
typeAnnotation = typeAnnotation.elementType;
Expand Down
2 changes: 1 addition & 1 deletion src/language-js/print/template-literal.js
Expand Up @@ -90,7 +90,7 @@ function printTemplateLiteral(path, print, options) {
isMemberExpression(expression) ||
expression.type === "ConditionalExpression" ||
expression.type === "SequenceExpression" ||
expression.type === "TSAsExpression" ||
isTSTypeExpression(expression) ||
isBinaryish(expression)
) {
expressionDoc = [indent([line, expressionDoc]), line];
Expand Down
4 changes: 2 additions & 2 deletions src/language-js/print/typescript.js
Expand Up @@ -143,8 +143,8 @@ function printTypescript(path, options, print) {
return printTypeParameters(path, options, print, "params");
case "TSTypeParameter":
return printTypeParameter(path, options, print);
case "TSSatisfiesExpression":
case "TSAsExpression": {
case "TSAsExpression":
case "TSSatisfiesExpression": {
const operator = node.type === "TSAsExpression" ? "as" : "satisfies";
parts.push(print("expression"), ` ${operator} `, print("typeAnnotation"));
const { parent } = path;
Expand Down
1 change: 1 addition & 0 deletions src/language-js/utils/index.js
Expand Up @@ -888,6 +888,7 @@ function startsWithNoLookaheadToken(node, predicate) {
return startsWithNoLookaheadToken(node.expressions[0], predicate);
case "TSSatisfiesExpression":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
return startsWithNoLookaheadToken(node.expression, predicate);
default:
Expand Down
Expand Up @@ -38,6 +38,10 @@ const bar8 = [1,2,3].reduce((carry, value) => {
return {...carry, [value]: true};
}, <{[key: number]: boolean}>{1: true});
const bar9 = [1,2,3].reduce((carry, value) => {
return [...carry, value];
}, [] as foo);
=====================================output=====================================
const bar1 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value]; }, [] as unknown as number[]
Expand Down Expand Up @@ -75,6 +79,10 @@ const bar8 = [1, 2, 3].reduce(
<{ [key: number]: boolean }>{ 1: true }
);
const bar9 = [1, 2, 3].reduce((carry, value) => {
return [...carry, value];
}, [] as foo);
================================================================================
`;
Expand Down
Expand Up @@ -29,3 +29,7 @@ const bar7 = [1,2,3].reduce((carry, value) => {
const bar8 = [1,2,3].reduce((carry, value) => {
return {...carry, [value]: true};
}, <{[key: number]: boolean}>{1: true});

const bar9 = [1,2,3].reduce((carry, value) => {
return [...carry, value];
}, [] as foo);
Expand Up @@ -29,28 +29,36 @@ const bar5 = [1,2,3].reduce((carry, value) => {
=====================================output=====================================
const bar1 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value] },
[] satisfies unknown satisfies number[]
(carry, value) => {
return [...carry, value]
},
[] satisfies unknown satisfies number[],
)
const bar2 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value] },
[1, 2, 3] satisfies unknown satisfies number[]
(carry, value) => {
return [...carry, value]
},
[1, 2, 3] satisfies unknown satisfies number[],
)
const bar3 = [1, 2, 3].reduce(
(carry, value) => { return { ...carry, [value]: true } },
{} satisfies unknown satisfies { [key: number]: boolean }
(carry, value) => {
return { ...carry, [value]: true }
},
{} satisfies unknown satisfies { [key: number]: boolean },
)
const bar4 = [1, 2, 3].reduce(
(carry, value) => { return { ...carry, [value]: true } },
{ 1: true } satisfies unknown satisfies { [key: number]: boolean }
(carry, value) => {
return { ...carry, [value]: true }
},
{ 1: true } satisfies unknown satisfies { [key: number]: boolean },
)
const bar5 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value] }, [] satisfies foo
)
const bar5 = [1, 2, 3].reduce((carry, value) => {
return [...carry, value]
}, [] satisfies foo)
================================================================================
`;
Expand Down Expand Up @@ -83,28 +91,36 @@ const bar5 = [1,2,3].reduce((carry, value) => {
=====================================output=====================================
const bar1 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value]; },
[] satisfies unknown satisfies number[]
(carry, value) => {
return [...carry, value];
},
[] satisfies unknown satisfies number[],
);
const bar2 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value]; },
[1, 2, 3] satisfies unknown satisfies number[]
(carry, value) => {
return [...carry, value];
},
[1, 2, 3] satisfies unknown satisfies number[],
);
const bar3 = [1, 2, 3].reduce(
(carry, value) => { return { ...carry, [value]: true }; },
{} satisfies unknown satisfies { [key: number]: boolean }
(carry, value) => {
return { ...carry, [value]: true };
},
{} satisfies unknown satisfies { [key: number]: boolean },
);
const bar4 = [1, 2, 3].reduce(
(carry, value) => { return { ...carry, [value]: true }; },
{ 1: true } satisfies unknown satisfies { [key: number]: boolean }
(carry, value) => {
return { ...carry, [value]: true };
},
{ 1: true } satisfies unknown satisfies { [key: number]: boolean },
);
const bar5 = [1, 2, 3].reduce(
(carry, value) => { return [...carry, value]; }, [] satisfies foo
);
const bar5 = [1, 2, 3].reduce((carry, value) => {
return [...carry, value];
}, [] satisfies foo);
================================================================================
`;
Expand Down Expand Up @@ -144,12 +160,12 @@ const extraRendererAttrs = ((attrs.rendererAttrs &&
Object.create(null)) satisfies FieldService.RendererAttributes
const annotate = (angular.injector satisfies any).$$annotate satisfies (
fn: Function
fn: Function,
) => string[]
const originalPrototype = originalConstructor.prototype satisfies TComponent &
InjectionTarget
, propertyToServiceName = originalPrototype._inject
InjectionTarget,
propertyToServiceName = originalPrototype._inject
this.previewPlayerHandle = setInterval(async () => {
if (this.previewIsPlaying) {
Expand Down Expand Up @@ -199,12 +215,12 @@ const extraRendererAttrs = ((attrs.rendererAttrs &&
Object.create(null)) satisfies FieldService.RendererAttributes;
const annotate = (angular.injector satisfies any).$$annotate satisfies (
fn: Function
fn: Function,
) => string[];
const originalPrototype = originalConstructor.prototype satisfies TComponent &
InjectionTarget
, propertyToServiceName = originalPrototype._inject;
InjectionTarget,
propertyToServiceName = originalPrototype._inject;
this.previewPlayerHandle = setInterval(async () => {
if (this.previewIsPlaying) {
Expand Down

0 comments on commit 509b999

Please sign in to comment.