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

Backport "Support TS4.9 satisfies operator (#13764)" #13783

Merged
merged 11 commits into from Nov 4, 2022
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