-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Correctly cache tagged template objects in modules #18300
Changes from 26 commits
c4b1a9d
166af8c
aa634ba
4beb9b0
6a9fa83
c966059
8fbb304
7871e08
9f669d0
e9c6dfe
1656790
b137f24
5565709
9907453
1cb5eb9
886a29b
1841afe
0b7538d
b406d54
4ec1643
d039942
5da45fb
f94bded
81b3e85
a23d1bf
e2c6aac
b80b2ee
babe3cb
8fd638c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16664,6 +16664,9 @@ namespace ts { | |
} | ||
|
||
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { | ||
if (languageVersion < ScriptTarget.ES2015) { | ||
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); | ||
} | ||
return getReturnTypeOfSignature(getResolvedSignature(node)); | ||
} | ||
|
||
|
@@ -24019,6 +24022,7 @@ namespace ts { | |
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; | ||
case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; | ||
case ExternalEmitHelpers.ExportStar: return "__exportStar"; | ||
case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not added in this PR, but all of the strings here are duplicated elsewhere in the emitter -- could we move this function to there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth considering a shorter name than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rbuckton Babel uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
default: Debug.fail("Unrecognized helper"); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3877,15 +3877,15 @@ namespace ts { | |
* @param expression The Expression node. | ||
*/ | ||
export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { | ||
const emittedExpression = skipPartiallyEmittedExpressions(expression); | ||
switch (emittedExpression.kind) { | ||
const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); | ||
switch (leftmostExpr.kind) { | ||
case SyntaxKind.CallExpression: | ||
return createParen(expression); | ||
|
||
case SyntaxKind.NewExpression: | ||
return (<NewExpression>emittedExpression).arguments | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would swap this back to the original order. |
||
? <LeftHandSideExpression>expression | ||
: createParen(expression); | ||
return !(leftmostExpr as NewExpression).arguments | ||
? createParen(expression) | ||
: <LeftHandSideExpression>expression; | ||
} | ||
|
||
return parenthesizeForAccess(expression); | ||
|
@@ -3966,7 +3966,7 @@ namespace ts { | |
} | ||
} | ||
|
||
const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind; | ||
const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; | ||
if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { | ||
return setTextRange(createParen(expression), expression); | ||
} | ||
|
@@ -4012,7 +4012,7 @@ namespace ts { | |
} | ||
} | ||
|
||
function getLeftmostExpression(node: Expression): Expression { | ||
function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) { | ||
while (true) { | ||
switch (node.kind) { | ||
case SyntaxKind.PostfixUnaryExpression: | ||
|
@@ -4028,6 +4028,10 @@ namespace ts { | |
continue; | ||
|
||
case SyntaxKind.CallExpression: | ||
if (stopAtCallExpressions) { | ||
return node; | ||
} | ||
// falls through | ||
case SyntaxKind.ElementAccessExpression: | ||
case SyntaxKind.PropertyAccessExpression: | ||
node = (<CallExpression | PropertyAccessExpression | ElementAccessExpression>node).expression; | ||
|
@@ -4040,10 +4044,11 @@ namespace ts { | |
|
||
return node; | ||
} | ||
|
||
} | ||
|
||
export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { | ||
if (!isBlock(body) && getLeftmostExpression(body).kind === SyntaxKind.ObjectLiteralExpression) { | ||
if (!isBlock(body) && getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression) { | ||
return setTextRange(createParen(<Expression>body), body); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -279,6 +279,13 @@ namespace ts { | |
let currentSourceFile: SourceFile; | ||
let currentText: string; | ||
let hierarchyFacts: HierarchyFacts; | ||
let taggedTemplateStringDeclarations: VariableDeclaration[]; | ||
|
||
function recordTaggedTemplateString(temp: Identifier) { | ||
taggedTemplateStringDeclarations = append( | ||
taggedTemplateStringDeclarations, | ||
createVariableDeclaration(temp)); | ||
} | ||
|
||
/** | ||
* Used to track if we are emitting body of the converted loop | ||
|
@@ -307,6 +314,7 @@ namespace ts { | |
|
||
currentSourceFile = undefined; | ||
currentText = undefined; | ||
taggedTemplateStringDeclarations = undefined; | ||
hierarchyFacts = HierarchyFacts.None; | ||
return visited; | ||
} | ||
|
@@ -520,6 +528,11 @@ namespace ts { | |
addCaptureThisForNodeIfNeeded(statements, node); | ||
statementOffset = addCustomPrologue(statements, node.statements, statementOffset, visitor); | ||
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); | ||
if (taggedTemplateStringDeclarations) { | ||
statements.push( | ||
createVariableStatement(/*modifiers*/ undefined, | ||
createVariableDeclarationList(taggedTemplateStringDeclarations))); | ||
} | ||
addRange(statements, endLexicalEnvironment()); | ||
exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); | ||
return updateSourceFileNode( | ||
|
@@ -3636,11 +3649,10 @@ namespace ts { | |
// Visit the tag expression | ||
const tag = visitNode(node.tag, visitor, isExpression); | ||
|
||
// Allocate storage for the template site object | ||
const temp = createTempVariable(hoistVariableDeclaration); | ||
|
||
// Build up the template arguments and the raw and cooked strings for the template. | ||
const templateArguments: Expression[] = [temp]; | ||
// We start out with 'undefined' for the first argument and revisit later | ||
// to avoid walking over the template string twice and shifting all our arguments over after the fact. | ||
const templateArguments: Expression[] = [undefined]; | ||
const cookedStrings: Expression[] = []; | ||
const rawStrings: Expression[] = []; | ||
const template = node.template; | ||
|
@@ -3658,16 +3670,25 @@ namespace ts { | |
} | ||
} | ||
|
||
// NOTE: The parentheses here is entirely optional as we are now able to auto- | ||
// parenthesize when rebuilding the tree. This should be removed in a | ||
// future version. It is here for now to match our existing emit. | ||
return createParen( | ||
inlineExpressions([ | ||
createAssignment(temp, createArrayLiteral(cookedStrings)), | ||
createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)), | ||
createCall(tag, /*typeArguments*/ undefined, templateArguments) | ||
]) | ||
); | ||
const helperCall = createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings)); | ||
|
||
// Create a variable to cache the template object if we're in a module. | ||
// Do not do this in the global scope, as any variable we currently generate could conflict with | ||
// variables from outside of the current compilation. In the future, we can revisit this behavior. | ||
if (isExternalModule(currentSourceFile)) { | ||
const tempVar = createTempVariable(recordTaggedTemplateString); | ||
templateArguments[0] = createLogicalOr( | ||
tempVar, | ||
createAssignment( | ||
tempVar, | ||
helperCall) | ||
); | ||
} | ||
else { | ||
templateArguments[0] = helperCall; | ||
} | ||
|
||
return createCall(tag, /*typeArguments*/ undefined, templateArguments); | ||
} | ||
|
||
/** | ||
|
@@ -4036,6 +4057,18 @@ namespace ts { | |
); | ||
} | ||
|
||
function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { | ||
context.requestEmitHelper(templateObjectHelper); | ||
return createCall( | ||
getHelperName("__makeTemplateObject"), | ||
/*typeArguments*/ undefined, | ||
[ | ||
cooked, | ||
raw | ||
] | ||
); | ||
} | ||
|
||
const extendsHelper: EmitHelper = { | ||
name: "typescript:extends", | ||
scoped: false, | ||
|
@@ -4052,4 +4085,21 @@ namespace ts { | |
}; | ||
})();` | ||
}; | ||
|
||
const templateObjectHelper: EmitHelper = { | ||
name: "typescript:makeTemplateObject", | ||
scoped: false, | ||
priority: 0, | ||
text: ` | ||
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about: cooked.raw = raw;
if (Object.freeze) Object.freeze(cooked);
return cooked; Or, the one-line alternative: return Object.defineProperty ? Object.defineProperty(cooked, "raw", { value: raw }) : (cooked.raw = raw, cooked); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uglify already produces a shorter output given this current implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slightly better one-liner (two less characters, just as readable and correct): return Object.defineProperty ? Object.defineProperty(cooked, "raw", { value: raw }) : cooked.raw = raw, cooked; Which is, coincidentally, how uglify restructures what daniel has right now when it minifies it. |
||
if (Object.defineProperty) { | ||
Object.defineProperty(cooked, "raw", { value: raw }); | ||
} | ||
else { | ||
cooked.raw = raw; | ||
} | ||
return cooked; | ||
};` | ||
}; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4271,22 +4271,24 @@ namespace ts { | |
*/ | ||
/* @internal */ | ||
export const enum ExternalEmitHelpers { | ||
Extends = 1 << 0, // __extends (used by the ES2015 class transformation) | ||
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations) | ||
Rest = 1 << 2, // __rest (used by ESNext object rest transformation) | ||
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation) | ||
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation) | ||
Param = 1 << 5, // __param (used by TypeScript decorators transformation) | ||
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation) | ||
Generator = 1 << 7, // __generator (used by ES2015 generator transformation) | ||
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations) | ||
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation) | ||
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations) | ||
Await = 1 << 11, // __await (used by ES2017 async generator transformation) | ||
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation) | ||
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation) | ||
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation) | ||
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation) | ||
Extends = 1 << 0, // __extends (used by the ES2015 class transformation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change is an example of why aligned comments make me sad :( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :( |
||
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations) | ||
Rest = 1 << 2, // __rest (used by ESNext object rest transformation) | ||
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation) | ||
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation) | ||
Param = 1 << 5, // __param (used by TypeScript decorators transformation) | ||
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation) | ||
Generator = 1 << 7, // __generator (used by ES2015 generator transformation) | ||
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations) | ||
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation) | ||
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations) | ||
Await = 1 << 11, // __await (used by ES2017 async generator transformation) | ||
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation) | ||
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation) | ||
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation) | ||
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation) | ||
MakeTemplateObject = 1 << 16, // __makeTemplateObject (used for constructing template string array objects) | ||
" LastPlusOne", | ||
|
||
// Helpers included by ES2015 for..of | ||
ForOfIncludes = Values, | ||
|
@@ -4304,7 +4306,7 @@ namespace ts { | |
SpreadIncludes = Read | Spread, | ||
|
||
FirstEmitHelper = Extends, | ||
LastEmitHelper = ExportStar | ||
LastEmitHelper = ExternalEmitHelpers[" LastPlusOne"] - 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also just move this next to the last one to make it obvious that these should be updated together. Same for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. 👎 for the magic string in the enum. |
||
} | ||
|
||
export const enum EmitHint { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparent change in file permissions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed.