From 4fd3e31d2bc8779e5f13ee9c11586a1b89edc214 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 16 Jul 2019 07:28:54 +1000 Subject: [PATCH 01/19] Fix import and export bugs --- src/LuaTransformer.ts | 342 ++++++++++++++++++++++---------------- src/TSHelper.ts | 54 ++++++ src/TSTLErrors.ts | 6 - test/unit/modules.spec.ts | 16 -- test/unit/require.spec.ts | 151 +++++++++++++++++ test/util.ts | 31 ++++ 6 files changed, 434 insertions(+), 166 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 027892887..d7ee76965 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -159,6 +159,8 @@ export class LuaTransformer { case ts.SyntaxKind.Block: return this.transformBlockAsDoStatement(node as ts.Block); // Declaration Statements + case ts.SyntaxKind.ExportAssignment: + return this.transformExportAssignment(node as ts.ExportAssignment); case ts.SyntaxKind.ExportDeclaration: return this.transformExportDeclaration(node as ts.ExportDeclaration); case ts.SyntaxKind.ImportDeclaration: @@ -238,19 +240,29 @@ export class LuaTransformer { return tstl.createDoStatement(statements, block); } + public transformExportAssignment(statement: ts.ExportAssignment): StatementVisitResult { + // export = [expression]; + // ____exports = [expression]; + if (statement.isExportEquals) { + return tstl.createAssignmentStatement( + this.createExportsIdentifier(), + this.transformExpression(statement.expression), + statement + ); + } + + // export default [expression]; + // ____exports.default = [expression]; + const defaultIdentifier = tstl.createStringLiteral("default"); + return tstl.createAssignmentStatement( + tstl.createTableIndexExpression(this.createExportsIdentifier(), defaultIdentifier), + this.transformExpression(statement.expression), + statement + ); + } + public transformExportDeclaration(statement: ts.ExportDeclaration): StatementVisitResult { if (statement.exportClause) { - if ( - statement.exportClause.elements.some( - e => - (e.name !== undefined && e.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) || - (e.propertyName !== undefined && - e.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) - ) - ) { - throw TSTLErrors.UnsupportedDefaultExport(statement); - } - if (!this.resolver.isValueAliasDeclaration(statement)) { return undefined; } @@ -261,17 +273,25 @@ export class LuaTransformer { if (statement.moduleSpecifier === undefined) { return exportSpecifiers.map(specifier => { - let exportedIdentifier: tstl.Expression | undefined; + const isDefaultExport = + (specifier.name !== undefined && + specifier.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) || + (specifier.propertyName !== undefined && + specifier.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword); + + let exportedExpression: tstl.Expression | undefined; if (specifier.propertyName !== undefined) { - exportedIdentifier = this.transformIdentifier(specifier.propertyName); + exportedExpression = this.transformIdentifier(specifier.propertyName); } else { const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(specifier); - exportedIdentifier = this.createShorthandIdentifier(exportedSymbol, specifier.name); + exportedExpression = this.createShorthandIdentifier(exportedSymbol, specifier.name); } return tstl.createAssignmentStatement( - this.createExportedIdentifier(this.transformIdentifier(specifier.name)), - exportedIdentifier + this.createExportedIdentifier( + isDefaultExport ? undefined : this.transformIdentifier(specifier.name) + ), + exportedExpression ); }); } @@ -342,16 +362,11 @@ export class LuaTransformer { } public transformImportDeclaration(statement: ts.ImportDeclaration): StatementVisitResult { - if (statement.importClause && !statement.importClause.namedBindings) { - throw TSTLErrors.DefaultImportsNotSupported(statement); - } - - const result: tstl.Statement[] = []; - const scope = this.peekScope(); if (scope === undefined) { throw TSTLErrors.UndefinedScope(); } + if (!this.options.noHoisting && !scope.importStatements) { scope.importStatements = []; } @@ -370,8 +385,13 @@ export class LuaTransformer { const importPath = moduleSpecifier.text.replace(new RegExp('"', "g"), ""); const requireCall = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral, shouldResolve); - if (!statement.importClause) { + const result: tstl.Statement[] = []; + + // import "./module"; + // require("module") + if (statement.importClause === undefined) { result.push(tstl.createExpressionStatement(requireCall)); + if (scope.importStatements) { scope.importStatements.push(...result); return undefined; @@ -380,83 +400,115 @@ export class LuaTransformer { } } - const imports = statement.importClause.namedBindings; - if (imports === undefined) { - throw TSTLErrors.UnsupportedImportType(statement.importClause); + // Create the require statement to extract values. + // local ____module = require("module") + const tstlIdentifier = (name: string) => "____" + tsHelper.fixInvalidLuaIdentifier(name); + const importUniqueName = tstl.createIdentifier(tstlIdentifier(path.basename(importPath))); + const requireStatement = tstl.createVariableDeclarationStatement( + tstl.createIdentifier(tstlIdentifier(path.basename(importPath))), + requireCall, + statement + ); + + let usingRequireStatement = false; + + // import defaultValue from "./module"; + // local defaultValue = __module.default + if (statement.importClause.name) { + const decorators = tsHelper.getCustomDecorators( + this.checker.getTypeAtLocation(statement.importClause), + this.checker + ); + if ( + this.resolver.isReferencedAliasDeclaration(statement.importClause) && + !decorators.has(DecoratorKind.Extension) && + !decorators.has(DecoratorKind.MetaExtension) + ) { + const propertyName = tstl.createStringLiteral("default", statement.importClause.name); + const defaultImportAssignmentStatement = tstl.createVariableDeclarationStatement( + this.transformIdentifier(statement.importClause.name), + tstl.createTableIndexExpression(importUniqueName, propertyName), + statement.importClause.name + ); + + result.push(defaultImportAssignmentStatement); + } + usingRequireStatement = true; } - if (ts.isNamedImports(imports)) { - const filteredElements = imports.elements.filter(e => { - const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(e), this.checker); - return ( - this.resolver.isReferencedAliasDeclaration(e) && - !decorators.has(DecoratorKind.Extension) && - !decorators.has(DecoratorKind.MetaExtension) + // import * as module from "./module"; + // local module = require("module") + if (statement.importClause.namedBindings && ts.isNamespaceImport(statement.importClause.namedBindings)) { + if (this.resolver.isReferencedAliasDeclaration(statement.importClause.namedBindings)) { + const requireStatement = tstl.createVariableDeclarationStatement( + this.transformIdentifier(statement.importClause.namedBindings.name), + requireCall, + statement ); - }); - // Elide import if all imported types are extension classes - if (filteredElements.length === 0) { - return undefined; + result.push(requireStatement); } + } - const tstlIdentifier = (name: string) => "____" + tsHelper.fixInvalidLuaIdentifier(name); - const importUniqueName = tstl.createIdentifier(tstlIdentifier(path.basename(importPath))); - const requireStatement = tstl.createVariableDeclarationStatement( - tstl.createIdentifier(tstlIdentifier(path.basename(importPath))), - requireCall, - statement - ); - result.push(requireStatement); + // import { a, b, c } from "./module"; + // local a = __module.a + // local b = __module.b + // local c = __module.c + if (statement.importClause.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) { + statement.importClause.namedBindings.elements + .filter(importSpecifier => { + const decorators = tsHelper.getCustomDecorators( + this.checker.getTypeAtLocation(importSpecifier), + this.checker + ); - filteredElements.forEach(importSpecifier => { - if (importSpecifier.propertyName) { - const propertyName = this.transformPropertyName(importSpecifier.propertyName); - const renamedImport = tstl.createVariableDeclarationStatement( - this.transformIdentifier(importSpecifier.name), - tstl.createTableIndexExpression(importUniqueName, propertyName), - importSpecifier + return ( + this.resolver.isReferencedAliasDeclaration(importSpecifier) && + !decorators.has(DecoratorKind.Extension) && + !decorators.has(DecoratorKind.MetaExtension) ); - result.push(renamedImport); - } else { - const name = tstl.createStringLiteral(importSpecifier.name.text); - const namedImport = tstl.createVariableDeclarationStatement( - this.transformIdentifier(importSpecifier.name), - tstl.createTableIndexExpression(importUniqueName, name), - importSpecifier + }) + .forEach(importSpecifier => { + const leftIdentifier = this.transformIdentifier(importSpecifier.name); + const propertyName = this.transformPropertyName( + importSpecifier.propertyName ? importSpecifier.propertyName : importSpecifier.name ); - result.push(namedImport); - } - }); - if (scope.importStatements) { - scope.importStatements.push(...result); - return undefined; - } else { - return result; - } - } else if (ts.isNamespaceImport(imports)) { - if (!this.resolver.isReferencedAliasDeclaration(imports)) { - return undefined; - } - const requireStatement = tstl.createVariableDeclarationStatement( - this.transformIdentifier(imports.name), - requireCall, - statement - ); - result.push(requireStatement); - if (scope.importStatements) { - scope.importStatements.push(...result); - return undefined; - } else { - return result; - } + const importAssignmentStatement = tstl.createVariableDeclarationStatement( + leftIdentifier, + tstl.createTableIndexExpression(importUniqueName, propertyName), + statement.importClause + ); + + result.push(importAssignmentStatement); + }); + usingRequireStatement = true; + } + + if (result.length === 0) { + return undefined; + } + + if (usingRequireStatement) { + result.unshift(requireStatement); + } + + if (scope.importStatements) { + scope.importStatements.push(...result); + return undefined; + } else { + return result; } } protected createModuleRequire(moduleSpecifier: ts.StringLiteral, resolveModule = true): tstl.CallExpression { const modulePathString = resolveModule - ? this.getImportPath(moduleSpecifier.text.replace(new RegExp('"', "g"), ""), moduleSpecifier) + ? tsHelper.getImportPath( + this.currentSourceFile.fileName, + moduleSpecifier.text.replace(new RegExp('"', "g"), ""), + moduleSpecifier, + this.options + ) : moduleSpecifier.text; const modulePath = tstl.createStringLiteral(modulePathString); return tstl.createCallExpression(tstl.createIdentifier("require"), [modulePath], moduleSpecifier); @@ -779,18 +831,33 @@ export class LuaTransformer { // [____exports.]className = {} const classTable: tstl.Expression = tstl.createTableExpression(); - const classVar = this.createLocalOrExportedOrGlobalDeclaration(className, classTable, statement); + const isDefaultExport = + statement.modifiers && statement.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); + + const defaultExportLeftHandSize = isDefaultExport + ? tstl.createTableIndexExpression(this.createExportsIdentifier(), tstl.createStringLiteral("default")) + : undefined; + + const classVar = defaultExportLeftHandSize + ? [tstl.createAssignmentStatement(defaultExportLeftHandSize, classTable, statement)] + : this.createLocalOrExportedOrGlobalDeclaration(className, classTable, statement); + result.push(...classVar); - const exportScope = this.getIdentifierExportScope(className); - if (exportScope) { - // local localClassName = ____exports.className - result.push( - tstl.createVariableDeclarationStatement( - localClassName, - this.createExportedIdentifier(tstl.cloneIdentifier(className), exportScope) - ) - ); + if (defaultExportLeftHandSize) { + // local localClassName = ____exports.default + result.push(tstl.createVariableDeclarationStatement(localClassName, defaultExportLeftHandSize)); + } else { + const exportScope = this.getIdentifierExportScope(className); + if (exportScope) { + // local localClassName = ____exports.className + result.push( + tstl.createVariableDeclarationStatement( + localClassName, + this.createExportedIdentifier(tstl.cloneIdentifier(className), exportScope) + ) + ); + } } // localClassName.name = className @@ -1879,11 +1946,6 @@ export class LuaTransformer { : undefined; const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context); - if (functionDeclaration.name === undefined) { - throw TSTLErrors.MissingFunctionName(functionDeclaration); - } - - const name = this.transformIdentifier(functionDeclaration.name); const [body, functionScope] = functionDeclaration.asteriskToken ? this.transformGeneratorFunction(functionDeclaration.parameters, functionDeclaration.body, restParamName) : this.transformFunctionBody(functionDeclaration.parameters, functionDeclaration.body, restParamName); @@ -1895,18 +1957,37 @@ export class LuaTransformer { restParamName, tstl.FunctionExpressionFlags.Declaration ); - // Remember symbols referenced in this function for hoisting later - if (!this.options.noHoisting && name.symbolId !== undefined) { - const scope = this.peekScope(); - if (scope === undefined) { - throw TSTLErrors.UndefinedScope(); - } - if (!scope.functionDefinitions) { - scope.functionDefinitions = new Map(); + + const name = functionDeclaration.name ? this.transformIdentifier(functionDeclaration.name) : undefined; + + if (name) { + // Remember symbols referenced in this function for hoisting later + if (!this.options.noHoisting && name.symbolId !== undefined) { + const scope = this.peekScope(); + if (scope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + if (!scope.functionDefinitions) { + scope.functionDefinitions = new Map(); + } + const functionInfo = { referencedSymbols: functionScope.referencedSymbols || new Map() }; + scope.functionDefinitions.set(name.symbolId, functionInfo); } - const functionInfo = { referencedSymbols: functionScope.referencedSymbols || new Map() }; - scope.functionDefinitions.set(name.symbolId, functionInfo); } + + const isDefaultExport = + functionDeclaration.modifiers && + functionDeclaration.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); + + if (isDefaultExport) { + return tstl.createAssignmentStatement( + tstl.createTableIndexExpression(this.createExportsIdentifier(), tstl.createStringLiteral("default")), + this.transformFunctionExpression(functionDeclaration) + ); + } else if (!name) { + throw TSTLErrors.MissingFunctionName(functionDeclaration); + } + return this.createLocalOrExportedOrGlobalDeclaration(name, functionExpression, functionDeclaration); } @@ -5044,14 +5125,19 @@ export class LuaTransformer { } protected createExportedIdentifier( - identifier: tstl.Identifier, + identifier?: tstl.Identifier, exportScope?: ts.SourceFile | ts.ModuleDeclaration ): tstl.AssignmentLeftHandSideExpression { + const stringLiteral = identifier + ? tstl.createStringLiteral(identifier.text) + : tstl.createStringLiteral("default"); + const exportTable = exportScope && ts.isModuleDeclaration(exportScope) ? this.createModuleLocalNameIdentifier(exportScope) : this.createExportsIdentifier(); - return tstl.createTableIndexExpression(exportTable, tstl.createStringLiteral(identifier.text)); + + return tstl.createTableIndexExpression(exportTable, stringLiteral); } protected getSymbolExportScope(symbol: ts.Symbol): ts.SourceFile | ts.ModuleDeclaration | undefined { @@ -5140,38 +5226,6 @@ export class LuaTransformer { } } - protected getAbsoluteImportPath(relativePath: string): string { - if (relativePath.charAt(0) !== "." && this.options.baseUrl) { - return path.resolve(this.options.baseUrl, relativePath); - } - - return path.resolve(path.dirname(this.currentSourceFile.fileName), relativePath); - } - - protected getImportPath(relativePath: string, node: ts.Node): string { - const rootDir = this.options.rootDir ? path.resolve(this.options.rootDir) : path.resolve("."); - const absoluteImportPath = path.format(path.parse(this.getAbsoluteImportPath(relativePath))); - const absoluteRootDirPath = path.format(path.parse(rootDir)); - if (absoluteImportPath.includes(absoluteRootDirPath)) { - return this.formatPathToLuaPath(absoluteImportPath.replace(absoluteRootDirPath, "").slice(1)); - } else { - throw TSTLErrors.UnresolvableRequirePath( - node, - `Cannot create require path. Module does not exist within --rootDir`, - relativePath - ); - } - } - - protected formatPathToLuaPath(filePath: string): string { - filePath = filePath.replace(/\.json$/, ""); - if (process.platform === "win32") { - // Windows can use backslashes - filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, "."); - } - return filePath.replace(/\.\//g, "").replace(/\//g, "."); - } - protected createSelfIdentifier(tsOriginal?: ts.Node): tstl.Identifier { return tstl.createIdentifier("self", tsOriginal, undefined, "this"); } diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 6a9dc5a32..e314a0382 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -1,6 +1,8 @@ import * as ts from "typescript"; +import * as path from "path"; import { Decorator, DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; +import * as TSTLErrors from "./TSTLErrors"; export enum ContextType { None, @@ -911,3 +913,55 @@ export function isSimpleExpression(expression: tstl.Expression): boolean { } return true; } + +export function getAbsoluteImportPath( + relativePath: string, + directoryPath: string, + options: ts.CompilerOptions +): string { + if (relativePath.charAt(0) !== "." && options.baseUrl) { + return path.resolve(options.baseUrl, relativePath); + } + + return path.resolve(directoryPath, relativePath); +} + +export function getImportPath( + fileName: string, + relativePath: string, + node: ts.Node, + options: ts.CompilerOptions +): string { + const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve("."); + + const absoluteImportPath = path.format( + path.parse(getAbsoluteImportPath(relativePath, path.dirname(fileName), options)) + ); + const absoluteRootDirPath = path.format(path.parse(rootDir)); + if (absoluteImportPath.includes(absoluteRootDirPath)) { + return formatPathToLuaPath(absoluteImportPath.replace(absoluteRootDirPath, "").slice(1)); + } else { + throw TSTLErrors.UnresolvableRequirePath( + node, + `Cannot create require path. Module does not exist within --rootDir`, + relativePath + ); + } +} + +export function getExportPath(fileName: string, options: ts.CompilerOptions): string { + const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve("."); + + const absolutePath = path.resolve(fileName.replace(/.ts$/, "")); + const absoluteRootDirPath = path.format(path.parse(rootDir)); + return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1)); +} + +export function formatPathToLuaPath(filePath: string): string { + filePath = filePath.replace(/\.json$/, ""); + if (process.platform === "win32") { + // Windows can use backslashes + filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, "."); + } + return filePath.replace(/\.\//g, "").replace(/\//g, "."); +} diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index cc351f2b7..94edfba79 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -7,9 +7,6 @@ const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ? export const CouldNotCast = (castName: string) => new Error(`Failed to cast all elements to expected type using ${castName}.`); -export const DefaultImportsNotSupported = (node: ts.Node) => - new TranspileError(`Default Imports are not supported, please use named imports instead!`, node); - export const ForbiddenEllipsisDestruction = (node: ts.Node) => new TranspileError(`Ellipsis destruction is not allowed.`, node); @@ -100,9 +97,6 @@ export const UndefinedTypeNode = (node: ts.Node) => new TranspileError("Failed t export const UnknownSuperType = (node: ts.Node) => new TranspileError("Unable to resolve type of super expression.", node); -export const UnsupportedDefaultExport = (node: ts.Node) => - new TranspileError(`Default exports are not supported.`, node); - export const UnsupportedImportType = (node: ts.Node) => new TranspileError(`Unsupported import type.`, node); export const UnsupportedKind = (description: string, kind: ts.SyntaxKind, node: ts.Node) => diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 5134067b7..1326523d0 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -62,16 +62,6 @@ describe("module import/export elision", () => { }); }); -test.each([ - "export { default } from '...'", - "export { x as default } from '...';", - "export { default as x } from '...';", -])("Export default keyword disallowed (%p)", exportStatement => { - expect(() => util.transpileString(exportStatement)).toThrowExactError( - TSTLErrors.UnsupportedDefaultExport(util.nodeStub) - ); -}); - test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌͼƛಠ", "_̀ः٠‿"])( "Import module names with invalid lua identifier characters (%p)", name => { @@ -89,12 +79,6 @@ test.each(["ke-bab", "dollar$", "singlequote'", "hash#", "s p a c e", "ɥɣɎɌ } ); -test("defaultImport", () => { - expect(() => { - util.transpileString(`import TestClass from "test"`); - }).toThrowExactError(TSTLErrors.DefaultImportsNotSupported(util.nodeStub)); -}); - test("lualibRequire", () => { const lua = util.transpileString(`let a = b instanceof c;`, { luaLibImport: tstl.LuaLibImportKind.Require, diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 3f864b3fc..37861bc59 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -187,3 +187,154 @@ test("ImportEquals declaration require", () => { expect(match[1]).toBe("foo.bar"); } }); + +test.only("Export Default From", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + export { default } from "./module"; + `, + "module.ts": ` + export const value = true; + export default value; + `, + }, + "default" + ); + + expect(result).toBe(true); +}); + +test.only.each(["export default value;", "export { value as default };"])( + "Default Import and Export (%p)", + exportStatement => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + export * from "./module"; + `, + "module.ts": ` + export const value = true; + ${exportStatement}; + `, + }, + "value" + ); + + expect(result).toBe(true); + } +); + +test.only("Default Import and Export Expression", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import defaultExport from "./module"; + export const value = defaultExport; + `, + "module.ts": ` + export default 1 + 2 + 3; + `, + }, + "value" + ); + + expect(result).toBe(6); +}); + +test.only("Import and Export Assignment", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import * as m from "./module"; + export const value = m; + `, + "module.ts": ` + export = true; + `, + }, + "value" + ); + + expect(result).toBe(true); +}); + +test.only("Mixed Exports, Default and Named Imports", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import defaultExport, { a, b, c } from "./module"; + export const value = defaultExport + b + c; + `, + "module.ts": ` + export const a = 1; + export default a; + export const b = 2; + export const c = 3; + `, + }, + "value" + ); + + expect(result).toBe(6); +}); + +test.only("Mixed Exports, Default and Namespace Import", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import defaultExport, * as ns from "./module"; + export const value = defaultExport + ns.b + ns.c; + `, + "module.ts": ` + export const a = 1; + export default a; + export const b = 2; + export const c = 3; + `, + }, + "value" + ); + + expect(result).toBe(6); +}); + +test.only("Export Default Function", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import defaultExport from "./module"; + export const value = defaultExport(); + `, + "module.ts": ` + export default function() { + return true; + } + `, + }, + "value" + ); + + expect(result).toBe(true); +}); + +test.only("Export Default Class", () => { + const result = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import defaultExport from "./module"; + export const value = defaultExport.method(); + `, + "module.ts": ` + export default class Test { + static method() { + return true; + } + } + `, + }, + "value" + ); + + expect(result).toBe(true); +}); diff --git a/test/util.ts b/test/util.ts index 6c314780b..0fdef0f9e 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,3 +1,4 @@ +import * as tsHelper from "../src/TSHelper"; import { lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; import * as fs from "fs"; import * as path from "path"; @@ -110,6 +111,36 @@ export function transpileAndExecute( return executeLua(lua); } +export function transpileAndExecuteProjectReturningMainExport( + typeScriptFiles: Record, + exportName: string, + options: tstl.CompilerOptions = {} +): any { + const mainFile = Object.keys(typeScriptFiles).find(typeScriptFileName => typeScriptFileName === "main.ts"); + if (!mainFile) { + throw new Error("An entry point file needs to be specified. This should be called main.ts"); + } + + const joinedTranspiledFiles = Object.keys(typeScriptFiles) + .filter(typeScriptFileName => typeScriptFileName !== "main.ts") + .map(typeScriptFileName => { + const modulePath = tsHelper.getExportPath(typeScriptFileName, options); + const tsCode = typeScriptFiles[typeScriptFileName]; + const luaCode = transpileString(tsCode, options); + return `package.preload["${modulePath}"] = function() + ${luaCode} + end`; + }) + .join("\n"); + + const luaCode = `return (function() + ${joinedTranspiledFiles} + ${transpileString(typeScriptFiles[mainFile])} + end)().${exportName}`; + + return executeLua(luaCode); +} + export function transpileExecuteAndReturnExport( tsStr: string, returnExport: string, From 289f2c2e9bb35baa6978bdb4c30d4d0d6591a35d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 16 Jul 2019 07:28:54 +1000 Subject: [PATCH 02/19] Add full import and export support --- src/LuaTransformer.ts | 281 +++++++++--------- src/TSHelper.ts | 42 +++ .../__snapshots__/transformation.spec.ts.snap | 11 + .../transformation/exportEquals.ts | 1 + .../unusedDefaultWithNamespaceImport.ts | 2 + test/unit/modules.spec.ts | 1 - test/unit/require.spec.ts | 32 +- test/util.ts | 14 +- 8 files changed, 228 insertions(+), 156 deletions(-) create mode 100644 test/translation/transformation/exportEquals.ts create mode 100644 test/translation/transformation/unusedDefaultWithNamespaceImport.ts diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index d7ee76965..418f16718 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, LuaTarget } from "./CompilerOptions"; -import { Decorator, DecoratorKind } from "./Decorator"; +import { DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; import { LuaLibFeature } from "./LuaLib"; import * as tsHelper from "./TSHelper"; @@ -100,6 +100,7 @@ export class LuaTransformer { protected currentSourceFile!: ts.SourceFile; protected isModule!: boolean; + protected visitedExportEquals!: boolean; protected resolver!: EmitResolver; /** @internal */ @@ -132,12 +133,13 @@ export class LuaTransformer { this.popScope(); if (this.isModule) { + const exportsTable = !this.visitedExportEquals ? tstl.createTableExpression() : undefined; + // local exports = {} + // or + // local exports statements.unshift( - tstl.createVariableDeclarationStatement( - this.createExportsIdentifier(), - tstl.createTableExpression() - ) + tstl.createVariableDeclarationStatement(this.createExportsIdentifier(), exportsTable) ); // return exports @@ -244,6 +246,10 @@ export class LuaTransformer { // export = [expression]; // ____exports = [expression]; if (statement.isExportEquals) { + // Stop the creation of the exports table. + // This should be the only export of the module. + this.visitedExportEquals = true; + return tstl.createAssignmentStatement( this.createExportsIdentifier(), this.transformExpression(statement.expression), @@ -253,7 +259,7 @@ export class LuaTransformer { // export default [expression]; // ____exports.default = [expression]; - const defaultIdentifier = tstl.createStringLiteral("default"); + const defaultIdentifier = this.createDefaultExportStringLiteral(statement); return tstl.createAssignmentStatement( tstl.createTableIndexExpression(this.createExportsIdentifier(), defaultIdentifier), this.transformExpression(statement.expression), @@ -267,98 +273,108 @@ export class LuaTransformer { return undefined; } - const exportSpecifiers = statement.exportClause.elements.filter(e => - this.resolver.isValueAliasDeclaration(e) - ); + const exportSpecifiers = tsHelper.getExportable(statement.exportClause, this.resolver); + // export { ... }; if (statement.moduleSpecifier === undefined) { - return exportSpecifiers.map(specifier => { - const isDefaultExport = - (specifier.name !== undefined && - specifier.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) || - (specifier.propertyName !== undefined && - specifier.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword); - - let exportedExpression: tstl.Expression | undefined; - if (specifier.propertyName !== undefined) { - exportedExpression = this.transformIdentifier(specifier.propertyName); - } else { - const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(specifier); - exportedExpression = this.createShorthandIdentifier(exportedSymbol, specifier.name); - } - - return tstl.createAssignmentStatement( - this.createExportedIdentifier( - isDefaultExport ? undefined : this.transformIdentifier(specifier.name) - ), - exportedExpression - ); - }); + return exportSpecifiers.map(exportSpecifier => this.transformExportSpecifier(exportSpecifier)); } - // First transpile as import clause - const importClause = ts.createImportClause( - undefined, - ts.createNamedImports(exportSpecifiers.map(s => ts.createImportSpecifier(s.propertyName, s.name))) - ); - - const importDeclaration = ts.createImportDeclaration( - statement.decorators, - statement.modifiers, - importClause, - statement.moduleSpecifier - ); - - // Wrap in block to prevent imports from hoisting out of `do` statement - const block = ts.createBlock([importDeclaration]); - const result = this.transformBlock(block).statements; - - // Now the module is imported, add the imports to the export table - for (const specifier of exportSpecifiers) { - result.push( - tstl.createAssignmentStatement( - this.createExportedIdentifier(this.transformIdentifier(specifier.name)), - this.transformIdentifier(specifier.name) - ) - ); - } + // export { ... } from "..."; + return this.transformExportSpecifiersFrom(statement, statement.moduleSpecifier, exportSpecifiers); + } else { + // export * from "..."; + return this.transformExportAllFrom(statement); + } + } - // Wrap this in a DoStatement to prevent polluting the scope. - return tstl.createDoStatement(this.filterUndefined(result), statement); + public transformExportSpecifier(node: ts.ExportSpecifier): tstl.AssignmentStatement { + let exportedExpression: tstl.Expression | undefined; + if (node.propertyName !== undefined) { + exportedExpression = this.transformIdentifier(node.propertyName); } else { - if (statement.moduleSpecifier === undefined) { - throw TSTLErrors.InvalidExportDeclaration(statement); - } + const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(node); + exportedExpression = this.createShorthandIdentifier(exportedSymbol, node.name); + } - if (!this.resolver.moduleExportsSomeValue(statement.moduleSpecifier)) { - return undefined; - } + const isDefault = tsHelper.isDefaultExportSpecifier(node); + const identifierToExport = isDefault + ? this.createDefaultExportIdentifier(node) + : this.transformIdentifier(node.name); + const exportAssignmentLeftHandSide = this.createExportedIdentifier(identifierToExport); + + return tstl.createAssignmentStatement(exportAssignmentLeftHandSide, exportedExpression, node); + } - const moduleRequire = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral); - const tempModuleIdentifier = tstl.createIdentifier("____export"); + public transformExportSpecifiersFrom( + statement: ts.ExportDeclaration, + moduleSpecifier: ts.Expression, + exportSpecifiers: ts.ExportSpecifier[] + ): tstl.Statement { + // First transpile as import clause + const importClause = ts.createImportClause( + undefined, + ts.createNamedImports(exportSpecifiers.map(s => ts.createImportSpecifier(s.propertyName, s.name))) + ); - const declaration = tstl.createVariableDeclarationStatement(tempModuleIdentifier, moduleRequire); + const importDeclaration = ts.createImportDeclaration( + statement.decorators, + statement.modifiers, + importClause, + moduleSpecifier + ); - const forKey = tstl.createIdentifier("____exportKey"); - const forValue = tstl.createIdentifier("____exportValue"); + // Wrap in block to prevent imports from hoisting out of `do` statement + const block = ts.createBlock([importDeclaration]); + const result = this.transformBlock(block).statements; - const body = tstl.createBlock([ + // Now the module is imported, add the imports to the export table + for (const specifier of exportSpecifiers) { + result.push( tstl.createAssignmentStatement( - tstl.createTableIndexExpression(this.createExportsIdentifier(), forKey), - forValue - ), - ]); - - const pairsIdentifier = tstl.createIdentifier("pairs"); - const forIn = tstl.createForInStatement( - body, - [tstl.cloneIdentifier(forKey), tstl.cloneIdentifier(forValue)], - [tstl.createCallExpression(pairsIdentifier, [tstl.cloneIdentifier(tempModuleIdentifier)])] + this.createExportedIdentifier(this.transformIdentifier(specifier.name)), + this.transformIdentifier(specifier.name) + ) ); + } + + // Wrap this in a DoStatement to prevent polluting the scope. + return tstl.createDoStatement(this.filterUndefined(result), statement); + } + + public transformExportAllFrom(statement: ts.ExportDeclaration): tstl.Statement | undefined { + if (statement.moduleSpecifier === undefined) { + throw TSTLErrors.InvalidExportDeclaration(statement); + } - // Wrap this in a DoStatement to prevent polluting the scope. - return tstl.createDoStatement([declaration, forIn], statement); + if (!this.resolver.moduleExportsSomeValue(statement.moduleSpecifier)) { + return undefined; } + + const moduleRequire = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral); + const tempModuleIdentifier = tstl.createIdentifier("____export"); + + const declaration = tstl.createVariableDeclarationStatement(tempModuleIdentifier, moduleRequire); + + const forKey = tstl.createIdentifier("____exportKey"); + const forValue = tstl.createIdentifier("____exportValue"); + + const body = tstl.createBlock([ + tstl.createAssignmentStatement( + tstl.createTableIndexExpression(this.createExportsIdentifier(), forKey), + forValue + ), + ]); + + const pairsIdentifier = tstl.createIdentifier("pairs"); + const forIn = tstl.createForInStatement( + body, + [tstl.cloneIdentifier(forKey), tstl.cloneIdentifier(forValue)], + [tstl.createCallExpression(pairsIdentifier, [tstl.cloneIdentifier(tempModuleIdentifier)])] + ); + + // Wrap this in a DoStatement to prevent polluting the scope. + return tstl.createDoStatement([declaration, forIn], statement); } public transformImportDeclaration(statement: ts.ImportDeclaration): StatementVisitResult { @@ -371,16 +387,7 @@ export class LuaTransformer { scope.importStatements = []; } - let shouldResolve = true; - const moduleOwnerSymbol = this.checker.getSymbolAtLocation(statement.moduleSpecifier); - if (moduleOwnerSymbol) { - const decorators = new Map(); - tsHelper.collectCustomDecorators(moduleOwnerSymbol, this.checker, decorators); - if (decorators.has(DecoratorKind.NoResolution)) { - shouldResolve = false; - } - } - + const shouldResolve = tsHelper.shouldResolveModulePath(statement.moduleSpecifier, this.checker); const moduleSpecifier = statement.moduleSpecifier as ts.StringLiteral; const importPath = moduleSpecifier.text.replace(new RegExp('"', "g"), ""); const requireCall = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral, shouldResolve); @@ -415,16 +422,8 @@ export class LuaTransformer { // import defaultValue from "./module"; // local defaultValue = __module.default if (statement.importClause.name) { - const decorators = tsHelper.getCustomDecorators( - this.checker.getTypeAtLocation(statement.importClause), - this.checker - ); - if ( - this.resolver.isReferencedAliasDeclaration(statement.importClause) && - !decorators.has(DecoratorKind.Extension) && - !decorators.has(DecoratorKind.MetaExtension) - ) { - const propertyName = tstl.createStringLiteral("default", statement.importClause.name); + if (tsHelper.shouldBeImported(statement.importClause, this.checker, this.resolver)) { + const propertyName = this.createDefaultExportStringLiteral(statement.importClause.name); const defaultImportAssignmentStatement = tstl.createVariableDeclarationStatement( this.transformIdentifier(statement.importClause.name), tstl.createTableIndexExpression(importUniqueName, propertyName), @@ -432,8 +431,8 @@ export class LuaTransformer { ); result.push(defaultImportAssignmentStatement); + usingRequireStatement = true; } - usingRequireStatement = true; } // import * as module from "./module"; @@ -455,34 +454,14 @@ export class LuaTransformer { // local b = __module.b // local c = __module.c if (statement.importClause.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) { - statement.importClause.namedBindings.elements - .filter(importSpecifier => { - const decorators = tsHelper.getCustomDecorators( - this.checker.getTypeAtLocation(importSpecifier), - this.checker - ); - - return ( - this.resolver.isReferencedAliasDeclaration(importSpecifier) && - !decorators.has(DecoratorKind.Extension) && - !decorators.has(DecoratorKind.MetaExtension) - ); - }) - .forEach(importSpecifier => { - const leftIdentifier = this.transformIdentifier(importSpecifier.name); - const propertyName = this.transformPropertyName( - importSpecifier.propertyName ? importSpecifier.propertyName : importSpecifier.name - ); - - const importAssignmentStatement = tstl.createVariableDeclarationStatement( - leftIdentifier, - tstl.createTableIndexExpression(importUniqueName, propertyName), - statement.importClause - ); + const assignmentStatements = statement.importClause.namedBindings.elements + .filter(importSpecifier => tsHelper.shouldBeImported(importSpecifier, this.checker, this.resolver)) + .map(importSpecifier => this.transformImportSpecifier(importSpecifier, importUniqueName)); - result.push(importAssignmentStatement); - }); - usingRequireStatement = true; + if (assignmentStatements.length > 0) { + usingRequireStatement = true; + } + result.push(...assignmentStatements); } if (result.length === 0) { @@ -501,6 +480,22 @@ export class LuaTransformer { } } + protected transformImportSpecifier( + importSpecifier: ts.ImportSpecifier, + moduleTableName: tstl.Identifier + ): tstl.VariableDeclarationStatement { + const leftIdentifier = this.transformIdentifier(importSpecifier.name); + const propertyName = this.transformPropertyName( + importSpecifier.propertyName ? importSpecifier.propertyName : importSpecifier.name + ); + + return tstl.createVariableDeclarationStatement( + leftIdentifier, + tstl.createTableIndexExpression(moduleTableName, propertyName), + importSpecifier + ); + } + protected createModuleRequire(moduleSpecifier: ts.StringLiteral, resolveModule = true): tstl.CallExpression { const modulePathString = resolveModule ? tsHelper.getImportPath( @@ -835,7 +830,10 @@ export class LuaTransformer { statement.modifiers && statement.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); const defaultExportLeftHandSize = isDefaultExport - ? tstl.createTableIndexExpression(this.createExportsIdentifier(), tstl.createStringLiteral("default")) + ? tstl.createTableIndexExpression( + this.createExportsIdentifier(), + this.createDefaultExportStringLiteral(statement) + ) : undefined; const classVar = defaultExportLeftHandSize @@ -1981,7 +1979,10 @@ export class LuaTransformer { if (isDefaultExport) { return tstl.createAssignmentStatement( - tstl.createTableIndexExpression(this.createExportsIdentifier(), tstl.createStringLiteral("default")), + tstl.createTableIndexExpression( + this.createExportsIdentifier(), + this.createDefaultExportStringLiteral(functionDeclaration) + ), this.transformFunctionExpression(functionDeclaration) ); } else if (!name) { @@ -5125,12 +5126,10 @@ export class LuaTransformer { } protected createExportedIdentifier( - identifier?: tstl.Identifier, + identifier: tstl.Identifier, exportScope?: ts.SourceFile | ts.ModuleDeclaration ): tstl.AssignmentLeftHandSideExpression { - const stringLiteral = identifier - ? tstl.createStringLiteral(identifier.text) - : tstl.createStringLiteral("default"); + const stringLiteral = tstl.createStringLiteral(identifier.text); const exportTable = exportScope && ts.isModuleDeclaration(exportScope) @@ -5140,6 +5139,14 @@ export class LuaTransformer { return tstl.createTableIndexExpression(exportTable, stringLiteral); } + protected createDefaultExportIdentifier(original: ts.Node): tstl.Identifier { + return tstl.createIdentifier("default", original); + } + + protected createDefaultExportStringLiteral(original: ts.Node): tstl.StringLiteral { + return tstl.createStringLiteral("default", original); + } + protected getSymbolExportScope(symbol: ts.Symbol): ts.SourceFile | ts.ModuleDeclaration | undefined { const exportedDeclaration = tsHelper.getExportedSymbolDeclaration(symbol); if (!exportedDeclaration) { diff --git a/src/TSHelper.ts b/src/TSHelper.ts index e314a0382..624473dae 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -3,6 +3,7 @@ import * as path from "path"; import { Decorator, DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; import * as TSTLErrors from "./TSTLErrors"; +import { EmitResolver } from "./LuaTransformer"; export enum ContextType { None, @@ -55,6 +56,47 @@ export function getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.T return extendedTypeNode && checker.getTypeAtLocation(extendedTypeNode); } +export function getExportable(exportSpecifiers: ts.NamedExports, resolver: EmitResolver): ts.ExportSpecifier[] { + return exportSpecifiers.elements.filter(exportSpecifier => isExportable(exportSpecifier, resolver)); +} + +export function isExportable(exportSpecifier: ts.ExportSpecifier, resolver: EmitResolver): boolean { + return resolver.isValueAliasDeclaration(exportSpecifier); +} + +export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean { + return ( + (node.name !== undefined && node.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) || + (node.propertyName !== undefined && node.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) + ); +} + +export function shouldResolveModulePath(moduleSpecifier: ts.Expression, checker: ts.TypeChecker): boolean { + const moduleOwnerSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleOwnerSymbol) { + const decorators = new Map(); + collectCustomDecorators(moduleOwnerSymbol, checker, decorators); + if (decorators.has(DecoratorKind.NoResolution)) { + return false; + } + } + return true; +} + +export function shouldBeImported( + importNode: ts.ImportClause | ts.ImportSpecifier, + checker: ts.TypeChecker, + resolver: EmitResolver +): boolean { + const decorators = getCustomDecorators(checker.getTypeAtLocation(importNode), checker); + + return ( + resolver.isReferencedAliasDeclaration(importNode) && + !decorators.has(DecoratorKind.Extension) && + !decorators.has(DecoratorKind.MetaExtension) + ); +} + export function isFileModule(sourceFile: ts.SourceFile): boolean { return sourceFile.statements.some(isStatementExported); } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 7e5ae77f0..35b32e2d0 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -194,6 +194,12 @@ TestEnum.val3 = \\"baz\\" TestEnum.baz = \\"val3\\"" `; +exports[`Transformation (exportEquals) 1`] = ` +"local ____exports +____exports = true +return ____exports" +`; + exports[`Transformation (exportStatement) 1`] = ` "local ____exports = {} local xyz = 4 @@ -660,6 +666,11 @@ exports[`Transformation (typeAssert) 1`] = ` local test2 = 10" `; +exports[`Transformation (unusedDefaultWithNamespaceImport) 1`] = ` +"local x = require(\\"module\\") +local ____ = x" +`; + exports[`Transformation (while) 1`] = ` "local d = 10 while d > 0 do diff --git a/test/translation/transformation/exportEquals.ts b/test/translation/transformation/exportEquals.ts new file mode 100644 index 000000000..ba27c6482 --- /dev/null +++ b/test/translation/transformation/exportEquals.ts @@ -0,0 +1 @@ +export = true; diff --git a/test/translation/transformation/unusedDefaultWithNamespaceImport.ts b/test/translation/transformation/unusedDefaultWithNamespaceImport.ts new file mode 100644 index 000000000..5b66a4b22 --- /dev/null +++ b/test/translation/transformation/unusedDefaultWithNamespaceImport.ts @@ -0,0 +1,2 @@ +import def, * as x from "module"; +x; diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 1326523d0..53674df46 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,6 +1,5 @@ import * as ts from "typescript"; import * as tstl from "../../src"; -import * as TSTLErrors from "../../src/TSTLErrors"; import * as util from "../util"; describe("module import/export elision", () => { diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 37861bc59..663e2fc1f 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -188,8 +188,8 @@ test("ImportEquals declaration require", () => { } }); -test.only("Export Default From", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Export Default From", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` export { default } from "./module"; @@ -205,10 +205,10 @@ test.only("Export Default From", () => { expect(result).toBe(true); }); -test.only.each(["export default value;", "export { value as default };"])( +test.each(["export default value;", "export { value as default };"])( "Default Import and Export (%p)", exportStatement => { - const result = util.transpileAndExecuteProjectReturningMainExport( + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` export * from "./module"; @@ -225,8 +225,8 @@ test.only.each(["export default value;", "export { value as default };"])( } ); -test.only("Default Import and Export Expression", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Default Import and Export Expression", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport from "./module"; @@ -242,8 +242,8 @@ test.only("Default Import and Export Expression", () => { expect(result).toBe(6); }); -test.only("Import and Export Assignment", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Import and Export Assignment", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import * as m from "./module"; @@ -259,8 +259,8 @@ test.only("Import and Export Assignment", () => { expect(result).toBe(true); }); -test.only("Mixed Exports, Default and Named Imports", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Mixed Exports, Default and Named Imports", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport, { a, b, c } from "./module"; @@ -279,8 +279,8 @@ test.only("Mixed Exports, Default and Named Imports", () => { expect(result).toBe(6); }); -test.only("Mixed Exports, Default and Namespace Import", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Mixed Exports, Default and Namespace Import", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport, * as ns from "./module"; @@ -299,8 +299,8 @@ test.only("Mixed Exports, Default and Namespace Import", () => { expect(result).toBe(6); }); -test.only("Export Default Function", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Export Default Function", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport from "./module"; @@ -318,8 +318,8 @@ test.only("Export Default Function", () => { expect(result).toBe(true); }); -test.only("Export Default Class", () => { - const result = util.transpileAndExecuteProjectReturningMainExport( +test("Export Default Class", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport from "./module"; diff --git a/test/util.ts b/test/util.ts index 0fdef0f9e..f12f81d5a 100644 --- a/test/util.ts +++ b/test/util.ts @@ -115,7 +115,7 @@ export function transpileAndExecuteProjectReturningMainExport( typeScriptFiles: Record, exportName: string, options: tstl.CompilerOptions = {} -): any { +): [any, string] { const mainFile = Object.keys(typeScriptFiles).find(typeScriptFileName => typeScriptFileName === "main.ts"); if (!mainFile) { throw new Error("An entry point file needs to be specified. This should be called main.ts"); @@ -138,7 +138,17 @@ export function transpileAndExecuteProjectReturningMainExport( ${transpileString(typeScriptFiles[mainFile])} end)().${exportName}`; - return executeLua(luaCode); + try { + return [executeLua(luaCode), luaCode]; + } catch (err) { + throw new Error(` + Encountered an error when executing the following Lua code: + + ${luaCode} + + ${err} + `); + } } export function transpileExecuteAndReturnExport( From 19966028cfc3849e972fad185ec57eecab058a08 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 12:56:58 +1000 Subject: [PATCH 03/19] Fix spelling mistake --- src/LuaTransformer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 418f16718..1e44ae850 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -829,22 +829,22 @@ export class LuaTransformer { const isDefaultExport = statement.modifiers && statement.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); - const defaultExportLeftHandSize = isDefaultExport + const defaultExportLeftHandSide = isDefaultExport ? tstl.createTableIndexExpression( this.createExportsIdentifier(), this.createDefaultExportStringLiteral(statement) ) : undefined; - const classVar = defaultExportLeftHandSize - ? [tstl.createAssignmentStatement(defaultExportLeftHandSize, classTable, statement)] + const classVar = defaultExportLeftHandSide + ? [tstl.createAssignmentStatement(defaultExportLeftHandSide, classTable, statement)] : this.createLocalOrExportedOrGlobalDeclaration(className, classTable, statement); result.push(...classVar); - if (defaultExportLeftHandSize) { + if (defaultExportLeftHandSide) { // local localClassName = ____exports.default - result.push(tstl.createVariableDeclarationStatement(localClassName, defaultExportLeftHandSize)); + result.push(tstl.createVariableDeclarationStatement(localClassName, defaultExportLeftHandSide)); } else { const exportScope = this.getIdentifierExportScope(className); if (exportScope) { From 879db6b1ee356c2e8629ccf78bb0a77559f6137e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:01:54 +1000 Subject: [PATCH 04/19] Remove exportedIdentifier stringliteral change --- src/LuaTransformer.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1e44ae850..e3402349b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -5129,14 +5129,11 @@ export class LuaTransformer { identifier: tstl.Identifier, exportScope?: ts.SourceFile | ts.ModuleDeclaration ): tstl.AssignmentLeftHandSideExpression { - const stringLiteral = tstl.createStringLiteral(identifier.text); - const exportTable = exportScope && ts.isModuleDeclaration(exportScope) ? this.createModuleLocalNameIdentifier(exportScope) : this.createExportsIdentifier(); - - return tstl.createTableIndexExpression(exportTable, stringLiteral); + return tstl.createTableIndexExpression(exportTable, tstl.createStringLiteral(identifier.text)); } protected createDefaultExportIdentifier(original: ts.Node): tstl.Identifier { From 7db0fc1640b32a5849f5734eb25f0dd450bad294 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:16:07 +1000 Subject: [PATCH 05/19] Fix export default from use case --- src/LuaTransformer.ts | 10 +++------- test/unit/require.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e3402349b..2c21e6325 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -289,13 +289,9 @@ export class LuaTransformer { } public transformExportSpecifier(node: ts.ExportSpecifier): tstl.AssignmentStatement { - let exportedExpression: tstl.Expression | undefined; - if (node.propertyName !== undefined) { - exportedExpression = this.transformIdentifier(node.propertyName); - } else { - const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(node); - exportedExpression = this.createShorthandIdentifier(exportedSymbol, node.name); - } + const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(node); + const exportedIdentifier = node.propertyName ? node.propertyName : node.name; + const exportedExpression = this.createShorthandIdentifier(exportedSymbol, exportedIdentifier); const isDefault = tsHelper.isDefaultExportSpecifier(node); const identifierToExport = isDefault diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 663e2fc1f..f615a38c7 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -211,14 +211,14 @@ test.each(["export default value;", "export { value as default };"])( const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` - export * from "./module"; + export { default } from "./module"; `, "module.ts": ` export const value = true; ${exportStatement}; `, }, - "value" + "default" ); expect(result).toBe(true); From 5d8a015d7c2ac2fc6771ab56a9b8412a47b2917c Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:21:30 +1000 Subject: [PATCH 06/19] Move default export modifier checking to tsHelper --- src/LuaTransformer.ts | 7 ++----- src/TSHelper.ts | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 2c21e6325..ca4ca5a51 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -822,8 +822,7 @@ export class LuaTransformer { // [____exports.]className = {} const classTable: tstl.Expression = tstl.createTableExpression(); - const isDefaultExport = - statement.modifiers && statement.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); + const isDefaultExport = tsHelper.hasDefaultExportModifier(statement.modifiers); const defaultExportLeftHandSide = isDefaultExport ? tstl.createTableIndexExpression( @@ -1969,9 +1968,7 @@ export class LuaTransformer { } } - const isDefaultExport = - functionDeclaration.modifiers && - functionDeclaration.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); + const isDefaultExport = tsHelper.hasDefaultExportModifier(functionDeclaration.modifiers); if (isDefaultExport) { return tstl.createAssignmentStatement( diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 624473dae..648afb17d 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -71,6 +71,10 @@ export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean { ); } +export function hasDefaultExportModifier(modifiers?: ts.NodeArray): boolean { + return modifiers && modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); +} + export function shouldResolveModulePath(moduleSpecifier: ts.Expression, checker: ts.TypeChecker): boolean { const moduleOwnerSymbol = checker.getSymbolAtLocation(moduleSpecifier); if (moduleOwnerSymbol) { From 5ab02cf7db3f8f294a929c1757499cf15dd0f04b Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:25:04 +1000 Subject: [PATCH 07/19] Remove duplicate test --- test/unit/require.spec.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index f615a38c7..12346d1e2 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -188,25 +188,8 @@ test("ImportEquals declaration require", () => { } }); -test("Export Default From", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - export { default } from "./module"; - `, - "module.ts": ` - export const value = true; - export default value; - `, - }, - "default" - ); - - expect(result).toBe(true); -}); - test.each(["export default value;", "export { value as default };"])( - "Default Import and Export (%p)", + "Export Default From (%p)", exportStatement => { const [result] = util.transpileAndExecuteProjectReturningMainExport( { From 3654f93abd59fbae11948f0ae4f98783d1d5e7bf Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:28:07 +1000 Subject: [PATCH 08/19] Add export equals test, fix require test formatting --- test/unit/require.spec.ts | 50 +++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 12346d1e2..0c9854434 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -188,25 +188,22 @@ test("ImportEquals declaration require", () => { } }); -test.each(["export default value;", "export { value as default };"])( - "Export Default From (%p)", - exportStatement => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - export { default } from "./module"; - `, - "module.ts": ` - export const value = true; - ${exportStatement}; - `, - }, - "default" - ); +test.each(["export default value;", "export { value as default };"])("Export Default From (%p)", exportStatement => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + export { default } from "./module"; + `, + "module.ts": ` + export const value = true; + ${exportStatement}; + `, + }, + "default" + ); - expect(result).toBe(true); - } -); + expect(result).toBe(true); +}); test("Default Import and Export Expression", () => { const [result] = util.transpileAndExecuteProjectReturningMainExport( @@ -321,3 +318,20 @@ test("Export Default Class", () => { expect(result).toBe(true); }); + +test("Export Equals", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import * as module from "./module"; + export const value = module; + `, + "module.ts": ` + export = true; + `, + }, + "value" + ); + + expect(result).toBe(true); +}); From 995569a0716b0bdccb9eec62ddb8402fc3582a46 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:34:13 +1000 Subject: [PATCH 09/19] Fix hasDefaultExportModifier return type --- src/TSHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 648afb17d..dc9c973e9 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -72,7 +72,7 @@ export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean { } export function hasDefaultExportModifier(modifiers?: ts.NodeArray): boolean { - return modifiers && modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword); + return modifiers ? modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword) : false; } export function shouldResolveModulePath(moduleSpecifier: ts.Expression, checker: ts.TypeChecker): boolean { From 7d198f177814e8863e46ac932b331be2c3d89156 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 13:46:33 +1000 Subject: [PATCH 10/19] Enable export default to work on name-less classes --- src/LuaTransformer.ts | 16 ++++++++++------ test/unit/require.spec.ts | 13 +++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ca4ca5a51..a5b107f3c 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -564,10 +564,6 @@ export class LuaTransformer { ): StatementVisitResult { this.classStack.push(statement); - if (statement.name === undefined && nameOverride === undefined) { - throw TSTLErrors.MissingClassName(statement); - } - let className: tstl.Identifier; let classNameText: string; if (nameOverride !== undefined) { @@ -577,7 +573,15 @@ export class LuaTransformer { className = this.transformIdentifier(statement.name); classNameText = statement.name.text; } else { - throw TSTLErrors.MissingClassName(statement); + const isDefaultExport = tsHelper.hasDefaultExportModifier(statement.modifiers); + if (isDefaultExport) { + const left = this.createExportedIdentifier(this.createDefaultExportIdentifier(statement)); + const right = this.transformClassExpression(statement); + + return tstl.createAssignmentStatement(left, right, statement); + } else { + throw TSTLErrors.MissingClassName(statement); + } } const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(statement), this.checker); @@ -3399,7 +3403,7 @@ export class LuaTransformer { } } - public transformClassExpression(expression: ts.ClassExpression): ExpressionVisitResult { + public transformClassExpression(expression: ts.ClassLikeDeclaration): ExpressionVisitResult { const className = expression.name !== undefined ? this.transformIdentifier(expression.name) diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 0c9854434..9b4ca930a 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -298,20 +298,17 @@ test("Export Default Function", () => { expect(result).toBe(true); }); -test("Export Default Class", () => { +test.each([ + "export default class Test { static method() { return true; } }", + "export default class { static method() { return true; } }", +])("Export Default Class", classDeclarationStatement => { const [result] = util.transpileAndExecuteProjectReturningMainExport( { "main.ts": ` import defaultExport from "./module"; export const value = defaultExport.method(); `, - "module.ts": ` - export default class Test { - static method() { - return true; - } - } - `, + "module.ts": classDeclarationStatement, }, "value" ); From 8ff268e909231e626e36001487bd1fe6be61ecd9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 19:38:09 +1000 Subject: [PATCH 11/19] Fix public and protected method signatures for import and export methods --- src/LuaTransformer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index a5b107f3c..4c4df7764 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -302,7 +302,7 @@ export class LuaTransformer { return tstl.createAssignmentStatement(exportAssignmentLeftHandSide, exportedExpression, node); } - public transformExportSpecifiersFrom( + protected transformExportSpecifiersFrom( statement: ts.ExportDeclaration, moduleSpecifier: ts.Expression, exportSpecifiers: ts.ExportSpecifier[] @@ -338,7 +338,7 @@ export class LuaTransformer { return tstl.createDoStatement(this.filterUndefined(result), statement); } - public transformExportAllFrom(statement: ts.ExportDeclaration): tstl.Statement | undefined { + protected transformExportAllFrom(statement: ts.ExportDeclaration): tstl.Statement | undefined { if (statement.moduleSpecifier === undefined) { throw TSTLErrors.InvalidExportDeclaration(statement); } @@ -476,7 +476,7 @@ export class LuaTransformer { } } - protected transformImportSpecifier( + public transformImportSpecifier( importSpecifier: ts.ImportSpecifier, moduleTableName: tstl.Identifier ): tstl.VariableDeclarationStatement { From 4d2be0492127305ace39b0788e36d3c8eb571e3c Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 19:46:31 +1000 Subject: [PATCH 12/19] Make default class declaration expressions use default as name --- src/LuaTransformer.ts | 4 ++++ test/unit/require.spec.ts | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 4c4df7764..d68bddf72 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3404,9 +3404,13 @@ export class LuaTransformer { } public transformClassExpression(expression: ts.ClassLikeDeclaration): ExpressionVisitResult { + const isDefaultExport = tsHelper.hasDefaultExportModifier(expression.modifiers); + const className = expression.name !== undefined ? this.transformIdentifier(expression.name) + : isDefaultExport + ? this.createDefaultExportIdentifier(expression) : tstl.createAnonymousIdentifier(); const classDeclaration = this.transformClassDeclaration(expression, className); diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 9b4ca930a..2a4d6f794 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -316,6 +316,23 @@ test.each([ expect(result).toBe(true); }); +test("Class exported by default has name `default`", () => { + const [result] = util.transpileAndExecuteProjectReturningMainExport( + { + "main.ts": ` + import moduleClass from "./module"; + export const value = moduleClass.name; + `, + "module.ts": ` + export default class {} + `, + }, + "value" + ); + + expect(result).toBe("default"); +}); + test("Export Equals", () => { const [result] = util.transpileAndExecuteProjectReturningMainExport( { From f7b5f023050bcc7ee0eac1c2932a899f067eea00 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 19:54:56 +1000 Subject: [PATCH 13/19] Make export equals transform to a local statement --- src/LuaTransformer.ts | 18 ++++++++++-------- .../__snapshots__/transformation.spec.ts.snap | 3 +-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index d68bddf72..cde35dc11 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -133,14 +133,16 @@ export class LuaTransformer { this.popScope(); if (this.isModule) { - const exportsTable = !this.visitedExportEquals ? tstl.createTableExpression() : undefined; - + // If export equals was not used. Create the exports table. // local exports = {} - // or - // local exports - statements.unshift( - tstl.createVariableDeclarationStatement(this.createExportsIdentifier(), exportsTable) - ); + if (!this.visitedExportEquals) { + statements.unshift( + tstl.createVariableDeclarationStatement( + this.createExportsIdentifier(), + tstl.createTableExpression() + ) + ); + } // return exports statements.push(tstl.createReturnStatement([this.createExportsIdentifier()])); @@ -250,7 +252,7 @@ export class LuaTransformer { // This should be the only export of the module. this.visitedExportEquals = true; - return tstl.createAssignmentStatement( + return tstl.createVariableDeclarationStatement( this.createExportsIdentifier(), this.transformExpression(statement.expression), statement diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 35b32e2d0..b292e6b6c 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -195,8 +195,7 @@ TestEnum.baz = \\"val3\\"" `; exports[`Transformation (exportEquals) 1`] = ` -"local ____exports -____exports = true +"local ____exports = true return ____exports" `; From b0bc74d35615f62aa1c456434624857b745dee3d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 20:01:40 +1000 Subject: [PATCH 14/19] Add undefined condition if export equals is ellidable --- src/LuaTransformer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index cde35dc11..510739d3d 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -245,6 +245,10 @@ export class LuaTransformer { } public transformExportAssignment(statement: ts.ExportAssignment): StatementVisitResult { + if (!this.resolver.isValueAliasDeclaration(statement)) { + return undefined; + } + // export = [expression]; // ____exports = [expression]; if (statement.isExportEquals) { From 16396e805068e77df9ca6e3008158057a9403c1e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 20:02:39 +1000 Subject: [PATCH 15/19] Inline isExportable with getExportable --- src/TSHelper.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index dc9c973e9..fa4d74594 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -57,11 +57,7 @@ export function getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.T } export function getExportable(exportSpecifiers: ts.NamedExports, resolver: EmitResolver): ts.ExportSpecifier[] { - return exportSpecifiers.elements.filter(exportSpecifier => isExportable(exportSpecifier, resolver)); -} - -export function isExportable(exportSpecifier: ts.ExportSpecifier, resolver: EmitResolver): boolean { - return resolver.isValueAliasDeclaration(exportSpecifier); + return exportSpecifiers.elements.filter(exportSpecifier => resolver.isValueAliasDeclaration(exportSpecifier)); } export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean { From d6e2689db929522f569800936831a62b246fbc35 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 20:07:05 +1000 Subject: [PATCH 16/19] Move visitedExportEquals to setupState --- src/LuaTransformer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 510739d3d..631794155 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -79,6 +79,8 @@ export class LuaTransformer { protected genVarCounter!: number; protected luaLibFeatureSet!: Set; + protected visitedExportEquals!: boolean; + protected scopeStack!: Scope[]; protected classStack!: ts.ClassLikeDeclaration[]; @@ -90,6 +92,8 @@ export class LuaTransformer { this.genVarCounter = 0; this.luaLibFeatureSet = new Set(); + this.visitedExportEquals = false; + this.scopeStack = []; this.classStack = []; @@ -100,7 +104,6 @@ export class LuaTransformer { protected currentSourceFile!: ts.SourceFile; protected isModule!: boolean; - protected visitedExportEquals!: boolean; protected resolver!: EmitResolver; /** @internal */ From e889875797a5d9648d04badf2ddbcad0954a8f52 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 17 Jul 2019 20:35:30 +1000 Subject: [PATCH 17/19] Merge class tests and ensure classes retain their names --- test/unit/require.spec.ts | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 2a4d6f794..216f8fbc6 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -299,38 +299,21 @@ test("Export Default Function", () => { }); test.each([ - "export default class Test { static method() { return true; } }", - "export default class { static method() { return true; } }", -])("Export Default Class", classDeclarationStatement => { + ["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.method(); + export const value = defaultExport.name; `, "module.ts": classDeclarationStatement, }, "value" ); - expect(result).toBe(true); -}); - -test("Class exported by default has name `default`", () => { - const [result] = util.transpileAndExecuteProjectReturningMainExport( - { - "main.ts": ` - import moduleClass from "./module"; - export const value = moduleClass.name; - `, - "module.ts": ` - export default class {} - `, - }, - "value" - ); - - expect(result).toBe("default"); + expect(result).toBe(expectedClassName); }); test("Export Equals", () => { From e3383ba08fa14a0e0520202fe1cc6c1b22f80516 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 19 Jul 2019 18:05:45 +1000 Subject: [PATCH 18/19] Change transformExportSpecifier to protected --- src/LuaTransformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 631794155..1ee4eaca6 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -297,7 +297,7 @@ export class LuaTransformer { } } - public transformExportSpecifier(node: ts.ExportSpecifier): tstl.AssignmentStatement { + protected transformExportSpecifier(node: ts.ExportSpecifier): tstl.AssignmentStatement { const exportedSymbol = this.checker.getExportSpecifierLocalTargetSymbol(node); const exportedIdentifier = node.propertyName ? node.propertyName : node.name; const exportedExpression = this.createShorthandIdentifier(exportedSymbol, exportedIdentifier); From f192edbebc4e6467813771ccc5e3a50e820a8a79 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 21 Jul 2019 10:00:13 +1000 Subject: [PATCH 19/19] Make importSpecifier protected and use if statement instead of nested ternary --- src/LuaTransformer.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ca406a496..a276e971b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -485,7 +485,7 @@ export class LuaTransformer { } } - public transformImportSpecifier( + protected transformImportSpecifier( importSpecifier: ts.ImportSpecifier, moduleTableName: tstl.Identifier ): tstl.VariableDeclarationStatement { @@ -3415,12 +3415,14 @@ export class LuaTransformer { public transformClassExpression(expression: ts.ClassLikeDeclaration): ExpressionVisitResult { const isDefaultExport = tsHelper.hasDefaultExportModifier(expression.modifiers); - const className = - expression.name !== undefined - ? this.transformIdentifier(expression.name) - : isDefaultExport - ? this.createDefaultExportIdentifier(expression) - : tstl.createAnonymousIdentifier(); + let className: tstl.Identifier; + if (expression.name) { + className = this.transformIdentifier(expression.name); + } else if (isDefaultExport) { + className = this.createDefaultExportIdentifier(expression); + } else { + className = tstl.createAnonymousIdentifier(); + } const classDeclaration = this.transformClassDeclaration(expression, className); return this.createImmediatelyInvokedFunctionExpression(