diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 277f4fdf7..3ef1f8612 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -121,11 +121,17 @@ export function cloneNode(node: T): T { return Object.assign({}, node); } +export function setNodePosition(node: T, position: TextRange): T { + node.line = position.line; + node.column = position.column; + + return node; +} + export function setNodeOriginal(node: T, tsOriginal: ts.Node): T { const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { - node.line = sourcePosition.line; - node.column = sourcePosition.column; + setNodePosition(node, sourcePosition); } return node; @@ -589,20 +595,19 @@ export function createStringLiteral(value: string | ts.__String, tsOriginal?: ts return expression; } -// There is no export function statement/declaration because those are just syntax sugar -// -// `function f () body end` becomes `f = function () body` end -// `function t.a.b.c.f () body end` becomes `t.a.b.c.f = function () body end` -// `local function f () body end` becomes `local f; f = function () body end` NOT `local f = function () body end` -// See https://www.lua.org/manual/5.3/manual.html 3.4.11 -// -// We should probably create helper functions to create the different export function declarations +export enum FunctionExpressionFlags { + None = 0x0, + Inline = 0x1, // Keep function on same line + Declaration = 0x2, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()` +} + export interface FunctionExpression extends Expression { kind: SyntaxKind.FunctionExpression; params?: Identifier[]; dots?: DotsLiteral; restParamName?: Identifier; body: Block; + flags: FunctionExpressionFlags; } export function isFunctionExpression(node: Node): node is FunctionExpression { @@ -614,6 +619,7 @@ export function createFunctionExpression( params?: Identifier[], dots?: DotsLiteral, restParamName?: Identifier, + flags = FunctionExpressionFlags.None, tsOriginal?: ts.Node, parent?: Node ): FunctionExpression @@ -627,6 +633,7 @@ export function createFunctionExpression( expression.dots = dots; setParent(restParamName, expression); expression.restParamName = restParamName; + expression.flags = flags; return expression; } @@ -862,3 +869,27 @@ export function createTableIndexExpression( } export type IdentifierOrTableIndexExpression = Identifier | TableIndexExpression; + +export type FunctionDefinition = (VariableDeclarationStatement | AssignmentStatement) & { + right: [FunctionExpression]; +}; + +export function isFunctionDefinition(statement: VariableDeclarationStatement | AssignmentStatement) + : statement is FunctionDefinition +{ + return statement.left.length === 1 + && statement.right + && statement.right.length === 1 + && isFunctionExpression(statement.right[0]); +} + +export type InlineFunctionExpression = FunctionExpression & { + body: { statements: [ReturnStatement]; }; +}; + +export function isInlineFunctionExpression(expression: FunctionExpression) : expression is InlineFunctionExpression { + return expression.body.statements + && expression.body.statements.length === 1 + && isReturnStatement(expression.body.statements[0]) + && (expression.flags & FunctionExpressionFlags.Inline) !== 0; +} diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 23ac3e753..5538cca89 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -213,25 +213,37 @@ export class LuaPrinter { private printDoStatement(statement: tstl.DoStatement): SourceNode { const chunks: SourceChunk[] = []; - chunks.push(this.indent("do\n")); - this.pushIndent(); - chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s))); - this.popIndent(); - chunks.push(this.indent("end\n")); + + if (statement.statements && statement.statements.length > 0) { + chunks.push(this.indent("do\n")); + this.pushIndent(); + chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s))); + this.popIndent(); + chunks.push(this.indent("end\n")); + } return this.concatNodes(...chunks); } private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): SourceNode { const chunks: SourceChunk[] = []; + chunks.push(this.indent("local ")); - chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e)))); - if (statement.right) { - chunks.push(" = "); - chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e)))); + if (tstl.isFunctionDefinition(statement)) { + // Print all local functions as `local function foo()` instead of `local foo = function` to allow recursion + chunks.push(this.printFunctionDefinition(statement)); + chunks.push("\n"); + + } else { + chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e)))); + + if (statement.right) { + chunks.push(" = "); + chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e)))); + } + chunks.push(";\n"); } - chunks.push(";\n"); return this.concatNodes(...chunks); } @@ -240,6 +252,19 @@ export class LuaPrinter { const chunks: SourceChunk[] = []; chunks.push(this.indent()); + + if (tstl.isFunctionDefinition(statement) + && (statement.right[0].flags & tstl.FunctionExpressionFlags.Declaration) !== 0) + { + // Use `function foo()` instead of `foo = function()` + const name = this.printExpression(statement.left[0]); + if (tsHelper.isValidLuaFunctionDeclarationName(name.toString())) { + chunks.push(this.printFunctionDefinition(statement)); + chunks.push("\n"); + return this.createSourceNode(statement, chunks); + } + } + chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e)))); chunks.push(" = "); chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e)))); @@ -359,7 +384,6 @@ export class LuaPrinter { const chunks: SourceChunk[] = []; chunks.push(...this.joinChunks(", ", statement.expressions.map(e => this.printExpression(e)))); - chunks.push(";\n"); return this.createSourceNode(statement, [this.indent(), "return ", ...chunks]); @@ -436,7 +460,7 @@ export class LuaPrinter { } } - private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { + private printFunctionParameters(expression: tstl.FunctionExpression): SourceChunk[] { const parameterChunks: SourceNode[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; @@ -445,10 +469,46 @@ export class LuaPrinter { parameterChunks.push(this.printDotsLiteral(expression.dots)); } + return this.joinChunks(", ", parameterChunks); + } + + private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { const chunks: SourceChunk[] = []; chunks.push("function("); - chunks.push(...this.joinChunks(", ", parameterChunks)); + chunks.push(...this.printFunctionParameters(expression)); + chunks.push(")"); + + if (tstl.isInlineFunctionExpression(expression)) { + const returnStatement = expression.body.statements[0]; + chunks.push(" "); + const returnNode: SourceChunk[] = [ + "return ", + ...this.joinChunks(", ", returnStatement.expressions.map(e => this.printExpression(e))), + ";", + ]; + chunks.push(this.createSourceNode(returnStatement, returnNode)); + chunks.push(" end"); + + } else { + chunks.push("\n"); + this.pushIndent(); + chunks.push(this.printBlock(expression.body)); + this.popIndent(); + chunks.push(this.indent("end")); + } + + return this.createSourceNode(expression, chunks); + } + + private printFunctionDefinition(statement: tstl.FunctionDefinition): SourceNode { + const expression = statement.right[0]; + const chunks: SourceChunk[] = []; + + chunks.push("function "); + chunks.push(this.printExpression(statement.left[0])); + chunks.push("("); + chunks.push(...this.printFunctionParameters(expression)); chunks.push(")\n"); this.pushIndent(); @@ -482,14 +542,18 @@ export class LuaPrinter { chunks.push("{"); - if (expression.fields) { - expression.fields.forEach((f, i) => { - if (i < expression.fields.length - 1) { - chunks.push(this.printTableFieldExpression(f), ", "); - } else { - chunks.push(this.printTableFieldExpression(f)); - } - }); + if (expression.fields && expression.fields.length > 0) { + if (expression.fields.length === 1) { + // Inline tables with only one entry + chunks.push(this.printTableFieldExpression(expression.fields[0])); + + } else { + chunks.push("\n"); + this.pushIndent(); + expression.fields.forEach(f => chunks.push(this.indent(), this.printTableFieldExpression(f), ",\n")); + this.popIndent(); + chunks.push(this.indent()); + } } chunks.push("}"); @@ -501,12 +565,7 @@ export class LuaPrinter { const chunks: SourceChunk[] = []; chunks.push(this.printOperator(expression.operator)); - - if (this.needsParentheses(expression.operand)) { - chunks.push("(", this.printExpression(expression.operand), ")"); - } else { - chunks.push(this.printExpression(expression.operand)); - } + chunks.push(this.printExpression(expression.operand)); return this.createSourceNode(expression, chunks); } @@ -514,28 +573,13 @@ export class LuaPrinter { private printBinaryExpression(expression: tstl.BinaryExpression): SourceNode { const chunks: SourceChunk[] = []; - if (this.needsParentheses(expression.left)) { - chunks.push("(", this.printExpression(expression.left), ")"); - } else { - chunks.push(this.printExpression(expression.left)); - } - + chunks.push(this.printExpression(expression.left)); chunks.push(" ", this.printOperator(expression.operator), " "); - - if (this.needsParentheses(expression.right)) { - chunks.push("(", this.printExpression(expression.right), ")"); - } else { - chunks.push(this.printExpression(expression.right)); - } + chunks.push(this.printExpression(expression.right)); return this.createSourceNode(expression, chunks); } - private needsParentheses(expression: tstl.Expression): boolean { - return tstl.isBinaryExpression(expression) || tstl.isUnaryExpression(expression) - || tstl.isFunctionExpression(expression); - } - private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode { return this.createSourceNode(expression, ["(", this.printExpression(expression.innerEpxression), ")"]); } @@ -544,11 +588,7 @@ export class LuaPrinter { const chunks = []; const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e))); - if (this.needsParentheses(expression.expression)) { - chunks.push("(", this.printExpression(expression.expression), ")(", ...parameterChunks, ")"); - } else { - chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")"); - } + chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")"); return this.concatNodes(...chunks); } diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 40d2b8eed..9fc558448 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -25,7 +25,7 @@ interface SymbolInfo { interface FunctionDefinitionInfo { referencedSymbols: Set; - assignment?: tstl.AssignmentStatement; + definition?: tstl.VariableDeclarationStatement | tstl.AssignmentStatement; } interface Scope { @@ -34,6 +34,7 @@ interface Scope { referencedSymbols?: Set; variableDeclarations?: tstl.VariableDeclarationStatement[]; functionDefinitions?: Map; + loopContinued?: boolean; } export class LuaTransformer { @@ -536,7 +537,9 @@ export class LuaTransformer { const constructorFunction = tstl.createFunctionExpression( tstl.createBlock(constructorBody), [this.createSelfIdentifier()], - tstl.createDotsLiteral() + tstl.createDotsLiteral(), + undefined, + tstl.FunctionExpressionFlags.Declaration ); result.push(tstl.createAssignmentStatement( this.createConstructorName(className), @@ -898,6 +901,7 @@ export class LuaTransformer { undefined, tstl.createDotsLiteral(), undefined, + tstl.FunctionExpressionFlags.Declaration, statement ), statement @@ -1014,7 +1018,13 @@ export class LuaTransformer { const result = tstl.createAssignmentStatement( this.createConstructorName(className), - tstl.createFunctionExpression(block, params, dotsLiteral, restParamName, undefined, undefined), + tstl.createFunctionExpression( + block, + params, + dotsLiteral, + restParamName, + tstl.FunctionExpressionFlags.Declaration + ), statement ); @@ -1032,7 +1042,10 @@ export class LuaTransformer { const [body] = this.transformFunctionBody(getAccessor.parameters, getAccessor.body); const accessorFunction = tstl.createFunctionExpression( tstl.createBlock(body), - [this.createSelfIdentifier()] + [this.createSelfIdentifier()], + undefined, + undefined, + tstl.FunctionExpressionFlags.Declaration ); const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className)); @@ -1067,7 +1080,8 @@ export class LuaTransformer { tstl.createBlock(body), params, dot, - restParam + restParam, + tstl.FunctionExpressionFlags.Declaration ); const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className)); @@ -1115,6 +1129,7 @@ export class LuaTransformer { paramNames, dots, restParamName, + tstl.FunctionExpressionFlags.Declaration, node.body ); @@ -1630,7 +1645,13 @@ export class LuaTransformer { restParamName ); const block = tstl.createBlock(body); - const functionExpression = tstl.createFunctionExpression(block, params, dotsLiteral, restParamName); + const functionExpression = tstl.createFunctionExpression( + block, + params, + dotsLiteral, + restParamName, + tstl.FunctionExpressionFlags.Declaration + ); // Remember symbols referenced in this function for hoisting later if (!this.options.noHoisting && name.symbolId !== undefined) { const scope = this.peekScope(); @@ -1684,7 +1705,7 @@ export class LuaTransformer { table = this.transformIdentifier(statement.initializer); } else { // Contain the expression in a temporary variable - table = tstl.createIdentifier("____"); + table = tstl.createAnnonymousIdentifier(); statements.push(tstl.createVariableDeclarationStatement( table, this.transformExpression(statement.initializer))); } @@ -1870,7 +1891,10 @@ export class LuaTransformer { public transformDoStatement(statement: ts.DoStatement): tstl.RepeatStatement { return tstl.createRepeatStatement( tstl.createBlock(this.transformLoopBody(statement)), - tstl.createUnaryExpression(this.transformExpression(statement.expression), tstl.SyntaxKind.NotOperator), + tstl.createUnaryExpression( + tstl.createParenthesizedExpression(this.transformExpression(statement.expression)), + tstl.SyntaxKind.NotOperator + ), statement ); } @@ -1940,7 +1964,7 @@ export class LuaTransformer { const scope = this.popScope(); const scopeId = scope.id; - if (this.options.luaTarget === LuaTarget.Lua51) { + if (!scope.loopContinued) { return body; } @@ -2223,7 +2247,10 @@ export class LuaTransformer { result.push(catchAssignment); - const notTryResult = tstl.createUnaryExpression(tryResult, tstl.SyntaxKind.NotOperator); + const notTryResult = tstl.createUnaryExpression( + tstl.createParenthesizedExpression(tryResult), + tstl.SyntaxKind.NotOperator + ); result.push(tstl.createIfStatement(notTryResult, this.transformBlock(statement.catchClause.block))); } else { @@ -2258,8 +2285,10 @@ export class LuaTransformer { throw TSTLErrors.UnsupportedForTarget("Continue statement", this.options.luaTarget, statement); } + const scope = this.findScope(ScopeType.Loop); + scope.loopContinued = true; return tstl.createGotoStatement( - `__continue${this.findScope(ScopeType.Loop).id}`, + `__continue${scope.id}`, statement ); } @@ -2310,9 +2339,8 @@ export class LuaTransformer { case ts.SyntaxKind.DeleteExpression: return this.transformDeleteExpression(expression as ts.DeleteExpression); case ts.SyntaxKind.FunctionExpression: - return this.transformFunctionExpression(expression as ts.ArrowFunction, this.createSelfIdentifier()); case ts.SyntaxKind.ArrowFunction: - return this.transformFunctionExpression(expression as ts.ArrowFunction, tstl.createIdentifier("____")); + return this.transformFunctionExpression(expression as ts.ArrowFunction); case ts.SyntaxKind.NewExpression: return this.transformNewExpression(expression as ts.NewExpression); case ts.SyntaxKind.ParenthesizedExpression: @@ -2878,10 +2906,10 @@ export class LuaTransformer { const val1Function = this.wrapInFunctionCall(val1); const val2Function = this.wrapInFunctionCall(val2); - // ((condition and (() => v1)) or (() => v2))() + // (condition and (() => v1) or (() => v2))() const conditionAnd = tstl.createBinaryExpression(condition, val1Function, tstl.SyntaxKind.AndOperator); const orExpression = tstl.createBinaryExpression(conditionAnd, val2Function, tstl.SyntaxKind.OrOperator); - return tstl.createCallExpression(orExpression, [], expression); + return tstl.createCallExpression(tstl.createParenthesizedExpression(orExpression), [], expression); } public transformConditionalExpression(expression: ts.ConditionalExpression): tstl.Expression { @@ -2893,7 +2921,7 @@ export class LuaTransformer { const val1 = this.transformExpression(expression.whenTrue); const val2 = this.transformExpression(expression.whenFalse); - // (condition and v1) or v2 + // condition and v1 or v2 const conditionAnd = tstl.createBinaryExpression(condition, val1, tstl.SyntaxKind.AndOperator); return tstl.createBinaryExpression( conditionAnd, @@ -2997,7 +3025,7 @@ export class LuaTransformer { const identifier = this.transformIdentifier(element.name); properties.push(tstl.createTableFieldExpression(identifier, name, element)); } else if (ts.isMethodDeclaration(element)) { - const expression = this.transformFunctionExpression(element, this.createSelfIdentifier()); + const expression = this.transformFunctionExpression(element); properties.push(tstl.createTableFieldExpression(expression, name, element)); } else { throw TSTLErrors.UnsupportedKind("object literal element", element.kind, node); @@ -3022,19 +3050,26 @@ export class LuaTransformer { ); } - public transformFunctionExpression( - node: ts.FunctionLikeDeclaration, - context: tstl.Identifier | undefined - ): ExpressionVisitResult - { + public transformFunctionExpression(node: ts.FunctionLikeDeclaration): ExpressionVisitResult { const type = this.checker.getTypeAtLocation(node); - const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void; + + let context: tstl.Identifier | undefined; + if (tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void) { + if (ts.isArrowFunction(node)) { + // dummy context for arrow functions with parameters + if (node.parameters.length > 0) { + context = tstl.createAnnonymousIdentifier(); + } + } else { + // self context + context = this.createSelfIdentifier(); + } + } + // Build parameter string - const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters( - node.parameters, - hasContext ? context : undefined - ); + const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters(node.parameters, context); + let flags = tstl.FunctionExpressionFlags.None; let body: ts.Block; if (ts.isBlock(node.body)) { body = node.body; @@ -3043,6 +3078,7 @@ export class LuaTransformer { body = ts.createBlock([returnExpression]); returnExpression.parent = body; body.parent = node.body.parent; + flags |= tstl.FunctionExpressionFlags.Inline; } const [transformedBody] = this.transformFunctionBody(node.parameters, body, spreadIdentifier); @@ -3051,6 +3087,7 @@ export class LuaTransformer { paramNames, dotsLiteral, spreadIdentifier, + flags, node ); } @@ -3091,6 +3128,11 @@ export class LuaTransformer { } public transformParenthesizedExpression(expression: ts.ParenthesizedExpression): tstl.Expression { + if (ts.isAssertionExpression(expression.expression)) { + // Strip parenthesis from casts + return this.transformExpression(expression.expression); + } + return tstl.createParenthesizedExpression( this.transformExpression(expression.expression), expression @@ -3249,8 +3291,10 @@ export class LuaTransformer { parameters = this.transformArguments(node.arguments, signature); const rawGetIdentifier = tstl.createIdentifier("rawget"); const rawGetCall = tstl.createCallExpression(rawGetIdentifier, [expr, ...parameters]); - return tstl.createBinaryExpression( - rawGetCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node); + return tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + rawGetCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node) + ); } else { const parameters = this.transformArguments(node.arguments, signature); const table = this.transformExpression(node.expression.expression); @@ -3590,20 +3634,24 @@ export class LuaTransformer { node.arguments.length === 1 ? this.createStringCall("find", node, caller, params[0]) : this.createStringCall( - "find", node, caller, params[0], - this.expressionPlusOne(params[1]), - tstl.createBooleanLiteral(true) - ); + "find", node, caller, params[0], + this.expressionPlusOne(params[1]), + tstl.createBooleanLiteral(true) + ); - return tstl.createBinaryExpression( + return tstl.createParenthesizedExpression( tstl.createBinaryExpression( - stringExpression, - tstl.createNumericLiteral(0), - tstl.SyntaxKind.OrOperator - ), - tstl.createNumericLiteral(1), - tstl.SyntaxKind.SubractionOperator, - node + tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + stringExpression, + tstl.createNumericLiteral(0), + tstl.SyntaxKind.OrOperator + ) + ), + tstl.createNumericLiteral(1), + tstl.SyntaxKind.SubractionOperator, + node + ) ); case "substr": if (node.arguments.length === 1) { @@ -3612,7 +3660,11 @@ export class LuaTransformer { } else { const arg1 = params[0]; const arg2 = params[1]; - const sumArg = tstl.createBinaryExpression(arg1, arg2, tstl.SyntaxKind.AdditionOperator); + const sumArg = tstl.createBinaryExpression( + tstl.createParenthesizedExpression(arg1), + tstl.createParenthesizedExpression(arg2), + tstl.SyntaxKind.AdditionOperator + ); return this.createStringCall("sub", node, caller, this.expressionPlusOne(arg1), sumArg); } case "substring": @@ -3974,11 +4026,13 @@ export class LuaTransformer { const condition = tstl.createBinaryExpression(typeCall, tableString, tstl.SyntaxKind.EqualityOperator); const andClause = tstl.createBinaryExpression(condition, objectString, tstl.SyntaxKind.AndOperator); - return tstl.createBinaryExpression( - andClause, - tstl.cloneNode(typeCall), - tstl.SyntaxKind.OrOperator, - node + return tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + andClause, + tstl.cloneNode(typeCall), + tstl.SyntaxKind.OrOperator, + node + ) ); } @@ -4017,24 +4071,29 @@ export class LuaTransformer { return this.createSelfIdentifier(thisKeyword); } - public transformTemplateExpression(expression: ts.TemplateExpression): tstl.BinaryExpression { - const parts: tstl.Expression[] = [tstl.createStringLiteral(tsHelper.escapeString(expression.head.text))]; + public transformTemplateExpression(expression: ts.TemplateExpression): tstl.Expression { + const parts: tstl.Expression[] = []; + + const head = tsHelper.escapeString(expression.head.text); + if (head.length > 0) { + parts.push(tstl.createStringLiteral(head, expression.head)); + } + expression.templateSpans.forEach(span => { const expr = this.transformExpression(span.expression); - const text = tstl.createStringLiteral(tsHelper.escapeString(span.literal.text)); + parts.push(tstl.createCallExpression(tstl.createIdentifier("tostring"), [expr])); - // tostring(expr).."text" - parts.push(tstl.createBinaryExpression( - tstl.createCallExpression(tstl.createIdentifier("tostring"), [expr]), - text, - tstl.SyntaxKind.ConcatOperator) - ); + const text = tsHelper.escapeString(span.literal.text); + if (text.length > 0) { + parts.push(tstl.createStringLiteral(text, span.literal)); + } }); + return parts.reduce((prev, current) => tstl.createBinaryExpression( prev, current, tstl.SyntaxKind.ConcatOperator) - ) as tstl.BinaryExpression; + ); } public transformPropertyName(propertyName: ts.PropertyName): tstl.Expression { @@ -4289,11 +4348,13 @@ export class LuaTransformer { } if ((this.isModule || this.currentNamespace || insideFunction || isLetOrConst) && isFirstDeclaration) { // local - const isFunctionType = functionDeclaration - || (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.initializer - && tsHelper.isFunctionTypeAtLocation(tsOriginal.initializer, this.checker)); - if (isFunctionType) { - // Split declaration and assignment for cuntions to allow recursion + const isPossibleWrappedFunction = !functionDeclaration + && tsOriginal + && ts.isVariableDeclaration(tsOriginal) + && tsOriginal.initializer + && tsHelper.isFunctionTypeAtLocation(tsOriginal.initializer, this.checker); + if (isPossibleWrappedFunction) { + // Split declaration and assignment for wrapped function types to allow recursion declaration = tstl.createVariableDeclarationStatement(lhs, undefined, tsOriginal, parent); assignment = tstl.createAssignmentStatement(lhs, rhs, tsOriginal, parent); @@ -4324,7 +4385,7 @@ export class LuaTransformer { // Remember function definitions for hoisting later const functionSymbolId = (lhs as tstl.Identifier).symbolId; if (functionSymbolId !== undefined) { - this.peekScope().functionDefinitions.get(functionSymbolId).assignment = assignment; + this.peekScope().functionDefinitions.get(functionSymbolId).definition = declaration || assignment; } } @@ -4423,6 +4484,9 @@ export class LuaTransformer { } private expressionPlusOne(expression: tstl.Expression): tstl.BinaryExpression { + if (tstl.isBinaryExpression(expression)) { + expression = tstl.createParenthesizedExpression(expression); + } return tstl.createBinaryExpression(expression, tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator); } @@ -4491,13 +4555,13 @@ export class LuaTransformer { if (scope.functionDefinitions) { for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { - const { line, column } = tstl.getOriginalPos(functionDefinition.assignment); - const assignmentPos = ts.getPositionOfLineAndCharacter( + const { line, column } = tstl.getOriginalPos(functionDefinition.definition); + const definitionPos = ts.getPositionOfLineAndCharacter( this.currentSourceFile, line, column); if (functionSymbolId !== symbolId // Don't recurse into self - && declaration.pos < assignmentPos // Ignore functions before symbol declaration + && declaration.pos < definitionPos // Ignore functions before symbol declaration && functionDefinition.referencedSymbols.has(symbolId) && this.shouldHoist(functionSymbolId, scope)) { @@ -4531,12 +4595,12 @@ export class LuaTransformer { } const result = statements.slice(); - const hoistedFunctions: tstl.AssignmentStatement[] = []; + const hoistedFunctions: Array = []; for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { if (this.shouldHoist(functionSymbolId, scope)) { - const i = result.indexOf(functionDefinition.assignment); + const i = result.indexOf(functionDefinition.definition); result.splice(i, 1); - hoistedFunctions.push(functionDefinition.assignment); + hoistedFunctions.push(functionDefinition.definition); } } if (hoistedFunctions.length > 0) { @@ -4557,10 +4621,8 @@ export class LuaTransformer { if (symbols.some(s => this.shouldHoist(s, scope))) { let assignment: tstl.AssignmentStatement | undefined; if (declaration.right) { - assignment = tstl.createAssignmentStatement( - declaration.left, - declaration.right - ); + assignment = tstl.createAssignmentStatement(declaration.left, declaration.right); + tstl.setNodePosition(assignment, declaration); // Preserve position info for sourcemap } const i = result.indexOf(declaration); if (i >= 0) { diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 717ba023c..d9ec50f2e 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -652,6 +652,14 @@ export class TSHelper { return match && match[0] === str; } + // Checks that a name is valid for use in lua function declaration syntax: + // 'foo.bar' => passes ('function foo.bar()' is valid) + // 'getFoo().bar' => fails ('function getFoo().bar()' would be illegal) + public static isValidLuaFunctionDeclarationName(str: string): boolean { + const match = str.match(/[a-zA-Z0-9_\.]+/); + return match && match[0] === str; + } + public static isFalsible(type: ts.Type, strictNullChecks: boolean): boolean { const falsibleFlags = ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 9c88af876..b0baaa3dd 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -14,31 +14,31 @@ local backQuoteInTemplateString = \\"\` \` \`\\"; local escapedCharsInQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\' \`\\"; local escapedCharsInDoubleQUotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\'\\"; local escapedCharsInTemplateString = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\' \`\\"; -local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. (tostring(\\"Level 1: \\\\n\\\\t\\\\t \\" .. (tostring(\\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. (tostring(\\"Last level \\\\n --\\") .. \\" \\\\n --\\")) .. \\" \\\\n --\\")) .. \\" \\\\n --\\");" +local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. tostring(\\"Level 1: \\\\n\\\\t\\\\t \\" .. tostring(\\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. tostring(\\"Last level \\\\n --\\") .. \\" \\\\n --\\") .. \\" \\\\n --\\") .. \\" \\\\n --\\";" `; exports[`Transformation (classExtension1) 1`] = ` -"MyClass.myFunction = function(self) -end;" +"function MyClass.myFunction(self) +end" `; exports[`Transformation (classExtension2) 1`] = ` -"TestClass.myFunction = function(self) -end;" +"function TestClass.myFunction(self) +end" `; exports[`Transformation (classExtension3) 1`] = ` -"RenamedTestClass.myFunction = function(self) -end; -RenamedMyClass.myFunction = function(self) -end;" +"function RenamedTestClass.myFunction(self) +end +function RenamedMyClass.myFunction(self) +end" `; exports[`Transformation (classExtension4) 1`] = ` "MyClass.test = \\"test\\"; MyClass.testP = \\"testP\\"; -MyClass.myFunction = function(self) -end;" +function MyClass.myFunction(self) +end" `; exports[`Transformation (classPureAbstract) 1`] = ` @@ -47,13 +47,13 @@ ClassB.__index = ClassB; ClassB.prototype = ClassB.prototype or {}; ClassB.prototype.__index = ClassB.prototype; ClassB.prototype.constructor = ClassB; -ClassB.new = function(...) +function ClassB.new(...) local self = setmetatable({}, ClassB.prototype); self:____constructor(...); return self; -end; -ClassB.prototype.____constructor = function(self) -end;" +end +function ClassB.prototype.____constructor(self) +end" `; exports[`Transformation (continue) 1`] = ` @@ -94,7 +94,7 @@ exports[`Transformation (continueNested) 1`] = ` local i = 0; while i < 5 do do - if (i % 2) == 0 then + if i % 2 == 0 then goto __continue1; end do @@ -121,7 +121,7 @@ exports[`Transformation (continueNestedConcurrent) 1`] = ` local i = 0; while i < 5 do do - if (i % 2) == 0 then + if i % 2 == 0 then goto __continue1; end do @@ -149,10 +149,7 @@ end" exports[`Transformation (do) 1`] = ` "local e = 10; repeat - do - e = e - 1; - end - ::__continue1:: + e = e - 1; until not (e > 0);" `; @@ -224,36 +221,43 @@ exports[`Transformation (for) 1`] = ` "do local i = 1; while i <= 100 do - do - end - ::__continue1:: i = i + 1; end end" `; exports[`Transformation (forIn) 1`] = ` -"for i in pairs({a = 1, b = 2, c = 3, d = 4}) do - do - end - ::__continue1:: +"for i in pairs({ + a = 1, + b = 2, + c = 3, + d = 4, +}) do end" `; exports[`Transformation (forOf) 1`] = ` -"local ____TS_array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +"local ____TS_array = { + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, +}; for ____TS_index = 1, #____TS_array do local i = ____TS_array[____TS_index]; - do - end - ::__continue1:: end" `; exports[`Transformation (functionRestArguments) 1`] = ` -"varargsFunction = function(self, a, ...) +"function varargsFunction(self, a, ...) local b = ({...}); -end;" +end" `; exports[`Transformation (getSetAccessors) 1`] = ` @@ -266,19 +270,19 @@ MyClass.prototype.__index = __TS__Index(MyClass.prototype); MyClass.prototype.____setters = {}; MyClass.prototype.__newindex = __TS__NewIndex(MyClass.prototype); MyClass.prototype.constructor = MyClass; -MyClass.new = function(...) +function MyClass.new(...) local self = setmetatable({}, MyClass.prototype); self:____constructor(...); return self; -end; -MyClass.prototype.____constructor = function(self) -end; -MyClass.prototype.____getters.field = function(self) +end +function MyClass.prototype.____constructor(self) +end +function MyClass.prototype.____getters.field(self) return self._field + 4; -end; -MyClass.prototype.____setters.field = function(self, v) +end +function MyClass.prototype.____setters.field(self, v) self._field = v * 2; -end; +end local instance = MyClass.new(); instance.field = 4; local b = instance.field; @@ -296,16 +300,16 @@ MyClass.__index = MyClass; MyClass.prototype = MyClass.prototype or {}; MyClass.prototype.__index = MyClass.prototype; MyClass.prototype.constructor = MyClass; -MyClass.new = function(...) +function MyClass.new(...) local self = setmetatable({}, MyClass.prototype); self:____constructor(...); return self; -end; -MyClass.prototype.____constructor = function(self) -end; -MyClass.prototype.varargsFunction = function(self, a, ...) +end +function MyClass.prototype.____constructor(self) +end +function MyClass.prototype.varargsFunction(self, a, ...) local b = ({...}); -end;" +end" `; exports[`Transformation (modulesChangedVariableExport) 1`] = ` @@ -321,13 +325,13 @@ exports.TestClass.__index = exports.TestClass; exports.TestClass.prototype = exports.TestClass.prototype or {}; exports.TestClass.prototype.__index = exports.TestClass.prototype; exports.TestClass.prototype.constructor = exports.TestClass; -exports.TestClass.new = function(...) +function exports.TestClass.new(...) local self = setmetatable({}, exports.TestClass.prototype); self:____constructor(...); return self; -end; -exports.TestClass.prototype.____constructor = function(self) -end; +end +function exports.TestClass.prototype.____constructor(self) +end return exports;" `; @@ -338,28 +342,28 @@ exports.TestClass.__index = exports.TestClass; exports.TestClass.prototype = exports.TestClass.prototype or {}; exports.TestClass.prototype.__index = exports.TestClass.prototype; exports.TestClass.prototype.constructor = exports.TestClass; -exports.TestClass.new = function(...) +function exports.TestClass.new(...) local self = setmetatable({}, exports.TestClass.prototype); self:____constructor(...); return self; -end; -exports.TestClass.prototype.____constructor = function(self) -end; -exports.TestClass.prototype.memberFunc = function(self) -end; +end +function exports.TestClass.prototype.____constructor(self) +end +function exports.TestClass.prototype.memberFunc(self) +end return exports;" `; exports[`Transformation (modulesFunctionExport) 1`] = ` "local exports = exports or {}; -exports.publicFunc = function(self) -end; +function exports.publicFunc(self) +end return exports;" `; exports[`Transformation (modulesFunctionNoExport) 1`] = ` -"publicFunc = function(self) -end;" +"function publicFunc(self) +end" `; exports[`Transformation (modulesImportAll) 1`] = `"local Test = require(\\"test\\");"`; @@ -406,8 +410,6 @@ exports[`Transformation (modulesNamespaceExport) 1`] = ` "local exports = exports or {}; exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; -do -end return exports;" `; @@ -433,26 +435,22 @@ do TestSpace.TestNestedSpace = TestSpace.TestNestedSpace or {}; local TestNestedSpace = TestSpace.TestNestedSpace; do - TestNestedSpace.innerFunc = function(self) - end; + function TestNestedSpace.innerFunc(self) + end end end return exports;" `; -exports[`Transformation (modulesNamespaceNoExport) 1`] = ` -"TestSpace = TestSpace or {}; -do -end" -`; +exports[`Transformation (modulesNamespaceNoExport) 1`] = `"TestSpace = TestSpace or {};"`; exports[`Transformation (modulesNamespaceWithMemberExport) 1`] = ` "local exports = exports or {}; exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do - TestSpace.innerFunc = function(self) - end; + function TestSpace.innerFunc(self) + end end return exports;" `; @@ -462,9 +460,8 @@ exports[`Transformation (modulesNamespaceWithMemberNoExport) 1`] = ` exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do - local innerFunc; - innerFunc = function(self) - end; + local function innerFunc(self) + end end return exports;" `; @@ -480,9 +477,8 @@ exports[`Transformation (modulesVariableNoExport) 1`] = `"local foo = \\"bar\\"; exports[`Transformation (namespace) 1`] = ` "myNamespace = myNamespace or {}; do - local nsMember; - nsMember = function(self) - end; + local function nsMember(self) + end end" `; @@ -492,30 +488,30 @@ MergedClass.__index = MergedClass; MergedClass.prototype = MergedClass.prototype or {}; MergedClass.prototype.__index = MergedClass.prototype; MergedClass.prototype.constructor = MergedClass; -MergedClass.new = function(...) +function MergedClass.new(...) local self = setmetatable({}, MergedClass.prototype); self:____constructor(...); return self; -end; -MergedClass.prototype.____constructor = function(self) - self.propertyFunc = function(____) +end +function MergedClass.prototype.____constructor(self) + self.propertyFunc = function() end; -end; -MergedClass.staticMethodA = function(self) -end; -MergedClass.staticMethodB = function(self) +end +function MergedClass.staticMethodA(self) +end +function MergedClass.staticMethodB(self) self:staticMethodA(); -end; -MergedClass.prototype.methodA = function(self) -end; -MergedClass.prototype.methodB = function(self) +end +function MergedClass.prototype.methodA(self) +end +function MergedClass.prototype.methodB(self) self:methodA(); self:propertyFunc(); -end; +end MergedClass = MergedClass or {}; do - MergedClass.namespaceFunc = function(self) - end; + function MergedClass.namespaceFunc(self) + end end local mergedClass = MergedClass.new(); mergedClass:methodB(); @@ -530,29 +526,26 @@ do myNamespace.myNestedNamespace = myNamespace.myNestedNamespace or {}; local myNestedNamespace = myNamespace.myNestedNamespace; do - local nsMember; - nsMember = function(self) - end; + local function nsMember(self) + end end end" `; exports[`Transformation (namespacePhantom) 1`] = ` -"nsMember = function(self) -end;" +"function nsMember(self) +end" `; exports[`Transformation (returnDefault) 1`] = ` -"myFunc = function(self) +"function myFunc(self) return; -end;" +end" `; exports[`Transformation (shorthandPropertyAssignment) 1`] = ` "local f; -f = function(____, x) - return ({x = x}); -end;" +f = function(____, x) return ({x = x}); end;" `; exports[`Transformation (tryCatch) 1`] = ` @@ -560,7 +553,7 @@ exports[`Transformation (tryCatch) 1`] = ` local ____TS_try, er = pcall(function() local a = 42; end); - if not ____TS_try then + if not (____TS_try) then local b = \\"fail\\"; end end" @@ -571,7 +564,7 @@ exports[`Transformation (tryCatchFinally) 1`] = ` local ____TS_try, er = pcall(function() local a = 42; end); - if not ____TS_try then + if not (____TS_try) then local b = \\"fail\\"; end do @@ -592,9 +585,9 @@ end" `; exports[`Transformation (tupleReturn) 1`] = ` -"tupleReturn = function(self) +"function tupleReturn(self) return 0, \\"foobar\\"; -end; +end tupleReturn(_G); noTupleReturn(_G); local a, b = tupleReturn(_G); @@ -607,19 +600,22 @@ e = ({tupleReturn(_G)}); f = noTupleReturn(_G); foo(_G, ({tupleReturn(_G)})); foo(_G, noTupleReturn(_G)); -tupleReturnFromVar = function(self) - local r = {1, \\"baz\\"}; +function tupleReturnFromVar(self) + local r = { + 1, + \\"baz\\", + }; return table.unpack(r); -end; -tupleReturnForward = function(self) +end +function tupleReturnForward(self) return tupleReturn(_G); -end; -tupleNoForward = function(self) +end +function tupleNoForward(self) return ({tupleReturn(_G)}); -end; -tupleReturnUnpack = function(self) +end +function tupleReturnUnpack(self) return table.unpack(tupleNoForward(_G)); -end;" +end" `; exports[`Transformation (typeAssert) 1`] = ` @@ -630,9 +626,6 @@ local test2 = 10;" exports[`Transformation (while) 1`] = ` "local d = 10; while d > 0 do - do - d = d - 1; - end - ::__continue1:: + d = d - 1; end" `; diff --git a/test/unit/assignments/assignments.spec.ts b/test/unit/assignments/assignments.spec.ts index 4102b6d26..54f925d41 100644 --- a/test/unit/assignments/assignments.spec.ts +++ b/test/unit/assignments/assignments.spec.ts @@ -5,10 +5,10 @@ import * as util from "../../util"; test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Const assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`const myvar = ${inp};`); expect(lua).toBe(`local myvar = ${out};`); @@ -17,10 +17,10 @@ test.each([ test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Let assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`let myvar = ${inp};`); expect(lua).toBe(`local myvar = ${out};`); @@ -29,10 +29,10 @@ test.each([ test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Var assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`var myvar = ${inp};`); expect(lua).toBe(`myvar = ${out};`); diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index e1acbfa18..072accf9b 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -177,10 +177,11 @@ test.each(["a>>b", "a>>=b"])("Unsupported bitop 5.3 (%p)", input => { test.each([ { input: "1+1", lua: "1 + 1;" }, - { input: "-1+1", lua: "(-1) + 1;" }, - { input: "1*30+4", lua: "(1 * 30) + 4;" }, + { input: "-1+1", lua: "-1 + 1;" }, + { input: "1*30+4", lua: "1 * 30 + 4;" }, { input: "1*(3+4)", lua: "1 * (3 + 4);" }, - { input: "1*(3+4*2)", lua: "1 * (3 + (4 * 2));" }, + { input: "1*(3+4*2)", lua: "1 * (3 + 4 * 2);" }, + { input: "10-(4+5)", lua: "10 - (4 + 5);" }, ])("Binary expressions ordering parentheses (%p)", ({ input, lua }) => { expect(util.transpileString(input)).toBe(lua); }); diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index d87f019fd..1b78813d8 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -763,21 +763,23 @@ test("forof forwarded lua iterator with tupleReturn", () => { }); test.each([ - "while (a < b) { i++; }", - "do { i++; } while (a < b)", - "for (let i = 0; i < 3; i++) {}", - "for (let a in b) {}", - "for (let a of b) {}", -])("loop versions (%p)", loop => { - const lua51 = util.transpileString(loop, { luaTarget: LuaTarget.Lua51 }); - const lua52 = util.transpileString(loop, { luaTarget: LuaTarget.Lua52 }); - const lua53 = util.transpileString(loop, { luaTarget: LuaTarget.Lua53 }); - const luajit = util.transpileString(loop, { luaTarget: LuaTarget.LuaJIT }); - - expect(lua51.indexOf("::__continue1::") !== -1).toBe(false); // No labels in 5.1 - expect(lua52.indexOf("::__continue1::") !== -1).toBe(true); // Labels from 5.2 onwards - expect(lua53.indexOf("::__continue1::") !== -1).toBe(true); - expect(luajit.indexOf("::__continue1::") !== -1).toBe(true); + "while (a < b) { i++; continue; }", + "do { i++; continue; } while (a < b)", + "for (let i = 0; i < 3; i++) { continue; }", + "for (let a in b) { continue; }", + "for (let a of b) { continue; }", +])("loop continue in different lua versions (%p)", loop => { + const lua51 = { luaTarget: LuaTarget.Lua51 }; + const lua52 = { luaTarget: LuaTarget.Lua52 }; + const lua53 = { luaTarget: LuaTarget.Lua53 }; + const luajit = { luaTarget: LuaTarget.LuaJIT }; + + expect(() => util.transpileString(loop, lua51)).toThrowError( + TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined), + ); + expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); + expect(util.transpileString(loop, lua53).indexOf("::__continue1::") !== -1).toBe(true); + expect(util.transpileString(loop, luajit).indexOf("::__continue1::") !== -1).toBe(true); }); test("for dead code after return", () => { diff --git a/test/unit/objectLiteral.spec.ts b/test/unit/objectLiteral.spec.ts index 21ae4abf0..3cc8ad2c2 100644 --- a/test/unit/objectLiteral.spec.ts +++ b/test/unit/objectLiteral.spec.ts @@ -2,11 +2,11 @@ import * as util from "../util"; const fs = require("fs"); test.each([ - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{"a":3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{["a"]:3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{["a"+123]:3,b:"4"}`, out: `{["a" .. 123] = 3, b = "4"};` }, - { inp: `{[myFunc()]:3,b:"4"}`, out: `{[myFunc(_G)] = 3, b = "4"};` }, + { inp: `{a:3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{"a":3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{["a"]:3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{["a"+123]:3,b:"4"}`, out: '{\n ["a" .. 123] = 3,\n b = "4",\n};' }, + { inp: `{[myFunc()]:3,b:"4"}`, out: '{\n [myFunc(_G)] = 3,\n b = "4",\n};' }, { inp: `{x}`, out: `{x = x};` }, ])("Object Literal (%p)", ({ inp, out }) => { const lua = util.transpileString(`const myvar = ${inp};`); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index b6873787b..e8a374780 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -7,7 +7,7 @@ test.each([ typeScriptSource: ` const abc = "foo"; const def = "bar"; - + const xyz = "baz";`, assertPatterns: [ @@ -30,8 +30,8 @@ test.each([ return abc();`, assertPatterns: [ - { luaPattern: "abc = function(", typeScriptPattern: "abc() {" }, - { luaPattern: "def = function(", typeScriptPattern: "def() {" }, + { luaPattern: "function abc(", typeScriptPattern: "function abc() {" }, + { luaPattern: "function def(", typeScriptPattern: "function def() {" }, { luaPattern: "return abc(", typeScriptPattern: "return abc(" }, ], }, @@ -94,7 +94,7 @@ test("sourceMapTraceback saves sourcemap in _G", () => { expect(sourceMap[sourceMapFiles[0]]).toBeDefined(); const assertPatterns = [ - { luaPattern: "abc = function(", typeScriptPattern: "abc() {" }, + { luaPattern: "function abc(", typeScriptPattern: "function abc() {" }, { luaPattern: `return "foo"`, typeScriptPattern: `return "foo"` }, ]; diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 3bb7ff981..42559ec2a 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -15,23 +15,23 @@ test("Spread Element Lua 5.1", () => { // Cant test functional because our VM doesn't run on 5.1 const options = { luaTarget: LuaTarget.Lua51, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[].push(...${JSON.stringify([1, 2, 3])});`, options); - expect(lua).toBe("__TS__ArrayPush({}, unpack({1, 2, 3}));"); + expect(lua).toBe("__TS__ArrayPush({}, unpack({\n 1,\n 2,\n 3,\n}));"); }); test("Spread Element Lua 5.2", () => { const options = { luaTarget: LuaTarget.Lua52, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{table.unpack({0, 1, 2})};"); + expect(lua).toBe("{table.unpack({\n 0,\n 1,\n 2,\n})};"); }); test("Spread Element Lua 5.3", () => { const options = { luaTarget: LuaTarget.Lua53, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{table.unpack({0, 1, 2})};"); + expect(lua).toBe("{table.unpack({\n 0,\n 1,\n 2,\n})};"); }); test("Spread Element Lua JIT", () => { const options = { luaTarget: "JiT" as LuaTarget, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{unpack({0, 1, 2})};"); + expect(lua).toBe("{unpack({\n 0,\n 1,\n 2,\n})};"); });