Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
344d505
add support for Lift Template Literal Restriction
Kingwl May 1, 2018
6ef797f
rename file and improve comment and tests
Kingwl May 1, 2018
3932d20
fix NoSubstitutionTemplateLiteral support
Kingwl May 2, 2018
dbbcdae
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl May 24, 2018
c642abe
Merge branch 'Lift-Template-Literal-Restriction' of https://github.co…
Kingwl May 25, 2018
e92b6bc
extract tagged template and add more test
Kingwl May 26, 2018
9bfb15b
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jun 8, 2018
2c973b1
avoid useless parameter
Kingwl Jun 8, 2018
c2580ed
fix incorrect return node if cannot transform
Kingwl Jun 8, 2018
21c91d6
accept baseline
Kingwl Jun 8, 2018
cd46d9f
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jun 30, 2018
472223b
correctly baseline
Kingwl Jun 30, 2018
789cbed
Merge branch 'master' of https://github.com/kingwl/typescript into Li…
Kingwl Aug 28, 2018
4fa33a1
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 28, 2018
2b97fca
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 29, 2018
78355db
accept baseline
Kingwl Aug 29, 2018
3da97e2
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jan 19, 2019
dde0b37
fix merge break
Kingwl Jan 20, 2019
8bf6c64
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Apr 10, 2019
c4ccddf
fix merge break
Kingwl Apr 10, 2019
3e87998
inline rescan template head or no subsititution template
Kingwl Apr 10, 2019
826802f
update scan error
Kingwl Apr 10, 2019
7b67e5d
add comment and fix lint
Kingwl Apr 10, 2019
8671a24
refactor and fix lint
Kingwl Apr 12, 2019
6237adf
avoid blank
Kingwl Apr 12, 2019
12a1252
Merge branch 'master' of https://github.com/microsoft/TypeScript into…
Kingwl Aug 19, 2019
8b65fdc
fix merge conflict
Kingwl Aug 19, 2019
c4cb21d
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Aug 27, 2019
36c3bb3
fix again
Kingwl Aug 27, 2019
b7f5c77
fix again
Kingwl Aug 27, 2019
fd47863
Merge branch 'master' into Lift-Template-Literal-Restriction
Kingwl Jan 9, 2020
569b35e
use multiple target
Kingwl Jan 9, 2020
45406d4
Merge branch 'master' into Lift-Template-Literal-Restriction
sandersn Feb 5, 2020
f8b9bbb
fix space lint
sandersn Feb 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
extract tagged template and add more test
  • Loading branch information
Kingwl committed May 26, 2018
commit e92b6bc927c68cd245e497f44bf0aa9045cd9a32
12 changes: 11 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3523,8 +3523,18 @@ namespace ts {
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
case SyntaxKind.TemplateTail:
case SyntaxKind.TemplateExpression:
if ((<NoSubstitutionTemplateLiteral | TemplateHead | TemplateMiddle | TemplateTail>node).templateFlags) {
transformFlags |= TransformFlags.AssertESNext;
break;
}
// falls through
case SyntaxKind.TaggedTemplateExpression:
if (hasInvalidEscape((<TaggedTemplateExpression>node).template)) {
transformFlags |= TransformFlags.AssertESNext;
break;
}
// falls through
case SyntaxKind.TemplateExpression:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.StaticKeyword:
case SyntaxKind.MetaProperty:
Expand Down
101 changes: 8 additions & 93 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3643,75 +3643,14 @@ namespace ts {
* @param node A TaggedTemplateExpression node.
*/
function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
// Visit the tag expression
const tag = visitNode(node.tag, visitor, isExpression);

// Build up the template arguments and the raw and cooked strings for the template.
// 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;
if (isNoSubstitutionTemplateLiteral(template)) {
cookedStrings.push(template.templateFlags ? createIdentifier("undefined") : createLiteral(template.text));
rawStrings.push(getRawLiteral(template));
}
else {
cookedStrings.push(template.head.templateFlags ? createIdentifier("undefined") : createLiteral(template.head.text));
rawStrings.push(getRawLiteral(template.head));
for (const templateSpan of template.templateSpans) {
cookedStrings.push(templateSpan.literal.templateFlags ? createIdentifier("undefined") : createLiteral(templateSpan.literal.text));
rawStrings.push(getRawLiteral(templateSpan.literal));
templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
}
}

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 = createUniqueName("templateObject");
recordTaggedTemplateString(tempVar);
templateArguments[0] = createLogicalOr(
tempVar,
createAssignment(
tempVar,
helperCall)
);
}
else {
templateArguments[0] = helperCall;
}

return createCall(tag, /*typeArguments*/ undefined, templateArguments);
}

/**
* Creates an ES5 compatible literal from an ES6 template literal.
*
* @param node The ES6 template literal.
*/
function getRawLiteral(node: LiteralLikeNode) {
// Find original source text, since we need to emit the raw strings of the tagged template.
// The raw strings contain the (escaped) strings of what the user wrote.
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);

// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
// thus we need to remove those characters.
// First template piece starts with "`", others with "}"
// Last template piece ends with "`", others with "${"
const isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
text = text.substring(1, text.length - (isLast ? 1 : 2));

// Newline normalization:
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
text = text.replace(/\r\n?/g, "\n");
return setTextRange(createLiteral(text), node);
return processTaggedTemplateExpression(
context,
node,
visitor,
currentSourceFile,
recordTaggedTemplateString,
ProcessLevel.All
);
}

/**
Expand Down Expand Up @@ -4054,18 +3993,6 @@ 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,
Expand All @@ -4082,16 +4009,4 @@ namespace ts {
};
})();`
};

const templateObjectHelper: EmitHelper = {
name: "typescript:makeTemplateObject",
scoped: false,
priority: 0,
text: `
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};`
};

}
37 changes: 35 additions & 2 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,27 @@ namespace ts {
let enabledSubstitutions: ESNextSubstitutionFlags;
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
let currentSourceFile: SourceFile;
let taggedTemplateStringDeclarations: VariableDeclaration[];

return chainBundle(transformSourceFile);

function recordTaggedTemplateString(temp: Identifier) {
taggedTemplateStringDeclarations = append(
taggedTemplateStringDeclarations,
createVariableDeclaration(temp));
}

function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}

const visited = visitEachChild(node, visitor, context);
currentSourceFile = node;
const visited = visitSourceFile(node);
addEmitHelpers(visited, context.readEmitHelpers());

currentSourceFile = undefined!;
taggedTemplateStringDeclarations = undefined!;
return visited;
}

Expand Down Expand Up @@ -99,6 +110,8 @@ namespace ts {
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.TaggedTemplateExpression:
return visitTaggedTemplateExpression(node as TaggedTemplateExpression);
default:
return visitEachChild(node, visitor, context);
}
Expand Down Expand Up @@ -637,6 +650,26 @@ namespace ts {
return updated;
}

function visitSourceFile(node: SourceFile): SourceFile {
const visited = visitEachChild(node, visitor, context);
const statement = concatenate(visited.statements, taggedTemplateStringDeclarations && [
createVariableStatement(/*modifiers*/ undefined,
createVariableDeclarationList(taggedTemplateStringDeclarations))
]);
return updateSourceFileNode(visited, setTextRange(createNodeArray(statement), node.statements));
}

function visitTaggedTemplateExpression (node: TaggedTemplateExpression) {
return processTaggedTemplateExpression(
context,
node,
visitor,
currentSourceFile,
recordTaggedTemplateString,
ProcessLevel.LiftRestriction
);
}

function transformAsyncGeneratorFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody {
resumeLexicalEnvironment();
const statements: Statement[] = [];
Expand Down
116 changes: 116 additions & 0 deletions src/compiler/transformers/taggedTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*@internal*/
namespace ts {
export enum ProcessLevel {
LiftRestriction,
All
}

export function processTaggedTemplateExpression(
context: TransformationContext,
node: TaggedTemplateExpression,
visitor: ((node: Node) => VisitResult<Node>) | undefined,
currentSourceFile: SourceFile,
recordTaggedTemplateString: (temp: Identifier) => void,
level: ProcessLevel) {

// Visit the tag expression
const tag = visitNode(node.tag, visitor, isExpression);

// Build up the template arguments and the raw and cooked strings for the template.
// 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;

if (level === ProcessLevel.LiftRestriction && !hasInvalidEscape(template)) return tag;

if (isNoSubstitutionTemplateLiteral(template)) {
cookedStrings.push(createTemplateCooked(template));
rawStrings.push(getRawLiteral(template, currentSourceFile));
}
else {
cookedStrings.push(createTemplateCooked(template.head));
rawStrings.push(getRawLiteral(template.head, currentSourceFile));
for (const templateSpan of template.templateSpans) {
cookedStrings.push(createTemplateCooked(templateSpan.literal));
rawStrings.push(getRawLiteral(templateSpan.literal, currentSourceFile));
templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression));
}
}

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 = createUniqueName("templateObject");
recordTaggedTemplateString(tempVar);
templateArguments[0] = createLogicalOr(
tempVar,
createAssignment(
tempVar,
helperCall)
);
}
else {
templateArguments[0] = helperCall;
}

return createCall(tag, /*typeArguments*/ undefined, templateArguments);
}

function createTemplateCooked (template: TemplateHead | TemplateMiddle | TemplateTail | NoSubstitutionTemplateLiteral) {
return template.templateFlags ? createIdentifier("undefined") : createLiteral(template.text);
}

/**
* Creates an ES5 compatible literal from an ES6 template literal.
*
* @param node The ES6 template literal.
*/
function getRawLiteral(node: LiteralLikeNode, currentSourceFile: SourceFile) {
// Find original source text, since we need to emit the raw strings of the tagged template.
// The raw strings contain the (escaped) strings of what the user wrote.
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n".
let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node);

// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"),
// thus we need to remove those characters.
// First template piece starts with "`", others with "}"
// Last template piece ends with "`", others with "${"
const isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail;
text = text.substring(1, text.length - (isLast ? 1 : 2));

// Newline normalization:
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV.
text = text.replace(/\r\n?/g, "\n");
return setTextRange(createLiteral(text), node);
}

function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
context.requestEmitHelper(templateObjectHelper);
return createCall(
getHelperName("__makeTemplateObject"),
/*typeArguments*/ undefined,
[
cooked,
raw
]
);
}

const templateObjectHelper: EmitHelper = {
name: "typescript:makeTemplateObject",
scoped: false,
priority: 0,
text: `
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};`
};
}
1 change: 1 addition & 0 deletions src/compiler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"visitor.ts",
"transformers/utilities.ts",
"transformers/destructuring.ts",
"transformers/taggedTemplate.ts",
"transformers/ts.ts",
"transformers/es2017.ts",
"transformers/esnext.ts",
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6433,4 +6433,11 @@ namespace ts {
export function isNamedImportsOrExports(node: Node): node is NamedImportsOrExports {
return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports;
}

/** @internal */
export function hasInvalidEscape(template: TemplateLiteral): boolean {
return template && !!(isNoSubstitutionTemplateLiteral(template)
? template.templateFlags
: (template.head.templateFlags || some(template.templateSpans, span => !!span.literal.templateFlags)));
}
}
1 change: 1 addition & 0 deletions src/harness/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"../compiler/visitor.ts",
"../compiler/transformers/utilities.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/taggedTemplate.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/es2017.ts",
"../compiler/transformers/esnext.ts",
Expand Down
1 change: 1 addition & 0 deletions src/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"../compiler/visitor.ts",
"../compiler/transformers/utilities.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/taggedTemplate.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/es2017.ts",
"../compiler/transformers/esnext.ts",
Expand Down
1 change: 1 addition & 0 deletions src/server/tsconfig.library.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"../compiler/visitor.ts",
"../compiler/transformers/utilities.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/taggedTemplate.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/es2017.ts",
"../compiler/transformers/esnext.ts",
Expand Down
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"../compiler/visitor.ts",
"../compiler/transformers/utilities.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/taggedTemplate.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/es2017.ts",
"../compiler/transformers/esnext.ts",
Expand Down
Loading