diff --git a/src/Compiler.ts b/src/Compiler.ts index 1afc69425..319226a96 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -95,7 +95,7 @@ export function compileFilesWithOptions(fileNames: string[], options: CompilerOp }); // Copy lualib to target dir - if (options.luaLibImport === LuaLibImportKind.Require) { + if (options.luaLibImport === LuaLibImportKind.Require || options.luaLibImport === LuaLibImportKind.Always) { fs.copyFileSync( path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), path.join(options.outDir, "lualib_bundle.lua") diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 7320fe7d5..5cb0aa66f 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -44,9 +44,10 @@ export enum LuaLibFeature { } export enum LuaLibImportKind { + None = "none", + Always = "always", Inline = "inline", Require = "require", - None = "none", } interface ExportInfo { @@ -140,6 +141,11 @@ export abstract class LuaTranspiler { } public importLuaLibFeature(feature: LuaLibFeature): void { + // Add additional lib requirements + if (feature === LuaLibFeature.Map || feature === LuaLibFeature.Set) { + this.luaLibFeatureSet.add(LuaLibFeature.InstanceOf); + } + // TODO inline imported features in output i option set this.luaLibFeatureSet.add(feature); } @@ -177,18 +183,17 @@ export abstract class LuaTranspiler { "-- https://github.com/Perryvw/TypescriptToLua\n"; } let result = header; - if (this.options.luaLibImport === LuaLibImportKind.Require) { + + // Transpile content first to gather some info on dependencies + let fileStatements = ""; + this.exportStack.push([]); + this.sourceFile.statements.forEach(s => fileStatements += this.transpileNode(s)); + + if ((this.options.luaLibImport === LuaLibImportKind.Require && this.luaLibFeatureSet.size > 0) + || this.options.luaLibImport === LuaLibImportKind.Always) { // require helper functions result += `require("lualib_bundle")\n`; } - if (this.isModule) { - // Shadow exports if it already exists - result += "local exports = exports or {}\n"; - } - - // Transpile content statements - this.exportStack.push([]); - this.sourceFile.statements.forEach(s => result += this.transpileNode(s)); // Inline lualib features if (this.options.luaLibImport === LuaLibImportKind.Inline) { @@ -199,6 +204,14 @@ export abstract class LuaTranspiler { } } + if (this.isModule) { + // Shadow exports if it already exists + result += "local exports = exports or {}\n"; + } + + // Add file systems after imports since order matters in Lua + result += fileStatements; + // Exports result += this.makeExports(); @@ -935,6 +948,8 @@ export abstract class LuaTranspiler { const name = this.transpileExpression(node.expression); const params = node.arguments ? this.transpileArguments(node.arguments, ts.createTrue()) : "true"; + this.checkForLuaLibType(this.checker.getTypeAtLocation(node)); + return `${name}.new(${params})`; } @@ -1146,6 +1161,8 @@ export abstract class LuaTranspiler { } } + this.checkForLuaLibType(type); + // Do not output path for member only enums if (tsHelper.isCompileMembersOnlyEnum(type, this.checker)) { return property; @@ -1673,4 +1690,17 @@ export abstract class LuaTranspiler { return result; } + + public checkForLuaLibType(type: ts.Type): void { + if (type.symbol) { + switch (this.checker.getFullyQualifiedName(type.symbol)) { + case "Map": + this.importLuaLibFeature(LuaLibFeature.Map); + return; + case "Set": + this.importLuaLibFeature(LuaLibFeature.Set); + return; + } + } + } } diff --git a/test/runner.ts b/test/runner.ts index dfb723b50..0d5d542fc 100644 --- a/test/runner.ts +++ b/test/runner.ts @@ -1,6 +1,9 @@ import { TestRunner, TestSet } from "alsatian"; import { TapBark } from "tap-bark"; +import * as fs from "fs"; +import * as path from "path"; + // create test set const testSet = TestSet.create(); @@ -10,6 +13,12 @@ testSet.addTestsFromFiles("./test/**/*.spec.ts"); // create a test runner const testRunner = new TestRunner(); +// Copy lualib to project root +fs.copyFileSync( + path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), + "lualib_bundle.lua" +); + // setup the output testRunner.outputStream // this will use alsatian's default output if you remove this @@ -19,8 +28,14 @@ testRunner.outputStream .pipe(process.stdout); // run the test set -testRunner.run(testSet); +testRunner.run(testSet) // this will be called after all tests have been run - // .then((results) => done()) + .then(result => { + // Remove lualib bundle again + fs.unlinkSync("lualib_bundle.lua"); + }) // this will be called if there was a problem - // .catch((error) => doSomethingWith(error)); \ No newline at end of file + .catch(error => { + // Remove lualib bundle again + fs.unlinkSync("lualib_bundle.lua"); + }); diff --git a/test/src/util.ts b/test/src/util.ts index 463172c22..e4d06b1c0 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -5,7 +5,7 @@ import { Expect } from "alsatian"; import { CompilerOptions } from "../../src/CommandLineParser"; import { createTranspiler } from "../../src/Compiler"; -import { LuaTarget, LuaTranspiler, TranspileError } from "../../src/Transpiler"; +import { LuaLibImportKind, LuaTarget, LuaTranspiler, TranspileError } from "../../src/Transpiler"; import {lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; @@ -13,7 +13,7 @@ const fs = require("fs"); const libSource = fs.readFileSync(path.join(path.dirname(require.resolve("typescript")), "lib.es6.d.ts")).toString(); -export function transpileString(str: string, options: CompilerOptions = { luaLibImport: "none", luaTarget: LuaTarget.Lua53 }): string { +export function transpileString(str: string, options: CompilerOptions = { luaLibImport: LuaLibImportKind.Require, luaTarget: LuaTarget.Lua53 }): string { const compilerHost = { directoryExists: () => true, fileExists: (fileName): boolean => true, @@ -114,8 +114,6 @@ export function transpileAndExecute(ts: string): any { return executeLua(transpileString(ts)); } -const tslualib = fs.readFileSync("dist/lualib/lualib_bundle.lua") + "\n"; - const jsonlib = fs.readFileSync("test/src/json.lua") + "\n"; -export const minimalTestLib = tslualib + jsonlib; \ No newline at end of file +export const minimalTestLib = jsonlib; diff --git a/test/unit/lualib/inlining.spec.ts b/test/unit/lualib/inlining.spec.ts new file mode 100644 index 000000000..948278867 --- /dev/null +++ b/test/unit/lualib/inlining.spec.ts @@ -0,0 +1,50 @@ +import { Expect, Test, TestCase } from "alsatian"; +import * as util from "../../src/util"; + +import { LuaLibImportKind, LuaTarget } from "../../../src/Transpiler"; + +export class InliningTests { + @Test("map constructor") + public mapConstructor(): void { + const lua = util.transpileString(`let mymap = new Map(); return mymap.size;`, + { luaLibImport: LuaLibImportKind.Inline, luaTarget: LuaTarget.Lua53 }); + const result = util.executeLua(lua); + + Expect(result).toBe(0); + } + + @Test("map foreach keys") + public mapForEachKeys(): void { + const lua = util.transpileString( + `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); + let count = 0; + mymap.forEach((value, key) => { count += key; }); + return count;`, + { luaLibImport: LuaLibImportKind.Inline, luaTarget: LuaTarget.Lua53 }); + + const result = util.executeLua(lua); + Expect(result).toBe(18); + } + + @Test("set constructor") + public setConstructor(): void { + const lua = util.transpileString(`class abc {} let def = new abc(); let myset = new Set(); return myset.size;`, + { luaLibImport: LuaLibImportKind.Inline, luaTarget: LuaTarget.Lua53 }); + const result = util.executeLua(lua); + + Expect(result).toBe(0); + } + + @Test("set foreach keys") + public setForEachKeys(): void { + const lua = util.transpileString( + `let myset = new Set([2, 3, 4]); + let count = 0; + myset.forEach((value, key) => { count += key; }); + return count;`, + { luaLibImport: LuaLibImportKind.Inline, luaTarget: LuaTarget.Lua53 }); + + const result = util.executeLua(lua); + Expect(result).toBe(9); + } +} diff --git a/test/unit/lualib/map.spec.ts b/test/unit/lualib/map.spec.ts index dfb638783..0928451c1 100644 --- a/test/unit/lualib/map.spec.ts +++ b/test/unit/lualib/map.spec.ts @@ -3,7 +3,7 @@ import * as util from "../../src/util"; export class MapTests { @Test("map constructor") - public mapConstructor() { + public mapConstructor(): void { const lua = util.transpileString(`let mymap = new Map(); return mymap.size;`); const result = util.executeLua(lua); @@ -11,7 +11,7 @@ export class MapTests { } @Test("map iterable constructor") - public mapIterableConstructor() { + public mapIterableConstructor(): void { const lua = util.transpileString(`let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.has("a") && mymap.has("b");`); const result = util.executeLua(lua); @@ -20,7 +20,7 @@ export class MapTests { } @Test("map iterable constructor map") - public mapIterableConstructor2() { + public mapIterableConstructor2(): void { const lua = util.transpileString(`let mymap = new Map(new Map([["a", "c"],["b", "d"]])); return mymap.has("a") && mymap.has("b");`); const result = util.executeLua(lua); @@ -29,7 +29,7 @@ export class MapTests { } @Test("map clear") - public mapClear() { + public mapClear(): void { const mapTS = `let mymap = new Map([["a", "c"],["b", "d"]]); mymap.clear();`; const lua = util.transpileString(mapTS + `return mymap.size;`); const size = util.executeLua(lua); @@ -41,7 +41,7 @@ export class MapTests { } @Test("map delete") - public mapDelete() { + public mapDelete(): void { const mapTS = `let mymap = new Map([["a", "c"],["b", "d"]]); mymap.delete("a");`; const lua = util.transpileString(mapTS + `return mymap.has("b") && !mymap.has("a");`); const contains = util.executeLua(lua); @@ -49,7 +49,7 @@ export class MapTests { } @Test("map entries") - public mapEntries() { + public mapEntries(): void { const lua = util.transpileString(`let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (var [key, value] of mymap.entries()) { count += key + value; } @@ -59,7 +59,7 @@ export class MapTests { } @Test("map foreach") - public mapForEach() { + public mapForEach(): void { const lua = util.transpileString( `let mymap = new Map([["a", 2],["b", 3],["c", 4]]); let count = 0; @@ -72,7 +72,7 @@ export class MapTests { } @Test("map foreach keys") - public mapForEachKeys() { + public mapForEachKeys(): void { const lua = util.transpileString( `let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; @@ -85,42 +85,42 @@ export class MapTests { } @Test("map get") - public mapGet() { + public mapGet(): void { const lua = util.transpileString(`let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("a");`); const result = util.executeLua(lua); Expect(result).toBe("c"); } @Test("map get missing") - public mapGetMissing() { + public mapGetMissing(): void { const lua = util.transpileString(`let mymap = new Map([["a", "c"],["b", "d"]]); return mymap.get("c");`); const result = util.executeLua(lua); Expect(result).toBe(null); } @Test("map has") - public mapHas() { + public mapHas(): void { const lua = util.transpileString(`let mymap = new Map([["a", "c"]]); return mymap.has("a");`); const contains = util.executeLua(lua); Expect(contains).toBe(true); } @Test("map has false") - public mapHasFalse() { + public mapHasFalse(): void { const lua = util.transpileString(`let mymap = new Map(); return mymap.has("a");`); const contains = util.executeLua(lua); Expect(contains).toBe(false); } @Test("map has null") - public mapHasNull() { + public mapHasNull(): void { const lua = util.transpileString(`let mymap = new Map([["a", "c"]]); return mymap.has(null);`); const contains = util.executeLua(lua); Expect(contains).toBe(false); } @Test("map keys") - public mapKeys() { + public mapKeys(): void { const lua = util.transpileString(`let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (var key of mymap.keys()) { count += key; } @@ -130,7 +130,7 @@ export class MapTests { } @Test("map set") - public mapSet() { + public mapSet(): void { const mapTS = `let mymap = new Map(); mymap.set("a", 5);`; const lua = util.transpileString(mapTS + `return mymap.has("a");`); const has = util.executeLua(lua); @@ -142,7 +142,7 @@ export class MapTests { } @Test("map values") - public mapValues() { + public mapValues(): void { const lua = util.transpileString(`let mymap = new Map([[5, 2],[6, 3],[7, 4]]); let count = 0; for (var value of mymap.values()) { count += value; } @@ -152,7 +152,7 @@ export class MapTests { } @Test("map size") - public mapSize() { + public mapSize(): void { Expect(util.transpileAndExecute(`let m = new Map(); return m.size;`)).toBe(0); Expect(util.transpileAndExecute(`let m = new Map(); m.set(1,3); return m.size;`)).toBe(1); Expect(util.transpileAndExecute(`let m = new Map([[1,2],[3,4]]); return m.size;`)).toBe(2); diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 31716fbf5..62187e060 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,28 +1,48 @@ import { Expect, Test, TestCase } from "alsatian"; -import * as ts from "typescript"; +import { LuaLibImportKind, LuaTarget } from "../../src/Transpiler"; +import * as ts from "typescript"; import * as util from "../src/util"; export class LuaModuleTests { @Test("defaultImport") - public defaultImport() { + public defaultImport(): void { Expect(() => { const lua = util.transpileString(`import TestClass from "test"`); }).toThrowError(Error, "Default Imports are not supported, please use named imports instead!"); } @Test("lualibRequire") - public lualibRequire() { + public lualibRequire(): void { + // Transpile + const lua = util.transpileString(`let a = b instanceof c;`, + { luaLibImport: LuaLibImportKind.Require, luaTarget: LuaTarget.LuaJIT }); + + // Assert + Expect(lua.startsWith(`require("lualib_bundle")`)); + } + + @Test("lualibRequireNoUses") + public lualibRequireNoUses(): void { + // Transpile + const lua = util.transpileString(``, { luaLibImport: LuaLibImportKind.Require, luaTarget: LuaTarget.LuaJIT }); + + // Assert + Expect(lua).toBe(``); + } + + @Test("lualibRequireAlways") + public lualibRequireAlways(): void { // Transpile - const lua = util.transpileString(``, {luaLibImport: "require", luaTarget: "JIT"}); + const lua = util.transpileString(``, { luaLibImport: LuaLibImportKind.Always, luaTarget: LuaTarget.LuaJIT }); // Assert Expect(lua).toBe(`require("lualib_bundle")`); } @Test("Import named bindings exception") - public namedBindigsException() { + public namedBindigsException(): void { const transpiler = util.makeTestTranspiler(); const mockDeclaration: any = { @@ -36,7 +56,7 @@ export class LuaModuleTests { } @Test("Non-exported module") - public nonExportedModule() { + public nonExportedModule(): void { const lua = util.transpileString("module g { export function test() { return 3; } } return g.test();"); const result = util.executeLua(lua);