diff --git a/dist/lualib/typescript.lua b/dist/lualib/typescript.lua index 9b8c8654d..177138e28 100644 --- a/dist/lualib/typescript.lua +++ b/dist/lualib/typescript.lua @@ -126,6 +126,16 @@ function TS_push(list, ...) end end +function TS_instanceof(obj, class) + while obj ~= nil do + if obj.__index == class then + return true + end + obj = obj.__base + end + return false +end + -- Set data structure implementation Set = Set or {} Set.__index = Set diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 24b307447..10bf07181 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -597,6 +597,7 @@ export abstract class LuaTranspiler { // Otherwise simply return the name return (node as ts.Identifier).text; case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: const text = (node as ts.StringLiteral).text; return `"${text}"`; case ts.SyntaxKind.TemplateExpression: @@ -639,6 +640,8 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.AsExpression: // Also ignore as casts return this.transpileExpression((node as ts.AsExpression).expression); + case ts.SyntaxKind.TypeOfExpression: + return this.transpileTypeOfExpression(node as ts.TypeOfExpression); default: throw new TranspileError( "Unsupported expression kind: " + tsHelper.enumName(node.kind, ts.SyntaxKind), @@ -755,6 +758,9 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.InKeyword: result = `${rhs}[${lhs}]~=nil`; break; + case ts.SyntaxKind.InstanceOfKeyword: + result = `TS_instanceof(${lhs}, ${rhs})`; + break; default: throw new TranspileError( "Unsupported binary operator kind: " + ts.tokenToString(node.operatorToken.kind), @@ -818,7 +824,7 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.MinusMinusToken: return `${operand}=${operand}-1`; case ts.SyntaxKind.ExclamationToken: - return `not ${operand}`; + return `(not ${operand})`; case ts.SyntaxKind.MinusToken: return `-${operand}`; default: @@ -1124,6 +1130,11 @@ export abstract class LuaTranspiler { } } + public transpileTypeOfExpression(node: ts.TypeOfExpression): string { + const expression = this.transpileExpression(node.expression); + return `(type(${expression}) == "table" and "object" or type(${expression}))`; + } + // Transpile a variable statement public transpileVariableStatement(node: ts.VariableStatement): string { let result = ""; @@ -1174,6 +1185,9 @@ export abstract class LuaTranspiler { } public transpileFunctionDeclaration(node: ts.FunctionDeclaration): string { + // Don't transpile functions without body (overload declarations) + if (!node.body) { return ""; } + let result = ""; const identifier = node.name; const methodName = identifier.escapedText; @@ -1221,6 +1235,9 @@ export abstract class LuaTranspiler { } public transpileMethodDeclaration(node: ts.MethodDeclaration, callPath: string): string { + // Don't transpile methods without body (overload declarations) + if (!node.body) { return ""; } + let result = ""; const identifier = node.name as ts.Identifier; const methodName = identifier.escapedText; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index e6a72eb17..c18799d4f 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -10,7 +10,7 @@ export class ExpressionTests { @TestCase("++i", "i=i+1") @TestCase("i--", "i=i-1") @TestCase("--i", "i=i-1") - @TestCase("!a", "not a") + @TestCase("!a", "(not a)") @TestCase("-a", "-a") @TestCase("delete tbl['test']", "tbl[\"test\"]=nil") @TestCase("delete tbl.test", "tbl.test=nil") @@ -19,15 +19,6 @@ export class ExpressionTests { Expect(util.transpileString(input)).toBe(lua); } - @TestCase("obj instanceof someClass", "Unsupported binary operator kind: instanceof") - @TestCase("typeof obj", "Unsupported expression kind: TypeOfExpression") - @Test("Prohibted Expressions") - public prohibtedExpressions(input: string, expectedError: string) { - Expect(() => { - util.transpileString(input); - }).toThrowError(Error, expectedError); - } - @TestCase("1+1", "1+1") @TestCase("1-1", "1-1") @TestCase("1*1", "1*1") diff --git a/test/unit/overloads.spec.ts b/test/unit/overloads.spec.ts new file mode 100644 index 000000000..69fa96cc1 --- /dev/null +++ b/test/unit/overloads.spec.ts @@ -0,0 +1,85 @@ +import { Expect, Test, TestCase } from "alsatian"; + +import * as util from "../src/util"; + +export class OverloadTests { + @Test("overload function1") + public overloadFunction1() { + const lua = util.transpileString( + `function abc(def: number): string; + function abc(def: string): string; + function abc(def: number | string): string { + if (typeof def == "number") { + return "jkl" + (def * 3); + } else { + return def; + } + } + return abc(3);`); + + const result = util.executeLua(lua); + + Expect(result).toBe("jkl9"); + } + + @Test("overload function2") + public overloadFunction2() { + const lua = util.transpileString( + `function abc(def: number): string; + function abc(def: string): string; + function abc(def: number | string): string { + if (typeof def == "number") { + return "jkl" + (def * 3); + } else { + return def; + } + } + return abc("ghj");`); + + const result = util.executeLua(lua); + + Expect(result).toBe("ghj"); + } + + @Test("overload method1") + public overloadMethod1() { + const lua = util.transpileString( + `class myclass { + static abc(def: number): string; + static abc(def: string): string; + static abc(def: number | string): string { + if (typeof def == "number") { + return "jkl" + (def * 3); + } else { + return def; + } + } + } + return myclass.abc(3);`); + + const result = util.executeLua(lua); + + Expect(result).toBe("jkl9"); + } + + @Test("overload method2") + public overloadMethod2() { + const lua = util.transpileString( + `class myclass { + static abc(def: number): string; + static abc(def: string): string; + static abc(def: number | string): string { + if (typeof def == "number") { + return "jkl" + (def * 3); + } else { + return def; + } + } + } + return myclass.abc("ghj");`); + + const result = util.executeLua(lua); + + Expect(result).toBe("ghj"); + } +} diff --git a/test/unit/typechecking.spec.ts b/test/unit/typechecking.spec.ts new file mode 100644 index 000000000..a31b899eb --- /dev/null +++ b/test/unit/typechecking.spec.ts @@ -0,0 +1,117 @@ +import { Expect, Test, TestCase } from "alsatian"; + +import * as util from "../src/util"; + +export class OverloadTests { + @TestCase("0") + @TestCase("30") + @TestCase("30_000") + @TestCase("30.00") + @Test("typeof number") + public typeofNumberTest(inp: string) { + const lua = util.transpileString(`return typeof ${inp};`); + const result = util.executeLua(lua); + + Expect(result).toBe("number"); + } + + @TestCase("\"abc\"") + @TestCase("`abc`") + @Test("typeof string") + public typeofStringTest(inp: string) { + const lua = util.transpileString(`return typeof ${inp};`); + const result = util.executeLua(lua); + + Expect(result).toBe("string"); + } + + @TestCase("false") + @TestCase("true") + @Test("typeof boolean") + public typeofBooleanTest(inp: string) { + const lua = util.transpileString(`return typeof ${inp};`); + const result = util.executeLua(lua); + + Expect(result).toBe("boolean"); + } + + @TestCase("{}") + @TestCase("[]") + @Test("typeof object literal") + public typeofObjectLiteral(inp: string) { + const lua = util.transpileString(`return typeof ${inp};`); + const result = util.executeLua(lua); + + Expect(result).toBe("object"); + } + + @Test("typeof class instance") + public typeofClassInstance() { + const lua = util.transpileString(`class myClass {} let inst = new myClass(); return typeof inst;`); + const result = util.executeLua(lua); + + Expect(result).toBe("object"); + } + + @Test("typeof function") + public typeofFunction() { + const lua = util.transpileString(`return typeof (() => 3);`); + const result = util.executeLua(lua); + + Expect(result).toBe("function"); + } + + @TestCase("null") + @TestCase("undefined") + @Test("typeof undefined") + public typeofUndefinedTest(inp: string) { + const lua = util.transpileString(`return typeof ${inp};`); + const result = util.executeLua(lua); + + Expect(result).toBe("nil"); + } + + @Test("instanceof") + public instanceOf() { + const lua = util.transpileString("class myClass {} let inst = new myClass(); return inst instanceof myClass;"); + const result = util.executeLua(lua); + + Expect(result).toBeTruthy(); + } + + @Test("instanceof inheritance") + public instanceOfInheritance() { + const lua = util.transpileString("class myClass {}\n" + + "class childClass extends myClass{}\n" + + "let inst = new childClass(); return inst instanceof myClass;"); + const result = util.executeLua(lua); + + Expect(result).toBeTruthy(); + } + + @Test("instanceof inheritance false") + public instanceOfInheritanceFalse() { + const lua = util.transpileString("class myClass {}\n" + + "class childClass extends myClass{}\n" + + "let inst = new myClass(); return inst instanceof childClass;"); + const result = util.executeLua(lua); + + Expect(result).toBe(false); + } + + @Test("null instanceof Object") + public nullInstanceOf() { + const lua = util.transpileString("return null instanceof Object;"); + const result = util.executeLua(lua); + + Expect(result).toBe(false); + } + + @Test("null instanceof Class") + public nullInstanceOfClass() { + const lua = util.transpileString("class myClass {} return null instanceof myClass;"); + const result = util.executeLua(lua); + + Expect(result).toBe(false); + } +}