From 23e0cd0225b93cd0bfe2c34bda6793d89cc66591 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 29 Oct 2021 21:46:56 +0200 Subject: [PATCH] Correctly export decorated classes --- src/transformation/utils/export.ts | 11 ++-- src/transformation/visitors/class/index.ts | 14 ++++- src/transformation/visitors/modules/export.ts | 9 ++-- test/unit/classes/decorators.spec.ts | 53 +++++++++++++++++++ 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/transformation/utils/export.ts b/src/transformation/utils/export.ts index 1edee936c..fafc26d86 100644 --- a/src/transformation/utils/export.ts +++ b/src/transformation/utils/export.ts @@ -10,10 +10,11 @@ export function hasDefaultExportModifier(node: ts.Node): boolean { return (node.modifiers ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); } -export const createDefaultExportIdentifier = (original: ts.Node): lua.Identifier => - lua.createIdentifier("default", original); +export function hasExportModifier(node: ts.Node): boolean { + return (node.modifiers ?? []).some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword); +} -export const createDefaultExportStringLiteral = (original: ts.Node): lua.StringLiteral => +export const createDefaultExportStringLiteral = (original?: ts.Node): lua.StringLiteral => lua.createStringLiteral("default", original); export function getExportedSymbolDeclaration(symbol: ts.Symbol): ts.Declaration | undefined { @@ -142,3 +143,7 @@ export function createExportedIdentifier( return lua.createTableIndexExpression(exportTable, lua.createStringLiteral(identifier.text)); } + +export function createDefaultExportExpression(node: ts.Node): lua.AssignmentLeftHandSideExpression { + return lua.createTableIndexExpression(createExportsIdentifier(), createDefaultExportStringLiteral(node), node); +} diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index 98d74f84a..2fb18a0cb 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -5,9 +5,10 @@ import { FunctionVisitor, TransformationContext } from "../../context"; import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations"; import { annotationRemoved } from "../../utils/diagnostics"; import { - createDefaultExportIdentifier, + createDefaultExportExpression, createExportedIdentifier, hasDefaultExportModifier, + hasExportModifier, isSymbolExported, } from "../../utils/export"; import { createSelfIdentifier, unwrapVisitorResult } from "../../utils/lua-ast"; @@ -30,7 +31,7 @@ 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 left = createDefaultExportExpression(declaration); const right = transformClassAsExpression(declaration, context); return [lua.createAssignmentStatement(left, right, declaration)]; } @@ -220,6 +221,15 @@ function transformClassLikeDeclaration( ); const decoratingStatement = lua.createAssignmentStatement(localClassName, decoratingExpression); result.push(decoratingStatement); + + if (hasExportModifier(classDeclaration)) { + const exportExpression = hasDefaultExportModifier(classDeclaration) + ? createDefaultExportExpression(classDeclaration) + : createExportedIdentifier(context, className); + + const classAssignment = lua.createAssignmentStatement(exportExpression, localClassName); + result.push(classAssignment); + } } superInfo.pop(); diff --git a/src/transformation/visitors/modules/export.ts b/src/transformation/visitors/modules/export.ts index 955f56866..07136f3e6 100644 --- a/src/transformation/visitors/modules/export.ts +++ b/src/transformation/visitors/modules/export.ts @@ -3,7 +3,7 @@ import * as lua from "../../../LuaAST"; import { assert } from "../../../utils"; import { FunctionVisitor, TransformationContext } from "../../context"; import { - createDefaultExportIdentifier, + createDefaultExportExpression, createDefaultExportStringLiteral, createExportedIdentifier, } from "../../utils/export"; @@ -114,10 +114,9 @@ function transformExportSpecifier(context: TransformationContext, node: ts.Expor const exportedExpression = createShorthandIdentifier(context, exportedSymbol, exportedIdentifier); const isDefault = isDefaultExportSpecifier(node); - const identifierToExport = isDefault - ? createDefaultExportIdentifier(node) - : transformIdentifier(context, node.name); - const exportAssignmentLeftHandSide = createExportedIdentifier(context, identifierToExport); + const exportAssignmentLeftHandSide = isDefault + ? createDefaultExportExpression(node) + : createExportedIdentifier(context, transformIdentifier(context, node.name)); return lua.createAssignmentStatement(exportAssignmentLeftHandSide, exportedExpression, node); } diff --git a/test/unit/classes/decorators.spec.ts b/test/unit/classes/decorators.spec.ts index dadb73655..5b7b6e2ff 100644 --- a/test/unit/classes/decorators.spec.ts +++ b/test/unit/classes/decorators.spec.ts @@ -184,3 +184,56 @@ describe("Decorators /w descriptors", () => { } ); }); + +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1149 +test("exported class with decorator", () => { + util.testModule` + import { MyClass } from "./other"; + const inst = new MyClass(); + export const result = inst.foo(); + ` + .addExtraFile( + "other.ts", + `function myDecorator(target: {new(): any}) { + return class extends target { + foo() { + return "overridden"; + } + } + } + + @myDecorator + export class MyClass { + foo() { + return "foo"; + } + }` + ) + .expectToEqual({ result: "overridden" }); +}); + +test("default exported class with decorator", () => { + util.testModule` + import MyClass from "./other"; + const inst = new MyClass(); + export const result = inst.foo(); + ` + .addExtraFile( + "other.ts", + `function myDecorator(target: {new(): any}) { + return class extends target { + foo() { + return "overridden"; + } + } + } + + @myDecorator + export default class { + foo() { + return "foo"; + } + }` + ) + .expectToEqual({ result: "overridden" }); +});