From 132cf7d4319e849533181934cf012d149288d5f5 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 17 Jan 2020 21:09:01 +0100 Subject: [PATCH 01/12] Updated tests to new TestBuilder --- .../invalidFunctionAssignments.spec.ts | 145 +++++++++--------- 1 file changed, 71 insertions(+), 74 deletions(-) diff --git a/test/unit/functions/validation/invalidFunctionAssignments.spec.ts b/test/unit/functions/validation/invalidFunctionAssignments.spec.ts index 4f2e385be..e1d7568fb 100644 --- a/test/unit/functions/validation/invalidFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/invalidFunctionAssignments.spec.ts @@ -9,183 +9,181 @@ import { invalidTestFunctionAssignments, invalidTestFunctionCasts } from "./func test.each(invalidTestFunctionAssignments)( "Invalid function variable declaration (%p)", (testFunction, functionType, isSelfConversion) => { - const code = ` - ${testFunction.definition || ""} - const fn: ${functionType} = ${testFunction.value}; - `; - const err = isSelfConversion + const expectedError = isSelfConversion ? UnsupportedSelfFunctionConversion(util.nodeStub) : UnsupportedNoSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + + util.testModule` + ${testFunction.definition || ""} + const fn: ${functionType} = ${testFunction.value}; + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionAssignments)( "Invalid function assignment (%p)", (testFunction, functionType, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub) + : UnsupportedNoSelfFunctionConversion(util.nodeStub); + + util.testModule` ${testFunction.definition || ""} let fn: ${functionType}; fn = ${testFunction.value}; - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub) - : UnsupportedNoSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionCasts)( "Invalid function assignment with cast (%p)", (testFunction, castedFunction, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub) + : UnsupportedNoSelfFunctionConversion(util.nodeStub); + + util.testModule` ${testFunction.definition || ""} let fn: typeof ${testFunction.value}; fn = ${castedFunction}; - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub) - : UnsupportedNoSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionAssignments)( "Invalid function argument (%p)", (testFunction, functionType, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub, "fn") + : UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn"); + + util.testModule` ${testFunction.definition || ""} declare function takesFunction(fn: ${functionType}); takesFunction(${testFunction.value}); - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub, "fn") - : UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn"); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test("Invalid lua lib function argument", () => { - const code = ` + util.testModule` declare function foo(this: void, value: string): void; declare const a: string[]; a.forEach(foo); - `; - const err = UnsupportedSelfFunctionConversion(util.nodeStub, "callbackfn"); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(UnsupportedSelfFunctionConversion(util.nodeStub, "callbackfn")); }); test.each(invalidTestFunctionCasts)( "Invalid function argument with cast (%p)", (testFunction, castedFunction, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedNoSelfFunctionConversion(util.nodeStub) + : UnsupportedSelfFunctionConversion(util.nodeStub); + + // TODO: Changed in #705 because of order change in `transformArguments`. + // After #412 both errors should be reported. + util.testModule` ${testFunction.definition || ""} declare function takesFunction(fn: typeof ${testFunction.value}); takesFunction(${castedFunction}); - `; - // TODO: Changed in #705 because of order change in `transformArguments`. - // After #412 both errors should be reported. - const err = isSelfConversion - ? UnsupportedNoSelfFunctionConversion(util.nodeStub) - : UnsupportedSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionAssignments)( "Invalid function generic argument (%p)", (testFunction, functionType, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub, "fn") + : UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn"); + + util.testModule` ${testFunction.definition || ""} declare function takesFunction(fn: T); takesFunction(${testFunction.value}); - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub, "fn") - : UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn"); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionAssignments)( "Invalid function return (%p)", (testFunction, functionType, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub) + : UnsupportedNoSelfFunctionConversion(util.nodeStub); + + util.testModule` ${testFunction.definition || ""} function returnsFunction(): ${functionType} { return ${testFunction.value}; } - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub) - : UnsupportedNoSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test.each(invalidTestFunctionCasts)( "Invalid function return with cast (%p)", (testFunction, castedFunction, isSelfConversion) => { - const code = ` + const expectedError = isSelfConversion + ? UnsupportedSelfFunctionConversion(util.nodeStub) + : UnsupportedNoSelfFunctionConversion(util.nodeStub); + + util.testModule` ${testFunction.definition || ""} function returnsFunction(): typeof ${testFunction.value} { return ${castedFunction}; } - `; - const err = isSelfConversion - ? UnsupportedSelfFunctionConversion(util.nodeStub) - : UnsupportedNoSelfFunctionConversion(util.nodeStub); - expect(() => util.transpileString(code, undefined, false)).toThrowExactError(err); + `.expectToHaveDiagnosticOfError(expectedError); } ); test("Interface method assignment", () => { - const code = ` + util.testFunction` class Foo { method(s: string): string { return s + "+method"; } lambdaProp: (s: string) => string = s => s + "+lambdaProp"; } + const foo: IFoo = new Foo(); + return foo.method("foo") + "|" + foo.lambdaProp("bar"); + ` + .setTsHeader( + ` interface IFoo { method: (s: string) => string; lambdaProp(s: string): string; } - const foo: IFoo = new Foo(); - return foo.method("foo") + "|" + foo.lambdaProp("bar"); - `; - const result = util.transpileAndExecute(code); - expect(result).toBe("foo+method|bar+lambdaProp"); + ` + ) + .expectToMatchJsResult(); }); test("Invalid function tuple assignment", () => { - const code = ` + util.testModule` interface Func { (this: void, s: string): string; } interface Meth { (this: {}, s: string): string; } declare function getTuple(): [number, Meth]; let [i, f]: [number, Func] = getTuple(); - `; - expect(() => util.transpileString(code)).toThrowExactError(UnsupportedNoSelfFunctionConversion(util.nodeStub)); + ` + .disableSemanticCheck() + .expectToHaveDiagnosticOfError(UnsupportedNoSelfFunctionConversion(util.nodeStub)); }); test("Invalid method tuple assignment", () => { - const code = ` + util.testModule` interface Func { (this: void, s: string): string; } interface Meth { (this: {}, s: string): string; } declare function getTuple(): [number, Func]; let [i, f]: [number, Meth] = getTuple(); - `; - expect(() => util.transpileString(code)).toThrowExactError(UnsupportedSelfFunctionConversion(util.nodeStub)); + `.expectToHaveDiagnosticOfError(UnsupportedSelfFunctionConversion(util.nodeStub)); }); test("Invalid interface method assignment", () => { - const code = ` + util.testModule` interface A { fn(s: string): string; } interface B { fn(this: void, s: string): string; } declare const a: A; const b: B = a; - `; - expect(() => util.transpileString(code)).toThrowExactError( - UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn") - ); + `.expectToHaveDiagnosticOfError(UnsupportedNoSelfFunctionConversion(util.nodeStub, "fn")); }); test.each([ @@ -194,13 +192,12 @@ test.each([ "{(this: void, s: string): string}", "{(this: any, s1: string, s2: string): string}", ])("Invalid function overload assignment (%p)", assignType => { - const code = ` + util.testModule` interface O { (this: any, s1: string, s2: string): string; (this: void, s: string): string; } declare const o: O; let f: ${assignType} = o; - `; - expect(() => util.transpileString(code)).toThrowExactError(UnsupportedOverloadAssignment(util.nodeStub)); + `.expectToHaveDiagnosticOfError(UnsupportedOverloadAssignment(util.nodeStub)); }); From 67e72a2a526771a5e700877698fbf0fc333f8410 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 17 Jan 2020 21:59:46 +0100 Subject: [PATCH 02/12] Updated classes/classes tests to new TestBuilder --- test/unit/classes/classes.spec.ts | 519 ++++++++++++------------------ 1 file changed, 212 insertions(+), 307 deletions(-) diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index 12751fd40..57c270b5b 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -1,89 +1,73 @@ import * as util from "../../util"; test("ClassFieldInitializer", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { field: number = 4; } - return new a().field;` - ); - - expect(result).toBe(4); + return new a().field; + `.expectToMatchJsResult(); }); test("ClassNumericLiteralFieldInitializer", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { 1: number = 4; } - return new a()[1];` - ); - - expect(result).toBe(4); + return new a()[1]; + `.expectToMatchJsResult(); }); test("ClassStringLiteralFieldInitializer", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { "field": number = 4; } - return new a()["field"];` - ); - - expect(result).toBe(4); + return new a()["field"]; + `.expectToMatchJsResult(); }); test("ClassComputedFieldInitializer", () => { - const result = util.transpileAndExecute( - `const field: "field" = "field"; + util.testFunction` + const field: "field" = "field"; class a { [field]: number = 4; } - return new a()[field];` - ); - - expect(result).toBe(4); + return new a()[field]; + `.expectToMatchJsResult(); }); test("ClassConstructor", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { field: number = 3; constructor(n: number) { this.field = n * 2; } } - return new a(4).field;` - ); - - expect(result).toBe(8); + return new a(4).field; + `.expectToMatchJsResult(); }); test("ClassConstructorAssignment", () => { - const result = util.transpileAndExecute( - `class a { constructor(public field: number) {} } - return new a(4).field;` - ); - - expect(result).toBe(4); + util.testFunction` + class a { constructor(public field: number) {} } + return new a(4).field; + `.expectToMatchJsResult(); }); test("ClassConstructorDefaultParameter", () => { - const result = util.transpileAndExecute( - `class a { public field: number; constructor(f: number = 3) { this.field = f; } } - return new a().field;` - ); - - expect(result).toBe(3); + util.testFunction` + class a { public field: number; constructor(f: number = 3) { this.field = f; } } + return new a().field; + `.expectToMatchJsResult(); }); test("ClassConstructorAssignmentDefault", () => { - const result = util.transpileAndExecute( - `class a { constructor(public field: number = 3) { } } - return new a().field;` - ); - - expect(result).toBe(3); + util.testFunction` + class a { constructor(public field: number = 3) { } } + return new a().field; + `.expectToMatchJsResult(); }); test("ClassConstructorPropertyInitiailizationOrder", () => { @@ -106,83 +90,69 @@ test("ClassConstructorPropertyInitiailizationFalsey", () => { }); test("ClassNewNoBrackets", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public field: number = 4; constructor() {} } let inst = new a; - return inst.field;` - ); - - expect(result).toBe(4); + return inst.field; + `.expectToMatchJsResult(); }); test("ClassStaticFields", () => { - const result = util.transpileAndExecute( - `class a { static field: number = 4; } - return a.field;` - ); - - expect(result).toBe(4); + util.testFunction` + class a { static field: number = 4; } + return a.field; + `.expectToMatchJsResult(); }); test("ClassStaticNumericLiteralFields", () => { - const result = util.transpileAndExecute( - `class a { static 1: number = 4; } - return a[1];` - ); - - expect(result).toBe(4); + util.testFunction` + class a { static 1: number = 4; } + return a[1]; + `.expectToMatchJsResult(); }); test("ClassStaticStringLiteralFields", () => { - const result = util.transpileAndExecute( - `class a { static "field": number = 4; } - return a["field"];` - ); - - expect(result).toBe(4); + util.testFunction` + class a { static "field": number = 4; } + return a["field"]; + `.expectToMatchJsResult(); }); test("ClassStaticComputedFields", () => { - const result = util.transpileAndExecute( - `const field: "field" = "field"; + util.testFunction` + const field: "field" = "field"; class a { static [field]: number = 4; } - return a[field];` - ); - - expect(result).toBe(4); + return a[field]; + `.expectToMatchJsResult(); }); test("classExtends", () => { - const result = util.transpileAndExecute( - `class a { field: number = 4; } + util.testFunction` + class a { field: number = 4; } class b extends a {} - return new b().field;` - ); - - expect(result).toBe(4); + return new b().field; + `.expectToMatchJsResult(); }); test("SubclassDefaultConstructor", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { field: number; constructor(field: number) { this.field = field; } } class b extends a {} - return new b(10).field;` - ); - - expect(result).toBe(10); + return new b(10).field; + `.expectToMatchJsResult(); }); test("SubsubclassDefaultConstructor", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { field: number; constructor(field: number) { this.field = field; @@ -190,15 +160,13 @@ test("SubsubclassDefaultConstructor", () => { } class b extends a {} class c extends b {} - return new c(10).field;` - ); - - expect(result).toBe(10); + return new c(10).field; + `.expectToMatchJsResult(); }); test("SubclassConstructor", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { field: number; constructor(field: number) { this.field = field; @@ -209,10 +177,8 @@ test("SubclassConstructor", () => { super(field + 1); } } - return new b(10).field;` - ); - - expect(result).toBe(11); + return new b(10).field; + `.expectToMatchJsResult(); }); test("Subclass constructor across merged namespace", () => { @@ -233,12 +199,14 @@ test("Subclass constructor across merged namespace", () => { } }`; - expect(util.transpileAndExecute("return (new NS.Sub()).prop", undefined, undefined, tsHeader)).toBe("foo"); + util.testExpression("(new NS.Sub()).prop") + .setTsHeader(tsHeader) + .expectToEqual("foo"); }); test("classSuper", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public field: number = 4; constructor(n: number) { this.field = n; @@ -249,15 +217,13 @@ test("classSuper", () => { super(5); } } - return new b().field;` - ); - - expect(result).toBe(5); + return new b().field; + `.expectToMatchJsResult(); }); test("classSuperSuper", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public field: number = 4; constructor(n: number) { this.field = n; @@ -273,15 +239,13 @@ test("classSuperSuper", () => { super(5); } } - return new c().field;` - ); - - expect(result).toBe(10); + return new c().field; + `.expectToMatchJsResult(); }); test("classSuperSkip", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public field: number = 4; constructor(n: number) { this.field = n; @@ -294,136 +258,119 @@ test("classSuperSkip", () => { super(5); } } - return new c().field;` - ); - - expect(result).toBe(5); + return new c().field; + `.expectToMatchJsResult(); }); test("renamedClassExtends", () => { - const result = util.transpileAndExecute( - `const b = new B(); - return b.value;`, - undefined, - undefined, - `namespace Classes { - export class Base { - public value: number; - constructor(){ this.value = 3; } + util.testFunction` + const b = new B(); + return b.value; + ` + .setTsHeader( + `namespace Classes { + export class Base { + public value: number; + constructor(){ this.value = 3; } + } } - } - - const A = Classes.Base; - class B extends A { - constructor(){ super(); } - };` - ); - expect(result).toBe(3); + const A = Classes.Base; + class B extends A { + constructor(){ super(); } + };` + ) + .expectToEqual(3); }); test("ClassMethodCall", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method(): number { return 4; } } let inst = new a(); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("ClassNumericLiteralMethodCall", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public 1(): number { return 4; } } let inst = new a(); - return inst[1]();` - ); - - expect(result).toBe(4); + return inst[1](); + `.expectToMatchJsResult(); }); test("ClassStringLiteralMethodCall", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public "method"(): number { return 4; } } let inst = new a(); - return inst["method"]();` - ); - - expect(result).toBe(4); + return inst["method"](); + `.expectToMatchJsResult(); }); test("ClassComputedMethodCall", () => { - const result = util.transpileAndExecute( - `const method: "method" = "method"; + util.testFunction` + const method: "method" = "method"; class a { public [method](): number { return 4; } } let inst = new a(); - return inst[method]();` - ); - - expect(result).toBe(4); + return inst[method](); + `.expectToMatchJsResult(); }); test("ClassToString", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public toString(): string { return "instance of a"; } } let inst = new a(); - return inst.toString();` - ); - - expect(result).toBe("instance of a"); + return inst.toString(); + `.expectToMatchJsResult(); }); test("HasOwnProperty true", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public test(): void { } } let inst = new a(); inst["prop"] = 17; - return inst.hasOwnProperty("prop");` - ); - - expect(result).toBe(true); + return inst.hasOwnProperty("prop"); + `.expectToMatchJsResult(); }); test("HasOwnProperty false", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public test(): void { } } let inst = new a(); inst["prop"] = 17; - return inst.hasOwnProperty("test");` - ); - - expect(result).toBe(false); + return inst.hasOwnProperty("test"); + `.expectToMatchJsResult(); }); test("CastClassMethodCall", () => { - const result = util.transpileAndExecute( - `interface result + util.testFunction` + interface result { val : number; } @@ -436,43 +383,37 @@ test("CastClassMethodCall", () => { let result = {val : 0}; (inst as a).method(result); (inst as a).method(result); - return result.val;` - ); - - expect(result).toBe(4); + return result.val; + `.expectToMatchJsResult(); }); test("ClassPropertyFunctionThis", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { constructor(private n: number) {} public method: () => number = () => this.n; } let inst = new a(4); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("ClassInheritedMethodCall", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method(): number { return 4; } } class b extends a {} let inst = new b(); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("ClassDoubleInheritedMethodCall", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method(): number { return 4; } @@ -480,15 +421,13 @@ test("ClassDoubleInheritedMethodCall", () => { class b extends a {} class c extends b {} let inst = new c(); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("ClassInheritedMethodCall2", () => { - const result = util.transpileAndExecute( - `class a {} + util.testFunction` + class a {} class b extends a { public method(): number { return 4; @@ -496,15 +435,13 @@ test("ClassInheritedMethodCall2", () => { } class c extends b {} let inst = new c(); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("ClassMethodOverride", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method(): number { return 2; } @@ -515,29 +452,25 @@ test("ClassMethodOverride", () => { } } let inst = new b(); - return inst.method();` - ); - - expect(result).toBe(4); + return inst.method(); + `.expectToMatchJsResult(); }); test("methodDefaultParameters", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method(b: number, c: number = 5): number { return b + c; } } let inst = new a(); - return inst.method(4);` - ); - - expect(result).toBe(9); + return inst.method(4); + `.expectToMatchJsResult(); }); test("CallSuperMethodNoArgs", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { a: number constructor(n: number) { this.a = n; @@ -555,15 +488,13 @@ test("CallSuperMethodNoArgs", () => { } } let inst = new b(6); - return inst.method();` - ); - - expect(result).toBe(6); + return inst.method(); + `.expectToMatchJsResult(); }); test("CallSuperMethodArgs", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { a: number constructor(n: number) { this.a = n; @@ -581,15 +512,13 @@ test("CallSuperMethodArgs", () => { } } let inst = new b(6); - return inst.method(4);` - ); - - expect(result).toBe(10); + return inst.method(4); + `.expectToMatchJsResult(); }); test("CallSuperExpressionMethod", () => { - const result = util.transpileAndExecute( - `let i = 0; + util.testFunction` + let i = 0; function make() { const j = i++; return class { @@ -605,15 +534,13 @@ test("CallSuperExpressionMethod", () => { inst.method(); inst.method(); inst.method(); - return i;` - ); - - expect(result).toBe(1); + return i; + `.expectToMatchJsResult(); }); test("CallSuperSuperMethod", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { a: number constructor(n: number) { this.a = n; @@ -639,15 +566,13 @@ test("CallSuperSuperMethod", () => { } } let inst = new c(6); - return inst.method();` - ); - - expect(result).toBe(6); + return inst.method(); + `.expectToMatchJsResult(); }); test("classExpression", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method() { return "instance of a"; } @@ -658,29 +583,25 @@ test("classExpression", () => { } } let inst = new b(); - return inst.method();` - ); - - expect(result).toBe("instance of b"); + return inst.method(); + `.expectToMatchJsResult(); }); test("Named Class Expression", () => { - const result = util.transpileAndExecute( - `const a = class MyClass { + util.testFunction` + const a = class MyClass { public method() { return "foo"; } } let inst = new a(); - return inst.method();` - ); - - expect(result).toBe("foo"); + return inst.method(); + `.expectToMatchJsResult(); }); test("classExpressionBaseClassMethod", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public method() { return 42; } @@ -688,15 +609,13 @@ test("classExpressionBaseClassMethod", () => { const b = class extends a { } let inst = new b(); - return inst.method();` - ); - - expect(result).toBe(42); + return inst.method(); + `.expectToMatchJsResult(); }); test("Class Method Runtime Override", () => { - const result = util.transpileAndExecute( - `class MyClass { + util.testFunction` + class MyClass { method(): number { return 4; } @@ -706,14 +625,12 @@ test("Class Method Runtime Override", () => { inst.method = () => { return 8; } - return inst.method();` - ); - - expect(result).toBe(8); + return inst.method(); + `.expectToMatchJsResult(); }); test("Exported class super call", () => { - const code = ` + util.testModule` export class Foo { prop: string; constructor(prop: string) { this.prop = prop; } @@ -724,100 +641,88 @@ test("Exported class super call", () => { } } export const baz = (new Bar()).prop; - `; - expect(util.transpileExecuteAndReturnExport(code, "baz")).toBe("bar"); + `.debug().expectToMatchJsResult(); }); -test.each([ - { input: "(new Foo())", expectResult: "foo" }, - { input: "Foo", expectResult: "bar" }, -])("Class method name collision (%p)", ({ input, expectResult }) => { - const code = ` +test.each(["(new Foo())", "Foo"])("Class method name collision (%p)", input => { + util.testFunction` class Foo { public method() { return "foo"; } public static method() { return "bar"; } } return ${input}.method(); - `; - expect(util.transpileAndExecute(code)).toBe(expectResult); + `.expectToMatchJsResult(); }); test("Class static instance of self", () => { - const code = ` + util.testFunction` class Foo { bar = "foobar"; static instance = new Foo(); } return Foo.instance.bar; - `; - expect(util.transpileAndExecute(code)).toBe("foobar"); + `.expectToMatchJsResult(); }); test("Class name", () => { - const code = ` + util.testFunction` class Foo {} return Foo.name; - `; - expect(util.transpileAndExecute(code)).toBe("Foo"); + `.expectToMatchJsResult(); }); test("Class name via constructor", () => { - const code = ` + util.testFunction` class Foo {} const foo = new Foo(); return foo.constructor.name; - `; - expect(util.transpileAndExecute(code)).toBe("Foo"); + `.expectToMatchJsResult(); }); test("Class expression name", () => { - const code = ` + util.testFunction` const foo = class Foo {}; return foo.name; - `; - expect(util.transpileAndExecute(code)).toBe("Foo"); + `.expectToMatchJsResult(); }); test("Class expression name via constructor", () => { - const code = ` + util.testFunction` const foo = class Foo {}; const bar = new foo(); return bar.constructor.name; - `; - expect(util.transpileAndExecute(code)).toBe("Foo"); + `.expectToMatchJsResult(); }); test("Class annonymous expression name", () => { - const code = ` + util.testFunction` const foo = class {}; return foo.name; - `; - expect(util.transpileAndExecute(code)).toBe("____"); + `.expectToMatchJsResult(); }); test("Class annonymous expression name via constructor", () => { - const code = ` + util.testFunction` const foo = class {}; const bar = new foo(); return bar.constructor.name; - `; - expect(util.transpileAndExecute(code)).toBe("____"); + `.expectToMatchJsResult(); }); test("Class field override in subclass", () => { - const code = ` + util.testFunction` class Foo { field = "foo"; } class Bar extends Foo { field = "bar"; } - return (new Foo()).field + (new Bar()).field;`; - expect(util.transpileAndExecute(code)).toBe("foobar"); + return (new Foo()).field + (new Bar()).field; + `.expectToMatchJsResult(); }); test("Class field override in subclass with constructors", () => { - const code = ` + util.testFunction` class Foo { field = "foo"; constructor() {} @@ -826,6 +731,6 @@ test("Class field override in subclass with constructors", () => { field = "bar"; constructor() { super(); } } - return (new Foo()).field + (new Bar()).field;`; - expect(util.transpileAndExecute(code)).toBe("foobar"); + return (new Foo()).field + (new Bar()).field; + `.expectToMatchJsResult(); }); From 3690477ca42f0b6192c2e015072754c9eab9ccaf Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 19 Jan 2020 16:44:49 +0100 Subject: [PATCH 03/12] Fixed exported class super call test --- test/unit/classes/classes.spec.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index 57c270b5b..cf1f609c3 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -630,18 +630,23 @@ test("Class Method Runtime Override", () => { }); test("Exported class super call", () => { - util.testModule` - export class Foo { - prop: string; - constructor(prop: string) { this.prop = prop; } - } - export class Bar extends Foo { - constructor() { - super("bar"); - } - } - export const baz = (new Bar()).prop; - `.debug().expectToMatchJsResult(); + util.testExpression` + (new Bar()).prop; + ` + .setTsHeader( + ` + export class Foo { + prop: string; + constructor(prop: string) { this.prop = prop; } + } + export class Bar extends Foo { + constructor() { + super("bar"); + } + } + ` + ) + .expectToMatchJsResult(); }); test.each(["(new Foo())", "Foo"])("Class method name collision (%p)", input => { From 7f4f650e38ed78d7b9b2f6ac4b2156352f8e5ef9 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 19 Jan 2020 17:40:58 +0100 Subject: [PATCH 04/12] Fixed inconsistency between reflection class names of JS and Lua --- src/transformation/context/context.ts | 5 ++++ src/transformation/visitors/class/index.ts | 33 ++++++++++++++-------- test/unit/classes/classes.spec.ts | 11 ++++++-- test/unit/modules/modules.spec.ts | 21 ++++---------- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/transformation/context/context.ts b/src/transformation/context/context.ts index e192348b3..888491d77 100644 --- a/src/transformation/context/context.ts +++ b/src/transformation/context/context.ts @@ -90,4 +90,9 @@ export class TransformationContext { : // TODO: https://github.com/microsoft/TypeScript/pull/28916 (this.superTransformNode(node as StatementLikeNode) as lua.Statement[]); } + + private identifierCounter = 0; + public createUniqueString(prefix: string): string { + return `${prefix}_${++this.identifierCounter}`; + } } diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index e9c8c108c..28a3d3816 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -45,14 +45,7 @@ export function transformClassAsExpression( context: TransformationContext, isDefaultExport = false ): lua.Expression { - let className: lua.Identifier; - if (expression.name) { - className = transformIdentifier(context, expression.name); - } else if (isDefaultExport) { - className = createDefaultExportIdentifier(expression); - } else { - className = lua.createAnonymousIdentifier(); - } + const className = isDefaultExport ? createDefaultExportIdentifier(expression) : lua.createAnonymousIdentifier(); pushScope(context, ScopeType.Function); const classDeclaration = unwrapVisitorResult(transformClassDeclaration(expression, context, className)); @@ -72,13 +65,10 @@ export function transformClassDeclaration( classStack.push(classDeclaration); let className: lua.Identifier; - let classNameText: string; if (nameOverride !== undefined) { className = nameOverride; - classNameText = nameOverride.text; } else if (classDeclaration.name !== undefined) { className = transformIdentifier(context, classDeclaration.name); - classNameText = classDeclaration.name.text; } else if (hasDefaultExportModifier(classDeclaration)) { const left = createExportedIdentifier(context, createDefaultExportIdentifier(classDeclaration)); const right = transformClassAsExpression(classDeclaration, context, true); @@ -193,7 +183,14 @@ export function transformClassDeclaration( if (!isExtension && !isMetaExtension) { result.push( - ...createClassSetup(context, classDeclaration, className, localClassName, classNameText, extendsType) + ...createClassSetup( + context, + classDeclaration, + className, + localClassName, + getReflectionClassName(context, classDeclaration), + extendsType + ) ); } else { for (const f of instanceFields) { @@ -349,3 +346,15 @@ export const transformSuperExpression: FunctionVisitor = (ex }; export const transformThisExpression: FunctionVisitor = node => createSelfIdentifier(node); + +function getReflectionClassName(context: TransformationContext, declaration: ts.ClassLikeDeclaration): string { + if (declaration.name) { + return declaration.name.text; + } else if (ts.isVariableDeclaration(declaration.parent) && ts.isIdentifier(declaration.parent.name)) { + return declaration.parent.name.text; + } else if (hasDefaultExportModifier(declaration)) { + return context.createUniqueString("default"); + } else { + return ""; + } +} diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index cf1f609c3..a880c8374 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -699,14 +699,19 @@ test("Class expression name via constructor", () => { `.expectToMatchJsResult(); }); -test("Class annonymous expression name", () => { +test("Anonymous class in variable declaration has name", () => { util.testFunction` const foo = class {}; - return foo.name; + const bar = foo; + return { a: foo.name, b: bar.name }; `.expectToMatchJsResult(); }); -test("Class annonymous expression name via constructor", () => { +test("Anonymous class expression outside variable assignment", () => { + util.testExpression`(class {}).name`.expectToMatchJsResult(); +}); + +test("Class anonymous expression name via constructor", () => { util.testFunction` const foo = class {}; const bar = new foo(); diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules/modules.spec.ts index 6b5a86068..365da39ac 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -176,21 +176,12 @@ test("Export Default Function", () => { }); test.each([ - ["Test", "export default class Test { static method() { return true; } }"], - ["default", "export default class { static method() { return true; } }"], -])("Export Default Class Name (%p)", (expectedClassName, classDeclarationStatement) => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import defaultExport from "./module"; - export const value = defaultExport.name; - `, - "module.ts": classDeclarationStatement, - }, - "value" - ); - - expect(result).toBe(expectedClassName); + "export default class Test { static method() { return true; } }", + "export default class { static method() { return true; } }", +])("Export Default Class Name (%p)", classDeclarationStatement => { + util.testModule(classDeclarationStatement) + .setReturnExport("default.name") + .expectToMatchJsResult(); }); test("Export Equals", () => { From 1bc474bd368580683275b2b73ab1de84074e3082 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 21 Jan 2020 20:56:03 +0100 Subject: [PATCH 05/12] Reverted unique default class name change --- src/transformation/context/context.ts | 5 ----- src/transformation/visitors/class/index.ts | 6 +++--- test/unit/modules/modules.spec.ts | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/transformation/context/context.ts b/src/transformation/context/context.ts index 888491d77..e192348b3 100644 --- a/src/transformation/context/context.ts +++ b/src/transformation/context/context.ts @@ -90,9 +90,4 @@ export class TransformationContext { : // TODO: https://github.com/microsoft/TypeScript/pull/28916 (this.superTransformNode(node as StatementLikeNode) as lua.Statement[]); } - - private identifierCounter = 0; - public createUniqueString(prefix: string): string { - return `${prefix}_${++this.identifierCounter}`; - } } diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 28a3d3816..43481856a 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -188,7 +188,7 @@ export function transformClassDeclaration( classDeclaration, className, localClassName, - getReflectionClassName(context, classDeclaration), + getReflectionClassName(classDeclaration), extendsType ) ); @@ -347,13 +347,13 @@ export const transformSuperExpression: FunctionVisitor = (ex export const transformThisExpression: FunctionVisitor = node => createSelfIdentifier(node); -function getReflectionClassName(context: TransformationContext, declaration: ts.ClassLikeDeclaration): string { +function getReflectionClassName(declaration: ts.ClassLikeDeclaration): string { if (declaration.name) { return declaration.name.text; } else if (ts.isVariableDeclaration(declaration.parent) && ts.isIdentifier(declaration.parent.name)) { return declaration.parent.name.text; } else if (hasDefaultExportModifier(declaration)) { - return context.createUniqueString("default"); + return "default"; } else { return ""; } diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules/modules.spec.ts index 365da39ac..482b0649b 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -175,15 +175,22 @@ test("Export Default Function", () => { expect(result).toBe(true); }); -test.each([ - "export default class Test { static method() { return true; } }", - "export default class { static method() { return true; } }", -])("Export Default Class Name (%p)", classDeclarationStatement => { - util.testModule(classDeclarationStatement) +test("default exported name class has correct name property", () => { + util.testModule` + export default class Test { static method() { return true; } } + ` .setReturnExport("default.name") .expectToMatchJsResult(); }); +test("default exported anonymous class has 'default' name property", () => { + util.testModule` + export default class { static method() { return true; } } + ` + .setReturnExport("default.name") + .expectToEqual("default"); +}); + test("Export Equals", () => { const [result] = util.transpileAndExecuteProjectReturningMainExport( { From 073049ffc4ab97350cbf651e22d49805e36ccba8 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 30 Jan 2020 22:14:01 +0100 Subject: [PATCH 06/12] Reverted change and changed tests to use testModule instead of setTsHeader --- src/transformation/visitors/class/index.ts | 9 ++- test/unit/classes/classes.spec.ts | 71 +++++++++++----------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 43481856a..fd3bd4406 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -45,7 +45,14 @@ export function transformClassAsExpression( context: TransformationContext, isDefaultExport = false ): lua.Expression { - const className = isDefaultExport ? createDefaultExportIdentifier(expression) : lua.createAnonymousIdentifier(); + let className: lua.Identifier; + if (expression.name) { + className = transformIdentifier(context, expression.name); + } else if (isDefaultExport) { + className = createDefaultExportIdentifier(expression); + } else { + className = lua.createAnonymousIdentifier(); + } pushScope(context, ScopeType.Function); const classDeclaration = unwrapVisitorResult(transformClassDeclaration(expression, context, className)); diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index a880c8374..a6aa3477b 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -182,7 +182,7 @@ test("SubclassConstructor", () => { }); test("Subclass constructor across merged namespace", () => { - const tsHeader = ` + util.testModule` namespace NS { export class Super { prop: string; @@ -197,11 +197,11 @@ test("Subclass constructor across merged namespace", () => { super(); } } - }`; - - util.testExpression("(new NS.Sub()).prop") - .setTsHeader(tsHeader) - .expectToEqual("foo"); + } + export const result = (new NS.Sub()).prop; + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); test("classSuper", () => { @@ -263,24 +263,24 @@ test("classSuperSkip", () => { }); test("renamedClassExtends", () => { - util.testFunction` - const b = new B(); - return b.value; - ` - .setTsHeader( - `namespace Classes { - export class Base { - public value: number; - constructor(){ this.value = 3; } - } + util.testModule` + namespace Classes { + export class Base { + public value: number; + constructor(){ this.value = 3; } } + } + + const A = Classes.Base; + class B extends A { + constructor(){ super(); } + } - const A = Classes.Base; - class B extends A { - constructor(){ super(); } - };` - ) - .expectToEqual(3); + const b = new B(); + export const result = b.value; + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); test("ClassMethodCall", () => { @@ -630,22 +630,19 @@ test("Class Method Runtime Override", () => { }); test("Exported class super call", () => { - util.testExpression` - (new Bar()).prop; + util.testModule` + export class Foo { + prop: string; + constructor(prop: string) { this.prop = prop; } + } + export class Bar extends Foo { + constructor() { + super("bar"); + } + } + export const result = (new Bar()).prop; ` - .setTsHeader( - ` - export class Foo { - prop: string; - constructor(prop: string) { this.prop = prop; } - } - export class Bar extends Foo { - constructor() { - super("bar"); - } - } - ` - ) + .setReturnExport("result") .expectToMatchJsResult(); }); From 4fd0e96051b5331210105fd2b205980a8064ecae Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 28 Mar 2020 18:01:50 +0100 Subject: [PATCH 07/12] Fixed some remaining merge issues --- src/transformation/visitors/class/index.ts | 1 - test/unit/classes/__snapshots__/classes.spec.ts.snap | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 0d57c7567..5f0ecb110 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -83,7 +83,6 @@ export function transformClassDeclaration( } else { // TypeScript error className = lua.createAnonymousIdentifier(); - classNameText = className.text; } const annotations = getTypeAnnotations(context.checker.getTypeAtLocation(classDeclaration)); diff --git a/test/unit/classes/__snapshots__/classes.spec.ts.snap b/test/unit/classes/__snapshots__/classes.spec.ts.snap index 0acca51b6..a900582b5 100644 --- a/test/unit/classes/__snapshots__/classes.spec.ts.snap +++ b/test/unit/classes/__snapshots__/classes.spec.ts.snap @@ -3,7 +3,7 @@ exports[`missing declaration name: code 1`] = ` "require(\\"lualib_bundle\\"); ____ = __TS__Class() -____.name = \\"____\\" +____.name = \\"\\" function ____.prototype.____constructor(self) end" `; From 68f8f55162bf49c92ace477790a6539325118352 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 28 Mar 2020 18:07:45 +0100 Subject: [PATCH 08/12] Added gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fcadb2cf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf From a44fa5ad5a1722b003e7ed1c6590cb7acfcb4217 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 3 Apr 2020 21:24:57 +0200 Subject: [PATCH 09/12] Refactored class transformation functions --- src/transformation/visitors/class/index.ts | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 5f0ecb110..15599f381 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -40,23 +40,28 @@ import { getExtendedNode, getExtendedType, isStaticNode } from "./utils"; export function transformClassAsExpression( expression: ts.ClassLikeDeclaration, - context: TransformationContext, - isDefaultExport = false + context: TransformationContext ): lua.Expression { - let className: lua.Identifier; - if (expression.name) { - className = transformIdentifier(context, expression.name); - } else if (isDefaultExport) { - className = createDefaultExportIdentifier(expression); - } else { - className = lua.createAnonymousIdentifier(); - } - pushScope(context, ScopeType.Function); - const classDeclaration = unwrapVisitorResult(transformClassDeclaration(expression, context, className)); + const { statements, name } = transformClassLikeDeclaration(expression, context); popScope(context); - return createImmediatelyInvokedFunctionExpression(classDeclaration, className, expression); + return createImmediatelyInvokedFunctionExpression(unwrapVisitorResult(statements), name, expression); +} + +export function transformClassDeclaration( + declaration: ts.ClassLikeDeclaration, + context: TransformationContext +): OneToManyVisitorResult { + // If declaration is a default export, transform to export variable assignment instead + if (hasDefaultExportModifier(declaration)) { + const left = createExportedIdentifier(context, createDefaultExportIdentifier(declaration)); + const right = transformClassAsExpression(declaration, context); + return lua.createAssignmentStatement(left, right, declaration); + } + + const { statements } = transformClassLikeDeclaration(declaration, context); + return statements; } const classSuperInfos = new WeakMap(); @@ -65,21 +70,16 @@ interface ClassSuperInfo { extendedTypeNode?: ts.ExpressionWithTypeArguments; } -export function transformClassDeclaration( +function transformClassLikeDeclaration( classDeclaration: ts.ClassLikeDeclaration, context: TransformationContext, nameOverride?: lua.Identifier -): OneToManyVisitorResult { +): { statements: OneToManyVisitorResult; name: lua.Identifier } { let className: lua.Identifier; if (nameOverride !== undefined) { className = nameOverride; } else if (classDeclaration.name !== undefined) { className = transformIdentifier(context, classDeclaration.name); - } else if (hasDefaultExportModifier(classDeclaration)) { - const left = createExportedIdentifier(context, createDefaultExportIdentifier(classDeclaration)); - const right = transformClassAsExpression(classDeclaration, context, true); - - return lua.createAssignmentStatement(left, right, classDeclaration); } else { // TypeScript error className = lua.createAnonymousIdentifier(); @@ -320,7 +320,7 @@ export function transformClassDeclaration( superInfo.pop(); - return result; + return { statements: result, name: className }; } export const transformSuperExpression: FunctionVisitor = (expression, context) => { From 389819da691d8188327ffd3b76c2fc7967fdee0a Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 5 Apr 2020 18:52:25 +0200 Subject: [PATCH 10/12] Also fixed #584, moved getReflectionName to setup and some other minor changes --- src/transformation/visitors/class/index.ts | 54 ++++++++-------------- src/transformation/visitors/class/setup.ts | 24 +++++++++- test/unit/classes/classes.spec.ts | 18 ++++++++ 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 15599f381..627190ccf 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -7,9 +7,9 @@ import { extensionAndMetaExtensionConflict, extensionCannotExport, extensionCannotExtend, - metaExtensionMissingExtends, - luaTableMustBeAmbient, luaTableCannotBeExtended, + luaTableMustBeAmbient, + metaExtensionMissingExtends, } from "../../utils/diagnostics"; import { createDefaultExportIdentifier, @@ -21,7 +21,6 @@ import { import { createImmediatelyInvokedFunctionExpression, createSelfIdentifier, - OneToManyVisitorResult, unwrapVisitorResult, } from "../../utils/lua-ast"; import { createSafeName, isUnsafeName } from "../../utils/safe-names"; @@ -35,9 +34,23 @@ import { createConstructorName, transformConstructorDeclaration } from "./member import { transformClassInstanceFields } from "./members/fields"; import { transformMethodDeclaration } from "./members/method"; import { checkForLuaLibType } from "./new"; -import { createClassSetup } from "./setup"; +import { createClassSetup, getReflectionClassName } from "./setup"; import { getExtendedNode, getExtendedType, isStaticNode } from "./utils"; +export const transformClassDeclaration: FunctionVisitor = (declaration, context) => { + // If declaration is a default export, transform to export variable assignment instead + if (hasDefaultExportModifier(declaration)) { + const left = createExportedIdentifier(context, createDefaultExportIdentifier(declaration)); + const right = transformClassAsExpression(declaration, context); + return [lua.createAssignmentStatement(left, right, declaration)]; + } + + const { statements } = transformClassLikeDeclaration(declaration, context); + return statements; +}; + +export const transformThisExpression: FunctionVisitor = node => createSelfIdentifier(node); + export function transformClassAsExpression( expression: ts.ClassLikeDeclaration, context: TransformationContext @@ -49,21 +62,6 @@ export function transformClassAsExpression( return createImmediatelyInvokedFunctionExpression(unwrapVisitorResult(statements), name, expression); } -export function transformClassDeclaration( - declaration: ts.ClassLikeDeclaration, - context: TransformationContext -): OneToManyVisitorResult { - // If declaration is a default export, transform to export variable assignment instead - if (hasDefaultExportModifier(declaration)) { - const left = createExportedIdentifier(context, createDefaultExportIdentifier(declaration)); - const right = transformClassAsExpression(declaration, context); - return lua.createAssignmentStatement(left, right, declaration); - } - - const { statements } = transformClassLikeDeclaration(declaration, context); - return statements; -} - const classSuperInfos = new WeakMap(); interface ClassSuperInfo { className: lua.Identifier; @@ -74,7 +72,7 @@ function transformClassLikeDeclaration( classDeclaration: ts.ClassLikeDeclaration, context: TransformationContext, nameOverride?: lua.Identifier -): { statements: OneToManyVisitorResult; name: lua.Identifier } { +): { statements: lua.Statement[]; name: lua.Identifier } { let className: lua.Identifier; if (nameOverride !== undefined) { className = nameOverride; @@ -198,7 +196,7 @@ function transformClassLikeDeclaration( classDeclaration, className, localClassName, - getReflectionClassName(classDeclaration), + getReflectionClassName(classDeclaration, className, context), extendedType ) ); @@ -348,17 +346,3 @@ export const transformSuperExpression: FunctionVisitor = (ex return lua.createTableIndexExpression(baseClassName, lua.createStringLiteral("prototype")); }; - -export const transformThisExpression: FunctionVisitor = node => createSelfIdentifier(node); - -function getReflectionClassName(declaration: ts.ClassLikeDeclaration): string { - if (declaration.name) { - return declaration.name.text; - } else if (ts.isVariableDeclaration(declaration.parent) && ts.isIdentifier(declaration.parent.name)) { - return declaration.parent.name.text; - } else if (hasDefaultExportModifier(declaration)) { - return "default"; - } else { - return ""; - } -} diff --git a/src/transformation/visitors/class/setup.ts b/src/transformation/visitors/class/setup.ts index a4ffc5c75..a674a1a27 100644 --- a/src/transformation/visitors/class/setup.ts +++ b/src/transformation/visitors/class/setup.ts @@ -17,7 +17,7 @@ export function createClassSetup( statement: ts.ClassLikeDeclarationBase, className: lua.Identifier, localClassName: lua.Identifier, - classNameText: string, + reflectionClassName: lua.Expression, extendsType?: ts.Type ): lua.Statement[] { const result: lua.Statement[] = []; @@ -56,7 +56,7 @@ export function createClassSetup( result.push( lua.createAssignmentStatement( lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("name")), - lua.createStringLiteral(classNameText), + reflectionClassName, statement ) ); @@ -79,3 +79,23 @@ export function createClassSetup( return result; } + +export function getReflectionClassName( + declaration: ts.ClassLikeDeclaration, + className: lua.Identifier, + context: TransformationContext +): lua.Expression { + if (declaration.name) { + return lua.createStringLiteral(declaration.name.text); + } else if (ts.isVariableDeclaration(declaration.parent) && ts.isIdentifier(declaration.parent.name)) { + return lua.createStringLiteral(declaration.parent.name.text); + } else if (hasDefaultExportModifier(declaration)) { + return lua.createStringLiteral("default"); + } + + if (getExtendedNode(context, declaration)) { + return lua.createTableIndexExpression(className, lua.createStringLiteral("name")); + } + + return lua.createStringLiteral(""); +} diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index f1114ed7c..c49195f49 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -767,3 +767,21 @@ test("missing declaration name", () => { class {} `.expectDiagnosticsToMatchSnapshot([1211]); }); + +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/584 +test("constructor class name available with constructor", () => { + util.testModule` + function Numbers(...message_type_args: number[]) { + return {}>(constructor: T) => { + return class extends constructor { + protected numbers = message_type_args; + }; + }; + } + + @Numbers(10, 20) + class MyClass {} + + export const result = new MyClass().constructor.name; + `.expectToMatchJsResult(); +}); From 72aaeb70a895e80355a49e0716b93f3dc0568dd0 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 5 Apr 2020 19:02:09 +0200 Subject: [PATCH 11/12] Moved getReflectionClassName to setup completely --- src/transformation/visitors/class/index.ts | 13 ++----------- src/transformation/visitors/class/setup.ts | 9 ++++----- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 627190ccf..59ecc10b7 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -34,7 +34,7 @@ import { createConstructorName, transformConstructorDeclaration } from "./member import { transformClassInstanceFields } from "./members/fields"; import { transformMethodDeclaration } from "./members/method"; import { checkForLuaLibType } from "./new"; -import { createClassSetup, getReflectionClassName } from "./setup"; +import { createClassSetup } from "./setup"; import { getExtendedNode, getExtendedType, isStaticNode } from "./utils"; export const transformClassDeclaration: FunctionVisitor = (declaration, context) => { @@ -190,16 +190,7 @@ function transformClassLikeDeclaration( } if (!isExtension && !isMetaExtension) { - result.push( - ...createClassSetup( - context, - classDeclaration, - className, - localClassName, - getReflectionClassName(classDeclaration, className, context), - extendedType - ) - ); + result.push(...createClassSetup(context, classDeclaration, className, localClassName, extendedType)); } else { for (const f of instanceFields) { const fieldName = transformPropertyName(context, f.name); diff --git a/src/transformation/visitors/class/setup.ts b/src/transformation/visitors/class/setup.ts index a674a1a27..151e68a77 100644 --- a/src/transformation/visitors/class/setup.ts +++ b/src/transformation/visitors/class/setup.ts @@ -17,7 +17,6 @@ export function createClassSetup( statement: ts.ClassLikeDeclarationBase, className: lua.Identifier, localClassName: lua.Identifier, - reflectionClassName: lua.Expression, extendsType?: ts.Type ): lua.Statement[] { const result: lua.Statement[] = []; @@ -56,7 +55,7 @@ export function createClassSetup( result.push( lua.createAssignmentStatement( lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("name")), - reflectionClassName, + getReflectionClassName(context, statement, className), statement ) ); @@ -81,9 +80,9 @@ export function createClassSetup( } export function getReflectionClassName( - declaration: ts.ClassLikeDeclaration, - className: lua.Identifier, - context: TransformationContext + context: TransformationContext, + declaration: ts.ClassLikeDeclarationBase, + className: lua.Identifier ): lua.Expression { if (declaration.name) { return lua.createStringLiteral(declaration.name.text); From edc9491b4c9ce3fd65884f689b27001504702f6c Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 5 Apr 2020 19:12:02 +0200 Subject: [PATCH 12/12] Moved tests --- test/unit/classes/classes.spec.ts | 30 ++++++++++++++++++++---------- test/unit/modules/modules.spec.ts | 16 ---------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index c49195f49..4b58db2cf 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -768,20 +768,30 @@ test("missing declaration name", () => { `.expectDiagnosticsToMatchSnapshot([1211]); }); +test("default exported name class has correct name property", () => { + util.testModule` + export default class Test { static method() { return true; } } + ` + .setReturnExport("default.name") + .expectToMatchJsResult(); +}); + +test("default exported anonymous class has 'default' name property", () => { + util.testModule` + export default class { static method() { return true; } } + ` + .setReturnExport("default.name") + .expectToEqual("default"); +}); + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/584 test("constructor class name available with constructor", () => { util.testModule` - function Numbers(...message_type_args: number[]) { - return {}>(constructor: T) => { - return class extends constructor { - protected numbers = message_type_args; - }; - }; - } + const decorator = any>(constructor: T) => class extends constructor {}; - @Numbers(10, 20) + @decorator class MyClass {} - - export const result = new MyClass().constructor.name; + + export const className = new MyClass().constructor.name; `.expectToMatchJsResult(); }); diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules/modules.spec.ts index 7003593aa..018503687 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -175,22 +175,6 @@ test("Export Default Function", () => { expect(result).toBe(true); }); -test("default exported name class has correct name property", () => { - util.testModule` - export default class Test { static method() { return true; } } - ` - .setReturnExport("default.name") - .expectToMatchJsResult(); -}); - -test("default exported anonymous class has 'default' name property", () => { - util.testModule` - export default class { static method() { return true; } } - ` - .setReturnExport("default.name") - .expectToEqual("default"); -}); - test("Export Equals", () => { const [result] = util.transpileAndExecuteProjectReturningMainExport( {