Skip to content

Commit

Permalink
Backport "Support TS4.9 satisfies operator (#13764)" (#13783)
Browse files Browse the repository at this point in the history
  • Loading branch information
sosukesuzuki committed Nov 4, 2022
1 parent ca246af commit ff0529a
Show file tree
Hide file tree
Showing 31 changed files with 1,460 additions and 128 deletions.
8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -24,10 +24,10 @@
"dependencies": {
"@angular/compiler": "12.2.16",
"@babel/code-frame": "7.16.7",
"@babel/parser": "7.18.0",
"@babel/parser": "7.20.1",
"@glimmer/syntax": "0.84.2",
"@iarna/toml": "2.2.5",
"@typescript-eslint/typescript-estree": "5.30.0",
"@typescript-eslint/typescript-estree": "5.36.2",
"acorn": "8.8.0",
"acorn-jsx": "5.3.2",
"angular-estree-parser": "2.5.1",
Expand Down Expand Up @@ -85,7 +85,7 @@
"semver": "7.3.7",
"string-width": "5.0.1",
"strip-ansi": "7.0.1",
"typescript": "4.7.2",
"typescript": "4.8.2",
"unicode-regex": "3.0.0",
"unified": "9.2.1",
"vnopts": "1.0.2",
Expand All @@ -102,7 +102,7 @@
"@types/file-entry-cache": "5.0.2",
"@types/find-cache-dir": "3.2.1",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "5.20.0",
"@typescript-eslint/eslint-plugin": "5.36.2",
"babel-jest": "27.5.1",
"benchmark": "2.1.4",
"browserslist-to-esbuild": "1.1.1",
Expand Down
16 changes: 6 additions & 10 deletions src/language-js/loc.js
Expand Up @@ -6,20 +6,16 @@ const isNonEmptyArray = require("../utils/is-non-empty-array.js");
* @typedef {import("./types/estree").Node} Node
*/

function locStart(node, opts) {
const { ignoreDecorators } = opts || {};
function locStart(node) {
const start = node.range ? node.range[0] : node.start;

// Handle nodes with decorators. They should start at the first decorator
if (!ignoreDecorators) {
const decorators =
(node.declaration && node.declaration.decorators) || node.decorators;

if (isNonEmptyArray(decorators)) {
return locStart(decorators[0]);
}
const decorators = node.declaration?.decorators ?? node.decorators;
if (isNonEmptyArray(decorators)) {
return Math.min(locStart(decorators[0]), start);
}

return node.range ? node.range[0] : node.start;
return start;
}

function locEnd(node) {
Expand Down
14 changes: 10 additions & 4 deletions src/language-js/needs-parens.js
Expand Up @@ -15,6 +15,7 @@ const {
isCallExpression,
isMemberExpression,
isObjectProperty,
isTSTypeExpression,
} = require("./utils/index.js");

function needsParens(path, options) {
Expand Down Expand Up @@ -244,14 +245,16 @@ function needsParens(path, options) {
// fallthrough
case "TSTypeAssertion":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "LogicalExpression":
switch (parent.type) {
case "TSSatisfiesExpression":
case "TSAsExpression":
// example: foo as unknown as Bar
return node.type !== "TSAsExpression";
return !isTSTypeExpression(node);

case "ConditionalExpression":
return node.type === "TSAsExpression";
return isTSTypeExpression(node);

case "CallExpression":
case "NewExpression":
Expand Down Expand Up @@ -282,7 +285,7 @@ function needsParens(path, options) {
case "AssignmentPattern":
return (
name === "left" &&
(node.type === "TSTypeAssertion" || node.type === "TSAsExpression")
(node.type === "TSTypeAssertion" || isTSTypeExpression(node))
);

case "LogicalExpression":
Expand Down Expand Up @@ -363,7 +366,7 @@ function needsParens(path, options) {
if (
parent.type === "UnaryExpression" ||
parent.type === "AwaitExpression" ||
parent.type === "TSAsExpression" ||
isTSTypeExpression(parent) ||
parent.type === "TSNonNullExpression"
) {
return true;
Expand All @@ -377,6 +380,7 @@ function needsParens(path, options) {
case "SpreadElement":
case "SpreadProperty":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
case "BindExpression":
return true;
Expand Down Expand Up @@ -612,6 +616,7 @@ function needsParens(path, options) {
case "TSTypeAssertion":
case "TypeCastExpression":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
return true;

Expand Down Expand Up @@ -661,6 +666,7 @@ function needsParens(path, options) {
return name === "object";

case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
case "BindExpression":
case "TaggedTemplateExpression":
Expand Down
47 changes: 25 additions & 22 deletions src/language-js/parse/postprocess/typescript.js
@@ -1,33 +1,36 @@
"use strict";

const isNonEmptyArray = require("../../../utils/is-non-empty-array.js");
const visitNode = require("./visit-node.js");
const throwSyntaxError = require("./throw-syntax-error.js");

// Copied from https://unpkg.com/typescript@4.8.2/lib/typescript.js
function getSourceFileOfNode(node) {
while (node && node.kind !== 305 /* SyntaxKind.SourceFile */) {
node = node.parent;
}
return node;
}

// Invalid decorators are removed since `@typescript-eslint/typescript-estree` v4
// https://github.com/typescript-eslint/typescript-eslint/pull/2375
function throwErrorForInvalidDecorator(
tsNode,
esTreeNode,
tsNodeToESTreeNodeMap
) {
const tsDecorators = tsNode.decorators;
if (!Array.isArray(tsDecorators)) {
// There is a `checkGrammarDecorators` in `typescript` package, consider use it directly in future
function throwErrorForInvalidDecorator(tsNode) {
const { illegalDecorators } = tsNode;
if (!isNonEmptyArray(illegalDecorators)) {
return;
}
const esTreeDecorators = esTreeNode.decorators;
if (
!Array.isArray(esTreeDecorators) ||
esTreeDecorators.length !== tsDecorators.length ||
tsDecorators.some((tsDecorator) => {
const esTreeDecorator = tsNodeToESTreeNodeMap.get(tsDecorator);
return !esTreeDecorator || !esTreeDecorators.includes(esTreeDecorator);
})
) {
throwSyntaxError(
esTreeNode,
"Leading decorators must be attached to a class declaration"
);
}

const [{ expression }] = illegalDecorators;

const sourceFile = getSourceFileOfNode(expression);
const [start, end] = [expression.pos, expression.end].map((position) => {
const { line, character: column } =
sourceFile.getLineAndCharacterOfPosition(position);
return { line: line + 1, column };
});

throwSyntaxError({ loc: { start, end } }, "Decorators are not valid here.");
}

// Values of abstract property is removed since `@typescript-eslint/typescript-estree` v5
Expand Down Expand Up @@ -66,7 +69,7 @@ function throwErrorForInvalidNodes(ast, options) {
return;
}

throwErrorForInvalidDecorator(tsNode, esTreeNode, tsNodeToESTreeNodeMap);
throwErrorForInvalidDecorator(tsNode);
throwErrorForInvalidAbstractProperty(tsNode, esTreeNode);
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/language-js/print/call-arguments.js
Expand Up @@ -16,6 +16,7 @@ const {
isCallExpression,
isStringLiteral,
isObjectProperty,
isTSTypeExpression,
} = require("../utils/index.js");

const {
Expand Down Expand Up @@ -190,7 +191,7 @@ function couldGroupArg(arg, arrowChainRecursion = false) {
(arg.type === "ArrayExpression" &&
(arg.elements.length > 0 || hasComment(arg))) ||
(arg.type === "TSTypeAssertion" && couldGroupArg(arg.expression)) ||
(arg.type === "TSAsExpression" && couldGroupArg(arg.expression)) ||
(isTSTypeExpression(arg) && couldGroupArg(arg.expression)) ||
arg.type === "FunctionExpression" ||
(arg.type === "ArrowFunctionExpression" &&
// we want to avoid breaking inside composite return types but not simple keywords
Expand Down
3 changes: 1 addition & 2 deletions src/language-js/print/decorators.js
Expand Up @@ -72,8 +72,7 @@ function hasDecoratorsBeforeExport(node) {
const decorators = node.declaration && node.declaration.decorators;

return (
isNonEmptyArray(decorators) &&
locStart(node, { ignoreDecorators: true }) > locStart(decorators[0])
isNonEmptyArray(decorators) && locStart(node) === locStart(decorators[0])
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/language-js/print/template-literal.js
Expand Up @@ -22,6 +22,7 @@ const {
isSimpleTemplateLiteral,
hasComment,
isMemberExpression,
isTSTypeExpression,
} = require("../utils/index.js");

function printTemplateLiteral(path, print, options) {
Expand Down Expand Up @@ -90,7 +91,7 @@ function printTemplateLiteral(path, print, options) {
isMemberExpression(expression) ||
expression.type === "ConditionalExpression" ||
expression.type === "SequenceExpression" ||
expression.type === "TSAsExpression" ||
isTSTypeExpression(expression) ||
isBinaryish(expression)
) {
printed = [indent([softline, printed]), softline];
Expand Down
3 changes: 2 additions & 1 deletion src/language-js/print/ternary.js
Expand Up @@ -6,6 +6,7 @@ const {
getComments,
isCallExpression,
isMemberExpression,
isTSTypeExpression,
} = require("../utils/index.js");
const { locStart, locEnd } = require("../loc.js");
const isBlockComment = require("../utils/is-block-comment.js");
Expand Down Expand Up @@ -165,7 +166,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
4 changes: 3 additions & 1 deletion src/language-js/print/typescript.js
Expand Up @@ -148,8 +148,10 @@ function printTypescript(path, options, print) {
return printTypeParameters(path, options, print, "params");
case "TSTypeParameter":
return printTypeParameter(path, options, print);
case "TSSatisfiesExpression":
case "TSAsExpression": {
parts.push(print("expression"), " as ", print("typeAnnotation"));
const operator = node.type === "TSAsExpression" ? "as" : "satisfies";
parts.push(print("expression"), ` ${operator} `, print("typeAnnotation"));
const parent = path.getParentNode();
if (
(isCallExpression(parent) && parent.callee === node) ||
Expand Down
11 changes: 10 additions & 1 deletion src/language-js/utils/index.js
Expand Up @@ -106,7 +106,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 @@ -998,6 +998,8 @@ function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) {
node.expressions[0],
forbidFunctionClassAndDoExpr
);
// @ts-expect-error
case "TSSatisfiesExpression":
case "TSAsExpression":
case "TSNonNullExpression":
return startsWithNoLookaheadToken(
Expand Down Expand Up @@ -1305,6 +1307,12 @@ const markerForIfWithoutBlockAndSameLineComment = Symbol(
"ifWithoutBlockAndSameLineComment"
);

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

module.exports = {
getFunctionParameters,
iterateFunctionParametersPath,
Expand Down Expand Up @@ -1369,4 +1377,5 @@ module.exports = {
getComments,
CommentCheckFlags,
markerForIfWithoutBlockAndSameLineComment,
isTSTypeExpression,
};
1 change: 1 addition & 0 deletions tests/config/format-test.js
Expand Up @@ -40,6 +40,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

0 comments on commit ff0529a

Please sign in to comment.