diff --git a/src/TSHelper.ts b/src/TSHelper.ts index e4bf76e3b..b817b59bc 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -27,6 +27,20 @@ export class TSHelper { return statements.some(statement => statement.kind === kind); } + public static getExtendedType(node: ts.ClassDeclaration, checker: ts.TypeChecker): ts.Type | undefined { + if (node.heritageClauses) { + for (const clause of node.heritageClauses) { + if (clause.token === ts.SyntaxKind.ExtendsKeyword) { + const superType = checker.getTypeAtLocation(clause.types[0]); + if (!this.isPureAbstractClass(superType, checker)) { + return superType; + } + } + } + } + return undefined; + } + public static isFileModule(sourceFile: ts.SourceFile) { if (sourceFile) { // Vanilla ts flags files as external module if they have an import or diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 395f1bcc3..05a35b411 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -37,6 +37,7 @@ export abstract class LuaTranspiler { public isModule: boolean; public sourceFile: ts.SourceFile; public loopStack: number[]; + public classStack: string[]; constructor(checker: ts.TypeChecker, options: ts.CompilerOptions, sourceFile: ts.SourceFile) { this.indent = ""; @@ -49,6 +50,7 @@ export abstract class LuaTranspiler { this.sourceFile = sourceFile; this.isModule = tsHelper.isFileModule(sourceFile); this.loopStack = []; + this.classStack = []; } public pushIndent(): void { @@ -131,7 +133,10 @@ export abstract class LuaTranspiler { // Shadow exports if it already exists result += "local exports = exports or {}\n"; } - result += this.transpileBlock(this.sourceFile); + + // Transpile content statements + this.sourceFile.statements.forEach(s => result += this.transpileNode(s)); + if (this.isModule) { result += "return exports\n"; } @@ -139,20 +144,8 @@ export abstract class LuaTranspiler { } // Transpile a block - public transpileBlock(node: ts.Node): string { - let result = ""; - - if (ts.isBlock(node)) { - node.statements.forEach(statement => { - result += this.transpileNode(statement); - }); - } else { - node.forEachChild(child => { - result += this.transpileNode(child); - }); - } - - return result; + public transpileBlock(block: ts.Block): string { + return block.statements.map(statement => this.transpileNode(statement)).join(""); } // Transpile a node of unknown kind. @@ -857,10 +850,9 @@ export abstract class LuaTranspiler { // Handle super calls properly if (node.expression.kind === ts.SyntaxKind.SuperKeyword) { - callPath = this.transpileExpression(node.expression); - params = - this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression); - return `self.__base.constructor(${params})`; + params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression); + const className = this.classStack[this.classStack.length - 1]; + return `${className}.__base.constructor(${params})`; } callPath = this.transpileExpression(node.expression); @@ -1288,60 +1280,17 @@ export abstract class LuaTranspiler { // Transpile a class declaration public transpileClass(node: ts.ClassDeclaration): string { - // Find extends class, ignore implements - let extendsType: ts.ExpressionWithTypeArguments | undefined; - let noClassOr = false; - if (node.heritageClauses) { node.heritageClauses.forEach(clause => { - if (clause.token === ts.SyntaxKind.ExtendsKeyword) { - const superType = this.checker.getTypeAtLocation(clause.types[0]); - // Ignore purely abstract types (decorated with /** @PureAbstract */) - if (!tsHelper.isPureAbstractClass(superType, this.checker)) { - extendsType = clause.types[0]; - } - noClassOr = tsHelper.hasCustomDecorator(superType, this.checker, "!NoClassOr"); - } - }); - } - if (!node.name) { - throw new TranspileError("Unexpected Error: Node has no Name", node); + throw new TranspileError("Class declaration has no name.", node); } let className = node.name.escapedText as string; - let result = ""; - // Skip header if this is an extension class + // Find out if this class is extension of exising class const isExtension = tsHelper.isExtensionClass(this.checker.getTypeAtLocation(node), this.checker); - if (!isExtension) { - // Write class declaration - const classOr = noClassOr ? "" : `${className} or `; - if (!extendsType) { - result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}{}\n`; - result += this.makeExport(className, node); - } else { - const baseName = (extendsType.expression as ts.Identifier).escapedText; - result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}${baseName}.new()\n`; - result += this.makeExport(className, node); - } - result += this.indent + `${className}.__index = ${className}\n`; - if (extendsType) { - const baseName = (extendsType.expression as ts.Identifier).escapedText; - result += this.indent + `${className}.__base = ${baseName}\n`; - } - result += this.indent + `function ${className}.new(construct, ...)\n`; - result += this.indent + ` local instance = setmetatable({}, ${className})\n`; - result += this.indent + ` if construct and ${className}.constructor then ` - + `${className}.constructor(instance, ...) end\n`; - result += this.indent + ` return instance\n`; - result += this.indent + `end\n`; - } else { - // export empty table - result += this.makeExport(className, node, true); - // Overwrite the original className with the class we are overriding for extensions - if (extendsType) { - className = (extendsType.expression as ts.Identifier).escapedText as string; - } - } + + // Get type that is extended + const extendsType = tsHelper.getExtendedType(node, this.checker); // Get all properties with value const properties = node.members.filter(ts.isPropertyDeclaration) @@ -1352,6 +1301,20 @@ export abstract class LuaTranspiler { const staticFields = properties.filter(isStatic); const instanceFields = properties.filter(prop => !isStatic(prop)); + let result = ""; + + if (!isExtension) { + result += this.transpileClassCreationMethods(node, instanceFields, extendsType); + } else { + // export empty table + result += this.makeExport(className, node, true); + } + + // Overwrite the original className with the class we are overriding for extensions + if (isExtension && extendsType) { + className = extendsType.symbol.escapedName as string; + } + // Add static declarations for (const field of staticFields) { const fieldName = (field.name as ts.Identifier).escapedText; @@ -1363,14 +1326,11 @@ export abstract class LuaTranspiler { const constructor = node.members.filter(ts.isConstructorDeclaration)[0]; if (constructor) { // Add constructor plus initialisation of instance fields - result += this.transpileConstructor(constructor, className, instanceFields); + result += this.transpileConstructor(constructor, className); } else if (!isExtension) { // Generate a constructor if none was defined - result += this.transpileConstructor( - ts.createConstructor([], [], [], ts.createBlock([], true)), - className, - instanceFields - ); + result += this.transpileConstructor(ts.createConstructor([], [], [], ts.createBlock([], true)), + className); } // Transpile get accessors @@ -1391,6 +1351,50 @@ export abstract class LuaTranspiler { return result; } + public transpileClassCreationMethods(node: ts.ClassDeclaration, instanceFields: ts.PropertyDeclaration[], + extendsType: ts.Type): string { + const className = node.name.escapedText as string; + + const noClassOr = extendsType && tsHelper.hasCustomDecorator(extendsType, this.checker, "!NoClassOr"); + + let result = ""; + + // Write class declaration + const classOr = noClassOr ? "" : `${className} or `; + if (!extendsType) { + result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}{}\n`; + result += this.makeExport(className, node); + } else { + const baseName = extendsType.symbol.escapedName; + result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}${baseName}.new()\n`; + result += this.makeExport(className, node); + } + result += this.indent + `${className}.__index = ${className}\n`; + if (extendsType) { + const baseName = extendsType.symbol.escapedName; + result += this.indent + `${className}.__base = ${baseName}\n`; + } + result += this.indent + `function ${className}.new(construct, ...)\n`; + result += this.indent + ` local instance = setmetatable({}, ${className})\n`; + result += this.indent + ` if construct and ${className}.constructor then ` + + `${className}.constructor(instance, ...) end\n`; + + for (const f of instanceFields) { + // Get identifier + const fieldIdentifier = f.name as ts.Identifier; + const fieldName = fieldIdentifier.escapedText; + + const value = this.transpileExpression(f.initializer); + + result += this.indent + ` instance.${fieldName} = ${value}\n`; + } + + result += this.indent + ` return instance\n`; + result += this.indent + `end\n`; + + return result; + } + public transpileGetAccessorDeclaration(getAccessor: ts.GetAccessorDeclaration, className: string): string { const name = (getAccessor.name as ts.Identifier).escapedText; @@ -1425,8 +1429,7 @@ export abstract class LuaTranspiler { } public transpileConstructor(node: ts.ConstructorDeclaration, - className: string, - instanceFields: ts.PropertyDeclaration[]): string { + className: string): string { const extraInstanceFields = []; const parameters = ["self"]; @@ -1446,19 +1449,11 @@ export abstract class LuaTranspiler { result += this.indent + ` self.${f} = ${f}\n`; } - for (const f of instanceFields) { - // Get identifier - const fieldIdentifier = f.name as ts.Identifier; - const fieldName = fieldIdentifier.escapedText; - - const value = this.transpileExpression(f.initializer); - - result += this.indent + ` self.${fieldName} = ${value}\n`; - } - // Transpile constructor body this.pushIndent(); + this.classStack.push(className); result += this.transpileBlock(node.body); + this.classStack.pop(); this.popIndent(); return result + this.indent + "end\n"; @@ -1512,7 +1507,7 @@ export abstract class LuaTranspiler { let result = `function(${paramNames.join(",")})\n`; this.pushIndent(); result += this.transpileParameterDefaultValues(defaultValueParams); - result += this.transpileBlock(node.body); + result += this.transpileBlock(node.body as ts.Block); this.popIndent(); return result + this.indent + "end\n"; } else { diff --git a/test/translation/lua/class.lua b/test/translation/lua/class.lua deleted file mode 100644 index c30af0b28..000000000 --- a/test/translation/lua/class.lua +++ /dev/null @@ -1,10 +0,0 @@ -ClassB = ClassB or ClassA.new() -ClassB.__index = ClassB -ClassB.__base = ClassA -function ClassB.new(construct, ...) - local instance = setmetatable({}, ClassB) - if construct and ClassB.constructor then ClassB.constructor(instance, ...) end - return instance -end -function ClassB.constructor(self) -end diff --git a/test/translation/lua/classConstructorAssignment.lua b/test/translation/lua/classConstructorAssignment.lua deleted file mode 100644 index 1aad52a2f..000000000 --- a/test/translation/lua/classConstructorAssignment.lua +++ /dev/null @@ -1,10 +0,0 @@ -Test = Test or {} -Test.__index = Test -function Test.new(construct, ...) - local instance = setmetatable({}, Test) - if construct and Test.constructor then Test.constructor(instance, ...) end - return instance -end -function Test.constructor(self,field) - self.field = field -end diff --git a/test/translation/lua/classDefaultConstructor.lua b/test/translation/lua/classDefaultConstructor.lua deleted file mode 100644 index 5bcfbd05a..000000000 --- a/test/translation/lua/classDefaultConstructor.lua +++ /dev/null @@ -1,9 +0,0 @@ -MyClass = MyClass or {} -MyClass.__index = MyClass -function MyClass.new(construct, ...) - local instance = setmetatable({}, MyClass) - if construct and MyClass.constructor then MyClass.constructor(instance, ...) end - return instance -end -function MyClass.constructor(self) -end diff --git a/test/translation/lua/classEmptyConstructor.lua b/test/translation/lua/classEmptyConstructor.lua deleted file mode 100644 index b7d9462a7..000000000 --- a/test/translation/lua/classEmptyConstructor.lua +++ /dev/null @@ -1,10 +0,0 @@ -Test = Test or {} -Test.__index = Test -function Test.new(construct, ...) - local instance = setmetatable({}, Test) - if construct and Test.constructor then Test.constructor(instance, ...) end - return instance -end -function Test.constructor(self) -end -local t = Test.new(true) diff --git a/test/translation/lua/classEmptyNew.lua b/test/translation/lua/classEmptyNew.lua deleted file mode 100644 index b7d9462a7..000000000 --- a/test/translation/lua/classEmptyNew.lua +++ /dev/null @@ -1,10 +0,0 @@ -Test = Test or {} -Test.__index = Test -function Test.new(construct, ...) - local instance = setmetatable({}, Test) - if construct and Test.constructor then Test.constructor(instance, ...) end - return instance -end -function Test.constructor(self) -end -local t = Test.new(true) diff --git a/test/translation/lua/classInstanceCall1.lua b/test/translation/lua/classInstanceCall1.lua deleted file mode 100644 index bee2e3a16..000000000 --- a/test/translation/lua/classInstanceCall1.lua +++ /dev/null @@ -1 +0,0 @@ -local x = ClassB.new(true):myFunc() diff --git a/test/translation/lua/classInstanceCall2.lua b/test/translation/lua/classInstanceCall2.lua deleted file mode 100644 index bee2e3a16..000000000 --- a/test/translation/lua/classInstanceCall2.lua +++ /dev/null @@ -1 +0,0 @@ -local x = ClassB.new(true):myFunc() diff --git a/test/translation/lua/classInstanceCall3.lua b/test/translation/lua/classInstanceCall3.lua deleted file mode 100644 index bee2e3a16..000000000 --- a/test/translation/lua/classInstanceCall3.lua +++ /dev/null @@ -1 +0,0 @@ -local x = ClassB.new(true):myFunc() diff --git a/test/translation/lua/classInstanceCall4.lua b/test/translation/lua/classInstanceCall4.lua deleted file mode 100644 index 23b540291..000000000 --- a/test/translation/lua/classInstanceCall4.lua +++ /dev/null @@ -1,11 +0,0 @@ -ClassB = ClassB or ClassC.new() -ClassB.__index = ClassB -ClassB.__base = ClassC -function ClassB.new(construct, ...) - local instance = setmetatable({}, ClassB) - if construct and ClassB.constructor then ClassB.constructor(instance, ...) end - return instance -end -function ClassB.constructor(self) -end -local x = ClassB.new(true):myFunc() \ No newline at end of file diff --git a/test/translation/lua/classMethodDefaultParameters.lua b/test/translation/lua/classMethodDefaultParameters.lua deleted file mode 100644 index 0b9768a31..000000000 --- a/test/translation/lua/classMethodDefaultParameters.lua +++ /dev/null @@ -1,14 +0,0 @@ -MyClass = MyClass or {} -MyClass.__index = MyClass -function MyClass.new(construct, ...) - local instance = setmetatable({}, MyClass) - if construct and MyClass.constructor then MyClass.constructor(instance, ...) end - return instance -end -function MyClass.constructor(self) -end -function MyClass.MyMethod(self,a,b) - if a==nil then a=3 end - if b==nil then b=5 end - return a+b -end diff --git a/test/translation/lua/classRegularConstructor.lua b/test/translation/lua/classRegularConstructor.lua deleted file mode 100644 index 5d99aadd3..000000000 --- a/test/translation/lua/classRegularConstructor.lua +++ /dev/null @@ -1,10 +0,0 @@ -Test = Test or {} -Test.__index = Test -function Test.new(construct, ...) - local instance = setmetatable({}, Test) - if construct and Test.constructor then Test.constructor(instance, ...) end - return instance -end -function Test.constructor(self,test,testNum) -end -local t = Test.new(true,"test",12) diff --git a/test/translation/lua/classStaticMembers.lua b/test/translation/lua/classStaticMembers.lua deleted file mode 100644 index d27e34d5b..000000000 --- a/test/translation/lua/classStaticMembers.lua +++ /dev/null @@ -1,10 +0,0 @@ -MyClass = MyClass or {} -MyClass.__index = MyClass -function MyClass.new(construct, ...) - local instance = setmetatable({}, MyClass) - if construct and MyClass.constructor then MyClass.constructor(instance, ...) end - return instance -end -MyClass.test = 0 -function MyClass.constructor(self) -end diff --git a/test/translation/lua/classSuperCall.lua b/test/translation/lua/classSuperCall.lua deleted file mode 100644 index 33d9ccd14..000000000 --- a/test/translation/lua/classSuperCall.lua +++ /dev/null @@ -1,11 +0,0 @@ -ClassB = ClassB or ClassA.new() -ClassB.__index = ClassB -ClassB.__base = ClassA -function ClassB.new(construct, ...) - local instance = setmetatable({}, ClassB) - if construct and ClassB.constructor then ClassB.constructor(instance, ...) end - return instance -end -function ClassB.constructor(self) - self.__base.constructor(self) -end diff --git a/test/translation/ts/class.ts b/test/translation/ts/class.ts deleted file mode 100644 index ffc144465..000000000 --- a/test/translation/ts/class.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare class ClassA {} -class ClassB extends ClassA {} \ No newline at end of file diff --git a/test/translation/ts/classConstructorAssignment.ts b/test/translation/ts/classConstructorAssignment.ts deleted file mode 100644 index 45bbd58cf..000000000 --- a/test/translation/ts/classConstructorAssignment.ts +++ /dev/null @@ -1,3 +0,0 @@ -class Test { - constructor(private field: number) {} -} diff --git a/test/translation/ts/classDefaultConstructor.ts b/test/translation/ts/classDefaultConstructor.ts deleted file mode 100644 index 36421bf8a..000000000 --- a/test/translation/ts/classDefaultConstructor.ts +++ /dev/null @@ -1 +0,0 @@ -class MyClass {} \ No newline at end of file diff --git a/test/translation/ts/classEmptyConstructor.ts b/test/translation/ts/classEmptyConstructor.ts deleted file mode 100644 index a01582838..000000000 --- a/test/translation/ts/classEmptyConstructor.ts +++ /dev/null @@ -1,7 +0,0 @@ -class Test { - constructor () { - - } -} - -let t = new Test(); diff --git a/test/translation/ts/classEmptyNew.ts b/test/translation/ts/classEmptyNew.ts deleted file mode 100644 index ffb7b1d59..000000000 --- a/test/translation/ts/classEmptyNew.ts +++ /dev/null @@ -1,7 +0,0 @@ -class Test { - constructor () { - - } -} - -let t = new Test; diff --git a/test/translation/ts/classExtension2.ts b/test/translation/ts/classExtension2.ts index f3c6f31bd..1b9d54732 100644 --- a/test/translation/ts/classExtension2.ts +++ b/test/translation/ts/classExtension2.ts @@ -2,7 +2,6 @@ class TestClass { } - /** !Extension */ class MyClass extends TestClass { myFunction() {} diff --git a/test/translation/ts/classInstanceCall1.ts b/test/translation/ts/classInstanceCall1.ts deleted file mode 100644 index e9e561b34..000000000 --- a/test/translation/ts/classInstanceCall1.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare class ClassA { - myFunc(); -} -declare class ClassB extends ClassA {} - -let x = new ClassB().myFunc(); diff --git a/test/translation/ts/classInstanceCall2.ts b/test/translation/ts/classInstanceCall2.ts deleted file mode 100644 index 835608831..000000000 --- a/test/translation/ts/classInstanceCall2.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare class ClassA { - myFunc(); -} -declare class ClassC extends ClassA {} -declare class ClassB extends ClassC {} - -let x = new ClassB().myFunc(); diff --git a/test/translation/ts/classInstanceCall3.ts b/test/translation/ts/classInstanceCall3.ts deleted file mode 100644 index cac94df9b..000000000 --- a/test/translation/ts/classInstanceCall3.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare class ClassC {} -declare class ClassA extends ClassC { - myFunc(); -} -declare class ClassB extends ClassA {} - -let x = new ClassB().myFunc(); diff --git a/test/translation/ts/classInstanceCall4.ts b/test/translation/ts/classInstanceCall4.ts deleted file mode 100644 index e6d8714be..000000000 --- a/test/translation/ts/classInstanceCall4.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare class ClassA { - myFunc(); -} -declare class ClassC extends ClassA {} -class ClassB extends ClassC {} - -let x = new ClassB().myFunc(); diff --git a/test/translation/ts/classMethodDefaultParameters.ts b/test/translation/ts/classMethodDefaultParameters.ts deleted file mode 100644 index fc157750f..000000000 --- a/test/translation/ts/classMethodDefaultParameters.ts +++ /dev/null @@ -1,5 +0,0 @@ -class MyClass { - public MyMethod(a: number = 3, b: number = 5) { - return a + b; - } -} diff --git a/test/translation/ts/classRegularConstructor.ts b/test/translation/ts/classRegularConstructor.ts deleted file mode 100644 index 0521ade73..000000000 --- a/test/translation/ts/classRegularConstructor.ts +++ /dev/null @@ -1,7 +0,0 @@ -class Test { - constructor (test: string, testNum: number) { - - } -} - -let t = new Test("test", 12); diff --git a/test/translation/ts/classStaticMembers.ts b/test/translation/ts/classStaticMembers.ts deleted file mode 100644 index 2fb89cfeb..000000000 --- a/test/translation/ts/classStaticMembers.ts +++ /dev/null @@ -1,3 +0,0 @@ -class MyClass { - public static test = 0; -} diff --git a/test/translation/ts/classSuperCall.ts b/test/translation/ts/classSuperCall.ts deleted file mode 100644 index 1cd159744..000000000 --- a/test/translation/ts/classSuperCall.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare class ClassA {} -class ClassB extends ClassA { - public constructor() { - super(); - } -} diff --git a/test/unit/class.spec.ts b/test/unit/class.spec.ts new file mode 100644 index 000000000..c2a88a4c5 --- /dev/null +++ b/test/unit/class.spec.ts @@ -0,0 +1,260 @@ +import { Expect, Test, TestCase } from "alsatian"; + +import * as ts from "typescript"; +import * as util from "../src/util"; + +export class ClassTests { + + @Test("ClassConstructor") + public classConstructor() { + // Transpile + const lua = util.transpileString( + `class a { + field: number; + constructor(n: number) { + this.field = n * 2; + } + } + return new a(4).field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(8); + } + + @Test("ClassConstructorAssignment") + public classConstructorAssignment() { + // Transpile + const lua = util.transpileString( + `class a { constructor(public field: number) {} } + return new a(4).field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("ClassNewNoBrackets") + public classNewNoBrackets() { + // Transpile + const lua = util.transpileString( + `class a { + public field: number = 4; + constructor() {} + } + let inst = new a; + return inst.field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("ClassStaticFields") + public classStaticFields() { + // Transpile + const lua = util.transpileString( + `class a { static field: number = 4; } + return a.field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("classExtends") + public classExtends() { + // Transpile + const lua = util.transpileString( + `class a { field: number = 4; } + class b extends a {} + return new b().field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("classSuper") + public classSuper() { + // Transpile + const lua = util.transpileString( + `class a { + public field: number = 4; + constructor(n: number) { + this.field = n; + } + } + class b extends a { + constructor() { + super(5); + } + } + return new b().field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(5); + } + + @Test("classSuperSuper") + public classSuperSuper() { + // Transpile + const lua = util.transpileString( + `class a { + public field: number = 4; + constructor(n: number) { + this.field = n; + } + } + class b extends a { + constructor(n: number) { + super(n * 2); + } + } + class c extends b { + constructor() { + super(5); + } + } + return new c().field;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(10); + } + + @Test("ClassMethodCall") + public classMethodCall() { + // Transpile + const lua = util.transpileString( + `class a { + public method(): number { + return 4; + } + } + let inst = new a(); + return inst.method();` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("ClassInheritedMethodCall") + public classInheritedMethodCall() { + // Transpile + const lua = util.transpileString( + `class a { + public method(): number { + return 4; + } + } + class b extends a {} + let inst = new b(); + return inst.method();` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("ClassDoubleInheritedMethodCall") + public classDoubleInheritedMethodCall() { + // Transpile + const lua = util.transpileString( + `class a { + public method(): number { + return 4; + } + } + class b extends a {} + class c extends b {} + let inst = new c(); + return inst.method();` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("ClassInheritedMethodCall2") + public classInheritedMethodCall2() { + // Transpile + const lua = util.transpileString( + `class a {} + class b extends a { + public method(): number { + return 4; + } + } + class c extends b {} + let inst = new c(); + return inst.method();` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(4); + } + + @Test("methodDefaultParameters") + public methodInheritedParameters() { + // Transpile + const lua = util.transpileString( + `class a { + public method(b: number, c: number = 5): number { + return b + c; + } + } + let inst = new a(); + return inst.method(4);` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(9); + } + + @Test("Class without name error") + public classWithoutNameError() { + const transpiler = util.makeTestTranspiler(); + + Expect(() => transpiler.transpileClass({} as ts.ClassDeclaration)) + .toThrowError(Error, "Class declaration has no name."); + } +} diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 024b40369..aee93c302 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -520,14 +520,6 @@ export class ExpressionTests { .toThrowError(Error, "Unsupported variable declaration type: FalseKeyword"); } - @Test("Class without name error") - public classWithoutNameError() { - const transpiler = util.makeTestTranspiler(); - - Expect(() => transpiler.transpileClass({} as ts.ClassDeclaration)) - .toThrowError(Error, "Unexpected Error: Node has no Name"); - } - @Test("Unsupported object literal element error") public unsupportedObjectLiteralElementError() { const transpiler = util.makeTestTranspiler();