diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fcadb2cf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 0736eec25..59ecc10b7 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"; @@ -38,25 +37,29 @@ import { checkForLuaLibType } from "./new"; import { createClassSetup } 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, - 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); } const classSuperInfos = new WeakMap(); @@ -65,28 +68,19 @@ interface ClassSuperInfo { extendedTypeNode?: ts.ExpressionWithTypeArguments; } -export function transformClassDeclaration( +function transformClassLikeDeclaration( classDeclaration: ts.ClassLikeDeclaration, context: TransformationContext, nameOverride?: lua.Identifier -): OneToManyVisitorResult { +): { statements: lua.Statement[]; name: lua.Identifier } { 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); - - return lua.createAssignmentStatement(left, right, classDeclaration); } else { // TypeScript error className = lua.createAnonymousIdentifier(); - classNameText = className.text; } const annotations = getTypeAnnotations(context.checker.getTypeAtLocation(classDeclaration)); @@ -196,9 +190,7 @@ export function transformClassDeclaration( } if (!isExtension && !isMetaExtension) { - result.push( - ...createClassSetup(context, classDeclaration, className, localClassName, classNameText, extendedType) - ); + result.push(...createClassSetup(context, classDeclaration, className, localClassName, extendedType)); } else { for (const f of instanceFields) { const fieldName = transformPropertyName(context, f.name); @@ -317,7 +309,7 @@ export function transformClassDeclaration( superInfo.pop(); - return result; + return { statements: result, name: className }; } export const transformSuperExpression: FunctionVisitor = (expression, context) => { @@ -345,5 +337,3 @@ export const transformSuperExpression: FunctionVisitor = (ex return lua.createTableIndexExpression(baseClassName, lua.createStringLiteral("prototype")); }; - -export const transformThisExpression: FunctionVisitor = node => createSelfIdentifier(node); diff --git a/src/transformation/visitors/class/setup.ts b/src/transformation/visitors/class/setup.ts index a4ffc5c75..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, - classNameText: string, 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")), - lua.createStringLiteral(classNameText), + getReflectionClassName(context, statement, className), statement ) ); @@ -79,3 +78,23 @@ export function createClassSetup( return result; } + +export function getReflectionClassName( + context: TransformationContext, + declaration: ts.ClassLikeDeclarationBase, + className: lua.Identifier +): 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/__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" `; diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index 0e6761fbc..4b58db2cf 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,14 +177,12 @@ 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", () => { - const tsHeader = ` + util.testModule` namespace NS { export class Super { prop: string; @@ -231,9 +197,11 @@ test("Subclass constructor across merged namespace", () => { super(); } } - }`; - - expect(util.transpileAndExecute("return (new NS.Sub()).prop", undefined, undefined, tsHeader)).toBe("foo"); + } + export const result = (new NS.Sub()).prop; + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); test("super without class", () => { @@ -257,8 +225,8 @@ test("super in unnamed class", () => { }); test("classSuper", () => { - const result = util.transpileAndExecute( - `class a { + util.testFunction` + class a { public field: number = 4; constructor(n: number) { this.field = n; @@ -269,15 +237,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; @@ -293,15 +259,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; @@ -314,19 +278,13 @@ 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 { + util.testModule` + namespace Classes { export class Base { public value: number; constructor(){ this.value = 3; } @@ -336,114 +294,103 @@ test("renamedClassExtends", () => { const A = Classes.Base; class B extends A { constructor(){ super(); } - };` - ); + } - expect(result).toBe(3); + const b = new B(); + export const result = b.value; + ` + .setReturnExport("result") + .expectToMatchJsResult(); }); 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; } @@ -456,43 +403,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; } @@ -500,15 +441,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; @@ -516,15 +455,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; } @@ -535,29 +472,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; @@ -575,15 +508,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; @@ -601,15 +532,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 { @@ -625,15 +554,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; @@ -659,15 +586,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"; } @@ -678,29 +603,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; } @@ -708,15 +629,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; } @@ -726,14 +645,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; } @@ -743,101 +660,96 @@ test("Exported class super call", () => { super("bar"); } } - export const baz = (new Bar()).prop; - `; - expect(util.transpileExecuteAndReturnExport(code, "baz")).toBe("bar"); + export const result = (new Bar()).prop; + ` + .setReturnExport("result") + .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 = ` +test("Anonymous class in variable declaration has name", () => { + util.testFunction` const foo = class {}; - return foo.name; - `; - expect(util.transpileAndExecute(code)).toBe("____"); + const bar = foo; + return { a: foo.name, b: bar.name }; + `.expectToMatchJsResult(); +}); + +test("Anonymous class expression outside variable assignment", () => { + util.testExpression`(class {}).name`.expectToMatchJsResult(); }); -test("Class annonymous expression name via constructor", () => { - const code = ` +test("Class anonymous expression name via constructor", () => { + 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() {} @@ -846,8 +758,8 @@ 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(); }); test("missing declaration name", () => { @@ -855,3 +767,31 @@ test("missing declaration name", () => { class {} `.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` + const decorator = any>(constructor: T) => class extends constructor {}; + + @decorator + class MyClass {} + + 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 14cea1337..018503687 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -175,24 +175,6 @@ test("Export Default Function", () => { expect(result).toBe(true); }); -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); -}); - test("Export Equals", () => { const [result] = util.transpileAndExecuteProjectReturningMainExport( {