Skip to content

Commit

Permalink
Support TS4.9 satisfies operator (#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 committed Nov 1, 2022
1 parent 7bfed17 commit 931661a
Show file tree
Hide file tree
Showing 26 changed files with 1,325 additions and 11 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>;
```
22 changes: 18 additions & 4 deletions src/language-js/needs-parens.js
Expand Up @@ -11,6 +11,7 @@ import {
isCallExpression,
isMemberExpression,
isObjectProperty,
isTSTypeExpression,
} from "./utils/index.js";

function needsParens(path, options) {
Expand Down Expand Up @@ -227,14 +228,20 @@ function needsParens(path, options) {
// fallthrough
case "TSTypeAssertion":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "LogicalExpression":
switch (parent.type) {
case "TSAsExpression":
// example: foo as unknown as Bar
return node.type !== "TSAsExpression";
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":
return node.type === "TSAsExpression";
return isTSTypeExpression(node);

case "CallExpression":
case "NewExpression":
Expand Down Expand Up @@ -265,7 +272,11 @@ function needsParens(path, options) {
case "AssignmentPattern":
return (
key === "left" &&
(node.type === "TSTypeAssertion" || node.type === "TSAsExpression")
(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 Expand Up @@ -360,6 +371,7 @@ function needsParens(path, options) {
case "SpreadElement":
case "SpreadProperty":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
case "BindExpression":
return true;
Expand Down Expand Up @@ -592,6 +604,7 @@ function needsParens(path, options) {
case "TSTypeAssertion":
case "TypeCastExpression":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
return true;

Expand Down Expand Up @@ -641,6 +654,7 @@ function needsParens(path, options) {
return key === "object";

case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
case "BindExpression":
case "TaggedTemplateExpression":
Expand Down
5 changes: 3 additions & 2 deletions src/language-js/print/call-arguments.js
Expand Up @@ -19,6 +19,7 @@ import {
isRegExpLiteral,
isSimpleType,
isCallLikeExpression,
isTSTypeExpression,
} from "../utils/index.js";

import {
Expand Down Expand Up @@ -186,7 +187,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 @@ -286,7 +287,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
3 changes: 2 additions & 1 deletion src/language-js/print/template-literal.js
Expand Up @@ -17,6 +17,7 @@ import {
isSimpleTemplateLiteral,
hasComment,
isMemberExpression,
isTSTypeExpression,
} from "../utils/index.js";

function printTemplateLiteral(path, print, options) {
Expand Down Expand Up @@ -87,7 +88,7 @@ function printTemplateLiteral(path, print, options) {
isMemberExpression(expression) ||
expression.type === "ConditionalExpression" ||
expression.type === "SequenceExpression" ||
expression.type === "TSAsExpression" ||
isTSTypeExpression(expression) ||
isBinaryish(expression)
) {
expressionDoc = [indent([softline, expressionDoc]), softline];
Expand Down
3 changes: 2 additions & 1 deletion src/language-js/print/ternary.js
Expand Up @@ -4,6 +4,7 @@ import {
getComments,
isCallExpression,
isMemberExpression,
isTSTypeExpression,
} from "../utils/index.js";
import { locStart, locEnd } from "../loc.js";
import isBlockComment from "../utils/is-block-comment.js";
Expand Down Expand Up @@ -161,7 +162,7 @@ function shouldExtraIndentForConditionalExpression(path) {

if (
(node.type === "NewExpression" && node.callee === child) ||
(node.type === "TSAsExpression" && node.expression === child)
(isTSTypeExpression(node) && node.expression === child)
) {
parent = path.getParentNode(ancestorCount + 1);
child = node;
Expand Down
6 changes: 4 additions & 2 deletions src/language-js/print/typescript.js
Expand Up @@ -142,8 +142,10 @@ function printTypescript(path, options, print) {
return printTypeParameters(path, options, print, "params");
case "TSTypeParameter":
return printTypeParameter(path, options, print);
case "TSAsExpression": {
parts.push(print("expression"), " as ", print("typeAnnotation"));
case "TSAsExpression":
case "TSSatisfiesExpression": {
const operator = node.type === "TSAsExpression" ? "as" : "satisfies";
parts.push(print("expression"), ` ${operator} `, print("typeAnnotation"));
const { parent } = path;
if (
(isCallExpression(parent) && parent.callee === node) ||
Expand Down
10 changes: 9 additions & 1 deletion src/language-js/utils/index.js
Expand Up @@ -62,7 +62,7 @@ function hasNakedLeftSide(node) {
node.type === "TaggedTemplateExpression" ||
node.type === "BindExpression" ||
(node.type === "UpdateExpression" && !node.prefix) ||
node.type === "TSAsExpression" ||
isTSTypeExpression(node) ||
node.type === "TSNonNullExpression"
);
}
Expand Down Expand Up @@ -898,6 +898,7 @@ function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) {
forbidFunctionClassAndDoExpr
);
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
return startsWithNoLookaheadToken(
node.expression,
Expand Down Expand Up @@ -1219,6 +1220,12 @@ const markerForIfWithoutBlockAndSameLineComment = Symbol(
"ifWithoutBlockAndSameLineComment"
);

function isTSTypeExpression(node) {
return (
node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression"
);
}

export {
getFunctionParameters,
iterateFunctionParametersPath,
Expand Down Expand Up @@ -1280,4 +1287,5 @@ export {
getComments,
CommentCheckFlags,
markerForIfWithoutBlockAndSameLineComment,
isTSTypeExpression,
};
1 change: 1 addition & 0 deletions tests/config/format-test.js
Expand Up @@ -37,6 +37,7 @@ const unstableTests = new Map(
"typescript/prettier-ignore/mapped-types.ts",
"js/comments/html-like/comment.js",
"js/for/continue-and-break-comment-without-blocks.js",
"typescript/satisfies-operators/comments-unstable.ts",
].map((fixture) => {
const [file, isUnstable = () => true] = Array.isArray(fixture)
? fixture
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) => {
Expand Down Expand Up @@ -95,6 +99,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);

0 comments on commit 931661a

Please sign in to comment.