diff --git a/.gitignore b/.gitignore index 4177f880c..b72fddcab 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ coverage/ # IDEA IDEs .idea/ + +typescript_lualib.lua diff --git a/README.md b/README.md index 476c97299..cd56bcef3 100644 --- a/README.md +++ b/README.md @@ -20,30 +20,6 @@ More detailed documentation and info on writing declarations can be found [on th `tstl -p path/to/tsconfig.json` -**Options** -``` -tstl [options] [files...] - -In addition to the options listed below you can also pass options for the -typescript compiler (For a list of options use tsc -h). - -NOTES: -- The tsc options might have no effect. -- Options in tsconfig.json are prioritized. - -Options: - --help Show help [boolean] - --version Show version number [boolean] - --lt, --luaTarget Specify Lua target version. - [string] [choices: "JIT", "5.1", "5.2", "5.3"] [default: "JIT"] - --ah, --addHeader Specify if a header will be added to compiled files. - [boolean] [default: true] - -Examples: - tstl path/to/file.ts [...] Compile files - tstl -p path/to/tsconfig.json Compile project -``` - **Example tsconfig.json** ``` { diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts new file mode 100644 index 000000000..bb40ebf28 --- /dev/null +++ b/src/CommandLineParser.ts @@ -0,0 +1,173 @@ +import * as ts from "typescript"; +import * as yargs from 'yargs'; +import * as fs from "fs"; +import * as path from "path"; + +// ES6 syntax broken +const dedent = require("dedent"); + +export interface CompilerOptions extends ts.CompilerOptions { + addHeader?: boolean; + luaTarget?: string; + dontRequireLuaLib?: boolean; +} + +export interface ParsedCommandLine extends ts.ParsedCommandLine { + options: CompilerOptions; +} + +export class CLIError extends Error { + +} + +const optionDeclarations: { [key: string]: yargs.Options } = { + 'luaTarget': { + alias: 'lt', + choices: ['JIT', '5.3'], + default: 'JIT', + describe: 'Specify Lua target version.', + type: 'string' + }, + 'addHeader': { + alias: 'ah', + describe: 'Specify if a header will be added to compiled files.', + default: false, + type: 'boolean' + }, + 'dontRequireLuaLib': { + describe: 'Dont require lua library that enables advanced Typescipt/JS functionality.', + default: false, + type: 'boolean' + }, +}; + + +/** + * Pares the supplied arguments. + * The result will include arguments supplied via CLI and arguments from tsconfig. + */ +export function parseCommandLine(args: string[]): ParsedCommandLine { + const parsedArgs = yargs + .usage(dedent(`Syntax: tstl [options] [files...] + + In addition to the options listed below you can also pass options for the typescript compiler (For a list of options use tsc -h). + Some tsc options might have no effect.`)) + .example('tstl path/to/file.ts [...]', 'Compile files') + .example('tstl -p path/to/tsconfig.json', 'Compile project') + .wrap(yargs.terminalWidth()) + .options(optionDeclarations) + .fail((msg, err) => { + throw new CLIError(msg); + }) + .parse(args) + + let commandLine = ts.parseCommandLine(args); + + // Run diagnostics to check for invalid tsc/tstl options + runDiagnostics(commandLine); + + // Add TSTL options from CLI + addTSTLOptions(commandLine, parsedArgs); + + // Load config + if (commandLine.options.project) { + findConfigFile(commandLine); + let configPath = commandLine.options.project; + let configContents = fs.readFileSync(configPath).toString(); + const configJson = ts.parseConfigFileTextToJson(configPath, configContents); + commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options); + } + + // Add TSTL options from tsconfig + addTSTLOptions(commandLine); + + // Run diagnostics again to check for errors in tsconfig + runDiagnostics(commandLine); + + if (commandLine.options.project && !commandLine.options.rootDir) { + commandLine.options.rootDir = path.dirname(commandLine.options.project); + } + + if (!commandLine.options.rootDir) { + commandLine.options.rootDir = process.cwd(); + } + + if (!commandLine.options.outDir) { + commandLine.options.outDir = commandLine.options.rootDir; + } + + return commandLine; +} + +function addTSTLOptions(commandLine: ts.ParsedCommandLine, additionalArgs?: yargs.Arguments, forceOverride?: boolean) { + additionalArgs = additionalArgs ? additionalArgs : commandLine.raw + // Add compiler options that are ignored by TS parsers + if (additionalArgs) { + for (const arg in additionalArgs) { + // dont override, this will prioritize CLI over tsconfig. + if (optionDeclarations[arg] && (!commandLine.options[arg] || forceOverride)) { + commandLine.options[arg] = additionalArgs[arg]; + } + } + } +} + +/** Check the current state of the ParsedCommandLine for errors */ +function runDiagnostics(commandLine: ts.ParsedCommandLine) { + const tsInvalidCompilerOptionErrorCode = 5023; + + if (commandLine.errors.length !== 0) { + // Generate a list of valid option names and aliases + let optionNames: string[] = []; + for (let key in optionDeclarations) { + optionNames.push(key); + let alias = optionDeclarations[key].alias; + if (alias) { + if (typeof alias === "string") { + optionNames.push(alias); + } else { + optionNames.push(...alias); + } + } + } + + commandLine.errors.forEach((err) => { + let ignore = false; + // Ignore errors caused by tstl specific compiler options + if (err.code == tsInvalidCompilerOptionErrorCode) { + for (const optionName of optionNames) { + if (err.messageText.toString().indexOf(optionName) !== -1) { + ignore = true; + } + } + if (!ignore) { + throw new CLIError(`error TS${err.code}: ${err.messageText}`); + } + } + }); + } +} + +/** Find configFile, function from ts api seems to be broken? */ +function findConfigFile(commandLine: ts.ParsedCommandLine) { + if (!commandLine.options.project) { + return; + } + let configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project); + if (fs.statSync(configPath).isDirectory()) { + configPath = path.join(configPath, 'tsconfig.json'); + } else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") { + // Search for tsconfig upwards in directory hierarchy starting from the file path + let dir = path.dirname(configPath).split(path.sep); + for (let i = dir.length; i > 0; i--) { + const searchPath = dir.slice(0, i).join("/") + path.sep + "tsconfig.json"; + + // If tsconfig.json was found, stop searching + if (ts.sys.fileExists(searchPath)) { + configPath = searchPath; + break; + } + } + } + commandLine.options.project = configPath; +} diff --git a/src/Compiler.ts b/src/Compiler.ts index 6f0e9ce17..71e04230f 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -3,18 +3,10 @@ import * as ts from "typescript"; import * as fs from "fs"; import * as path from "path"; -import * as yargs from 'yargs' - -// ES6 syntax broken -import dedent = require("dedent") import { LuaTranspiler, TranspileError } from "./Transpiler"; import { TSHelper as tsEx } from "./TSHelper"; - -interface CompilerOptions extends ts.CompilerOptions { - addHeader?: boolean; - luaTarget?: string; -} +import { CompilerOptions, parseCommandLine } from "./CommandLineParser"; function compile(fileNames: string[], options: CompilerOptions): void { let program = ts.createProgram(fileNames, options); @@ -39,14 +31,6 @@ function compile(fileNames: string[], options: CompilerOptions): void { process.exit(1); } - if (!options.rootDir) { - options.rootDir = process.cwd(); - } - - if (!options.outDir) { - options.outDir = options.rootDir; - } - program.getSourceFiles().forEach(sourceFile => { if (!sourceFile.isDeclarationFile) { try { @@ -83,146 +67,13 @@ function compile(fileNames: string[], options: CompilerOptions): void { }); // Copy lualib to target dir - // This isnt run in sync because copyFileSync wont report errors. - fs.copyFile(path.resolve(__dirname, "../dist/lualib/typescript.lua"), path.join(options.outDir, "typescript_lualib.lua"), (err: NodeJS.ErrnoException) => { - if (err) { - console.log("ERROR: copying lualib to output."); - process.exit(1); - } - else { - process.exit(0); - } - }); -} - -function printAST(node: ts.Node, indent: number) { - let indentStr = ""; - for (let i = 0; i < indent; i++) indentStr += " "; - - console.log(indentStr + tsEx.enumName(node.kind, ts.SyntaxKind)); - node.forEachChild(child => printAST(child, indent + 1)); -} - -// Polyfill for report diagnostics -function logError(commandLine: ts.ParsedCommandLine, tstlOptionKeys: ReadonlyArray) { - const tsInvalidCompilerOptionErrorCode = 5023; - let ignoredErrorCount = 0; - - if (commandLine.errors.length !== 0) { - commandLine.errors.forEach((err) => { - // Ignore errors caused by tstl specific compiler options - if (err.code == tsInvalidCompilerOptionErrorCode) { - for (const key of tstlOptionKeys) { - if (err.messageText.toString().indexOf(key) != -1) { - ignoredErrorCount += 1; - } - } - } - else { - console.log(err.messageText); - } - }); - if (commandLine.errors.length > ignoredErrorCount) { - process.exit(1); - } - } + fs.copyFileSync(path.resolve(__dirname, "../dist/lualib/typescript.lua"), path.join(options.outDir, "typescript_lualib.lua")); } -function executeCommandLine(args: ReadonlyArray) { - const tstlOptions: {[key: string]: yargs.Options} = { - 'lt': { - alias: 'luaTarget', - choices: ['JIT', '5.1', '5.2', '5.3'], - default: 'JIT', - describe: 'Specify Lua target version.', - type: 'string' - }, - 'ah': { - alias: 'addHeader', - describe: 'Specify if a header will be added to compiled files.', - default: true, - type: 'boolean' - } - }; - - const tstlOptionKeys = []; - for (let key in tstlOptions) { - let optionName = key; - if (tstlOptions[key].alias) { - optionName = tstlOptions[key].alias as string; - } - - tstlOptionKeys.push(optionName); - } - - const argv = yargs - .usage(dedent(`tstl [options] [files...] - - In addition to the options listed below you can also pass options for the typescript compiler (For a list of options use tsc -h). - - NOTES: - - The tsc options might have no effect. - - Options in tsconfig.json are prioritized.`)) - .example('tstl path/to/file.ts [...]', 'Compile files') - .example('tstl -p path/to/tsconfig.json', 'Compile project') - .options(tstlOptions) - .argv; - - let commandLine = ts.parseCommandLine(args); - - logError(commandLine, tstlOptionKeys); - - // Add tstl CLI options - for (const key of tstlOptionKeys) { - commandLine.options[key] = argv[key]; - } - - let configPath; - if (commandLine.options.project) { - configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project); - if (fs.statSync(configPath).isDirectory()) { - configPath = path.join(configPath, 'tsconfig.json'); - } else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") { - // Search for tsconfig upwards in directory hierarchy starting from the file path - let dir = path.dirname(configPath).split(path.sep); - let found = false; - for (let i = dir.length; i > 0; i--) { - const searchPath = dir.slice(0, i).join("/") + path.sep + "tsconfig.json"; - - // If tsconfig.json was found, stop searching - if (ts.sys.fileExists(searchPath)) { - configPath = searchPath; - found = true; - break; - } - } - - if (!found) { - console.error("Tried to build project but could not find tsconfig.json!"); - process.exit(1); - } - } - commandLine.options.project = configPath; - let configContents = fs.readFileSync(configPath).toString(); - const configJson = ts.parseConfigFileTextToJson(configPath, configContents); - commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options); - - // Add compiler options that are ignored by TS parsers - // Options supplied in tsconfig are prioritized to allow for CLI defaults - for (const compilerOption in commandLine.raw.compilerOptions) { - if (tstlOptionKeys.indexOf(compilerOption) != -1) { - commandLine.options[compilerOption] = commandLine.raw.compilerOptions[compilerOption]; - } - } - } - - if (configPath && !commandLine.options.rootDir) { - commandLine.options.rootDir = path.dirname(configPath); - } - - logError(commandLine, tstlOptionKeys); - - compile(commandLine.fileNames, commandLine.options); +export function execCommandLine(argv?: string[]) { + argv = argv ? argv : process.argv.slice(2); + let commandLine = parseCommandLine(argv); + compile(commandLine.fileNames, commandLine.options) } -executeCommandLine(process.argv.slice(2)); +execCommandLine(); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 02d7da404..81102a627 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -53,32 +53,32 @@ export class TSHelper { } static isStringType(type: ts.Type): boolean { - return (type.flags & ts.TypeFlags.String) != 0 - || (type.flags & ts.TypeFlags.StringLike) != 0 - || (type.flags & ts.TypeFlags.StringLiteral) != 0 + return (type.flags & ts.TypeFlags.String) !== 0 + || (type.flags & ts.TypeFlags.StringLike) !== 0 + || (type.flags & ts.TypeFlags.StringLiteral) !== 0 } static isArrayType(type: ts.Type): boolean { - return (type.flags & ts.TypeFlags.Object) != 0 + return (type.flags & ts.TypeFlags.Object) !== 0 && (type).symbol && (type).symbol.escapedName == "Array"; } static isTupleType(type: ts.Type): boolean { - return (type.flags & ts.TypeFlags.Object) != 0 - && (type).typeArguments != undefined; + return (type.flags & ts.TypeFlags.Object) !== 0 + && (type).typeArguments !== undefined; } static isCompileMembersOnlyEnum(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol - && ((type.symbol.flags & ts.SymbolFlags.Enum) != 0) - && type.symbol.getDocumentationComment(checker)[0] != undefined + && ((type.symbol.flags & ts.SymbolFlags.Enum) !== 0) + && type.symbol.getDocumentationComment(checker)[0] !== undefined && this.hasCustomDecorator(type, checker, "!CompileMembersOnly"); } static isPureAbstractClass(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol - && ((type.symbol.flags & ts.SymbolFlags.Class) != 0) + && ((type.symbol.flags & ts.SymbolFlags.Class) !== 0) && this.hasCustomDecorator(type, checker, "!PureAbstract"); } @@ -90,13 +90,13 @@ export class TSHelper { static isPhantom(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol - && ((type.symbol.flags & ts.SymbolFlags.Namespace) != 0) + && ((type.symbol.flags & ts.SymbolFlags.Namespace) !== 0) && this.hasCustomDecorator(type, checker, "!Phantom"); } static isTupleReturnFunction(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol - && ((type.symbol.flags & ts.SymbolFlags.Function) != 0) + && ((type.symbol.flags & ts.SymbolFlags.Function) !== 0) && this.hasCustomDecorator(type, checker, "!TupleReturn"); } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index f5b814103..2cfff1e04 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import { TSHelper as tsEx } from "./TSHelper"; import { ForHelper } from "./ForHelper"; +import { CompilerOptions } from "./CommandLineParser"; import * as path from "path"; @@ -22,7 +23,7 @@ export class LuaTranspiler { public static AvailableLuaTargets = [Target.LuaJIT, Target.Lua53]; // Transpile a source file - static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: ts.CompilerOptions): string { + static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: CompilerOptions): string { let transpiler = new LuaTranspiler(checker, options, node); const header = options.addHeader ? "--=======================================================================================\n" @@ -31,7 +32,7 @@ export class LuaTranspiler { + "--=======================================================================================\n" : ""; let result = header; - if (!options.dontRequireLualib) { + if (!options.dontRequireLuaLib) { // require helper functions result += `require("typescript_lualib")\n`; } @@ -217,7 +218,9 @@ export class LuaTranspiler { transpileNamespace(node: ts.ModuleDeclaration): string { // If phantom namespace just transpile the body as normal - if (tsEx.isPhantom(this.checker.getTypeAtLocation(node), this.checker)) return this.transpileNode(node.body); + if (tsEx.isPhantom(this.checker.getTypeAtLocation(node), this.checker) && node.body) { + return this.transpileNode(node.body); + } const defName = this.definitionName(node.name.text); let result = this.indent + this.accessPrefix(node) + `${node.name.text} = ${node.name.text} or {}\n`; @@ -229,7 +232,9 @@ export class LuaTranspiler { result += this.indent + "do\n"; this.pushIndent(); this.namespace.push(node.name.text); - result += this.transpileNode(node.body); + if (node.body) { + result += this.transpileNode(node.body); + } this.namespace.pop(); this.popIndent(); result += this.indent + "end\n"; @@ -453,7 +458,7 @@ export class LuaTranspiler { this.popIndent(); tryFunc += "end"; let catchFunc = "function(e)\nend"; - if (node.catchClause) { + if (node.catchClause && node.catchClause.variableDeclaration) { let variableName = (node.catchClause.variableDeclaration.name).escapedText; catchFunc = this.indent + `function(${variableName})\n`; this.pushIndent(); @@ -790,9 +795,10 @@ export class LuaTranspiler { case ts.TypeFlags.String: case ts.TypeFlags.StringLiteral: return this.transpileStringCallExpression(node); - case ts.TypeFlags.Object: - if (tsEx.isArrayType(expType)) - return this.transpileArrayCallExpression(node); + + } + if (tsEx.isArrayType(expType)) { + return this.transpileArrayCallExpression(node); } if (expType.symbol && (expType.symbol.flags & ts.SymbolFlags.Namespace)) { @@ -1118,7 +1124,7 @@ export class LuaTranspiler { // Transpile a class declaration transpileClass(node: ts.ClassDeclaration): string { // Find extends class, ignore implements - let extendsType; + let extendsType: ts.ExpressionWithTypeArguments | undefined; let noClassOr = false; if (node.heritageClauses) node.heritageClauses.forEach(clause => { if (clause.token == ts.SyntaxKind.ExtendsKeyword) { @@ -1131,6 +1137,10 @@ export class LuaTranspiler { } }); + if (!node.name) { + throw new TranspileError("Unexpected Error: Node has no Name", node) + } + let className = node.name.escapedText; let result = ""; diff --git a/test/integration/cli.spec.ts b/test/integration/cli.spec.ts new file mode 100644 index 000000000..68577d97a --- /dev/null +++ b/test/integration/cli.spec.ts @@ -0,0 +1,25 @@ +import { Expect, Test, TestCase, Teardown } from "alsatian"; + +import { CompilerOptions, parseCommandLine, ParsedCommandLine } from "../../src/CommandLineParser"; + + +export class CLITests { + + @Test("defaultOption") + @TestCase("luaTarget", "JIT") + @TestCase("addHeader", false) + @TestCase("dontRequireLuaLib", false) + @TestCase("rootDir", process.cwd()) + @TestCase("outDir", process.cwd()) + public defaultOptions(option: string, expected: string) { + let parsedCommandLine = parseCommandLine([]); + + Expect(parsedCommandLine.options[option]).toBe(expected); + } + + @Test("InvalidLuaTarget") + public invalidLuaTarget() { + // Don't check error message because the yargs library messes the message up. + Expect(() => parseCommandLine(['--luaTarget', '42'])).toThrow(); + } +} diff --git a/test/integration/compiler.spec.ts b/test/integration/compiler.spec.ts index 02965fa97..8d7ea26a8 100644 --- a/test/integration/compiler.spec.ts +++ b/test/integration/compiler.spec.ts @@ -1,7 +1,7 @@ import { Expect, Teardown, Test, TestCase } from "alsatian"; -import { execSync } from "child_process" -import { existsSync, removeSync, unlink } from "fs-extra" -import { resolve } from "path" +import * as fs from "fs-extra"; +import * as path from "path"; +import { execCommandLine } from "../../src/Compiler"; export class CompilerTests { @@ -11,20 +11,20 @@ export class CompilerTests { @TestCase('tsconfig.bothDirOptions.json', ['outDir/typescript_lualib.lua', 'outDir/folder/file.lua']) @Test("Compile options: outDir and rootDir") public compileProject(tsconfig: string, expectedFiles: string[]) { - const compilerPath = resolve('dist/Compiler.js'); - const tsconfigPath = resolve('test/integration/project', tsconfig); + const compilerPath = path.resolve('dist/Compiler.js'); + const tsconfigPath = path.resolve('test/integration/project', tsconfig); // Compile project - execSync(`node ${compilerPath} -p ${tsconfigPath}`); + execCommandLine(['-p', tsconfigPath]); expectedFiles.forEach(relativePath => { - const absolutePath = resolve('test/integration/project', relativePath); + const absolutePath = path.resolve('test/integration/project', relativePath); // Assert - Expect(existsSync(absolutePath)).toBe(true); + Expect(fs.existsSync(absolutePath)).toBe(true); // Delete file - unlink(absolutePath, (error) => { + fs.unlink(absolutePath, (error) => { throw error; }); }); @@ -33,7 +33,7 @@ export class CompilerTests { @Teardown public teardown() { // Delete outDir folder - removeSync('test/integration/project/outDir'); + fs.removeSync('test/integration/project/outDir'); } } diff --git a/test/integration/lua/conditionals.spec.ts b/test/integration/lua/conditionals.spec.ts index f0081bc17..c53eea117 100644 --- a/test/integration/lua/conditionals.spec.ts +++ b/test/integration/lua/conditionals.spec.ts @@ -14,7 +14,6 @@ export class LuaConditionalsTests { return 0; } return 1;` - , util.dummyTypes.Number ); // Execute @@ -36,7 +35,6 @@ export class LuaConditionalsTests { } else { return 1; }` - , util.dummyTypes.Number ); // Execute @@ -63,7 +61,6 @@ export class LuaConditionalsTests { return 2; } return 3;` - , util.dummyTypes.Number ); // Execute @@ -91,7 +88,6 @@ export class LuaConditionalsTests { } else { return 3; }` - , util.dummyTypes.Number ); // Execute @@ -123,7 +119,6 @@ export class LuaConditionalsTests { break; } return result;` - , util.dummyTypes.Number ); // Execute @@ -158,7 +153,6 @@ export class LuaConditionalsTests { break; } return result;` - , util.dummyTypes.Number ); // Execute @@ -205,7 +199,6 @@ export class LuaConditionalsTests { break; } return result;` - , util.dummyTypes.Number ); // Execute diff --git a/test/integration/lua/curry.spec.ts b/test/integration/lua/curry.spec.ts index b2316c38a..e023f104b 100644 --- a/test/integration/lua/curry.spec.ts +++ b/test/integration/lua/curry.spec.ts @@ -8,7 +8,6 @@ export class LuaCurryTests { // Transpile let lua = util.transpileString( `(x: number) => (y: number) => x + y;` - , util.dummyTypes.Number ); // Assert Expect(lua).toBe(`function(x) return function(y) return x+y end end`); @@ -22,7 +21,6 @@ export class LuaCurryTests { let lua = util.transpileString( `let add = (x: number) => (y: number) => x + y; return add(${x})(${y})` - , util.dummyTypes.Number ); // Execute diff --git a/test/integration/lua/error.spec.ts b/test/integration/lua/error.spec.ts index b705972cc..4d03bca53 100644 --- a/test/integration/lua/error.spec.ts +++ b/test/integration/lua/error.spec.ts @@ -8,7 +8,6 @@ export class LuaErrorTests { // Transpile let lua = util.transpileString( `throw "Some Error"` - , util.dummyTypes.Number ); // Assert Expect(lua).toBe(`error("Some Error")`); @@ -20,7 +19,6 @@ export class LuaErrorTests { Expect(() => { let lua = util.transpileString( `throw Error("Some Error")` - , util.dummyTypes.Number ); }).toThrowError(Error, "Unsupported throw expression, only string literals are supported"); } diff --git a/test/integration/lua/loops.spec.ts b/test/integration/lua/loops.spec.ts index d387555ef..68ff788e9 100644 --- a/test/integration/lua/loops.spec.ts +++ b/test/integration/lua/loops.spec.ts @@ -1,4 +1,4 @@ -import { Expect, Test, TestCase } from "alsatian"; +import { Expect, Test, TestCase, FocusTest } from "alsatian"; import * as util from "../../src/util" const deepEqual = require('deep-equal') @@ -13,7 +13,6 @@ export class LuaLoopTests { `while (i < arrTest.length) { continue; }` - , util.dummyTypes.Array ); }).toThrowError(Error, "Continue is not supported in Lua") } @@ -31,7 +30,6 @@ export class LuaLoopTests { i++; } return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -51,7 +49,6 @@ export class LuaLoopTests { arrTest[i] = arrTest[i] + 1; } return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -71,7 +68,6 @@ export class LuaLoopTests { arrTest[i] = arrTest[i] + 1; } return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -92,7 +88,6 @@ export class LuaLoopTests { arrTest[i] = arrTest[i] + 1; } return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -117,7 +112,6 @@ export class LuaLoopTests { arrTest[i] = arrTest[i] + 1; } return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -134,7 +128,6 @@ export class LuaLoopTests { let lua = util.transpileString( `for (let i = 0; i < 30; i = i + 10) { }` - , util.dummyTypes.None ); // Execute @@ -149,7 +142,6 @@ export class LuaLoopTests { let lua = util.transpileString( `for (let i = arrTest.length - 1; i; i-- { }` - , util.dummyTypes.None ); // Execute @@ -167,7 +159,6 @@ export class LuaLoopTests { objTest[key] = objTest[key] + 1; } return JSONStringify(objTest);` - , util.dummyTypes.Object ); // Execute @@ -187,7 +178,6 @@ export class LuaLoopTests { for (let key in arrTest) { arrTest[key]++; }` - , util.dummyTypes.Array ); }).toThrowError(Error, "Iterating over arrays with 'for in' is not allowed."); } @@ -198,12 +188,11 @@ export class LuaLoopTests { // Transpile let lua = util.transpileString( `let objTest = ${JSON.stringify(inp)}; - let arrResultTest = {}; + let arrResultTest = []; for (let value of objTest) { arrResultTest.push(value + 1) } return JSONStringify(arrResultTest);` - , util.dummyTypes.Array ); // Execute diff --git a/test/integration/lua/lualib.spec.ts b/test/integration/lua/lualib.spec.ts index b9795ac14..111e396c6 100644 --- a/test/integration/lua/lualib.spec.ts +++ b/test/integration/lua/lualib.spec.ts @@ -13,7 +13,6 @@ export class LuaLibArrayTests { arrTest[index] = arrTest[index] + 1; }) return JSONStringify(arrTest);` - , util.dummyTypes.Array ); // Execute @@ -32,7 +31,7 @@ export class LuaLibArrayTests { @Test("array.map") public map(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return JSONStringify([${inp.toString()}].map(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].map(${func}))`); // Execute let result = util.executeLua(lua); @@ -51,7 +50,7 @@ export class LuaLibArrayTests { @Test("array.filter") public filter(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return JSONStringify([${inp.toString()}].filter(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].filter(${func}))`); // Execute let result = util.executeLua(lua); @@ -67,7 +66,7 @@ export class LuaLibArrayTests { @Test("array.every") public every(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return [${inp.toString()}].every(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return [${inp.toString()}].every(${func}))`); // Execute let result = util.executeLua(lua); @@ -83,7 +82,7 @@ export class LuaLibArrayTests { @Test("array.some") public some(inp: T[], func: string) { // Transpile - let lua = util.transpileString(`return [${inp.toString()}].some(${func}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return [${inp.toString()}].some(${func}))`); // Execute let result = util.executeLua(lua); @@ -102,7 +101,7 @@ export class LuaLibArrayTests { @Test("array.slice") public slice(inp: T[], start: number, end?: number) { // Transpile - let lua = util.transpileString(`return JSONStringify([${inp.toString()}].slice(${start}, ${end}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].slice(${start}, ${end}))`); // Execute let result = util.executeLua(lua); @@ -124,8 +123,7 @@ export class LuaLibArrayTests { let lua = util.transpileString( `let spliceTestTable = [${inp.toString()}]; spliceTestTable.splice(${start}, ${deleteCount}, ${newElements}); - return JSONStringify(spliceTestTable);`, - util.dummyTypes.Array + return JSONStringify(spliceTestTable);` ); // Execute @@ -146,7 +144,7 @@ export class LuaLibArrayTests { @Test("array.splice[Remove]") public spliceRemove(inp: T[], start: number, deleteCount?: number, ...newElements: any[]) { // Transpile - let lua = util.transpileString(`return JSONStringify([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`, util.dummyTypes.Array); + let lua = util.transpileString(`return JSONStringify([${inp.toString()}].splice(${start}, ${deleteCount}, ${newElements}))`); // Execute let result = util.executeLua(lua); @@ -177,8 +175,7 @@ export class LuaLibArrayTests { // Transpile let lua = util.transpileString( `let joinTestTable = ${JSON.stringify(inp)}; - return joinTestTable.join(${seperatorLua});`, - util.dummyTypes.Array + return joinTestTable.join(${seperatorLua});` ); // Execute @@ -197,7 +194,7 @@ export class LuaLibArrayTests { // Transpile let lua = util.transpileString( `return ${JSON.stringify(inp)}.indexOf("${element}"))` - , util.dummyTypes.Array + ); // Execute @@ -216,8 +213,7 @@ export class LuaLibArrayTests { let lua = util.transpileString( `let [x, y, z] = ${JSON.stringify(inp)} return z; - ` - , util.dummyTypes.Number); + `); // Execute let result = util.executeLua(lua); @@ -236,7 +232,7 @@ export class LuaLibArrayTests { testArray.push(${inp.join(', ')}); return JSONStringify(testArray); ` - , util.dummyTypes.Array); + ); // Execute let result = util.executeLua(lua); diff --git a/test/integration/lua/math.spec.ts b/test/integration/lua/math.spec.ts index 4681fba70..0ea01d9a4 100644 --- a/test/integration/lua/math.spec.ts +++ b/test/integration/lua/math.spec.ts @@ -14,7 +14,6 @@ export class MathTests { // Transpile let lua = util.transpileString( inp, - util.dummyTypes.Math ); // Assert diff --git a/test/integration/lua/modules.spec.ts b/test/integration/lua/modules.spec.ts index 1f5b845b8..152b5554e 100644 --- a/test/integration/lua/modules.spec.ts +++ b/test/integration/lua/modules.spec.ts @@ -134,7 +134,7 @@ export class LuaModuleTests { @Test("modules") public modules(inp: string, expected: string) { // Transpile - let lua = util.transpileString(inp, util.dummyTypes.Object); + let lua = util.transpileString(inp); // Assert // Dont test for correct indention this allows easier test case definition @@ -144,14 +144,14 @@ export class LuaModuleTests { @Test("defaultImport") public defaultImport() { Expect(() => { - let lua = util.transpileString(`import TestClass from "test"`, util.dummyTypes.Object); + let lua = util.transpileString(`import TestClass from "test"`); }).toThrowError(Error, "Default Imports are not supported, please use named imports instead!"); } @Test("lualibRequire") public lualibRequire() { // Transpile - let lua = util.transpileString(``, util.dummyTypes.None, {dontRequireLualib: false, luaTarget: "JIT"}); + let lua = util.transpileString(``, {dontRequireLuaLib: false, luaTarget: "JIT"}); // Assert Expect(dedent(lua)).toBe(`require("typescript_lualib")`); diff --git a/test/integration/lua/string.spec.ts b/test/integration/lua/string.spec.ts index 12fe29460..4299437da 100644 --- a/test/integration/lua/string.spec.ts +++ b/test/integration/lua/string.spec.ts @@ -12,7 +12,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return String.fromCharCode(${inp.toString()})`, - util.dummyTypes.String ); // Execute @@ -32,7 +31,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".replace("${searchValue}", "${replaceValue}")`, - util.dummyTypes.String ); // Execute @@ -54,7 +52,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return ${concatStr}`, - util.dummyTypes.String ); // Execute @@ -64,23 +61,6 @@ export class StringTests { Expect(result).toBe(expected); } - @TestCase("hello test", new RegExp("123", "g"), "") - @IgnoreTest() - @Test("string.replace[Regex]") - public replaceRegex(inp: string, searchValue: string, replaceValue: string) { - // Transpile - let lua = util.transpileString( - `return "${inp}".replace("${searchValue}", "${replaceValue}")`, - util.dummyTypes.String - ); - - // Execute - let result = util.executeLua(lua); - - // Assert - Expect(result).toBe(inp.replace(searchValue, replaceValue)); - } - @TestCase("hello test", "") @TestCase("hello test", "t") @TestCase("hello test", "h") @@ -90,7 +70,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".indexOf("${searchValue}")`, - util.dummyTypes.String ); // Execute @@ -110,7 +89,6 @@ export class StringTests { let paramStr = end ? `${start}, ${end}` : `${start}`; let lua = util.transpileString( `return "${inp}".substring(${paramStr})`, - util.dummyTypes.String ); // Execute @@ -128,7 +106,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".length`, - util.dummyTypes.String ); // Execute @@ -144,7 +121,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".toLowerCase()`, - util.dummyTypes.String ); // Execute @@ -160,7 +136,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".toUpperCase()`, - util.dummyTypes.String ); // Execute @@ -182,7 +157,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return JSONStringify("${inp}".split("${separator}"))`, - util.dummyTypes.String ); // Execute @@ -201,7 +175,6 @@ export class StringTests { // Transpile let lua = util.transpileString( `return "${inp}".charAt(${index})`, - util.dummyTypes.String ); // Execute diff --git a/test/src/util.ts b/test/src/util.ts index 107aa9761..53f50d9aa 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -1,22 +1,41 @@ import * as ts from "typescript"; -import { LuaTranspiler, TranspileError } from "../../dist/Transpiler"; +import * as path from "path"; + +import { LuaTranspiler, TranspileError } from "../../src/Transpiler"; +import { CompilerOptions } from "../../src/CommandLineParser"; const LuaVM = require("lua.vm.js"); const fs = require("fs"); -export namespace dummyTypes { - export const None = {}; - export const Array = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Array" } }; - export const Object = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Object" } }; - export const Number = { flags: ts.TypeFlags.Number, symbol: { escapedName: "Number" } }; - export const String = { flags: ts.TypeFlags.String, symbol: { escapedName: "String" } }; - export const Math = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Math" } }; -} +const libSource = fs.readFileSync(path.join(path.dirname(require.resolve('typescript')), 'lib.d.ts')).toString(); + +export function transpileString(str: string, options: CompilerOptions = { dontRequireLuaLib: true }): string { + let compilerHost = { + getSourceFile: (filename, languageVersion) => { + if (filename === "file.ts") { + return ts.createSourceFile(filename, str, ts.ScriptTarget.Latest, false); + } + if (filename === "lib.d.ts") { + return ts.createSourceFile(filename, libSource, ts.ScriptTarget.Latest, false); + } + return undefined; + }, + writeFile: (name, text, writeByteOrderMark) => { + // we dont care about the js output + }, + getDefaultLibFileName: () => "lib.d.ts", + useCaseSensitiveFileNames: () => false, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "", + getNewLine: () => "\n", + fileExists: (fileName): boolean => true, + readFile: () => "", + directoryExists: () => true, + getDirectories: () => [] + }; + let program = ts.createProgram(["file.ts"], options, compilerHost); -export function transpileString(str: string, dummyType: any = dummyTypes.None, options: ts.CompilerOptions = { dontRequireLualib: true }): string { - const dummyChecker = { getTypeAtLocation: function() { return dummyType; } } - const file = ts.createSourceFile("____internal_test_file.tstl", str, ts.ScriptTarget.Latest); - const result = LuaTranspiler.transpileSourceFile(file, dummyChecker, options); + const result = LuaTranspiler.transpileSourceFile(program.getSourceFile("file.ts"), program.getTypeChecker(), options); return result.trim(); } @@ -28,7 +47,7 @@ export function transpileFile(path: string): string { const diagnostics = ts.getPreEmitDiagnostics(program).filter(diag => diag.code != 6054); diagnostics.forEach(diagnostic => console.log(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`)); - const options: ts.CompilerOptions = { dontRequireLualib: true }; + const options: ts.CompilerOptions = { dontRequireLuaLib: true }; const lua = LuaTranspiler.transpileSourceFile(program.getSourceFile(path), checker, options); return lua.trim(); } diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index e7cc17c8e..a176ec9b0 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -95,7 +95,7 @@ export class ExpressionTests { @TestCase("a>>>=b", "a=bit.rshift(a,b)") @Test("Bitop [JIT]") public bitOperatorOverrideJIT(input: string, lua: string) { - Expect(util.transpileString(input, util.dummyTypes.None, { luaTarget: 'JIT', dontRequireLualib: true })).toBe(lua); + Expect(util.transpileString(input, { luaTarget: 'JIT', dontRequireLuaLib: true })).toBe(lua); } @TestCase("a&b", "a&b") @@ -110,7 +110,7 @@ export class ExpressionTests { @TestCase("a>>>=b", "a=a>>>b") @Test("Bitop [5.3]") public bitOperatorOverride53(input: string, lua: string) { - Expect(util.transpileString(input, util.dummyTypes.None, { luaTarget: '5.3', dontRequireLualib: true })).toBe(lua); + Expect(util.transpileString(input, { luaTarget: '5.3', dontRequireLuaLib: true })).toBe(lua); }