From 9b9b4a2bc4dcb8b0a3bf949c3b1ce8e3938c1fed Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 20 Jun 2019 21:58:07 +1000 Subject: [PATCH 1/9] Implement Tagged Template Literals --- src/LuaTransformer.ts | 33 +++++++++++ test/unit/taggedTemplateLiterals.spec.ts | 72 ++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/unit/taggedTemplateLiterals.spec.ts diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index a4144e126..da5cac050 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2644,6 +2644,8 @@ export class LuaTransformer { case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.NoSubstitutionTemplateLiteral: return this.transformStringLiteral(expression as ts.StringLiteral); + case ts.SyntaxKind.TaggedTemplateExpression: + return this.transformTaggedTemplateExpression(expression as ts.TaggedTemplateExpression); case ts.SyntaxKind.TemplateExpression: return this.transformTemplateExpression(expression as ts.TemplateExpression); case ts.SyntaxKind.NumericLiteral: @@ -4624,6 +4626,37 @@ export class LuaTransformer { return this.createSelfIdentifier(thisKeyword); } + public transformTaggedTemplateExpression(expression: ts.TaggedTemplateExpression): ExpressionVisitResult { + const strings: ts.StringLiteral[] = []; + const expressions: ts.Expression[] = []; + if (ts.isTemplateExpression(expression.template)) { + // Expressions are in the string. + strings.push(ts.createStringLiteral(expression.template.head.text)); + strings.push(...expression.template.templateSpans.map(span => ts.createStringLiteral(span.literal.text))); + expressions.push(...expression.template.templateSpans.map(span => span.expression)); + } else if (ts.isNoSubstitutionTemplateLiteral(expression.template)) { + // No expressions are in the string. + strings.push(ts.createStringLiteral(expression.template.text)); + } else { + throw TSTLErrors.UnsupportedKind("TaggedTemplateExpression", expression.kind, expression); + } + const stringArray = ts.createArrayLiteral(strings); + const signature = this.checker.getResolvedSignature(expression); + const signatureDeclaration = signature && signature.getDeclaration(); + let parameters: tstl.Expression[]; + if ( + signatureDeclaration && + tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void + ) { + const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G"); + parameters = this.transformArguments([stringArray, ...expressions], signature, context); + } else { + parameters = this.transformArguments([stringArray, ...expressions], signature); + } + const leftHandSideExpression = this.transformExpression(expression.tag); + return tstl.createCallExpression(leftHandSideExpression, parameters); + } + public transformTemplateExpression(expression: ts.TemplateExpression): ExpressionVisitResult { const parts: tstl.Expression[] = []; diff --git a/test/unit/taggedTemplateLiterals.spec.ts b/test/unit/taggedTemplateLiterals.spec.ts new file mode 100644 index 000000000..66eeeb92d --- /dev/null +++ b/test/unit/taggedTemplateLiterals.spec.ts @@ -0,0 +1,72 @@ +import * as util from "../util"; + +test.each([ + { + callExpression: "func``", + expectedResult: "", + }, + { + callExpression: "func`hello`", + expectedResult: "hello", + }, + { + callExpression: "func`hello ${1} ${2} ${3}`", + expectedResult: "hello 1 2 3", + }, + { + callExpression: "func`hello ${(() => 'iife')()}`", + expectedResult: "hello iife", + }, + { + callExpression: "func`hello ${1 + 2 + 3} arithmetic`", + expectedResult: "hello 6 arithmetic", + }, + { + callExpression: "func`begin ${'middle'} end`", + expectedResult: "begin middle end", + }, + { + callExpression: "func`hello ${func`hello`}`", + expectedResult: "hello hello", + }, + { + callExpression: "obj.func`hello ${'propertyAccessExpression'}`", + expectedResult: "hello propertyAccessExpression", + }, + { + callExpression: "obj['func']`hello ${'elementAccessExpression'}`", + expectedResult: "hello elementAccessExpression", + }, +])("TaggedTemplateLiteral call (%p)", ({ callExpression, expectedResult }) => { + const result = util.transpileAndExecute(` + function func(strings: TemplateStringsArray, ...expressions: any[]) { + const toJoin = []; + for (let i = 0; i < strings.length; ++i) { + if (strings[i]) { + toJoin.push(strings[i]); + } + if (expressions[i]) { + toJoin.push(expressions[i]); + } + } + return toJoin.join(""); + } + const obj = { + func + }; + return ${callExpression}; + `); + + expect(result).toBe(expectedResult); +}); + +test("TaggedTemplateLiteral no self parameter", () => { + const result = util.transpileAndExecute(` + function func(this: void, strings: TemplateStringsArray, ...expressions: any[]) { + return strings.join(""); + } + return func\`noSelfParameter\`; + `); + + expect(result).toBe("noSelfParameter"); +}); From 742aa9d035e1bfd423d0ab6a6a72baf972ba4145 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 20 Jun 2019 22:09:56 +1000 Subject: [PATCH 2/9] Add unicode escape test --- test/unit/taggedTemplateLiterals.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/taggedTemplateLiterals.spec.ts b/test/unit/taggedTemplateLiterals.spec.ts index 66eeeb92d..0b6d48f84 100644 --- a/test/unit/taggedTemplateLiterals.spec.ts +++ b/test/unit/taggedTemplateLiterals.spec.ts @@ -29,6 +29,10 @@ test.each([ callExpression: "func`hello ${func`hello`}`", expectedResult: "hello hello", }, + { + callExpression: "func`hello \\u00A9`", + expectedResult: "hello ©", + }, { callExpression: "obj.func`hello ${'propertyAccessExpression'}`", expectedResult: "hello propertyAccessExpression", From 51ed012e55b9bbf9fed9fda4c03f0bce5dfcaace Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 20 Jun 2019 23:19:09 +1000 Subject: [PATCH 3/9] Keep track of raw strings --- src/LuaTransformer.ts | 39 ++++++++++++++++++ test/unit/taggedTemplateLiterals.spec.ts | 52 ++++++++++++++++++------ 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 88c382c50..f8cdfb66f 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4638,31 +4638,70 @@ export class LuaTransformer { public transformTaggedTemplateExpression(expression: ts.TaggedTemplateExpression): ExpressionVisitResult { const strings: ts.StringLiteral[] = []; + const rawStrings: string[] = []; const expressions: ts.Expression[] = []; if (ts.isTemplateExpression(expression.template)) { // Expressions are in the string. strings.push(ts.createStringLiteral(expression.template.head.text)); + rawStrings.push(expression.template.head.getText()); strings.push(...expression.template.templateSpans.map(span => ts.createStringLiteral(span.literal.text))); + rawStrings.push(...expression.template.templateSpans.map(span => span.literal.getText())); expressions.push(...expression.template.templateSpans.map(span => span.expression)); } else if (ts.isNoSubstitutionTemplateLiteral(expression.template)) { // No expressions are in the string. strings.push(ts.createStringLiteral(expression.template.text)); + rawStrings.push(expression.template.getText()); } else { throw TSTLErrors.UnsupportedKind("TaggedTemplateExpression", expression.kind, expression); } + + // Preserve some special characters + const preservedRawStrings = rawStrings.map(rawString => { + const preservedString = rawString + .replace(/`/g, "") + .replace(/\$/g, "") + .replace(/\{/g, "") + .replace(/\}/g, "") + .replace(/\\/g, "\\\\"); + return tstl.createStringLiteral(preservedString); + }); + + // Construct call expression const stringArray = ts.createArrayLiteral(strings); + const rawStringArray = tstl.createTableExpression( + preservedRawStrings.map(rawString => tstl.createTableFieldExpression(rawString)) + ); const signature = this.checker.getResolvedSignature(expression); const signatureDeclaration = signature && signature.getDeclaration(); let parameters: tstl.Expression[]; + let tableLiteralExpression: tstl.TableExpression | undefined; + + // Evaluate if `self` should be used if ( signatureDeclaration && tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void ) { const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G"); parameters = this.transformArguments([stringArray, ...expressions], signature, context); + tableLiteralExpression = + parameters.length > 1 && parameters[1].kind === tstl.SyntaxKind.TableExpression + ? (parameters[1] as tstl.TableExpression) + : undefined; } else { parameters = this.transformArguments([stringArray, ...expressions], signature); + tableLiteralExpression = + parameters.length > 0 && parameters[0].kind === tstl.SyntaxKind.TableExpression + ? (parameters[0] as tstl.TableExpression) + : undefined; } + + // Keep track of raw strings. + if (tableLiteralExpression && tableLiteralExpression.fields) { + tableLiteralExpression.fields.push( + tstl.createTableFieldExpression(rawStringArray, tstl.createStringLiteral("raw")) + ); + } + const leftHandSideExpression = this.transformExpression(expression.tag); return tstl.createCallExpression(leftHandSideExpression, parameters); } diff --git a/test/unit/taggedTemplateLiterals.spec.ts b/test/unit/taggedTemplateLiterals.spec.ts index 0b6d48f84..54974a1f6 100644 --- a/test/unit/taggedTemplateLiterals.spec.ts +++ b/test/unit/taggedTemplateLiterals.spec.ts @@ -1,47 +1,59 @@ import * as util from "../util"; -test.each([ +const testCases = [ { callExpression: "func``", - expectedResult: "", + joinAllResult: "", + joinRawResult: "", }, { callExpression: "func`hello`", - expectedResult: "hello", + joinAllResult: "hello", + joinRawResult: "hello", }, { callExpression: "func`hello ${1} ${2} ${3}`", - expectedResult: "hello 1 2 3", + joinAllResult: "hello 1 2 3", + joinRawResult: "hello ", }, { callExpression: "func`hello ${(() => 'iife')()}`", - expectedResult: "hello iife", + joinAllResult: "hello iife", + joinRawResult: "hello ", }, { callExpression: "func`hello ${1 + 2 + 3} arithmetic`", - expectedResult: "hello 6 arithmetic", + joinAllResult: "hello 6 arithmetic", + joinRawResult: "hello arithmetic", }, { callExpression: "func`begin ${'middle'} end`", - expectedResult: "begin middle end", + joinAllResult: "begin middle end", + joinRawResult: "begin end", }, { callExpression: "func`hello ${func`hello`}`", - expectedResult: "hello hello", + joinAllResult: "hello hello", + joinRawResult: "hello ", }, { callExpression: "func`hello \\u00A9`", - expectedResult: "hello ©", + joinAllResult: "hello ©", + joinRawResult: "hello \\u00A9", }, { callExpression: "obj.func`hello ${'propertyAccessExpression'}`", - expectedResult: "hello propertyAccessExpression", + joinAllResult: "hello propertyAccessExpression", + joinRawResult: "hello ", }, { callExpression: "obj['func']`hello ${'elementAccessExpression'}`", - expectedResult: "hello elementAccessExpression", + joinAllResult: "hello elementAccessExpression", + joinRawResult: "hello ", }, -])("TaggedTemplateLiteral call (%p)", ({ callExpression, expectedResult }) => { +]; + +test.each(testCases)("TaggedTemplateLiteral call (%p)", ({ callExpression, joinAllResult }) => { const result = util.transpileAndExecute(` function func(strings: TemplateStringsArray, ...expressions: any[]) { const toJoin = []; @@ -61,7 +73,21 @@ test.each([ return ${callExpression}; `); - expect(result).toBe(expectedResult); + expect(result).toBe(joinAllResult); +}); + +test.each(testCases)("TaggedTemplateLiteral raw preservation (%p)", ({ callExpression, joinRawResult }) => { + const result = util.transpileAndExecute(` + function func(strings: TemplateStringsArray, ...expressions: any[]) { + return strings.raw.join(""); + } + const obj = { + func + }; + return ${callExpression}; + `); + + expect(result).toBe(joinRawResult); }); test("TaggedTemplateLiteral no self parameter", () => { From bf24fbc9109b45650a870a0f5065a42b73bec5bb Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 21 Jun 2019 10:12:16 +1000 Subject: [PATCH 4/9] Improve raw string preservation and use type narrowing --- src/LuaTransformer.ts | 53 ++++++++++++++++-------- test/unit/taggedTemplateLiterals.spec.ts | 15 +++++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index f8cdfb66f..ed11e14c3 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4640,36 +4640,53 @@ export class LuaTransformer { const strings: ts.StringLiteral[] = []; const rawStrings: string[] = []; const expressions: ts.Expression[] = []; + if (ts.isTemplateExpression(expression.template)) { // Expressions are in the string. strings.push(ts.createStringLiteral(expression.template.head.text)); - rawStrings.push(expression.template.head.getText()); + // If there is a head, it beings with "`" and ends with "${" + rawStrings.push( + expression.template.head + .getText() + .replace(/\$\{/, "") + .replace(/^`/, "") + ); strings.push(...expression.template.templateSpans.map(span => ts.createStringLiteral(span.literal.text))); - rawStrings.push(...expression.template.templateSpans.map(span => span.literal.getText())); + // Following spans end with "}" + rawStrings.push( + ...expression.template.templateSpans.map(span => { + if (span.literal.kind === ts.SyntaxKind.TemplateMiddle) { + // The middle sections end with "${" + return span.literal + .getText() + .replace(/^\}/, "") + .replace(/\$\{$/, ""); + } else { + // The tail ends with "`" + return span.literal + .getText() + .replace(/^\}/, "") + .replace(/`$/, ""); + } + }) + ); expressions.push(...expression.template.templateSpans.map(span => span.expression)); - } else if (ts.isNoSubstitutionTemplateLiteral(expression.template)) { + } else { // No expressions are in the string. strings.push(ts.createStringLiteral(expression.template.text)); - rawStrings.push(expression.template.getText()); - } else { - throw TSTLErrors.UnsupportedKind("TaggedTemplateExpression", expression.kind, expression); - } - - // Preserve some special characters - const preservedRawStrings = rawStrings.map(rawString => { - const preservedString = rawString - .replace(/`/g, "") - .replace(/\$/g, "") - .replace(/\{/g, "") - .replace(/\}/g, "") - .replace(/\\/g, "\\\\"); - return tstl.createStringLiteral(preservedString); + rawStrings.push(expression.template.getText().replace(/(^`|`$)/g, "")); + } + + // Preserve "\" + const preparedRawStrings = rawStrings.map(rawString => { + const preparedString = rawString.replace(/\\/g, "\\\\"); + return tstl.createStringLiteral(preparedString); }); // Construct call expression const stringArray = ts.createArrayLiteral(strings); const rawStringArray = tstl.createTableExpression( - preservedRawStrings.map(rawString => tstl.createTableFieldExpression(rawString)) + preparedRawStrings.map(rawString => tstl.createTableFieldExpression(rawString)) ); const signature = this.checker.getResolvedSignature(expression); const signatureDeclaration = signature && signature.getDeclaration(); diff --git a/test/unit/taggedTemplateLiterals.spec.ts b/test/unit/taggedTemplateLiterals.spec.ts index 54974a1f6..715556192 100644 --- a/test/unit/taggedTemplateLiterals.spec.ts +++ b/test/unit/taggedTemplateLiterals.spec.ts @@ -41,6 +41,21 @@ const testCases = [ joinAllResult: "hello ©", joinRawResult: "hello \\u00A9", }, + { + callExpression: "func`hello $ { }`", + joinAllResult: "hello $ { }", + joinRawResult: "hello $ { }", + }, + { + callExpression: "func`hello { ${'brackets'} }`", + joinAllResult: "hello { brackets }", + joinRawResult: "hello { }", + }, + { + callExpression: "func`hello \\``", + joinAllResult: "hello `", + joinRawResult: "hello \\`", + }, { callExpression: "obj.func`hello ${'propertyAccessExpression'}`", joinAllResult: "hello propertyAccessExpression", From cd147fbd72bfb44e637a2b9241c74e156334ed1e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 23 Jun 2019 15:39:27 +1000 Subject: [PATCH 5/9] Simplfy method using TypeScript's example --- src/LuaTransformer.ts | 91 ++++++++++++++----------------------------- src/TSHelper.ts | 9 +++++ 2 files changed, 38 insertions(+), 62 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ed11e14c3..fc42c83e7 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4637,83 +4637,50 @@ export class LuaTransformer { } public transformTaggedTemplateExpression(expression: ts.TaggedTemplateExpression): ExpressionVisitResult { - const strings: ts.StringLiteral[] = []; + const strings: string[] = []; const rawStrings: string[] = []; const expressions: ts.Expression[] = []; if (ts.isTemplateExpression(expression.template)) { // Expressions are in the string. - strings.push(ts.createStringLiteral(expression.template.head.text)); - // If there is a head, it beings with "`" and ends with "${" - rawStrings.push( - expression.template.head - .getText() - .replace(/\$\{/, "") - .replace(/^`/, "") - ); - strings.push(...expression.template.templateSpans.map(span => ts.createStringLiteral(span.literal.text))); - // Following spans end with "}" - rawStrings.push( - ...expression.template.templateSpans.map(span => { - if (span.literal.kind === ts.SyntaxKind.TemplateMiddle) { - // The middle sections end with "${" - return span.literal - .getText() - .replace(/^\}/, "") - .replace(/\$\{$/, ""); - } else { - // The tail ends with "`" - return span.literal - .getText() - .replace(/^\}/, "") - .replace(/`$/, ""); - } - }) - ); + strings.push(expression.template.head.text); + rawStrings.push(tsHelper.getRawLiteral(expression.template.head)); + strings.push(...expression.template.templateSpans.map(span => span.literal.text)); + rawStrings.push(...expression.template.templateSpans.map(span => tsHelper.getRawLiteral(span.literal))); expressions.push(...expression.template.templateSpans.map(span => span.expression)); } else { // No expressions are in the string. - strings.push(ts.createStringLiteral(expression.template.text)); - rawStrings.push(expression.template.getText().replace(/(^`|`$)/g, "")); + strings.push(expression.template.text); + rawStrings.push(tsHelper.getRawLiteral(expression.template)); } - // Preserve "\" - const preparedRawStrings = rawStrings.map(rawString => { - const preparedString = rawString.replace(/\\/g, "\\\\"); - return tstl.createStringLiteral(preparedString); - }); + // Construct string arrays for parameters. + const stringArray = ts.createArrayLiteral(strings.map(partialString => ts.createStringLiteral(partialString))); - // Construct call expression - const stringArray = ts.createArrayLiteral(strings); - const rawStringArray = tstl.createTableExpression( - preparedRawStrings.map(rawString => tstl.createTableFieldExpression(rawString)) - ); + // Evaluate if there is a self parameter to be used. const signature = this.checker.getResolvedSignature(expression); const signatureDeclaration = signature && signature.getDeclaration(); - let parameters: tstl.Expression[]; - let tableLiteralExpression: tstl.TableExpression | undefined; - - // Evaluate if `self` should be used - if ( + const useSelfParameter = signatureDeclaration && - tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void - ) { - const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G"); - parameters = this.transformArguments([stringArray, ...expressions], signature, context); - tableLiteralExpression = - parameters.length > 1 && parameters[1].kind === tstl.SyntaxKind.TableExpression - ? (parameters[1] as tstl.TableExpression) - : undefined; - } else { - parameters = this.transformArguments([stringArray, ...expressions], signature); - tableLiteralExpression = - parameters.length > 0 && parameters[0].kind === tstl.SyntaxKind.TableExpression - ? (parameters[0] as tstl.TableExpression) - : undefined; - } - - // Keep track of raw strings. + tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void; + const context = useSelfParameter ? (this.isStrict ? ts.createNull() : ts.createIdentifier("_G")) : undefined; + + // Argument evaluation. + const parameters = this.transformArguments([stringArray, ...expressions], signature, context); + + // Assign raw strings to the "raw" property of the string table. + const tableParameterIndex = useSelfParameter ? 1 : 0; + const tableLiteralExpression = + parameters.length > tableParameterIndex && + parameters[tableParameterIndex].kind === tstl.SyntaxKind.TableExpression + ? (parameters[tableParameterIndex] as tstl.TableExpression) + : undefined; if (tableLiteralExpression && tableLiteralExpression.fields) { + const rawStringArray = tstl.createTableExpression( + rawStrings.map(stringLiteral => + tstl.createTableFieldExpression(tstl.createStringLiteral(stringLiteral)) + ) + ); tableLiteralExpression.fields.push( tstl.createTableFieldExpression(rawStringArray, tstl.createStringLiteral("raw")) ); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index eeb6373e1..79c7f3183 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -758,6 +758,15 @@ export class TSHelper { return declarations.length > 0 ? declarations.reduce((p, c) => (p.pos < c.pos ? p : c)) : undefined; } + public static getRawLiteral(node: ts.LiteralLikeNode): string { + let text = node.getText(); + const isLast = + node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === ts.SyntaxKind.TemplateTail; + text = text.substring(1, text.length - (isLast ? 1 : 2)); + text = text.replace(/\r\n?/g, "\n").replace(/\\/g, "\\\\"); + return text; + } + public static isFirstDeclaration(node: ts.VariableDeclaration, checker: ts.TypeChecker): boolean { const symbol = checker.getSymbolAtLocation(node.name); if (!symbol) { From 7357109d6d41c01777ac159225d5048e20c396b8 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 23 Jun 2019 22:42:56 +1000 Subject: [PATCH 6/9] Fix ContextType reference --- src/LuaTransformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 2a913b448..e0c4795e2 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4674,7 +4674,7 @@ export class LuaTransformer { const signatureDeclaration = signature && signature.getDeclaration(); const useSelfParameter = signatureDeclaration && - tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void; + tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void; const context = useSelfParameter ? (this.isStrict ? ts.createNull() : ts.createIdentifier("_G")) : undefined; // Argument evaluation. From 33dbac40564253ca80cb3d78cd6daf3b531d93f2 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 23 Jun 2019 23:12:50 +1000 Subject: [PATCH 7/9] Remove unnecessary check --- src/LuaTransformer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e0c4795e2..ee1c1aff0 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4683,7 +4683,6 @@ export class LuaTransformer { // Assign raw strings to the "raw" property of the string table. const tableParameterIndex = useSelfParameter ? 1 : 0; const tableLiteralExpression = - parameters.length > tableParameterIndex && parameters[tableParameterIndex].kind === tstl.SyntaxKind.TableExpression ? (parameters[tableParameterIndex] as tstl.TableExpression) : undefined; From 016a039c2f069e24b1cf03f5db20f2f235b92f85 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 23 Jun 2019 23:29:01 +1000 Subject: [PATCH 8/9] Reduce checks and use filterUndefinedAndCast --- src/LuaTransformer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ee1c1aff0..4c561afc9 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4682,11 +4682,11 @@ export class LuaTransformer { // Assign raw strings to the "raw" property of the string table. const tableParameterIndex = useSelfParameter ? 1 : 0; - const tableLiteralExpression = - parameters[tableParameterIndex].kind === tstl.SyntaxKind.TableExpression - ? (parameters[tableParameterIndex] as tstl.TableExpression) - : undefined; - if (tableLiteralExpression && tableLiteralExpression.fields) { + const [tableLiteralExpression] = this.filterUndefinedAndCast( + [parameters[tableParameterIndex]], + tstl.isTableExpression + ); + if (tableLiteralExpression.fields) { const rawStringArray = tstl.createTableExpression( rawStrings.map(stringLiteral => tstl.createTableFieldExpression(tstl.createStringLiteral(stringLiteral)) From 5158042e58e5c0ae1e5fbed2626420559eb7e6e6 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 1 Jul 2019 16:41:14 +1000 Subject: [PATCH 9/9] Add transformContextualCallExpression to transform call expression left hand side --- src/LuaTransformer.ts | 117 +++++++++++++---------- test/unit/taggedTemplateLiterals.spec.ts | 16 +++- 2 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 4c561afc9..e4083a162 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3809,20 +3809,8 @@ export class LuaTransformer { !signatureDeclaration || tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void ) { - if ( - luaKeywords.has(node.expression.name.text) || - !tsHelper.isValidLuaIdentifier(node.expression.name.text) - ) { - return this.transformElementCall(node); - } else { - // table:name() - return tstl.createMethodCallExpression( - table, - this.transformIdentifier(node.expression.name), - parameters, - node - ); - } + // table:name() + return this.transformContextualCallExpression(node, parameters); } else { // table.name() const callPath = tstl.createTableIndexExpression( @@ -3842,43 +3830,67 @@ export class LuaTransformer { } const signature = this.checker.getResolvedSignature(node); - let parameters = this.transformArguments(node.arguments, signature); - const signatureDeclaration = signature && signature.getDeclaration(); + const parameters = this.transformArguments(node.arguments, signature); if ( !signatureDeclaration || tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void ) { - // Pass left-side as context + // A contextual parameter must be given to this call expression + return this.transformContextualCallExpression(node, parameters); + } else { + // No context + const expression = this.transformExpression(node.expression); + return tstl.createCallExpression(expression, parameters); + } + } - const context = this.transformExpression(node.expression.expression); - if (tsHelper.isExpressionWithEvaluationEffect(node.expression.expression)) { + public transformContextualCallExpression( + node: ts.CallExpression | ts.TaggedTemplateExpression, + transformedArguments: tstl.Expression[] + ): ExpressionVisitResult { + const left = ts.isCallExpression(node) ? node.expression : node.tag; + const leftHandSideExpression = this.transformExpression(left); + if ( + ts.isPropertyAccessExpression(left) && + !luaKeywords.has(left.name.text) && + tsHelper.isValidLuaIdentifier(left.name.text) + ) { + // table:name() + const table = this.transformExpression(left.expression); + return tstl.createMethodCallExpression( + table, + this.transformIdentifier(left.name), + transformedArguments, + node + ); + } else if (ts.isElementAccessExpression(left) || ts.isPropertyAccessExpression(left)) { + const context = this.transformExpression(left.expression); + if (tsHelper.isExpressionWithEvaluationEffect(left.expression)) { // Inject context parameter - if (node.arguments.length > 0) { - parameters.unshift(tstl.createIdentifier("____TS_self")); - } else { - parameters = [tstl.createIdentifier("____TS_self")]; - } + transformedArguments.unshift(tstl.createIdentifier("____TS_self")); // Cache left-side if it has effects //(function() local ____TS_self = context; return ____TS_self[argument](parameters); end)() - const argumentExpression = ts.isElementAccessExpression(node.expression) - ? node.expression.argumentExpression - : ts.createStringLiteral(node.expression.name.text); + const argumentExpression = ts.isElementAccessExpression(left) + ? left.argumentExpression + : ts.createStringLiteral(left.name.text); const argument = this.transformExpression(argumentExpression); const selfIdentifier = tstl.createIdentifier("____TS_self"); const selfAssignment = tstl.createVariableDeclarationStatement(selfIdentifier, context); const index = tstl.createTableIndexExpression(selfIdentifier, argument); - const callExpression = tstl.createCallExpression(index, parameters); + const callExpression = tstl.createCallExpression(index, transformedArguments); return this.createImmediatelyInvokedFunctionExpression([selfAssignment], callExpression, node); } else { - const expression = this.transformExpression(node.expression); - return tstl.createCallExpression(expression, [context, ...parameters]); + const expression = this.transformExpression(left); + return tstl.createCallExpression(expression, [context, ...transformedArguments]); } + } else if (ts.isIdentifier(left)) { + const context = this.isStrict ? tstl.createNilLiteral() : tstl.createIdentifier("_G"); + transformedArguments.unshift(context); + return tstl.createCallExpression(leftHandSideExpression, transformedArguments, node); } else { - // No context - const expression = this.transformExpression(node.expression); - return tstl.createCallExpression(expression, parameters); + throw TSTLErrors.UnsupportedKind("Left Hand Side Call Expression", left.kind, left); } } @@ -4666,8 +4678,20 @@ export class LuaTransformer { rawStrings.push(tsHelper.getRawLiteral(expression.template)); } - // Construct string arrays for parameters. - const stringArray = ts.createArrayLiteral(strings.map(partialString => ts.createStringLiteral(partialString))); + // Construct table with strings and literal strings + const stringTableLiteral = tstl.createTableExpression( + strings.map(partialString => tstl.createTableFieldExpression(tstl.createStringLiteral(partialString))) + ); + if (stringTableLiteral.fields) { + const rawStringArray = tstl.createTableExpression( + rawStrings.map(stringLiteral => + tstl.createTableFieldExpression(tstl.createStringLiteral(stringLiteral)) + ) + ); + stringTableLiteral.fields.push( + tstl.createTableFieldExpression(rawStringArray, tstl.createStringLiteral("raw")) + ); + } // Evaluate if there is a self parameter to be used. const signature = this.checker.getResolvedSignature(expression); @@ -4675,30 +4699,17 @@ export class LuaTransformer { const useSelfParameter = signatureDeclaration && tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void; - const context = useSelfParameter ? (this.isStrict ? ts.createNull() : ts.createIdentifier("_G")) : undefined; // Argument evaluation. - const parameters = this.transformArguments([stringArray, ...expressions], signature, context); + const callArguments = this.transformArguments(expressions, signature); + callArguments.unshift(stringTableLiteral); - // Assign raw strings to the "raw" property of the string table. - const tableParameterIndex = useSelfParameter ? 1 : 0; - const [tableLiteralExpression] = this.filterUndefinedAndCast( - [parameters[tableParameterIndex]], - tstl.isTableExpression - ); - if (tableLiteralExpression.fields) { - const rawStringArray = tstl.createTableExpression( - rawStrings.map(stringLiteral => - tstl.createTableFieldExpression(tstl.createStringLiteral(stringLiteral)) - ) - ); - tableLiteralExpression.fields.push( - tstl.createTableFieldExpression(rawStringArray, tstl.createStringLiteral("raw")) - ); + if (useSelfParameter) { + return this.transformContextualCallExpression(expression, callArguments); } const leftHandSideExpression = this.transformExpression(expression.tag); - return tstl.createCallExpression(leftHandSideExpression, parameters); + return tstl.createCallExpression(leftHandSideExpression, callArguments); } public transformTemplateExpression(expression: ts.TemplateExpression): ExpressionVisitResult { diff --git a/test/unit/taggedTemplateLiterals.spec.ts b/test/unit/taggedTemplateLiterals.spec.ts index 715556192..6ab4e68cd 100644 --- a/test/unit/taggedTemplateLiterals.spec.ts +++ b/test/unit/taggedTemplateLiterals.spec.ts @@ -105,13 +105,19 @@ test.each(testCases)("TaggedTemplateLiteral raw preservation (%p)", ({ callExpre expect(result).toBe(joinRawResult); }); -test("TaggedTemplateLiteral no self parameter", () => { - const result = util.transpileAndExecute(` +test.each(["func`noSelfParameter`", "obj.func`noSelfParameter`", "obj[`func`]`noSelfParameter`"])( + "TaggedTemplateLiteral no self parameter", + callExpression => { + const result = util.transpileAndExecute(` function func(this: void, strings: TemplateStringsArray, ...expressions: any[]) { return strings.join(""); } - return func\`noSelfParameter\`; + const obj = { + func + }; + return ${callExpression}; `); - expect(result).toBe("noSelfParameter"); -}); + expect(result).toBe("noSelfParameter"); + } +);