Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions dist/lualib/typescript.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = "";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 1 addition & 10 deletions test/unit/expressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
85 changes: 85 additions & 0 deletions test/unit/overloads.spec.ts
Original file line number Diff line number Diff line change
@@ -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");
}
}
117 changes: 117 additions & 0 deletions test/unit/typechecking.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}