diff --git a/changelog_unreleased/typescript/13764.md b/changelog_unreleased/typescript/13764.md new file mode 100644 index 000000000000..43f84930a7ae --- /dev/null +++ b/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. + + +```tsx +const palette = { + red: [255, 0, 0], + green: "#00ff00", + blue: [0, 0, 255] +} satisfies Record; +``` diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index da00845f28c4..cd10882ef5d3 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -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": @@ -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": diff --git a/src/language-js/print/call-arguments.js b/src/language-js/print/call-arguments.js index 081dd31f182c..b71c1562ce55 100644 --- a/src/language-js/print/call-arguments.js +++ b/src/language-js/print/call-arguments.js @@ -13,13 +13,13 @@ import { isCallExpression, isStringLiteral, isObjectProperty, - isTSTypeExpression, getCallArgumentSelector, isSimpleCallArgument, isBinaryish, isRegExpLiteral, isSimpleType, isCallLikeExpression, + isTSTypeExpression, } from "../utils/index.js"; import { @@ -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 @@ -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; diff --git a/src/language-js/print/template-literal.js b/src/language-js/print/template-literal.js index 0c442678f102..5cb2067e28e8 100644 --- a/src/language-js/print/template-literal.js +++ b/src/language-js/print/template-literal.js @@ -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]; diff --git a/src/language-js/print/typescript.js b/src/language-js/print/typescript.js index eb857ddd806a..6264b8b1c943 100644 --- a/src/language-js/print/typescript.js +++ b/src/language-js/print/typescript.js @@ -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; diff --git a/src/language-js/utils/index.js b/src/language-js/utils/index.js index 08e651ad4b98..9532efd1f9b2 100644 --- a/src/language-js/utils/index.js +++ b/src/language-js/utils/index.js @@ -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: diff --git a/tests/format/typescript/argument-expansion/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/argument-expansion/__snapshots__/jsfmt.spec.js.snap index 1e8ae638db0f..96398f761e3c 100644 --- a/tests/format/typescript/argument-expansion/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/argument-expansion/__snapshots__/jsfmt.spec.js.snap @@ -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[] @@ -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); + ================================================================================ `; diff --git a/tests/format/typescript/argument-expansion/argument_expansion.ts b/tests/format/typescript/argument-expansion/argument_expansion.ts index 37f77d346e1b..9230cac27569 100644 --- a/tests/format/typescript/argument-expansion/argument_expansion.ts +++ b/tests/format/typescript/argument-expansion/argument_expansion.ts @@ -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); diff --git a/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap index fc83415bdb19..53ff56d24ebe 100644 --- a/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap @@ -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) ================================================================================ `; @@ -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); ================================================================================ `; @@ -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) { @@ -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) {