From 4c0663f8285e6469dec93f75271be239a9deeeaa Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 21 Oct 2024 14:53:33 -0400 Subject: [PATCH 1/3] More rigorous ASI prevention when emitting return --- src/compiler/emitter.ts | 91 +++++++++++-- ...tatementNoAsiAfterTransform(target=es5).js | 122 ++++++++++++++++++ ...ementNoAsiAfterTransform(target=esnext).js | 118 +++++++++++++++++ .../returnStatementNoAsiAfterTransform.ts | 64 +++++++++ 4 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/returnStatementNoAsiAfterTransform(target=es5).js create mode 100644 tests/baselines/reference/returnStatementNoAsiAfterTransform(target=esnext).js create mode 100644 tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 417ed4c2470f4..5092e627accf3 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -3226,17 +3226,88 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri * Wraps an expression in parens if we would emit a leading comment that would introduce a line separator * between the node and its parent. */ - function parenthesizeExpressionForNoAsi(node: Expression) { - if (!commentsDisabled && isPartiallyEmittedExpression(node) && willEmitLeadingNewLine(node)) { - const parseNode = getParseTreeNode(node); - if (parseNode && isParenthesizedExpression(parseNode)) { - // If the original node was a parenthesized expression, restore it to preserve comment and source map emit - const parens = factory.createParenthesizedExpression(node.expression); - setOriginalNode(parens, node); - setTextRange(parens, parseNode); - return parens; + function parenthesizeExpressionForNoAsi(node: Expression): Expression { + if (!commentsDisabled) { + switch (node.kind) { + case SyntaxKind.PartiallyEmittedExpression: + if (willEmitLeadingNewLine(node)) { + const parseNode = getParseTreeNode(node); + if (parseNode && isParenthesizedExpression(parseNode)) { + // If the original node was a parenthesized expression, restore it to preserve comment and source map emit + const parens = factory.createParenthesizedExpression((node as PartiallyEmittedExpression).expression); + setOriginalNode(parens, node); + setTextRange(parens, parseNode); + return parens; + } + return factory.createParenthesizedExpression(node); + } + return factory.updatePartiallyEmittedExpression( + node as PartiallyEmittedExpression, + parenthesizeExpressionForNoAsi((node as PartiallyEmittedExpression).expression) + ); + case SyntaxKind.PropertyAccessExpression: + return factory.updatePropertyAccessExpression( + node as PropertyAccessExpression, + parenthesizeExpressionForNoAsi((node as PropertyAccessExpression).expression), + (node as PropertyAccessExpression).name); + case SyntaxKind.ElementAccessExpression: + return factory.updateElementAccessExpression( + node as ElementAccessExpression, + parenthesizeExpressionForNoAsi((node as ElementAccessExpression).expression), + (node as ElementAccessExpression).argumentExpression); + case SyntaxKind.CallExpression: + return factory.updateCallExpression( + node as CallExpression, + parenthesizeExpressionForNoAsi((node as CallExpression).expression), + (node as CallExpression).typeArguments, + (node as CallExpression).arguments, + ); + case SyntaxKind.TaggedTemplateExpression: + return factory.updateTaggedTemplateExpression( + node as TaggedTemplateExpression, + parenthesizeExpressionForNoAsi((node as TaggedTemplateExpression).tag), + (node as TaggedTemplateExpression).typeArguments, + (node as TaggedTemplateExpression).template, + ); + case SyntaxKind.PostfixUnaryExpression: + return factory.updatePostfixUnaryExpression( + node as PostfixUnaryExpression, + parenthesizeExpressionForNoAsi((node as PostfixUnaryExpression).operand) + ); + case SyntaxKind.BinaryExpression: + return factory.updateBinaryExpression( + node as BinaryExpression, + parenthesizeExpressionForNoAsi((node as BinaryExpression).left), + (node as BinaryExpression).operatorToken, + (node as BinaryExpression).right, + ); + case SyntaxKind.ConditionalExpression: + return factory.updateConditionalExpression( + node as ConditionalExpression, + parenthesizeExpressionForNoAsi((node as ConditionalExpression).condition), + (node as ConditionalExpression).questionToken, + (node as ConditionalExpression).whenTrue, + (node as ConditionalExpression).colonToken, + (node as ConditionalExpression).whenFalse, + ); + case SyntaxKind.AsExpression: + return factory.updateAsExpression( + node as AsExpression, + parenthesizeExpressionForNoAsi((node as AsExpression).expression), + (node as AsExpression).type + ); + case SyntaxKind.SatisfiesExpression: + return factory.updateSatisfiesExpression( + node as SatisfiesExpression, + parenthesizeExpressionForNoAsi((node as SatisfiesExpression).expression), + (node as SatisfiesExpression).type + ); + case SyntaxKind.NonNullExpression: + return factory.updateNonNullExpression( + node as NonNullExpression, + parenthesizeExpressionForNoAsi((node as NonNullExpression).expression) + ); } - return factory.createParenthesizedExpression(node); } return node; } diff --git a/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=es5).js b/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=es5).js new file mode 100644 index 0000000000000..0485eb813e407 --- /dev/null +++ b/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=es5).js @@ -0,0 +1,122 @@ +//// [tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts] //// + +//// [returnStatementNoAsiAfterTransform.ts] +declare var a: any; + +function t1() { + return ( + // comment + a as any + ); +} +function t2() { + return ( + // comment + a as any + ) + 1; +} +function t3() { + return ( + // comment + a as any + ) ? 0 : 1; +} +function t4() { + return ( + // comment + a as any + ).b; +} +function t5() { + return ( + // comment + a as any + )[a]; +} +function t6() { + return ( + // comment + a as any + )(); +} +function t7() { + return ( + // comment + a as any + )``; +} +function t8() { + return ( + // comment + a as any + ) as any; +} +function t9() { + return ( + // comment + a as any + ) satisfies any; +} +function t10() { + return ( + // comment + a as any + )!; +} + + +//// [returnStatementNoAsiAfterTransform.js] +var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; +function t1() { + return ( + // comment + a); +} +function t2() { + return ( + // comment + a) + 1; +} +function t3() { + return ( + // comment + a) ? 0 : 1; +} +function t4() { + return ( + // comment + a).b; +} +function t5() { + return ( + // comment + a)[a]; +} +function t6() { + return ( + // comment + a)(); +} +function t7() { + return ( + // comment + a)(__makeTemplateObject([""], [""])); +} +function t8() { + return ( + // comment + a); +} +function t9() { + return ( + // comment + a); +} +function t10() { + return ( + // comment + a); +} diff --git a/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=esnext).js b/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=esnext).js new file mode 100644 index 0000000000000..65e3b7057f673 --- /dev/null +++ b/tests/baselines/reference/returnStatementNoAsiAfterTransform(target=esnext).js @@ -0,0 +1,118 @@ +//// [tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts] //// + +//// [returnStatementNoAsiAfterTransform.ts] +declare var a: any; + +function t1() { + return ( + // comment + a as any + ); +} +function t2() { + return ( + // comment + a as any + ) + 1; +} +function t3() { + return ( + // comment + a as any + ) ? 0 : 1; +} +function t4() { + return ( + // comment + a as any + ).b; +} +function t5() { + return ( + // comment + a as any + )[a]; +} +function t6() { + return ( + // comment + a as any + )(); +} +function t7() { + return ( + // comment + a as any + )``; +} +function t8() { + return ( + // comment + a as any + ) as any; +} +function t9() { + return ( + // comment + a as any + ) satisfies any; +} +function t10() { + return ( + // comment + a as any + )!; +} + + +//// [returnStatementNoAsiAfterTransform.js] +function t1() { + return ( + // comment + a); +} +function t2() { + return ( + // comment + a) + 1; +} +function t3() { + return ( + // comment + a) ? 0 : 1; +} +function t4() { + return ( + // comment + a).b; +} +function t5() { + return ( + // comment + a)[a]; +} +function t6() { + return ( + // comment + a)(); +} +function t7() { + return ( + // comment + a) ``; +} +function t8() { + return ( + // comment + a); +} +function t9() { + return ( + // comment + a); +} +function t10() { + return ( + // comment + a); +} diff --git a/tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts b/tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts new file mode 100644 index 0000000000000..ba76ca6f50156 --- /dev/null +++ b/tests/cases/conformance/statements/returnStatements/returnStatementNoAsiAfterTransform.ts @@ -0,0 +1,64 @@ +// @target: es5,esnext +// @noTypesAndSymbols: true +declare var a: any; + +function t1() { + return ( + // comment + a as any + ); +} +function t2() { + return ( + // comment + a as any + ) + 1; +} +function t3() { + return ( + // comment + a as any + ) ? 0 : 1; +} +function t4() { + return ( + // comment + a as any + ).b; +} +function t5() { + return ( + // comment + a as any + )[a]; +} +function t6() { + return ( + // comment + a as any + )(); +} +function t7() { + return ( + // comment + a as any + )``; +} +function t8() { + return ( + // comment + a as any + ) as any; +} +function t9() { + return ( + // comment + a as any + ) satisfies any; +} +function t10() { + return ( + // comment + a as any + )!; +} From 4eefae3925f945fe1a72fa9977df0134fd0593a5 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 21 Oct 2024 15:01:34 -0400 Subject: [PATCH 2/3] Fix formatting --- src/compiler/emitter.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5092e627accf3..01a96579861f4 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -3243,18 +3243,20 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri } return factory.updatePartiallyEmittedExpression( node as PartiallyEmittedExpression, - parenthesizeExpressionForNoAsi((node as PartiallyEmittedExpression).expression) + parenthesizeExpressionForNoAsi((node as PartiallyEmittedExpression).expression), ); case SyntaxKind.PropertyAccessExpression: return factory.updatePropertyAccessExpression( node as PropertyAccessExpression, parenthesizeExpressionForNoAsi((node as PropertyAccessExpression).expression), - (node as PropertyAccessExpression).name); + (node as PropertyAccessExpression).name, + ); case SyntaxKind.ElementAccessExpression: return factory.updateElementAccessExpression( node as ElementAccessExpression, parenthesizeExpressionForNoAsi((node as ElementAccessExpression).expression), - (node as ElementAccessExpression).argumentExpression); + (node as ElementAccessExpression).argumentExpression, + ); case SyntaxKind.CallExpression: return factory.updateCallExpression( node as CallExpression, @@ -3272,7 +3274,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri case SyntaxKind.PostfixUnaryExpression: return factory.updatePostfixUnaryExpression( node as PostfixUnaryExpression, - parenthesizeExpressionForNoAsi((node as PostfixUnaryExpression).operand) + parenthesizeExpressionForNoAsi((node as PostfixUnaryExpression).operand), ); case SyntaxKind.BinaryExpression: return factory.updateBinaryExpression( @@ -3294,18 +3296,18 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri return factory.updateAsExpression( node as AsExpression, parenthesizeExpressionForNoAsi((node as AsExpression).expression), - (node as AsExpression).type + (node as AsExpression).type, ); case SyntaxKind.SatisfiesExpression: return factory.updateSatisfiesExpression( node as SatisfiesExpression, parenthesizeExpressionForNoAsi((node as SatisfiesExpression).expression), - (node as SatisfiesExpression).type + (node as SatisfiesExpression).type, ); case SyntaxKind.NonNullExpression: return factory.updateNonNullExpression( node as NonNullExpression, - parenthesizeExpressionForNoAsi((node as NonNullExpression).expression) + parenthesizeExpressionForNoAsi((node as NonNullExpression).expression), ); } } From 2dcadef741ec8005f1fb6b85aef5fdcd627f2163 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 21 Oct 2024 15:05:52 -0400 Subject: [PATCH 3/3] Add tests for 'yield' --- ...tatementNoAsiAfterTransform(target=es5).js | 219 ++++++++++++++++++ ...ementNoAsiAfterTransform(target=esnext).js | 118 ++++++++++ .../yieldStatementNoAsiAfterTransform.ts | 65 ++++++ 3 files changed, 402 insertions(+) create mode 100644 tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=es5).js create mode 100644 tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=esnext).js create mode 100644 tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts diff --git a/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=es5).js b/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=es5).js new file mode 100644 index 0000000000000..b34722da6afe5 --- /dev/null +++ b/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=es5).js @@ -0,0 +1,219 @@ +//// [tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts] //// + +//// [yieldStatementNoAsiAfterTransform.ts] +declare var a: any; + +function *t1() { + yield ( + // comment + a as any + ); +} +function *t2() { + yield ( + // comment + a as any + ) + 1; +} +function *t3() { + yield ( + // comment + a as any + ) ? 0 : 1; +} +function *t4() { + yield ( + // comment + a as any + ).b; +} +function *t5() { + yield ( + // comment + a as any + )[a]; +} +function *t6() { + yield ( + // comment + a as any + )(); +} +function *t7() { + yield ( + // comment + a as any + )``; +} +function *t8() { + yield ( + // comment + a as any + ) as any; +} +function *t9() { + yield ( + // comment + a as any + ) satisfies any; +} +function *t10() { + yield ( + // comment + a as any + )!; +} + + +//// [yieldStatementNoAsiAfterTransform.js] +var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +function t1() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t2() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a + 1]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t3() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a ? 0 : 1]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t4() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a.b]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t5() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a[a]]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t6() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a()]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t7() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a(__makeTemplateObject([""], [""]))]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t8() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t9() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} +function t10() { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, + // comment + a]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +} diff --git a/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=esnext).js b/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=esnext).js new file mode 100644 index 0000000000000..71167e5407143 --- /dev/null +++ b/tests/baselines/reference/yieldStatementNoAsiAfterTransform(target=esnext).js @@ -0,0 +1,118 @@ +//// [tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts] //// + +//// [yieldStatementNoAsiAfterTransform.ts] +declare var a: any; + +function *t1() { + yield ( + // comment + a as any + ); +} +function *t2() { + yield ( + // comment + a as any + ) + 1; +} +function *t3() { + yield ( + // comment + a as any + ) ? 0 : 1; +} +function *t4() { + yield ( + // comment + a as any + ).b; +} +function *t5() { + yield ( + // comment + a as any + )[a]; +} +function *t6() { + yield ( + // comment + a as any + )(); +} +function *t7() { + yield ( + // comment + a as any + )``; +} +function *t8() { + yield ( + // comment + a as any + ) as any; +} +function *t9() { + yield ( + // comment + a as any + ) satisfies any; +} +function *t10() { + yield ( + // comment + a as any + )!; +} + + +//// [yieldStatementNoAsiAfterTransform.js] +function* t1() { + yield ( + // comment + a); +} +function* t2() { + yield ( + // comment + a) + 1; +} +function* t3() { + yield ( + // comment + a) ? 0 : 1; +} +function* t4() { + yield ( + // comment + a).b; +} +function* t5() { + yield ( + // comment + a)[a]; +} +function* t6() { + yield ( + // comment + a)(); +} +function* t7() { + yield ( + // comment + a) ``; +} +function* t8() { + yield ( + // comment + a); +} +function* t9() { + yield ( + // comment + a); +} +function* t10() { + yield ( + // comment + a); +} diff --git a/tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts b/tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts new file mode 100644 index 0000000000000..818159c7197e6 --- /dev/null +++ b/tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts @@ -0,0 +1,65 @@ +// @target: es5,esnext +// @lib: esnext +// @noTypesAndSymbols: true +declare var a: any; + +function *t1() { + yield ( + // comment + a as any + ); +} +function *t2() { + yield ( + // comment + a as any + ) + 1; +} +function *t3() { + yield ( + // comment + a as any + ) ? 0 : 1; +} +function *t4() { + yield ( + // comment + a as any + ).b; +} +function *t5() { + yield ( + // comment + a as any + )[a]; +} +function *t6() { + yield ( + // comment + a as any + )(); +} +function *t7() { + yield ( + // comment + a as any + )``; +} +function *t8() { + yield ( + // comment + a as any + ) as any; +} +function *t9() { + yield ( + // comment + a as any + ) satisfies any; +} +function *t10() { + yield ( + // comment + a as any + )!; +}