From 19d47de53dd394dc538c6021c331bed401de9057 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 6 Apr 2019 17:27:25 +0500 Subject: [PATCH 01/44] New emit pipeline and API/CLI refactor --- build_lualib.ts | 42 ++-- jest.config.js | 7 +- package-lock.json | 6 +- package.json | 2 +- src/API.ts | 111 ++++++++++ src/CommandLineParser.ts | 17 +- src/Compiler.ts | 186 ----------------- src/Emit.ts | 65 ++++++ src/LuaTranspiler.ts | 158 -------------- src/Transpile.ts | 192 ++++++++++++++++++ src/index.ts | 10 +- src/tstl.ts | 166 ++++++++++++++- test/compiler/errorreport.spec.ts | 45 ++-- test/compiler/outfile.spec.ts | 32 ++- test/compiler/project.spec.ts | 10 +- test/compiler/runner.ts | 30 +++ test/compiler/watcher_proccess.ts | 5 - test/compiler/watchmode.spec.ts | 53 ++--- test/translation/transformation.spec.ts | 2 +- test/unit/assignmentDestructuring.spec.ts | 2 +- test/unit/commandLineParser.spec.ts | 12 +- .../configuration/mixed/index.spec.ts | 4 +- .../compiler/configuration/options.spec.ts | 19 -- test/unit/conditionals.spec.ts | 2 +- test/unit/expressions.spec.ts | 2 +- test/unit/json.spec.ts | 4 +- test/unit/loops.spec.ts | 4 +- test/unit/lualib/inlining.spec.ts | 2 +- test/unit/modules.spec.ts | 2 +- test/unit/require.spec.ts | 45 ++-- test/unit/sourcemaps.spec.ts | 8 +- test/unit/spreadElement.spec.ts | 4 +- test/util.ts | 122 +++++------ 33 files changed, 767 insertions(+), 604 deletions(-) create mode 100644 src/API.ts delete mode 100644 src/Compiler.ts create mode 100644 src/Emit.ts delete mode 100644 src/LuaTranspiler.ts create mode 100644 src/Transpile.ts create mode 100644 test/compiler/runner.ts delete mode 100644 test/compiler/watcher_proccess.ts delete mode 100644 test/unit/compiler/configuration/options.spec.ts diff --git a/build_lualib.ts b/build_lualib.ts index be376fef6..f9d24ee48 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -1,31 +1,29 @@ import * as fs from "fs"; import * as glob from "glob"; -import { compile } from "./src/Compiler"; -import { LuaLib as luaLib, LuaLibFeature } from "./src/LuaLib"; +import * as path from "path"; +import * as tstl from "./src"; +import { LuaLib } from "./src/LuaLib"; -const bundlePath = "./dist/lualib/lualib_bundle.lua"; +const options: tstl.CompilerOptions = { + skipLibCheck: true, + types: [], + luaLibImport: tstl.LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.Lua51, + noHeader: true, + outDir: path.join(__dirname, "./dist/lualib"), + rootDir: path.join(__dirname, "./src/lualib"), +}; -compile([ - "--skipLibCheck", - "--types", - "node", - "--luaLibImport", - "none", - "--luaTarget", - "5.1", - "--noHeader", - "--outDir", - "./dist/lualib", - "--rootDir", - "./src/lualib", - "--noHeader", - "true", - ...glob.sync("./src/lualib/**/*.ts"), -]); +// TODO: Check diagnostics +const { transpiledFiles } = tstl.transpileFiles(glob.sync("./src/lualib/**/*.ts"), options); +tstl.emitTranspiledFiles(options, transpiledFiles); +const bundlePath = path.join(__dirname, "./dist/lualib/lualib_bundle.lua"); if (fs.existsSync(bundlePath)) { fs.unlinkSync(bundlePath); } -const bundle = luaLib.loadFeatures(Object.keys(LuaLibFeature).map(lib => LuaLibFeature[lib])); -fs.writeFileSync(bundlePath, bundle); +fs.writeFileSync( + bundlePath, + LuaLib.loadFeatures(Object.keys(tstl.LuaLibFeature).map(lib => tstl.LuaLibFeature[lib])), +); diff --git a/jest.config.js b/jest.config.js index adedec88f..7327a27af 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,7 +3,12 @@ const isCI = require("is-ci"); /** @type {Partial} */ module.exports = { testMatch: ["**/test/**/*.spec.ts"], - collectCoverageFrom: ["/src/**/*", "!/src/lualib/**/*"], + collectCoverageFrom: [ + "/src/**/*", + "!/src/lualib/**/*", + // https://github.com/facebook/jest/issues/5274 + "!/src/tstl.ts", + ], watchPathIgnorePatterns: ["/watch\\.ts$"], testEnvironment: "node", diff --git a/package-lock.json b/package-lock.json index dda947979..17805d8db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -764,9 +764,9 @@ "dev": true }, "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", + "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==", "dev": true }, "@types/stack-utils": { diff --git a/package.json b/package.json index 1f15d68ea..7014c1171 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@types/glob": "^5.0.35", "@types/jest": "^24.0.11", - "@types/node": "^9.6.23", + "@types/node": "^11.13.0", "fengari": "^0.1.2", "glob": "^7.1.2", "jest": "^24.5.0", diff --git a/src/API.ts b/src/API.ts new file mode 100644 index 000000000..281476cc6 --- /dev/null +++ b/src/API.ts @@ -0,0 +1,111 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { parseConfigFileContent } from "./CommandLineParser"; +import { CompilerOptions } from "./CompilerOptions"; +import { getTranspileOutput, TranspiledFile, TranspilationResult } from "./Transpile"; + +export function transpileFiles( + rootNames: string[], + options: CompilerOptions = {} +): TranspilationResult { + const program = ts.createProgram(rootNames, options); + const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...diagnostics, + ]); + + return { transpiledFiles, diagnostics: [...allDiagnostics] }; +} + +export function transpileProject( + fileName: string, + options?: CompilerOptions +): TranspilationResult { + const parseResult = parseConfigFileContent( + fs.readFileSync(fileName, "utf8"), + fileName, + options + ); + if (parseResult.isValid === false) { + // TODO: Return diagnostics + throw new Error(parseResult.errorMessage); + } + + return transpileFiles(parseResult.result.fileNames, parseResult.result.options); +} + +const libCache: { [key: string]: ts.SourceFile } = {}; +export function createVirtualProgram( + input: Record, + options?: CompilerOptions +): ts.Program { + const compilerHost: ts.CompilerHost = { + fileExists: () => true, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "", + getDefaultLibFileName: ts.getDefaultLibFileName, + readFile: () => "", + getNewLine: () => "\n", + useCaseSensitiveFileNames: () => false, + writeFile: () => {}, + + getSourceFile: filename => { + if (filename in input) { + return ts.createSourceFile( + filename, + input[filename], + ts.ScriptTarget.Latest, + false + ); + } + + if (filename.startsWith("lib.")) { + if (libCache[filename]) return libCache[filename]; + const typeScriptDir = path.dirname(require.resolve("typescript")); + const filePath = path.join(typeScriptDir, filename); + const content = fs.readFileSync(filePath, "utf8"); + + libCache[filename] = ts.createSourceFile( + filename, + content, + ts.ScriptTarget.Latest, + false + ); + + return libCache[filename]; + } + }, + }; + + return ts.createProgram(Object.keys(input), options, compilerHost); +} + +export interface TranspileStringResult { + file: TranspiledFile; + diagnostics: ts.Diagnostic[]; +} + +export function transpileString( + input: string | Record, + options: CompilerOptions = {} +): TranspileStringResult { + const programFiles = typeof input === "object" ? input : { "main.ts": input }; + const mainFileName = + typeof input === "string" + ? "main.ts" + : Object.keys(input).find(x => /\bmain\.[a-z]+$/.test(x)); + if (mainFileName === undefined) throw new Error('Input should have a file named "main"'); + + const program = createVirtualProgram(programFiles, options); + const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...diagnostics, + ]); + + return { file: transpiledFiles.get(mainFileName), diagnostics: [...allDiagnostics] }; +} diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 50a1fa738..e03242199 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -13,7 +13,7 @@ type ArgumentParseResult = { isValid: true; result: T; increment?: number } | { isValid: false, errorMessage: string }; -interface ParsedCommandLine extends ts.ParsedCommandLine { +export interface ParsedCommandLine extends ts.ParsedCommandLine { options: CompilerOptions; } @@ -161,19 +161,13 @@ function readTsConfig(parsedCommandLine: ts.ParsedCommandLine): CLIParseResult } const configPath = options.project; - const parsedJsonConfig = parseTsConfigFile(configPath, options); - - return parsedJsonConfig; + const configContent = fs.readFileSync(configPath, "utf8"); + return parseConfigFileContent(configContent, configPath, options); } return { isValid: true, result: parsedCommandLine }; } -export function parseTsConfigFile(filePath: string, existingOptions?: ts.CompilerOptions): CLIParseResult { - const configContents = fs.readFileSync(filePath).toString(); - return parseTsConfigString(configContents, filePath, existingOptions); -} - -export function parseTsConfigString( +export function parseConfigFileContent( tsConfigString: string, configPath: string, existingOptions?: ts.CompilerOptions @@ -183,7 +177,8 @@ export function parseTsConfigString( configJson.config, ts.sys, path.dirname(configPath), - existingOptions + existingOptions, + configPath ); for (const key in parsedJsonConfig.raw) { diff --git a/src/Compiler.ts b/src/Compiler.ts deleted file mode 100644 index 306182f34..000000000 --- a/src/Compiler.ts +++ /dev/null @@ -1,186 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as ts from "typescript"; -import * as CommandLineParser from "./CommandLineParser"; -import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; -import { LuaTranspiler, TranspileResult } from "./LuaTranspiler"; - -export function compile(argv: string[]): void { - const parseResult = CommandLineParser.parseCommandLine(argv); - - if (parseResult.isValid === true) { - - if (parseResult.result.options.help) { - console.log(CommandLineParser.getHelpString()); - return; - } - - if (parseResult.result.options.version) { - console.log(CommandLineParser.version); - return; - } - - /* istanbul ignore if: tested in test/compiler/watchmode.spec with subproccess */ - if (parseResult.result.options.watch) { - watchWithOptions(parseResult.result.fileNames, parseResult.result.options); - } else { - compileFilesWithOptions(parseResult.result.fileNames, parseResult.result.options); - } - } else { - console.error(`Invalid CLI input: ${parseResult.errorMessage}`); - } -} - -/* istanbul ignore next: tested in test/compiler/watchmode.spec with subproccess */ -export function watchWithOptions(fileNames: string[], options: CompilerOptions): void { - let host: ts.WatchCompilerHost; - let config = false; - if (options.project) { - config = true; - host = ts.createWatchCompilerHost(options.project, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram); - } else { - host = ts.createWatchCompilerHost(fileNames, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram); - } - - let fullRecompile = true; - host.afterProgramCreate = program => { - const transpiler = new LuaTranspiler(program.getProgram()); - let status = transpiler.reportErrors(); - - if (status === 0) { - if (fullRecompile) { - status = transpiler.emitFilesAndReportErrors(); - } else { - while (true) { - const currentFile = program.getSemanticDiagnosticsOfNextAffectedFile(); - if (!currentFile) { break; } - - if ("fileName" in currentFile.affected) { // test if currentFile.affected is `ts.SourceFile` - const fileStatus = transpiler.emitSourceFile(currentFile.affected); - status |= fileStatus; - } else { - for (const sourceFile of currentFile.affected.getSourceFiles()) { - const fileStatus = transpiler.emitSourceFile(sourceFile); - status |= fileStatus; - } - } - } - } - // do a full recompile after transpiler error. - fullRecompile = status !== 0; - } - - const errorDiagnostic: ts.Diagnostic = { - category: undefined, - code: 6194, - file: undefined, - length: 0, - messageText: "Found 0 errors. Watching for file changes.", - start: 0, - }; - if (status !== 0) { - errorDiagnostic.messageText = "Found Errors. Watching for file changes."; - errorDiagnostic.code = 6193; - } - host.onWatchStatusChange(errorDiagnostic, host.getNewLine(), program.getCompilerOptions()); - }; - - if (config) { - ts.createWatchProgram( - host as ts.WatchCompilerHostOfConfigFile - ); - } else { - ts.createWatchProgram( - host as ts.WatchCompilerHostOfFilesAndCompilerOptions - ); - } -} - -export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void { - const program = ts.createProgram(fileNames, options); - - const transpiler = new LuaTranspiler(program); - - transpiler.emitFilesAndReportErrors(); -} - -const libCache: {[key: string]: ts.SourceFile} = {}; - -const defaultCompilerOptions: CompilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, -}; - -export function createStringCompilerProgram( - input: string | { [filename: string]: string }, - options: CompilerOptions = defaultCompilerOptions, - filePath = "file.ts" -): ts.Program { - const compilerHost = { - directoryExists: () => true, - fileExists: (fileName): boolean => true, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "", - getDefaultLibFileName: ts.getDefaultLibFileName, - getDirectories: () => [], - getNewLine: () => "\n", - - getSourceFile: (filename: string) => { - switch (typeof input) { - case "string": - if (filename === filePath) { - return ts.createSourceFile(filename, input, ts.ScriptTarget.Latest, false); - } - break; - case "object": - if (filename in input) { - return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false); - } - break; - } - - if (filename.startsWith('lib.')) { - if (libCache[filename]) return libCache[filename]; - const typeScriptDir = path.dirname(require.resolve("typescript")); - const filePath = path.join(typeScriptDir, filename); - const content = fs.readFileSync(filePath, 'utf8'); - - libCache[filename] = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, false); - return libCache[filename]; - } - - return undefined; - }, - - readFile: () => "", - - useCaseSensitiveFileNames: () => false, - // Don't write output - writeFile: (name, text, writeByteOrderMark) => undefined, - }; - const filePaths = typeof input === "string" ? [filePath] : Object.keys(input); - return ts.createProgram(filePaths, options, compilerHost); -} - -export function transpileString( - input: string | { [filename: string]: string }, - options: CompilerOptions = defaultCompilerOptions, - ignoreDiagnostics = false, - filePath = "file.ts" -): TranspileResult { - const program = createStringCompilerProgram(input, options, filePath); - - if (!ignoreDiagnostics) { - const diagnostics = ts.getPreEmitDiagnostics(program); - const typeScriptErrors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); - - if (typeScriptErrors.length > 0) { - typeScriptErrors.forEach(e => console.warn(e.messageText)); - throw new Error("Encountered invalid TypeScript."); - } - } - - const transpiler = new LuaTranspiler(program); - - return transpiler.transpileSourceFile(program.getSourceFile(filePath)); -} diff --git a/src/Emit.ts b/src/Emit.ts new file mode 100644 index 000000000..6065cc668 --- /dev/null +++ b/src/Emit.ts @@ -0,0 +1,65 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; +import { TranspiledFile } from "./Transpile"; + +const trimExt = (filePath: string) => + path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); + +let lualibContent: string; +export function emitTranspiledFiles( + options: CompilerOptions, + transpiledFiles: Map, + writeFile = ts.sys.writeFile +): void { + const { rootDir, outDir, outFile, luaLibImport } = options; + + for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { + let outPath = fileName; + if (outDir !== rootDir) { + const relativeSourcePath = path.resolve(fileName).replace(path.resolve(rootDir), ""); + outPath = path.join(outDir, relativeSourcePath); + } + + // change extension or rename to outFile + if (outFile) { + if (path.isAbsolute(outFile)) { + outPath = outFile; + } else { + // append to workingDir or outDir + outPath = path.resolve(options.outDir, outFile); + } + } else { + outPath = trimExt(outPath) + ".lua"; + } + + if (lua !== undefined) { + writeFile(outPath, lua); + } + + if (sourceMap !== undefined && options.sourceMap) { + writeFile(outPath + ".map", sourceMap); + } + + if (declaration !== undefined) { + writeFile(trimExt(outPath) + ".d.ts", declaration); + } + + if (declarationMap !== undefined) { + writeFile(trimExt(outPath) + ".d.ts.map", declarationMap); + } + } + + if (luaLibImport === LuaLibImportKind.Require || luaLibImport === LuaLibImportKind.Always) { + if (lualibContent === undefined) { + lualibContent = fs.readFileSync( + path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), + "utf8" + ); + } + + const outPath = path.join(outDir, "lualib_bundle.lua"); + writeFile(outPath, lualibContent); + } +} diff --git a/src/LuaTranspiler.ts b/src/LuaTranspiler.ts deleted file mode 100644 index 771a32642..000000000 --- a/src/LuaTranspiler.ts +++ /dev/null @@ -1,158 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; -import * as tstl from "./LuaAST"; -import { LuaPrinter } from "./LuaPrinter"; -import { LuaTransformer } from "./LuaTransformer"; - -export interface TranspileResult { - lua: string; - luaAST: tstl.Node; - sourceMap: string; -} - -export class LuaTranspiler { - private program: ts.Program; - - private options: CompilerOptions; - - private luaTransformer: LuaTransformer; - - private luaPrinter: LuaPrinter; - - constructor(program: ts.Program) { - this.program = program; - this.options = this.getOptions(program); - this.luaTransformer = new LuaTransformer(this.program, this.options); - this.luaPrinter = new LuaPrinter(this.options); - } - - private getOptions(program: ts.Program): CompilerOptions { - const options = program.getCompilerOptions() as CompilerOptions; - - // Make options case-insenstive - if (options.luaTarget) { - options.luaTarget = options.luaTarget.toLowerCase() as LuaTarget; - } - if (options.luaLibImport) { - options.luaLibImport = options.luaLibImport.toLocaleLowerCase() as LuaLibImportKind; - } - - return options; - } - - public reportErrors(): number { - // Get all diagnostics, ignore unsupported extension - const diagnostics = ts.getPreEmitDiagnostics(this.program).filter(diag => diag.code !== 6054); - diagnostics.forEach(diag => this.reportDiagnostic(diag)); - - // If there are errors dont emit - if (diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error).length > 0) { - if (!this.options.watch) { - process.exit(1); - } else { - return 1; - } - } - - return 0; - } - - public emitLuaLib(): string { - const outPath = path.join(this.options.outDir, "lualib_bundle.lua"); - fs.copyFileSync( - path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), - outPath - ); - return outPath; - } - - public emitFilesAndReportErrors(): number { - let status = this.reportErrors(); - - if (status > 0) { - return status; - } - - this.program.getSourceFiles().forEach(sourceFile => { - const sourceStatus = this.emitSourceFile(sourceFile); - status |= sourceStatus; - }); - - // Copy lualib to target dir - if (this.options.luaLibImport === LuaLibImportKind.Require - || this.options.luaLibImport === LuaLibImportKind.Always - ) { - this.emitLuaLib(); - } - - return status; - } - - public emitSourceFile(sourceFile: ts.SourceFile): number { - if (!sourceFile.isDeclarationFile) { - try { - const rootDir = this.options.rootDir; - - const { lua, luaAST, sourceMap } = this.transpileSourceFile(sourceFile); - - let outPath = sourceFile.fileName; - if (this.options.outDir !== this.options.rootDir) { - const relativeSourcePath = path.resolve(sourceFile.fileName).replace(path.resolve(rootDir), ""); - outPath = path.join(this.options.outDir, relativeSourcePath); - } - - // change extension or rename to outFile - if (this.options.outFile) { - if (path.isAbsolute(this.options.outFile)) { - outPath = this.options.outFile; - } else { - // append to workingDir or outDir - outPath = path.resolve(this.options.outDir, this.options.outFile); - } - } else { - const fileNameLua = path.basename(outPath, path.extname(outPath)) + ".lua"; - outPath = path.join(path.dirname(outPath), fileNameLua); - } - - // Write output - ts.sys.writeFile(outPath, lua); - if (this.options.sourceMap) { - ts.sys.writeFile(outPath + ".map", sourceMap); - } - } catch (exception) { - /* istanbul ignore else: Testing else part would require to add a bug/exception to our code */ - if (exception.node) { - const pos = ts.getLineAndCharacterOfPosition(sourceFile, exception.node.pos); - // Graciously handle transpilation errors - console.error("Encountered error parsing file: " + exception.message); - console.error(`${sourceFile.fileName} (${1 + pos.line},${pos.character})\n${exception.stack}`); - return 1; - } else { - throw exception; - } - } - } - return 0; - } - - public transpileSourceFile(sourceFile: ts.SourceFile): TranspileResult { - // Transform AST - const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); - // Print AST - const [lua, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); - - return { lua, luaAST, sourceMap }; - } - - public reportDiagnostic(diagnostic: ts.Diagnostic): void { - if (diagnostic.file) { - const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); - const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - console.log(`${diagnostic.code}: ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); - } else { - console.log(`${diagnostic.code}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`); - } - } -} diff --git a/src/Transpile.ts b/src/Transpile.ts new file mode 100644 index 000000000..b5583ff57 --- /dev/null +++ b/src/Transpile.ts @@ -0,0 +1,192 @@ +import * as ts from "typescript"; +import { CompilerOptions } from "./CompilerOptions"; +import { LuaPrinter } from "./LuaPrinter"; +import { LuaTransformer } from "./LuaTransformer"; +import { TranspileError } from "./TranspileError"; + +function getCustomTransformers( + options: CompilerOptions, + customTransformers: ts.CustomTransformers, + onSourceFile: (sourceFile: ts.SourceFile) => void +): ts.CustomTransformers { + // TODO: https://github.com/Microsoft/TypeScript/issues/28310 + const forEachSourceFile = ( + node: ts.SourceFile, + callback: (sourceFile: ts.SourceFile) => ts.SourceFile + ) => + ts.isBundle(node) + ? ((ts.updateBundle(node, node.sourceFiles.map(callback)) as unknown) as ts.SourceFile) + : callback(node); + + const luaTransformer: ts.TransformerFactory = () => node => + forEachSourceFile(node, sourceFile => { + onSourceFile(sourceFile); + return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext); + }); + + return { + afterDeclarations: customTransformers.afterDeclarations, + before: [ + ...(customTransformers.before || []), + ...(customTransformers.after || []), + luaTransformer, + ], + }; +} + +export interface TranspiledFile { + lua?: string; + sourceMap?: string; + declaration?: string; + declarationMap?: string; +} + +export interface TranspilationResult { + diagnostics: ts.Diagnostic[]; + transpiledFiles: Map; +} + +export interface GetTranspileOutputOptions { + program: ts.Program; + options: CompilerOptions; + customTransformers?: ts.CustomTransformers; + sourceFiles?: ts.SourceFile[]; + printer?: LuaPrinter; + transformer?: LuaTransformer; +} + +export function getTranspileOutput({ + program, + options, + customTransformers = {}, + sourceFiles: targetSourceFiles, + printer = new LuaPrinter(options), + transformer = new LuaTransformer(program, options), +}: GetTranspileOutputOptions): TranspilationResult { + const { noEmit, emitDeclarationOnly, noEmitOnError } = options; + + const diagnostics: ts.Diagnostic[] = []; + const transpiledFiles = new Map(); + const updateTranspiledFile = (filePath: string, file: TranspiledFile) => { + if (transpiledFiles.has(filePath)) { + Object.assign(transpiledFiles.get(filePath), file); + } else { + transpiledFiles.set(filePath, file); + } + }; + + if (noEmitOnError) { + const preEmitDiagnostics = [ + ...program.getOptionsDiagnostics(), + ...program.getGlobalDiagnostics(), + ]; + + if (targetSourceFiles) { + for (const sourceFile of targetSourceFiles) { + preEmitDiagnostics.push(...program.getSyntacticDiagnostics(sourceFile)); + preEmitDiagnostics.push(...program.getSemanticDiagnostics(sourceFile)); + } + } else { + preEmitDiagnostics.push(...program.getSyntacticDiagnostics()); + preEmitDiagnostics.push(...program.getSemanticDiagnostics()); + } + + if (options.declaration || options.composite) { + preEmitDiagnostics.push(...program.getDeclarationDiagnostics()); + } + + if (preEmitDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0) { + return { diagnostics: preEmitDiagnostics, transpiledFiles }; + } + } + + const processSourceFile = (sourceFile: ts.SourceFile) => { + try { + const [luaAST, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); + if (!noEmit && !emitDeclarationOnly) { + const [lua, sourceMap] = printer.print( + luaAST, + lualibFeatureSet, + sourceFile.fileName + ); + updateTranspiledFile(sourceFile.fileName, { lua, sourceMap }); + } + } catch (err) { + /* istanbul ignore if: Testing it would require to add a bug/exception to our code */ + if (!(err instanceof TranspileError)) throw err; + + diagnostics.push({ + category: ts.DiagnosticCategory.Error, + code: 0, + file: sourceFile, + start: err.node.getStart(), + length: err.node.getWidth(), + messageText: err.message, + }); + + updateTranspiledFile(sourceFile.fileName, { + lua: `error(${JSON.stringify(err.message)})\n`, + sourceMap: "", + }); + } + }; + + const transformers = getCustomTransformers(options, customTransformers, processSourceFile); + + const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { + for (const sourceFile of sourceFiles) { + const isDeclaration = fileName.endsWith(".d.ts"); + const isDeclarationMap = fileName.endsWith(".d.ts.map"); + if (isDeclaration) { + updateTranspiledFile(sourceFile.fileName, { declaration: data }); + } else if (isDeclarationMap) { + updateTranspiledFile(sourceFile.fileName, { declarationMap: data }); + } + } + }; + + const isEmittableJsonFile = (sourceFile: ts.SourceFile) => + sourceFile.flags & ts.NodeFlags.JsonFile && + !emitDeclarationOnly && + !program.isSourceFileFromExternalLibrary(sourceFile); + + // We always have to emit to get transformer diagnostics + const programOptions = program.getCompilerOptions(); + const programNoEmit = programOptions.noEmit; + programOptions.noEmit = false; + + if (targetSourceFiles) { + for (const sourceFile of targetSourceFiles) { + if (isEmittableJsonFile(sourceFile)) { + processSourceFile(sourceFile); + } else { + diagnostics.push( + ...program.emit(sourceFile, writeFile, undefined, false, transformers) + .diagnostics + ); + } + } + } else { + diagnostics.push( + ...program.emit(undefined, writeFile, undefined, false, transformers).diagnostics + ); + + // JSON files don't get through transformers and aren't written when outDir is the same as rootDir + program + .getSourceFiles() + .filter(isEmittableJsonFile) + .forEach(processSourceFile); + } + + programOptions.noEmit = programNoEmit; + + if ( + noEmit || + (noEmitOnError && + diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0) + ) { + transpiledFiles.clear(); + } + + return { diagnostics, transpiledFiles }; +} diff --git a/src/index.ts b/src/index.ts index eb153d7bc..c6d245322 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,9 @@ -export { parseCommandLine } from "./CommandLineParser"; -export { compile, compileFilesWithOptions, transpileString, watchWithOptions } from "./Compiler"; +export { transpileFiles, transpileProject, transpileString, TranspileStringResult } from "./API"; +export { parseConfigFileContent } from "./CommandLineParser"; export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; +export * from "./Emit"; +export * from "./LuaAST"; export { LuaLibFeature } from "./LuaLib"; -export { LuaTranspiler } from "./LuaTranspiler"; +export { LuaPrinter } from "./LuaPrinter"; +export { LuaTransformer } from "./LuaTransformer"; +export * from "./Transpile"; diff --git a/src/tstl.ts b/src/tstl.ts index 5055ec8aa..03b6bfad7 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -1,5 +1,167 @@ #!/usr/bin/env node +import * as ts from "typescript"; +import { transpileFiles } from "./API"; +import * as CommandLineParser from "./CommandLineParser"; +import { CompilerOptions } from "./CompilerOptions"; +import { emitTranspiledFiles } from "./Emit"; +import { getTranspileOutput } from "./Transpile"; -import { compile } from "./Compiler"; +function createDiagnosticReporter(pretty: boolean): ts.DiagnosticReporter { + const host: ts.FormatDiagnosticsHost = { + getCurrentDirectory: () => ts.sys.getCurrentDirectory(), + getNewLine: () => ts.sys.newLine, + getCanonicalFileName: fileName => + ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), + }; -compile(process.argv.slice(2)); + if (!pretty) { + return diagnostic => ts.sys.write(ts.formatDiagnostic(diagnostic, host)); + } + + return diagnostic => { + ts.sys.write( + ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine() + ); + }; +} + +function shouldBePretty(options?: CompilerOptions): boolean { + return !options || options.pretty === undefined + ? ts.sys.writeOutputIsTTY !== undefined && ts.sys.writeOutputIsTTY() + : Boolean(options.pretty); +} + +let reportDiagnostic = createDiagnosticReporter(shouldBePretty()); +function updateReportDiagnostic(options?: ts.CompilerOptions): void { + if (shouldBePretty(options)) { + reportDiagnostic = createDiagnosticReporter(true); + } +} + +function executeCommandLine(argv: string[]): void { + const commandLine = CommandLineParser.parseCommandLine(argv); + if (commandLine.isValid === false) { + // TODO: Use diagnostics + console.error(`Invalid CLI input: ${commandLine.errorMessage}`); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + updateReportDiagnostic(commandLine.result.options); + + if (commandLine.result.options.help) { + console.log(CommandLineParser.version); + console.log(CommandLineParser.getHelpString()); + return ts.sys.exit(ts.ExitStatus.Success); + } + + if (commandLine.result.options.version) { + console.log(CommandLineParser.version); + return ts.sys.exit(ts.ExitStatus.Success); + } + + if (commandLine.result.options.watch) { + if (commandLine.result.options.project) { + const host = ts.createWatchCompilerHost( + commandLine.result.options.project, + commandLine.result.options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram + ); + updateWatchCompilerHost(host, commandLine.result.options); + ts.createWatchProgram(host); + } else { + const host = ts.createWatchCompilerHost( + commandLine.result.fileNames, + commandLine.result.options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram + ); + updateWatchCompilerHost(host, commandLine.result.options); + ts.createWatchProgram(host); + } + } else { + const { diagnostics, transpiledFiles } = transpileFiles( + commandLine.result.fileNames, + commandLine.result.options + ); + emitTranspiledFiles(commandLine.result.options, transpiledFiles); + + diagnostics.forEach(reportDiagnostic); + if (diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length === 0) { + return ts.sys.exit(ts.ExitStatus.Success); + } + + if (transpiledFiles.size === 0) { + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } else { + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsGenerated); + } + } +} + +function updateWatchCompilerHost( + host: ts.WatchCompilerHost, + options: CompilerOptions +): void { + let fullRecompile = true; + host.afterProgramCreate = builderProgram => { + const program = builderProgram.getProgram(); + const preEmitDiagnostics = ts.getPreEmitDiagnostics(program); + + let sourceFiles: ts.SourceFile[] | undefined; + if (!fullRecompile) { + sourceFiles = []; + while (true) { + const currentFile = builderProgram.getSemanticDiagnosticsOfNextAffectedFile(); + if (!currentFile) break; + + if ("fileName" in currentFile.affected) { + sourceFiles.push(currentFile.affected); + } else { + sourceFiles.push(...currentFile.affected.getSourceFiles()); + } + } + } + + const { diagnostics: emitDiagnostics, transpiledFiles } = getTranspileOutput({ + program, + options, + sourceFiles, + }); + emitTranspiledFiles(options, transpiledFiles); + + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...preEmitDiagnostics, + ...emitDiagnostics, + ]); + + diagnostics.forEach(reportDiagnostic); + + const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error); + // do a full recompile after an error + fullRecompile = errors.length > 0; + + const watchErrorSummaryDiagnostic: ts.Diagnostic = { + file: undefined, + start: undefined, + length: undefined, + + category: ts.DiagnosticCategory.Message, + code: errors.length === 1 ? 6193 : 6194, + messageText: + errors.length === 1 + ? "Found 1 error. Watching for file changes." + : `Found ${errors.length} errors. Watching for file changes.`, + }; + + host.onWatchStatusChange( + watchErrorSummaryDiagnostic, + host.getNewLine(), + builderProgram.getCompilerOptions() + ); + }; +} + +if ((ts.sys as any).setBlocking) (ts.sys as any).setBlocking(); + +executeCommandLine(ts.sys.args); diff --git a/test/compiler/errorreport.spec.ts b/test/compiler/errorreport.spec.ts index 1c588ee04..25fb46f75 100644 --- a/test/compiler/errorreport.spec.ts +++ b/test/compiler/errorreport.spec.ts @@ -1,24 +1,33 @@ +import * as fs from "fs"; import * as path from "path"; -import { compileFilesWithOptions } from "../../src/Compiler"; +import { runCli } from "./runner"; -test.each([ - { - errorMsg: - "Encountered error parsing file: Default Imports are not supported, please use named imports instead!", - fileName: "default_import.ts", - }, -])("Compile project (%p)", ({ errorMsg, fileName }) => { - jest.spyOn(console, "log").mockReturnValue(undefined); - const errorMock = jest.spyOn(console, "error").mockReturnValue(undefined); - const exitMock = jest.spyOn(process, "exit").mockReturnValue(undefined as never); +const srcFilePath = path.resolve(__dirname, "testfiles", "default_import.ts"); +const outFilePath = path.resolve(__dirname, "testfiles", "default_import.lua"); - fileName = path.resolve(__dirname, "testfiles", fileName); - compileFilesWithOptions([fileName], { outDir: ".", rootDir: ".", types: [] }); +afterEach(() => { + try { + fs.unlinkSync(outFilePath); + } catch (err) { + if (err.code !== "ENOENT") throw err; + } +}); - jest.restoreAllMocks(); +test("Compile project", async () => { + const { exitCode, output } = await runCli([ + srcFilePath, + "--outDir", + ".", + "--rootDir", + ".", + "--types", + "node", + ]); - expect(exitMock).toHaveBeenCalledWith(1); - expect(errorMock).toHaveBeenCalledTimes(2); - expect(errorMock).toHaveBeenNthCalledWith(1, errorMsg); - expect(errorMock).toHaveBeenNthCalledWith(2, expect.any(String)); + expect(exitCode).toBe(2); + expect(fs.existsSync(outFilePath)).toBe(true); + expect(output).toContain("Cannot find module './default_export'."); + expect(output).toContain( + "Default Imports are not supported, please use named imports instead!", + ); }); diff --git a/test/compiler/outfile.spec.ts b/test/compiler/outfile.spec.ts index e82e7341b..cc5554d1a 100644 --- a/test/compiler/outfile.spec.ts +++ b/test/compiler/outfile.spec.ts @@ -1,25 +1,20 @@ import * as fs from "fs"; import * as path from "path"; -import { compile } from "../../src/Compiler"; +import { runCli } from "./runner"; -let outFileRelPath: string; -let outFileAbsPath: string; - -beforeAll(() => { - outFileRelPath = "./testfiles/out_file.script"; - outFileAbsPath = path.join(__dirname, outFileRelPath); -}); +const outFileRelPath = "./testfiles/out_file.script"; +const outFileAbsPath = path.join(__dirname, outFileRelPath); afterEach(() => { - fs.unlink(outFileAbsPath, err => { - if (err) { - throw err; - } - }); + try { + fs.unlinkSync(outFileAbsPath); + } catch (err) { + if (err.code !== "ENOENT") throw err; + } }); -test("Outfile absoulte path", () => { - compile([ +test("Outfile absoulte path", async () => { + const { exitCode } = await runCli([ "--types", "node", "--skipLibCheck", @@ -28,11 +23,12 @@ test("Outfile absoulte path", () => { path.join(__dirname, "./testfiles/out_file.ts"), ]); + expect(exitCode).toBe(0); expect(fs.existsSync(outFileAbsPath)).toBe(true); }); -test("Outfile relative path", () => { - compile([ +test("Outfile relative path", async () => { + const { exitCode, output } = await runCli([ "--types", "node", "--skipLibCheck", @@ -43,5 +39,7 @@ test("Outfile relative path", () => { path.join(__dirname, "./testfiles/out_file.ts"), ]); + expect(output).not.toContain("error TS"); + expect(exitCode).toBe(0); expect(fs.existsSync(outFileAbsPath)).toBe(true); }); diff --git a/test/compiler/project.spec.ts b/test/compiler/project.spec.ts index c72c0041e..0b70b6d35 100644 --- a/test/compiler/project.spec.ts +++ b/test/compiler/project.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { compile } from "../../src/Compiler"; +import { runCli } from "./runner"; /** * Find all files inside a dir, recursively. @@ -78,17 +78,19 @@ test.each([ "out_dir/test_src/main.lua", ], }, -])("Compile project (%p)", ({ projectName, tsconfig, expectedFiles }) => { +])("Compile project (%p)", async ({ projectName, tsconfig, expectedFiles }) => { const relPathToProject = path.join("projects", projectName); // Setup we cant do this in beforeEach because we need the projectname existingFiles = getAllFiles(path.resolve(__dirname, relPathToProject)); filesAfterCompile = []; - // Setup End const tsconfigPath = path.resolve(__dirname, relPathToProject, tsconfig); - compile(["-p", tsconfigPath]); + const { exitCode, output } = await runCli(["-p", tsconfigPath]); + + expect(output).not.toContain("error TS"); + expect(exitCode).toBe(0); filesAfterCompile = getAllFiles(path.resolve(__dirname, relPathToProject)); expectedFiles = expectedFiles.map(relPath => diff --git a/test/compiler/runner.ts b/test/compiler/runner.ts new file mode 100644 index 000000000..e1d33b020 --- /dev/null +++ b/test/compiler/runner.ts @@ -0,0 +1,30 @@ +import { ChildProcess, fork } from "child_process"; +import * as path from "path"; + +jest.setTimeout(20000); + +const cliPath = path.join(__dirname, "../../src/tstl.ts"); + +export function forkCli(args: string[]): ChildProcess { + return fork(cliPath, args, { + stdio: "pipe", + execArgv: ["--require", "ts-node/register/transpile-only"], + }); +} + +export interface CliResult { + exitCode: number; + output: string; +} + +export async function runCli(args: string[]): Promise { + const child = forkCli(args); + + let output = ""; + child.stdout.on("data", data => (output += data)); + child.stderr.on("data", data => (output += data)); + + return new Promise(resolve => { + child.on("close", exitCode => resolve({ exitCode, output })); + }); +} diff --git a/test/compiler/watcher_proccess.ts b/test/compiler/watcher_proccess.ts deleted file mode 100644 index 25b4bd344..000000000 --- a/test/compiler/watcher_proccess.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { compile } from "../../src/Compiler"; - -process.on("message", args => { - compile(args); -}); diff --git a/test/compiler/watchmode.spec.ts b/test/compiler/watchmode.spec.ts index f4115dcc3..9cacad298 100644 --- a/test/compiler/watchmode.spec.ts +++ b/test/compiler/watchmode.spec.ts @@ -1,6 +1,6 @@ -import { fork } from "child_process"; import * as fs from "fs"; import * as path from "path"; +import { forkCli } from "./runner"; let testsCleanup: Array<() => void> = []; afterEach(() => { @@ -36,39 +36,30 @@ test.each([ args: ["-w", "-p", path.join(__dirname, "./projects/watchmode/")], fileToChange: path.join(__dirname, "./projects/watchmode/watch.ts"), }, -])( - "Watch single File (%p)", - async ({ args, fileToChange }) => { - const fileToChangeOut = fileToChange.replace(".ts", ".lua"); - const originalTS = fs.readFileSync(fileToChange, "utf-8"); +])("Watch single File (%p)", async ({ args, fileToChange }) => { + const fileToChangeOut = fileToChange.replace(".ts", ".lua"); + const originalTS = fs.readFileSync(fileToChange, "utf-8"); - const child = fork(path.join(__dirname, "watcher_proccess.ts"), [], { - silent: true, - execArgv: ["--require", "ts-node/register/transpile-only"], - }); + const child = forkCli(args); - testsCleanup.push(() => { - try { - fs.unlinkSync(fileToChangeOut); - } catch (err) { - if (err.code !== "ENOENT") throw err; - } - fs.writeFileSync(fileToChange, originalTS); - child.kill(); - }); - - child.send(args); + testsCleanup.push(() => { + try { + fs.unlinkSync(fileToChangeOut); + } catch (err) { + if (err.code !== "ENOENT") throw err; + } + fs.writeFileSync(fileToChange, originalTS); + child.kill(); + }); - await waitForFileExists(fileToChangeOut); - const initialResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); + await waitForFileExists(fileToChangeOut); + const initialResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); - fs.unlinkSync(fileToChangeOut); - fs.writeFileSync(fileToChange, "class MyTest2 {}"); + fs.unlinkSync(fileToChangeOut); + fs.writeFileSync(fileToChange, "class MyTest2 {}"); - await waitForFileExists(fileToChangeOut); - const updatedResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); + await waitForFileExists(fileToChangeOut); + const updatedResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); - expect(initialResultLua).not.toEqual(updatedResultLua); - }, - 20000, -); + expect(initialResultLua).not.toEqual(updatedResultLua); +}); diff --git a/test/translation/transformation.spec.ts b/test/translation/transformation.spec.ts index bd246dddf..99f18c31c 100644 --- a/test/translation/transformation.spec.ts +++ b/test/translation/transformation.spec.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; +import { LuaLibImportKind } from "../../src"; import * as util from "../util"; -import { LuaLibImportKind } from "../../src/CompilerOptions"; const fixturesPath = path.join(__dirname, "./transformation"); const fixtures = fs diff --git a/test/unit/assignmentDestructuring.spec.ts b/test/unit/assignmentDestructuring.spec.ts index 34ccacee5..6473f5f3b 100644 --- a/test/unit/assignmentDestructuring.spec.ts +++ b/test/unit/assignmentDestructuring.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; import * as util from "../util"; const assignmentDestruturingTs = ` diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 2e4f8d1d2..e825ba521 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -1,5 +1,9 @@ -import { findConfigFile, parseCommandLine, parseTsConfigString } from "../../src/CommandLineParser"; -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; +import { + findConfigFile, + parseCommandLine, + parseConfigFileContent, +} from "../../src/CommandLineParser"; test.each([ { args: [""], expected: LuaLibImportKind.Inline }, @@ -7,6 +11,7 @@ test.each([ { args: ["--luaLibImport", "always"], expected: LuaLibImportKind.Always }, { args: ["--luaLibImport", "inline"], expected: LuaLibImportKind.Inline }, { args: ["--luaLibImport", "require"], expected: LuaLibImportKind.Require }, + { args: ["--luaLibImport", "NoNe"], expected: LuaLibImportKind.None }, ])("CLI parser luaLibImportKind (%p)", ({ args, expected }) => { const result = parseCommandLine(args); if (result.isValid === true) { @@ -26,6 +31,7 @@ test.each([ { args: ["--luaTarget", "5.1"], expected: LuaTarget.Lua51 }, { args: ["--luaTarget", "5.2"], expected: LuaTarget.Lua52 }, { args: ["--luaTarget", "jit"], expected: LuaTarget.LuaJIT }, + { args: ["--luaTarget", "JiT"], expected: LuaTarget.LuaJIT }, { args: ["--luaTarget", "JIT"], expected: LuaTarget.LuaJIT }, { args: ["--luaTarget", "5.3"], expected: LuaTarget.Lua53 }, ])("CLI parser luaTarget (%p)", ({ args, expected }) => { @@ -225,7 +231,7 @@ test.each([ { tsConfig: `{ tstl: { noHeader: true } }`, expected: true }, { tsConfig: `{ tstl: { noHeader: "true" } }`, expected: true }, ])("TsConfig noHeader (%p)", ({ tsConfig, expected }) => { - const result = parseTsConfigString(tsConfig, ""); + const result = parseConfigFileContent(tsConfig, ""); if (result.isValid) { expect(result.result.options.noHeader).toBe(expected); diff --git a/test/unit/compiler/configuration/mixed/index.spec.ts b/test/unit/compiler/configuration/mixed/index.spec.ts index cb98a7d45..da7577ae0 100644 --- a/test/unit/compiler/configuration/mixed/index.spec.ts +++ b/test/unit/compiler/configuration/mixed/index.spec.ts @@ -1,8 +1,8 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; +import { CompilerOptions, LuaLibImportKind } from "../../../../../src"; import { parseCommandLine } from "../../../../../src/CommandLineParser"; -import { CompilerOptions, LuaLibImportKind } from "../../../../../src/CompilerOptions"; test("tsconfig.json mixed with cmd line args", () => { const rootPath = __dirname; @@ -11,6 +11,8 @@ test("tsconfig.json mixed with cmd line args", () => { ts.parseConfigFileTextToJson(tsConfigPath, fs.readFileSync(tsConfigPath).toString()).config, ts.sys, path.dirname(tsConfigPath), + undefined, + tsConfigPath, ); const parsedArgs = parseCommandLine([ diff --git a/test/unit/compiler/configuration/options.spec.ts b/test/unit/compiler/configuration/options.spec.ts deleted file mode 100644 index 31b9a6fe1..000000000 --- a/test/unit/compiler/configuration/options.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { LuaLibImportKind, LuaTarget } from "../../../../src/CompilerOptions"; -import * as util from "../../../util"; - -test.each([LuaTarget.LuaJIT, "jit", "JiT"])("Options luaTarget case-insensitive (%p)", target => { - const options = { luaTarget: target as LuaTarget }; - const result = util.transpileString("~a", options); - - expect(result).toBe("local ____ = bit.bnot(a)"); -}); - -test.each([LuaLibImportKind.None, "none", "NoNe"])( - "Options luaLibImport case-insensitive (%p)", - importKind => { - const options = { luaLibImport: importKind as LuaLibImportKind }; - const result = util.transpileString("const a = new Map();", options); - - expect(result).toBe("local a = Map.new()"); - }, -); diff --git a/test/unit/conditionals.spec.ts b/test/unit/conditionals.spec.ts index 6d79a37a8..d1aac1a4a 100644 --- a/test/unit/conditionals.spec.ts +++ b/test/unit/conditionals.spec.ts @@ -1,4 +1,4 @@ -import { LuaTarget } from "../../src/CompilerOptions"; +import { LuaTarget } from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 0fd43b914..580c9df8d 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; diff --git a/test/unit/json.spec.ts b/test/unit/json.spec.ts index 079188cba..e651f71d6 100644 --- a/test/unit/json.spec.ts +++ b/test/unit/json.spec.ts @@ -12,7 +12,7 @@ test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } "JSON (%p)", json => { const lua = util - .transpileString(json, jsonOptions, false, "file.json") + .transpileString({ "main.json": json }, jsonOptions, false) .replace(/^return ([\s\S]+)$/, "return JSONStringify($1)"); const result = util.executeLua(lua); @@ -21,7 +21,7 @@ test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } ); test("Empty JSON", () => { - expect(() => util.transpileString("", jsonOptions, false, "file.json")).toThrowExactError( + expect(() => util.transpileString({ "main.json": "" }, jsonOptions, false)).toThrowExactError( TSTLErrors.InvalidJsonFileContent(util.nodeStub), ); }); diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 1b78813d8..8f59dd627 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; @@ -774,7 +774,7 @@ test.each([ const lua53 = { luaTarget: LuaTarget.Lua53 }; const luajit = { luaTarget: LuaTarget.LuaJIT }; - expect(() => util.transpileString(loop, lua51)).toThrowError( + expect(() => util.transpileString(loop, lua51)).toThrowExactError( TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined), ); expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); diff --git a/test/unit/lualib/inlining.spec.ts b/test/unit/lualib/inlining.spec.ts index f4a6e2b45..fcb5ba938 100644 --- a/test/unit/lualib/inlining.spec.ts +++ b/test/unit/lualib/inlining.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../../src"; import * as util from "../../util"; test("map constructor", () => { diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index c02f052de..172ebf42d 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 747ce7b7a..c10ea2be4 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -2,70 +2,70 @@ import * as util from "../util"; test.each([ { - filePath: "file.ts", + filePath: "main.ts", usedPath: "./folder/Module", expectedPath: "folder.Module", options: { rootDir: "." }, throwsError: false, }, { - filePath: "file.ts", + filePath: "main.ts", usedPath: "./folder/Module", expectedPath: "folder.Module", options: { rootDir: "./" }, throwsError: false, }, { - filePath: "src/file.ts", + filePath: "src/main.ts", usedPath: "./folder/Module", expectedPath: "src.folder.Module", options: { rootDir: "." }, throwsError: false, }, { - filePath: "file.ts", + filePath: "main.ts", usedPath: "folder/Module", expectedPath: "folder.Module", options: { rootDir: ".", baseUrl: "." }, throwsError: false, }, { - filePath: "file.ts", + filePath: "main.ts", usedPath: "folder/Module", expectedPath: "folder.Module", options: { rootDir: "./", baseUrl: "." }, throwsError: false, }, { - filePath: "src/file.ts", + filePath: "src/main.ts", usedPath: "./folder/Module", expectedPath: "folder.Module", options: { rootDir: "src" }, throwsError: false, }, { - filePath: "src/file.ts", + filePath: "src/main.ts", usedPath: "./folder/Module", expectedPath: "folder.Module", options: { rootDir: "./src" }, throwsError: false, }, { - filePath: "file.ts", + filePath: "main.ts", usedPath: "../Module", expectedPath: "", options: { rootDir: "./src" }, throwsError: true, }, { - filePath: "src/dir/file.ts", + filePath: "src/dir/main.ts", usedPath: "../Module", expectedPath: "Module", options: { rootDir: "./src" }, throwsError: false, }, { - filePath: "src/dir/dir/file.ts", + filePath: "src/dir/dir/main.ts", usedPath: "../../dir/Module", expectedPath: "dir.Module", options: { rootDir: "./src" }, @@ -74,17 +74,11 @@ test.each([ ])( "require paths root from --baseUrl or --rootDir (%p)", ({ filePath, usedPath, expectedPath, options, throwsError }) => { + const input = { [filePath]: `import * from "${usedPath}";` }; if (throwsError) { - expect(() => - util.transpileString(`import * from "${usedPath}";`, options, true, filePath), - ).toThrow(); + expect(() => util.transpileString(input, options)).toThrow(); } else { - const lua = util.transpileString( - `import * from "${usedPath}";`, - options, - true, - filePath, - ); + const lua = util.transpileString(input, options); const regex = /require\("(.*?)"\)/; const match = regex.exec(lua); expect(match[1]).toBe(expectedPath); @@ -98,15 +92,10 @@ test.each([ ])( "noResolution on ambient modules causes no path alterations (%p)", ({ comment, expectedPath }) => { - const lua = util.transpileString( - { - "src/file.ts": `import * as fake from "fake";`, - "module.d.ts": `${comment} declare module "fake" {}`, - }, - undefined, - true, - "src/file.ts", - ); + const lua = util.transpileString({ + "src/main.ts": `import * as fake from "fake";`, + "module.d.ts": `${comment} declare module "fake" {}`, + }); const regex = /require\("(.*?)"\)/; expect(regex.exec(lua)[1]).toBe(expectedPath); }, diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index e8a374780..71a3777dc 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -1,6 +1,6 @@ +import { Position, SourceMapConsumer } from "source-map"; +import { LuaLibImportKind } from "../../src"; import * as util from "../util"; -import { LuaLibImportKind } from "../../src/CompilerOptions"; -import { SourceMapConsumer, Position } from "source-map"; test.each([ { @@ -47,7 +47,9 @@ test.each([ }, ])("Source map has correct mapping (%p)", async ({ typeScriptSource, assertPatterns }) => { // Act - const { lua, sourceMap } = util.transpileStringResult(typeScriptSource); + const { + file: { lua, sourceMap }, + } = util.transpileStringResult(typeScriptSource); // Assert const consumer = await new SourceMapConsumer(sourceMap); diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 00acb8d05..0b521e809 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src/CompilerOptions"; +import { LuaLibImportKind, LuaTarget } from "../../src"; import * as util from "../util"; test.each([{ inp: [] }, { inp: [1, 2, 3] }, { inp: [1, "test", 3] }])( @@ -31,7 +31,7 @@ test("Spread Element Lua 5.3", () => { }); test("Spread Element Lua JIT", () => { - const options = { luaTarget: "JiT" as LuaTarget, luaLibImport: LuaLibImportKind.None }; + const options = { luaTarget: LuaTarget.LuaJIT, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); expect(lua).toBe("local ____ = {unpack({\n 0,\n 1,\n 2,\n})}"); }); diff --git a/test/util.ts b/test/util.ts index d09cd7824..ced6a5fd8 100644 --- a/test/util.ts +++ b/test/util.ts @@ -2,13 +2,8 @@ import { lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; -import { - createStringCompilerProgram, - transpileString as compilerTranspileString, -} from "../src/Compiler"; -import { CompilerOptions, LuaLibImportKind, LuaTarget } from "../src/CompilerOptions"; -import { LuaTransformer } from "../src/LuaTransformer"; -import { TranspileResult } from "../src/LuaTranspiler"; +import * as tstl from "../src"; +import { createVirtualProgram } from "../src/API"; export const nodeStub = ts.createNode(ts.SyntaxKind.Unknown); @@ -36,81 +31,54 @@ expect.extend({ executionError = err; } - expect(() => { - if (executionError) throw executionError; - }).toThrowError(error.constructor as any); - expect(() => { - if (executionError) throw executionError; - }).toThrowError(error); + // TODO: + expect(executionError).toBeDefined(); + expect(executionError.message).toContain(error.message); return { pass: true, message: () => "" }; }, }); -function compilerTranspile( - str: string | { [filename: string]: string }, - options: CompilerOptions = {}, - ignoreDiagnostics = true, - filePath = "file.ts", -): TranspileResult { - return compilerTranspileString( - str, - { - luaLibImport: LuaLibImportKind.Inline, - luaTarget: LuaTarget.Lua53, - noHeader: true, - skipLibCheck: true, - target: ts.ScriptTarget.ESNext, - lib: [ - "lib.es2015.d.ts", - "lib.es2016.d.ts", - "lib.es2017.d.ts", - "lib.es2018.d.ts", - "lib.esnext.d.ts", - ], - ...options, - }, - ignoreDiagnostics, - filePath, - ); -} - export function transpileString( str: string | { [filename: string]: string }, - options: CompilerOptions = {}, + options: tstl.CompilerOptions = {}, ignoreDiagnostics = true, - filePath = "file.ts", ): string { - const { lua } = transpileStringResult(str, options, ignoreDiagnostics, filePath); + const { + diagnostics, + file: { lua }, + } = transpileStringResult(str, options); + + const errors = diagnostics + .filter(d => d.category === ts.DiagnosticCategory.Error) + .filter(d => (ignoreDiagnostics ? d.code === 0 : true)); + + if (errors.length > 0) { + throw new Error(errors.map(d => d.messageText).join("\n")); + } + return lua.trim(); } export function transpileStringResult( - str: string | { [filename: string]: string }, - options: CompilerOptions = {}, - ignoreDiagnostics = true, - filePath = "file.ts", -): TranspileResult { - return compilerTranspileString( - str, - { - luaLibImport: LuaLibImportKind.Inline, - luaTarget: LuaTarget.Lua53, - noHeader: true, - skipLibCheck: true, - target: ts.ScriptTarget.ESNext, - lib: [ - "lib.es2015.d.ts", - "lib.es2016.d.ts", - "lib.es2017.d.ts", - "lib.es2018.d.ts", - "lib.esnext.d.ts", - ], - ...options, - }, - ignoreDiagnostics, - filePath, - ); + input: string | { [filename: string]: string }, + options: tstl.CompilerOptions = {}, +): tstl.TranspileStringResult { + return tstl.transpileString(input, { + luaLibImport: tstl.LuaLibImportKind.Inline, + luaTarget: tstl.LuaTarget.Lua53, + noHeader: true, + skipLibCheck: true, + target: ts.ScriptTarget.ESNext, + lib: [ + "lib.es2015.d.ts", + "lib.es2016.d.ts", + "lib.es2017.d.ts", + "lib.es2018.d.ts", + "lib.esnext.d.ts", + ], + ...options, + }); } const lualibContent = fs.readFileSync( @@ -154,14 +122,16 @@ export function executeLua(luaStr: string, withLib = true): any { } // Get a mock transformer to use for testing -export function makeTestTransformer(target: LuaTarget = LuaTarget.Lua53): LuaTransformer { +export function makeTestTransformer( + target: tstl.LuaTarget = tstl.LuaTarget.Lua53, +): tstl.LuaTransformer { const options = { luaTarget: target }; - return new LuaTransformer(ts.createProgram([], options), options); + return new tstl.LuaTransformer(ts.createProgram([], options), options); } export function transpileAndExecute( tsStr: string, - compilerOptions?: CompilerOptions, + compilerOptions?: tstl.CompilerOptions, luaHeader?: string, tsHeader?: string, ): any { @@ -179,7 +149,7 @@ export function transpileAndExecute( export function transpileExecuteAndReturnExport( tsStr: string, returnExport: string, - compilerOptions?: CompilerOptions, + compilerOptions?: tstl.CompilerOptions, luaHeader?: string, ): any { const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; @@ -195,10 +165,10 @@ export function transpileExecuteAndReturnExport( export function parseTypeScript( typescript: string, - target: LuaTarget = LuaTarget.Lua53, + target: tstl.LuaTarget = tstl.LuaTarget.Lua53, ): [ts.SourceFile, ts.TypeChecker] { - const program = createStringCompilerProgram(typescript, { luaTarget: target }); - return [program.getSourceFile("file.ts"), program.getTypeChecker()]; + const program = createVirtualProgram({ "main.ts": typescript }, { luaTarget: target }); + return [program.getSourceFile("main.ts"), program.getTypeChecker()]; } export function findFirstChild( From e87c8ee530e021ad398c0b5eba53fa92f2539738 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 6 Apr 2019 19:01:41 +0500 Subject: [PATCH 02/44] Move high-level API to index file --- src/API.ts | 111 ------------------------------------------------- src/index.ts | 112 +++++++++++++++++++++++++++++++++++++++++++++++++- src/tstl.ts | 17 ++++---- test/util.ts | 3 +- tsconfig.json | 3 +- 5 files changed, 121 insertions(+), 125 deletions(-) delete mode 100644 src/API.ts diff --git a/src/API.ts b/src/API.ts deleted file mode 100644 index 281476cc6..000000000 --- a/src/API.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as ts from "typescript"; -import { parseConfigFileContent } from "./CommandLineParser"; -import { CompilerOptions } from "./CompilerOptions"; -import { getTranspileOutput, TranspiledFile, TranspilationResult } from "./Transpile"; - -export function transpileFiles( - rootNames: string[], - options: CompilerOptions = {} -): TranspilationResult { - const program = ts.createProgram(rootNames, options); - const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); - - const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...diagnostics, - ]); - - return { transpiledFiles, diagnostics: [...allDiagnostics] }; -} - -export function transpileProject( - fileName: string, - options?: CompilerOptions -): TranspilationResult { - const parseResult = parseConfigFileContent( - fs.readFileSync(fileName, "utf8"), - fileName, - options - ); - if (parseResult.isValid === false) { - // TODO: Return diagnostics - throw new Error(parseResult.errorMessage); - } - - return transpileFiles(parseResult.result.fileNames, parseResult.result.options); -} - -const libCache: { [key: string]: ts.SourceFile } = {}; -export function createVirtualProgram( - input: Record, - options?: CompilerOptions -): ts.Program { - const compilerHost: ts.CompilerHost = { - fileExists: () => true, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "", - getDefaultLibFileName: ts.getDefaultLibFileName, - readFile: () => "", - getNewLine: () => "\n", - useCaseSensitiveFileNames: () => false, - writeFile: () => {}, - - getSourceFile: filename => { - if (filename in input) { - return ts.createSourceFile( - filename, - input[filename], - ts.ScriptTarget.Latest, - false - ); - } - - if (filename.startsWith("lib.")) { - if (libCache[filename]) return libCache[filename]; - const typeScriptDir = path.dirname(require.resolve("typescript")); - const filePath = path.join(typeScriptDir, filename); - const content = fs.readFileSync(filePath, "utf8"); - - libCache[filename] = ts.createSourceFile( - filename, - content, - ts.ScriptTarget.Latest, - false - ); - - return libCache[filename]; - } - }, - }; - - return ts.createProgram(Object.keys(input), options, compilerHost); -} - -export interface TranspileStringResult { - file: TranspiledFile; - diagnostics: ts.Diagnostic[]; -} - -export function transpileString( - input: string | Record, - options: CompilerOptions = {} -): TranspileStringResult { - const programFiles = typeof input === "object" ? input : { "main.ts": input }; - const mainFileName = - typeof input === "string" - ? "main.ts" - : Object.keys(input).find(x => /\bmain\.[a-z]+$/.test(x)); - if (mainFileName === undefined) throw new Error('Input should have a file named "main"'); - - const program = createVirtualProgram(programFiles, options); - const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); - - const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...diagnostics, - ]); - - return { file: transpiledFiles.get(mainFileName), diagnostics: [...allDiagnostics] }; -} diff --git a/src/index.ts b/src/index.ts index c6d245322..116aa047d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,10 @@ -export { transpileFiles, transpileProject, transpileString, TranspileStringResult } from "./API"; +import * as fs from "fs"; +import * as path from "path"; +import * as ts from "typescript"; +import { parseConfigFileContent } from "./CommandLineParser"; +import { CompilerOptions } from "./CompilerOptions"; +import { getTranspileOutput, TranspilationResult, TranspiledFile } from "./Transpile"; + export { parseConfigFileContent } from "./CommandLineParser"; export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; export * from "./Emit"; @@ -7,3 +13,107 @@ export { LuaLibFeature } from "./LuaLib"; export { LuaPrinter } from "./LuaPrinter"; export { LuaTransformer } from "./LuaTransformer"; export * from "./Transpile"; + +export function transpileFiles( + rootNames: string[], + options: CompilerOptions = {} +): TranspilationResult { + const program = ts.createProgram(rootNames, options); + const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...diagnostics, + ]); + + return { transpiledFiles, diagnostics: [...allDiagnostics] }; +} + +export function transpileProject(fileName: string, options?: CompilerOptions): TranspilationResult { + const parseResult = parseConfigFileContent( + fs.readFileSync(fileName, "utf8"), + fileName, + options + ); + if (parseResult.isValid === false) { + // TODO: Return diagnostics + throw new Error(parseResult.errorMessage); + } + + return transpileFiles(parseResult.result.fileNames, parseResult.result.options); +} + +const libCache: { [key: string]: ts.SourceFile } = {}; + +/** @internal */ +export function createVirtualProgram( + input: Record, + options?: CompilerOptions +): ts.Program { + const compilerHost: ts.CompilerHost = { + fileExists: () => true, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "", + getDefaultLibFileName: ts.getDefaultLibFileName, + readFile: () => "", + getNewLine: () => "\n", + useCaseSensitiveFileNames: () => false, + writeFile: () => {}, + + getSourceFile: filename => { + if (filename in input) { + return ts.createSourceFile( + filename, + input[filename], + ts.ScriptTarget.Latest, + false + ); + } + + if (filename.startsWith("lib.")) { + if (libCache[filename]) return libCache[filename]; + const typeScriptDir = path.dirname(require.resolve("typescript")); + const filePath = path.join(typeScriptDir, filename); + const content = fs.readFileSync(filePath, "utf8"); + + libCache[filename] = ts.createSourceFile( + filename, + content, + ts.ScriptTarget.Latest, + false + ); + + return libCache[filename]; + } + }, + }; + + return ts.createProgram(Object.keys(input), options, compilerHost); +} + +export interface TranspileStringResult { + file: TranspiledFile; + diagnostics: ts.Diagnostic[]; +} + +export function transpileString( + input: string | Record, + options: CompilerOptions = {} +): TranspileStringResult { + const programFiles = typeof input === "object" ? input : { "main.ts": input }; + const mainFileName = + typeof input === "string" + ? "main.ts" + : Object.keys(input).find(x => /\bmain\.[a-z]+$/.test(x)); + if (mainFileName === undefined) throw new Error('Input should have a file named "main"'); + + const program = createVirtualProgram(programFiles, options); + const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...diagnostics, + ]); + + return { file: transpiledFiles.get(mainFileName), diagnostics: [...allDiagnostics] }; +} diff --git a/src/tstl.ts b/src/tstl.ts index 03b6bfad7..3179166f5 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -1,10 +1,7 @@ #!/usr/bin/env node import * as ts from "typescript"; -import { transpileFiles } from "./API"; +import * as tstl from "."; import * as CommandLineParser from "./CommandLineParser"; -import { CompilerOptions } from "./CompilerOptions"; -import { emitTranspiledFiles } from "./Emit"; -import { getTranspileOutput } from "./Transpile"; function createDiagnosticReporter(pretty: boolean): ts.DiagnosticReporter { const host: ts.FormatDiagnosticsHost = { @@ -25,7 +22,7 @@ function createDiagnosticReporter(pretty: boolean): ts.DiagnosticReporter { }; } -function shouldBePretty(options?: CompilerOptions): boolean { +function shouldBePretty(options?: tstl.CompilerOptions): boolean { return !options || options.pretty === undefined ? ts.sys.writeOutputIsTTY !== undefined && ts.sys.writeOutputIsTTY() : Boolean(options.pretty); @@ -80,11 +77,11 @@ function executeCommandLine(argv: string[]): void { ts.createWatchProgram(host); } } else { - const { diagnostics, transpiledFiles } = transpileFiles( + const { diagnostics, transpiledFiles } = tstl.transpileFiles( commandLine.result.fileNames, commandLine.result.options ); - emitTranspiledFiles(commandLine.result.options, transpiledFiles); + tstl.emitTranspiledFiles(commandLine.result.options, transpiledFiles); diagnostics.forEach(reportDiagnostic); if (diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length === 0) { @@ -101,7 +98,7 @@ function executeCommandLine(argv: string[]): void { function updateWatchCompilerHost( host: ts.WatchCompilerHost, - options: CompilerOptions + options: tstl.CompilerOptions ): void { let fullRecompile = true; host.afterProgramCreate = builderProgram => { @@ -123,12 +120,12 @@ function updateWatchCompilerHost( } } - const { diagnostics: emitDiagnostics, transpiledFiles } = getTranspileOutput({ + const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.getTranspileOutput({ program, options, sourceFiles, }); - emitTranspiledFiles(options, transpiledFiles); + tstl.emitTranspiledFiles(options, transpiledFiles); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...preEmitDiagnostics, diff --git a/test/util.ts b/test/util.ts index ced6a5fd8..a47b7a223 100644 --- a/test/util.ts +++ b/test/util.ts @@ -3,7 +3,6 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; import * as tstl from "../src"; -import { createVirtualProgram } from "../src/API"; export const nodeStub = ts.createNode(ts.SyntaxKind.Unknown); @@ -167,7 +166,7 @@ export function parseTypeScript( typescript: string, target: tstl.LuaTarget = tstl.LuaTarget.Lua53, ): [ts.SourceFile, ts.TypeChecker] { - const program = createVirtualProgram({ "main.ts": typescript }, { luaTarget: target }); + const program = tstl.createVirtualProgram({ "main.ts": typescript }, { luaTarget: target }); return [program.getSourceFile("main.ts"), program.getTypeChecker()]; } diff --git a/tsconfig.json b/tsconfig.json index 84d667105..74dc5be14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "declaration": true, "sourceMap": true, "target": "es2017", - "module": "commonjs" + "module": "commonjs", + "stripInternal": true }, "include": ["src"], "exclude": ["src/lualib"] From 8b8eacae0570b7c21db4e702e5ca10c469048bea Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 6 Apr 2019 19:09:04 +0500 Subject: [PATCH 03/44] Extract multi-file `transpileString` input to `transpileVirtualProgram` --- src/index.ts | 33 ++++++++++++++++++++++----------- test/util.ts | 10 +++++++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/index.ts b/src/index.ts index 116aa047d..044c0f4c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -91,25 +91,36 @@ export function createVirtualProgram( return ts.createProgram(Object.keys(input), options, compilerHost); } -export interface TranspileStringResult { +export interface VirtualProgramResult { file: TranspiledFile; diagnostics: ts.Diagnostic[]; } export function transpileString( - input: string | Record, + content: string, options: CompilerOptions = {} -): TranspileStringResult { - const programFiles = typeof input === "object" ? input : { "main.ts": input }; - const mainFileName = - typeof input === "string" - ? "main.ts" - : Object.keys(input).find(x => /\bmain\.[a-z]+$/.test(x)); - if (mainFileName === undefined) throw new Error('Input should have a file named "main"'); - - const program = createVirtualProgram(programFiles, options); +): VirtualProgramResult { + const program = createVirtualProgram({ "main.ts": content }, options); const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...diagnostics, + ]); + + return { file: transpiledFiles.get("main.ts"), diagnostics: [...allDiagnostics] }; +} +export function transpileVirtualProgram( + files: Record, + options: CompilerOptions = {} +): VirtualProgramResult { + const mainFileName = Object.keys(files).find(x => /\bmain\.[a-z]+$/.test(x)); + if (mainFileName === undefined) { + throw new Error('Virtual program should have a file named "main"'); + } + + const program = createVirtualProgram(files, options); + const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...diagnostics, diff --git a/test/util.ts b/test/util.ts index a47b7a223..b1bd32a43 100644 --- a/test/util.ts +++ b/test/util.ts @@ -62,8 +62,8 @@ export function transpileString( export function transpileStringResult( input: string | { [filename: string]: string }, options: tstl.CompilerOptions = {}, -): tstl.TranspileStringResult { - return tstl.transpileString(input, { +): tstl.VirtualProgramResult { + const optionsWithDefaults = { luaLibImport: tstl.LuaLibImportKind.Inline, luaTarget: tstl.LuaTarget.Lua53, noHeader: true, @@ -77,7 +77,11 @@ export function transpileStringResult( "lib.esnext.d.ts", ], ...options, - }); + }; + + return typeof input === "string" + ? tstl.transpileString(input, optionsWithDefaults) + : tstl.transpileVirtualProgram(input, optionsWithDefaults); } const lualibContent = fs.readFileSync( From 02699cdc93a7782a2e38525446f6dee0d97f1531 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 6 Apr 2019 19:36:14 +0500 Subject: [PATCH 04/44] Return an results from emitTranspiledFiles instead of a using a callback --- build_lualib.ts | 4 +++- src/Emit.ts | 24 +++++++++++++++--------- src/tstl.ts | 8 ++++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/build_lualib.ts b/build_lualib.ts index f9d24ee48..4a7c0a6c9 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import * as glob from "glob"; import * as path from "path"; +import * as ts from "typescript"; import * as tstl from "./src"; import { LuaLib } from "./src/LuaLib"; @@ -16,7 +17,8 @@ const options: tstl.CompilerOptions = { // TODO: Check diagnostics const { transpiledFiles } = tstl.transpileFiles(glob.sync("./src/lualib/**/*.ts"), options); -tstl.emitTranspiledFiles(options, transpiledFiles); +const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); +emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const bundlePath = path.join(__dirname, "./dist/lualib/lualib_bundle.lua"); if (fs.existsSync(bundlePath)) { diff --git a/src/Emit.ts b/src/Emit.ts index 6065cc668..9da6774d6 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -1,20 +1,24 @@ import * as fs from "fs"; import * as path from "path"; -import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { TranspiledFile } from "./Transpile"; const trimExt = (filePath: string) => path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); +export interface OutputFile { + name: string; + text: string; +} + let lualibContent: string; export function emitTranspiledFiles( options: CompilerOptions, - transpiledFiles: Map, - writeFile = ts.sys.writeFile -): void { + transpiledFiles: Map +): OutputFile[] { const { rootDir, outDir, outFile, luaLibImport } = options; + const files: OutputFile[] = []; for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { let outPath = fileName; if (outDir !== rootDir) { @@ -35,19 +39,19 @@ export function emitTranspiledFiles( } if (lua !== undefined) { - writeFile(outPath, lua); + files.push({ name: outPath, text: lua }); } if (sourceMap !== undefined && options.sourceMap) { - writeFile(outPath + ".map", sourceMap); + files.push({ name: outPath + ".map", text: sourceMap }); } if (declaration !== undefined) { - writeFile(trimExt(outPath) + ".d.ts", declaration); + files.push({ name: trimExt(outPath) + ".d.ts", text: declaration }); } if (declarationMap !== undefined) { - writeFile(trimExt(outPath) + ".d.ts.map", declarationMap); + files.push({ name: trimExt(outPath) + ".d.ts.map", text: declarationMap }); } } @@ -60,6 +64,8 @@ export function emitTranspiledFiles( } const outPath = path.join(outDir, "lualib_bundle.lua"); - writeFile(outPath, lualibContent); + files.push({ name: outPath, text: lualibContent }); } + + return files; } diff --git a/src/tstl.ts b/src/tstl.ts index 3179166f5..698f1760e 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -81,7 +81,9 @@ function executeCommandLine(argv: string[]): void { commandLine.result.fileNames, commandLine.result.options ); - tstl.emitTranspiledFiles(commandLine.result.options, transpiledFiles); + + const emitResult = tstl.emitTranspiledFiles(commandLine.result.options, transpiledFiles); + emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); diagnostics.forEach(reportDiagnostic); if (diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length === 0) { @@ -125,7 +127,9 @@ function updateWatchCompilerHost( options, sourceFiles, }); - tstl.emitTranspiledFiles(options, transpiledFiles); + + const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...preEmitDiagnostics, From de5ec24b2278ca83bd1c9ae355ee47bb23ce1602 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 14 Apr 2019 23:14:16 +0500 Subject: [PATCH 05/44] Refactor CommandLineParser and further CLI refactor --- src/CommandLineParser.ts | 433 ++++++------------ src/LuaPrinter.ts | 9 +- src/Transpile.ts | 10 +- src/diagnostics.ts | 55 +++ src/index.ts | 17 +- src/tstl.ts | 288 ++++++++---- test/unit/commandLineParser.spec.ts | 239 ++++------ .../configuration/mixed/index.spec.ts | 42 -- .../configuration/mixed/project-tsconfig.json | 8 - test/util.ts | 1 - 10 files changed, 505 insertions(+), 597 deletions(-) create mode 100644 src/diagnostics.ts delete mode 100644 test/unit/compiler/configuration/mixed/index.spec.ts delete mode 100644 test/unit/compiler/configuration/mixed/project-tsconfig.json diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index e03242199..5cc5b5d42 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -1,68 +1,56 @@ -import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; - -export type CLIParseResult = ParseResult; - -type ParseResult = - { isValid: true; result: T } - | { isValid: false, errorMessage: string}; - -type ArgumentParseResult = - { isValid: true; result: T; increment?: number } - | { isValid: false, errorMessage: string }; +import * as diagnostics from "./diagnostics"; export interface ParsedCommandLine extends ts.ParsedCommandLine { options: CompilerOptions; } -interface BaseCLIOption { - aliases: string[]; +interface CommandLineOptionBase { describe: string; - type: string; + aliases?: string[]; } -interface CLIOption extends BaseCLIOption { - choices: T[]; - default: T; +interface CommandLineOptionOfEnum extends CommandLineOptionBase { + type: "enum"; + choices: string[]; } -const optionDeclarations: {[key: string]: CLIOption} = { +interface CommandLineOptionOfBoolean extends CommandLineOptionBase { + type: "boolean"; +} + +type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean; +const optionDeclarations: Record = { luaLibImport: { - choices: [LuaLibImportKind.Inline, LuaLibImportKind.Require, LuaLibImportKind.Always, LuaLibImportKind.None], - default: LuaLibImportKind.Inline, describe: "Specifies how js standard features missing in lua are imported.", type: "enum", - } as CLIOption, + choices: Object.values(LuaLibImportKind), + }, luaTarget: { aliases: ["lt"], - choices: [LuaTarget.LuaJIT, LuaTarget.Lua53, LuaTarget.Lua52, LuaTarget.Lua51], - default: LuaTarget.LuaJIT, describe: "Specify Lua target version.", type: "enum", - } as CLIOption, + choices: Object.values(LuaTarget), + }, noHeader: { - default: false, describe: "Specify if a header will be added to compiled files.", type: "boolean", - } as CLIOption, + }, noHoisting: { - default: false, describe: "Disables hoisting.", type: "boolean", - } as CLIOption, + }, sourceMapTraceback: { - default: false, describe: "Applies the source map to show source TS files and lines in error tracebacks.", type: "boolean", - } as CLIOption, + }, }; -export const { version } = require("../package.json"); +export const version = `Version ${require("../package.json").version}`; const helpString = - `Version ${version}\n` + "Syntax: tstl [options] [files...]\n\n" + "Examples: tstl path/to/file.ts [...]\n" + @@ -72,72 +60,16 @@ const helpString = "for the typescript compiler (For a list of options use tsc -h).\n" + "Some tsc options might have no effect."; -/** - * Parse the supplied arguments. - * The result will include arguments supplied via CLI and arguments from tsconfig. - */ -export function parseCommandLine(args: string[]): CLIParseResult -{ - let commandLine = ts.parseCommandLine(args); - - // Run diagnostics to check for invalid tsc options - const diagnosticsResult = runTsDiagnostics(commandLine); - if (diagnosticsResult.isValid === false) { - return diagnosticsResult; - } - - // This will add TS and TSTL options from a tsconfig - const configResult = readTsConfig(commandLine); - if (configResult.isValid === true) { - commandLine = configResult.result; - } else { - return { isValid: false, errorMessage: configResult.errorMessage }; - } - - // Run diagnostics to check for invalid tsconfig - const diagnosticsResult2 = runTsDiagnostics(commandLine); - if (diagnosticsResult2.isValid === false) { - return diagnosticsResult2; - } - - // Merge TSTL CLI options in (highest priority) will also set defaults if none specified - const tstlCLIResult = parseTSTLOptions(commandLine, args); - if (tstlCLIResult.isValid === true) { - commandLine = tstlCLIResult.result; - } else { - return { isValid: false, errorMessage: tstlCLIResult.errorMessage }; - } - - 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 { isValid: true, result: commandLine as ParsedCommandLine }; -} - export function getHelpString(): string { let result = helpString + "\n\n"; result += "Options:\n"; - for (const optionName in optionDeclarations) { - const option = optionDeclarations[optionName]; - const aliasStrings = option.aliases - ? option.aliases.map(a => "-" + a) - : []; + for (const [optionName, option] of Object.entries(optionDeclarations)) { + const aliasStrings = (option.aliases || []).map(a => "-" + a); const optionString = aliasStrings.concat(["--" + optionName]).join("|"); - const parameterDescribe = option.choices - ? option.choices.join("|") - : option.type; + const parameterDescribe = option.type === "enum" ? option.choices.join("|") : option.type; const spacing = " ".repeat(Math.max(1, 45 - optionString.length - parameterDescribe.length)); @@ -147,138 +79,123 @@ export function getHelpString(): string { return result; } -function readTsConfig(parsedCommandLine: ts.ParsedCommandLine): CLIParseResult -{ - const options = parsedCommandLine.options; +export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): ParsedCommandLine { + for (const key in parsedConfigFile.raw) { + const option = optionDeclarations[key]; + if (!option) continue; - // Load config - if (options.project) { - const findProjectPathResult = findConfigFile(options); - if (findProjectPathResult.isValid === true) { - options.project = findProjectPathResult.result; + const value = readValue(parsedConfigFile.raw[key], option.type); + if (option.type === "enum" && !option.choices.includes(value as string)) { + parsedConfigFile.errors.push( + diagnostics.argumentForOptionMustBe(key, option.choices.join(", ")) + ); } else { - return { isValid: false, errorMessage: findProjectPathResult.errorMessage }; - } - - const configPath = options.project; - const configContent = fs.readFileSync(configPath, "utf8"); - return parseConfigFileContent(configContent, configPath, options); - } - return { isValid: true, result: parsedCommandLine }; -} - -export function parseConfigFileContent( - tsConfigString: string, - configPath: string, - existingOptions?: ts.CompilerOptions -): CLIParseResult { - const configJson = ts.parseConfigFileTextToJson(configPath, tsConfigString); - const parsedJsonConfig = ts.parseJsonConfigFileContent( - configJson.config, - ts.sys, - path.dirname(configPath), - existingOptions, - configPath - ); - - for (const key in parsedJsonConfig.raw) { - const option = optionDeclarations[key]; - if (option !== undefined) { - const value = readValue(parsedJsonConfig.raw[key], option.type, key); - if (option.choices) { - if (option.choices.indexOf(value) < 0) { - return { - isValid: false, - errorMessage: `Unknown ${key} value '${value}'.\nAccepted values: ${option.choices}`, - }; - } - } // console.warn(`[Deprectated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` // + `look like { "compilerOptions": { }, "tstl": { } }`); - parsedJsonConfig.options[key] = value; + if (parsedConfigFile.options[key] === undefined) { + parsedConfigFile.options[key] = value; + } } } // Eventually we will only look for the tstl object for tstl options - if (parsedJsonConfig.raw.tstl) { - for (const key in parsedJsonConfig.raw.tstl) { + if (parsedConfigFile.raw.tstl) { + for (const key in parsedConfigFile.raw.tstl) { const option = optionDeclarations[key]; - if (option !== undefined) { - const value = readValue(parsedJsonConfig.raw.tstl[key], option.type, key); - if (option.choices) { - if (option.choices.indexOf(value) < 0) { - return { - isValid: false, - errorMessage: `Unknown ${key} value '${value}'.\nAccepted values: ${option.choices}`, - }; - } + if (!option) continue; + + const value = readValue(parsedConfigFile.raw.tstl[key], option.type); + if (option.type === "enum" && !option.choices.includes(value as string)) { + parsedConfigFile.errors.push( + diagnostics.argumentForOptionMustBe(key, option.choices.join(", ")) + ); + } else { + if (parsedConfigFile.options[key] === undefined) { + parsedConfigFile.options[key] = value; } - - parsedJsonConfig.options[key] = value; } } } - return { isValid: true, result: parsedJsonConfig }; + return parsedConfigFile; +} + +export function parseCommandLine(args: string[]): ParsedCommandLine { + const commandLine = updateParsedCommandLine(ts.parseCommandLine(args), args); + + // TODO: Remove + 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 parseTSTLOptions(commandLine: ts.ParsedCommandLine, args: string[]): CLIParseResult { - const result = {}; +function updateParsedCommandLine( + parsedCommandLine: ts.ParsedCommandLine, + args: string[] +): ParsedCommandLine { + // Generate a list of valid option names and aliases + const optionNames = Object.keys(optionDeclarations) + .map(n => `--${n}`) + .concat(...Object.values(optionDeclarations).map(o => (o.aliases || []).map(a => `-${a}`))); + + // Ignore errors caused by tstl specific compiler options + const tsInvalidCompilerOptionErrorCode = 5023; + parsedCommandLine.errors = parsedCommandLine.errors.filter(err => { + return !( + err.code === tsInvalidCompilerOptionErrorCode && + optionNames.some(optionName => String(err.messageText).endsWith(`'${optionName}'.`)) + ); + }); + for (let i = 0; i < args.length; i++) { - if (args[i].startsWith("--")) { - const argumentName = args[i].substr(2); - const option = optionDeclarations[argumentName]; - if (option) { - const argumentResult = getArgumentValue(argumentName, i, args); - if (argumentResult.isValid === true) { - result[argumentName] = argumentResult.result; - // Skip value from being considered as option - i += argumentResult.increment !== undefined ? argumentResult.increment : 1; - } else { - return { isValid: false, errorMessage: argumentResult.errorMessage }; - } - } - } else if (args[i].startsWith("-")) { - const argument = args[i].substr(1); - let argumentName: string; + if (!args[i].startsWith("-")) continue; + + const hasTwoDashes = args[i].startsWith("--"); + const parameterValue = args[i].substr(hasTwoDashes ? 2 : 1); + let argumentName = optionDeclarations[parameterValue] && parameterValue; + if (!hasTwoDashes && !argumentName) { for (const key in optionDeclarations) { - if (optionDeclarations[key].aliases && optionDeclarations[key].aliases.indexOf(argument) >= 0) { + if ((optionDeclarations[key].aliases || []).includes(parameterValue)) { argumentName = key; break; } } + } - if (argumentName) { - const argumentResult = getArgumentValue(argumentName, i, args); - if (argumentResult.isValid === true) { - result[argumentName] = argumentResult.result; - // Skip value from being considered as option - i += argumentResult.increment !== undefined ? argumentResult.increment : 1; - } else { - return { isValid: false, errorMessage: argumentResult.errorMessage }; - } + if (argumentName) { + const argumentResult = getArgumentValue(argumentName, i, args); + if (argumentResult.isValid === true) { + parsedCommandLine.options[argumentName] = argumentResult.result; + // Skip value from being considered as option + i += argumentResult.increment; + } else { + parsedCommandLine.errors.push(argumentResult.error); } } } - for (const option in result) { - commandLine.options[option] = result[option]; - } - // Add defaults if not set - const defaultOptions = getDefaultOptions(); - for (const option in defaultOptions) { - if (!commandLine.options[option]) { - commandLine.options[option] = defaultOptions[option]; - } - } - return { isValid: true, result: commandLine }; + + return parsedCommandLine; } +type ArgumentParseResult = + | { isValid: true; result: string | boolean; increment: number } + | { isValid: false; error: ts.Diagnostic }; + function getArgumentValue( argumentName: string, argumentIndex: number, args: string[] -): ArgumentParseResult -{ +): ArgumentParseResult { const option = optionDeclarations[argumentName]; const argument = args[argumentIndex + 1]; @@ -288,119 +205,47 @@ function getArgumentValue( } if (argument === undefined) { - return { isValid: false, errorMessage: `Missing value for parameter ${argumentName}`}; + return { isValid: false, error: diagnostics.compilerOptionExpectsAnArgument(argumentName) }; } - const value = readValue(argument, option.type, argumentName); + const value = readValue(argument, option.type); - if (option.choices) { - if (option.choices.indexOf(value) < 0) { - return { - isValid: false, - errorMessage: `Unknown ${argumentName} value '${value}'. Accepted values are: ${option.choices}`, - }; - } + if (option.type === "enum" && option.choices && !option.choices.includes(value as string)) { + return { + isValid: false, + error: diagnostics.argumentForOptionMustBe( + `--${argumentName}`, + option.choices.join(", ") + ), + }; } - return { isValid: true, result: value }; + return { isValid: true, result: value, increment: 1 }; } -function readValue(value: string | boolean, valueType: string, parameterName: string): string | boolean { - if (valueType === "boolean") { - return value === true || value === "true" || value === "t" - ? true - : false; - } else if (valueType === "enum") { +function readValue(value: string | boolean, type: CommandLineOption["type"]): string | boolean { + if (type === "boolean") { + return value === true || value === "true" || value === "t"; + } else if (type === "enum") { return value.toString().toLowerCase(); - } else { - return value; } } -function getDefaultOptions(): CompilerOptions { - const options: CompilerOptions = {}; - - for (const optionName in optionDeclarations) { - if (optionDeclarations[optionName].default !== undefined) { - options[optionName] = optionDeclarations[optionName].default; - } - } - - return options; -} - -/** Check the current state of the ParsedCommandLine for errors */ -function runTsDiagnostics(commandLine: ts.ParsedCommandLine): ParseResult { - // Remove files that dont exist - commandLine.fileNames = commandLine.fileNames.filter(file => fs.existsSync(file) || fs.existsSync(file + ".ts")); - - const tsInvalidCompilerOptionErrorCode = 5023; - if (commandLine.errors.length !== 0) { - // Generate a list of valid option names and aliases - const optionNames: string[] = []; - for (const key of Object.keys(optionDeclarations)) { - optionNames.push(key); - const alias = optionDeclarations[key].aliases; - if (alias) { - if (typeof alias === "string") { - optionNames.push(alias); - } else { - optionNames.push(...alias); - } - } - } - - for (const err of commandLine.errors) { - // Ignore errors caused by tstl specific compiler options - if (err.code === tsInvalidCompilerOptionErrorCode) { - let ignore = false; - for (const optionName of optionNames) { - if (err.messageText.toString().indexOf(optionName) !== -1) { - ignore = true; - break; - } - } - - if (!ignore) { - return { isValid: false, errorMessage: `error TS${err.code}: ${err.messageText}`}; - } - } - } - } - - return { isValid: true, result: true }; -} - -/** Find configFile, function from ts api seems to be broken? */ -export function findConfigFile(options: ts.CompilerOptions): ParseResult { - if (!options.project) { - return { isValid: false, errorMessage: `error no base path provided, could not find config.`}; - } - let configPath = options.project; - // If the project path is wrapped in double quotes, remove them - if (/^".*"$/.test(configPath)) { - configPath = configPath.substring(1, configPath.length - 1); - } - /* istanbul ignore if: Testing else part is not really possible via automated tests */ - if (!path.isAbsolute(configPath)) { - // TODO check if options.project can even contain non absolute paths - configPath = path.join(process.cwd(), configPath); - } - 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 - const 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; - } - } - } +export function parseConfigFileWithSystem( + configFileName: string, + commandLineOptions: CompilerOptions, + system = ts.sys +): ParsedCommandLine { + const { config, error } = ts.readConfigFile(configFileName, system.readFile); + if (error) return { options: {}, fileNames: [], errors: [error] }; + + const parsedConfigFile = ts.parseJsonConfigFileContent( + config, + system, + path.dirname(configFileName), + commandLineOptions, + configFileName + ); - return { isValid: true, result: configPath }; + return updateParsedConfigFile(parsedConfigFile); } diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index f4a68dcdc..ac2e3cb84 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -117,7 +117,7 @@ export class LuaPrinter { let header = ""; - if (this.options.noHeader === undefined || this.options.noHeader === false) { + if (!this.options.noHeader) { header += `--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\n`; } @@ -129,8 +129,11 @@ export class LuaPrinter { header += `require("lualib_bundle");\n`; } // Inline lualib features - else if (this.options.luaLibImport === LuaLibImportKind.Inline && luaLibFeatures.size > 0) - { + else if ( + (this.options.luaLibImport === undefined || + this.options.luaLibImport === LuaLibImportKind.Inline) && + luaLibFeatures.size > 0 + ) { header += "-- Lua Library inline imports\n"; header += LuaLib.loadFeatures(luaLibFeatures); } diff --git a/src/Transpile.ts b/src/Transpile.ts index b5583ff57..9fadaf451 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -91,11 +91,11 @@ export function getTranspileOutput({ preEmitDiagnostics.push(...program.getSemanticDiagnostics()); } - if (options.declaration || options.composite) { + if (preEmitDiagnostics.length === 0 && (options.declaration || options.composite)) { preEmitDiagnostics.push(...program.getDeclarationDiagnostics()); } - if (preEmitDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0) { + if (preEmitDiagnostics.length > 0) { return { diagnostics: preEmitDiagnostics, transpiledFiles }; } } @@ -180,11 +180,7 @@ export function getTranspileOutput({ programOptions.noEmit = programNoEmit; - if ( - noEmit || - (noEmitOnError && - diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0) - ) { + if (noEmit || (noEmitOnError && diagnostics.length > 0)) { transpiledFiles.clear(); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts new file mode 100644 index 000000000..f8bcc3d5f --- /dev/null +++ b/src/diagnostics.ts @@ -0,0 +1,55 @@ +import * as ts from "typescript"; + +export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Message, + code: errorCount === 1 ? 6193 : 6194, + messageText: + errorCount === 1 + ? "Found 1 error. Watching for file changes." + : `Found ${errorCount} errors. Watching for file changes.`, +}); + +const createCommandLineError = ( + code: number, + getMessage: (...args: Args) => string +) => (...args: Args) => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Error, + code, + messageText: getMessage(...args), +}); + +export const optionProjectCannotBeMixedWithSourceFilesOnACommandLine = createCommandLineError( + 5042, + () => "Option 'project' cannot be mixed with source files on a command line." +); + +export const cannotFindATsconfigJsonAtTheSpecifiedDirectory = createCommandLineError( + 5057, + (dir: string) => `Cannot find a tsconfig.json file at the specified directory: '${dir}'.` +); + +export const theSpecifiedPathDoesNotExist = createCommandLineError( + 5058, + (dir: string) => `The specified path does not exist: '${dir}'.` +); + +export const compilerOptionExpectsAnArgument = createCommandLineError( + 6044, + (name: string) => `Compiler option '${name}' expects an argument.` +); + +export const argumentForOptionMustBe = createCommandLineError( + 6046, + (name: string, values: string) => `Argument for '${name}' option must be: ${values}.` +); + +export const optionBuildMustBeFirstCommandLineArgument = createCommandLineError( + 6369, + () => "Option '--build' must be the first command line argument." +); diff --git a/src/index.ts b/src/index.ts index 044c0f4c2..b37786a92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; -import { parseConfigFileContent } from "./CommandLineParser"; +import { parseConfigFileWithSystem } from "./CommandLineParser"; import { CompilerOptions } from "./CompilerOptions"; import { getTranspileOutput, TranspilationResult, TranspiledFile } from "./Transpile"; -export { parseConfigFileContent } from "./CommandLineParser"; +export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./CommandLineParser"; export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; export * from "./Emit"; export * from "./LuaAST"; @@ -30,17 +30,12 @@ export function transpileFiles( } export function transpileProject(fileName: string, options?: CompilerOptions): TranspilationResult { - const parseResult = parseConfigFileContent( - fs.readFileSync(fileName, "utf8"), - fileName, - options - ); - if (parseResult.isValid === false) { - // TODO: Return diagnostics - throw new Error(parseResult.errorMessage); + const parseResult = parseConfigFileWithSystem(fileName, options); + if (parseResult.errors.length > 0) { + return { diagnostics: parseResult.errors, transpiledFiles: new Map() }; } - return transpileFiles(parseResult.result.fileNames, parseResult.result.options); + return transpileFiles(parseResult.fileNames, parseResult.options); } const libCache: { [key: string]: ts.SourceFile } = {}; diff --git a/src/tstl.ts b/src/tstl.ts index 698f1760e..ea67ed01f 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -1,111 +1,248 @@ #!/usr/bin/env node +import * as path from "path"; import * as ts from "typescript"; import * as tstl from "."; import * as CommandLineParser from "./CommandLineParser"; +import * as cliDiagnostics from "./diagnostics"; function createDiagnosticReporter(pretty: boolean): ts.DiagnosticReporter { - const host: ts.FormatDiagnosticsHost = { - getCurrentDirectory: () => ts.sys.getCurrentDirectory(), - getNewLine: () => ts.sys.newLine, - getCanonicalFileName: fileName => - ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), - }; - - if (!pretty) { - return diagnostic => ts.sys.write(ts.formatDiagnostic(diagnostic, host)); - } + return (ts as any).createDiagnosticReporter(ts.sys, pretty); +} - return diagnostic => { - ts.sys.write( - ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine() - ); - }; +function createWatchStatusReporter(options?: ts.CompilerOptions): ts.WatchStatusReporter { + return (ts as any).createWatchStatusReporter(ts.sys, shouldBePretty(options)); } -function shouldBePretty(options?: tstl.CompilerOptions): boolean { +function shouldBePretty(options?: ts.CompilerOptions): boolean { return !options || options.pretty === undefined ? ts.sys.writeOutputIsTTY !== undefined && ts.sys.writeOutputIsTTY() : Boolean(options.pretty); } -let reportDiagnostic = createDiagnosticReporter(shouldBePretty()); +let reportDiagnostic = createDiagnosticReporter(false); function updateReportDiagnostic(options?: ts.CompilerOptions): void { - if (shouldBePretty(options)) { - reportDiagnostic = createDiagnosticReporter(true); + reportDiagnostic = createDiagnosticReporter(shouldBePretty(options)); +} + +export function locateConfigFile(commandLine: tstl.ParsedCommandLine): string | undefined { + const { project } = commandLine.options; + if (!project) { + if (commandLine.fileNames.length === 0) { + const searchPath = path.posix.normalize(ts.sys.getCurrentDirectory()); + return ts.findConfigFile(searchPath, ts.sys.fileExists); + } + return; + } + + if (commandLine.fileNames.length !== 0) { + reportDiagnostic(cliDiagnostics.optionProjectCannotBeMixedWithSourceFilesOnACommandLine()); + ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + + let fileOrDirectory = path.posix.normalize(project); + if (!path.isAbsolute(fileOrDirectory)) { + fileOrDirectory = path.posix.join(ts.sys.getCurrentDirectory(), fileOrDirectory); + } + + if (!fileOrDirectory || ts.sys.directoryExists(fileOrDirectory)) { + const configFileName = path.posix.join(fileOrDirectory, "tsconfig.json"); + if (ts.sys.fileExists(configFileName)) { + return configFileName; + } else { + reportDiagnostic( + cliDiagnostics.cannotFindATsconfigJsonAtTheSpecifiedDirectory(project) + ); + ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } else { + if (ts.sys.fileExists(fileOrDirectory)) { + return fileOrDirectory; + } else { + reportDiagnostic(cliDiagnostics.theSpecifiedPathDoesNotExist(project)); + ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } } } -function executeCommandLine(argv: string[]): void { - const commandLine = CommandLineParser.parseCommandLine(argv); - if (commandLine.isValid === false) { - // TODO: Use diagnostics - console.error(`Invalid CLI input: ${commandLine.errorMessage}`); +function executeCommandLine(args: string[]): void { + if (args.length > 0 && args[0].startsWith("-")) { + const firstOption = args[0].slice(args[0].startsWith("--") ? 2 : 1).toLowerCase(); + if (firstOption === "build" || firstOption === "b") { + return performBuild(args.slice(1)); + } + } + + const commandLine = CommandLineParser.parseCommandLine(args); + + if (commandLine.options.build) { + reportDiagnostic(cliDiagnostics.optionBuildMustBeFirstCommandLineArgument()); return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); } - updateReportDiagnostic(commandLine.result.options); + if (commandLine.errors.length > 0) { + commandLine.errors.forEach(reportDiagnostic); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + } - if (commandLine.result.options.help) { + if (commandLine.options.version) { console.log(CommandLineParser.version); - console.log(CommandLineParser.getHelpString()); return ts.sys.exit(ts.ExitStatus.Success); } - if (commandLine.result.options.version) { + if (commandLine.options.help) { console.log(CommandLineParser.version); + console.log(CommandLineParser.getHelpString()); return ts.sys.exit(ts.ExitStatus.Success); } - if (commandLine.result.options.watch) { - if (commandLine.result.options.project) { - const host = ts.createWatchCompilerHost( - commandLine.result.options.project, - commandLine.result.options, - ts.sys, - ts.createSemanticDiagnosticsBuilderProgram - ); - updateWatchCompilerHost(host, commandLine.result.options); - ts.createWatchProgram(host); + const configFileName = locateConfigFile(commandLine); + const commandLineOptions = commandLine.options; + if (configFileName) { + const configParseResult = CommandLineParser.parseConfigFileWithSystem( + configFileName, + commandLineOptions + ); + + updateReportDiagnostic(configParseResult.options); + if (configParseResult.options.watch) { + createWatchOfConfigFile(configFileName, commandLineOptions); } else { - const host = ts.createWatchCompilerHost( - commandLine.result.fileNames, - commandLine.result.options, - ts.sys, - ts.createSemanticDiagnosticsBuilderProgram + performCompilation( + configParseResult.fileNames, + configParseResult.projectReferences, + configParseResult.options, + ts.getConfigFileParsingDiagnostics(configParseResult) ); - updateWatchCompilerHost(host, commandLine.result.options); - ts.createWatchProgram(host); } } else { - const { diagnostics, transpiledFiles } = tstl.transpileFiles( - commandLine.result.fileNames, - commandLine.result.options - ); - - const emitResult = tstl.emitTranspiledFiles(commandLine.result.options, transpiledFiles); - emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); - - diagnostics.forEach(reportDiagnostic); - if (diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length === 0) { - return ts.sys.exit(ts.ExitStatus.Success); - } - - if (transpiledFiles.size === 0) { - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); + updateReportDiagnostic(commandLineOptions); + if (commandLineOptions.watch) { + createWatchOfFilesAndCompilerOptions(commandLine.fileNames, commandLineOptions); } else { - return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsGenerated); + performCompilation( + commandLine.fileNames, + commandLine.projectReferences, + commandLineOptions + ); } } } -function updateWatchCompilerHost( - host: ts.WatchCompilerHost, +function performBuild(_args: string[]): void { + console.log("Option '--build' is not supported."); + return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); +} + +function performCompilation( + rootNames: string[], + projectReferences: ReadonlyArray | undefined, + options: tstl.CompilerOptions, + configFileParsingDiagnostics?: ReadonlyArray +): void { + const program = ts.createProgram({ + rootNames, + options, + projectReferences, + configFileParsingDiagnostics, + }); + + const { transpiledFiles, diagnostics: emitDiagnostics } = tstl.getTranspileOutput({ + program, + options, + }); + + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...emitDiagnostics, + ]); + + const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); + + diagnostics.forEach(reportDiagnostic); + const exitCode = + diagnostics.length === 0 + ? ts.ExitStatus.Success + : transpiledFiles.size === 0 + ? ts.ExitStatus.DiagnosticsPresent_OutputsSkipped + : ts.ExitStatus.DiagnosticsPresent_OutputsGenerated; + + return ts.sys.exit(exitCode); +} + +function createWatchOfConfigFile( + configFileName: string, + optionsToExtend: tstl.CompilerOptions +): void { + const watchCompilerHost = ts.createWatchCompilerHost( + configFileName, + optionsToExtend, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram, + undefined, + createWatchStatusReporter(optionsToExtend) + ); + + updateWatchCompilationHost(watchCompilerHost, optionsToExtend); + ts.createWatchProgram(watchCompilerHost); +} + +function createWatchOfFilesAndCompilerOptions( + rootFiles: string[], options: tstl.CompilerOptions +): void { + const watchCompilerHost = ts.createWatchCompilerHost( + rootFiles, + options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram, + undefined, + createWatchStatusReporter(options) + ); + + updateWatchCompilationHost(watchCompilerHost, options); + ts.createWatchProgram(watchCompilerHost); +} + +interface ConfigFileSnapshot { + options: tstl.CompilerOptions; + configFileParsingDiagnostics: ts.Diagnostic[]; +} + +function updateWatchCompilationHost( + host: ts.WatchCompilerHost, + optionsToExtend: tstl.CompilerOptions ): void { let fullRecompile = true; + const configFileMap = new WeakMap(); + host.afterProgramCreate = builderProgram => { const program = builderProgram.getProgram(); - const preEmitDiagnostics = ts.getPreEmitDiagnostics(program); + const compilerOptions = builderProgram.getCompilerOptions(); + + let options = optionsToExtend; + let configFileParsingDiagnostics: ts.Diagnostic[] = []; + const configFile = compilerOptions.configFile as ts.TsConfigSourceFile | undefined; + const configFilePath = compilerOptions.configFilePath as string | undefined; + if (configFile && configFilePath) { + if (configFileMap.has(configFile)) { + ({ options, configFileParsingDiagnostics } = configFileMap.get(configFile)!); + } else { + const parsedConfigFile = CommandLineParser.updateParsedConfigFile( + ts.parseJsonSourceFileConfigFileContent( + configFile, + ts.sys, + path.dirname(configFilePath), + optionsToExtend, + configFilePath + ) + ); + + ({ options, errors: configFileParsingDiagnostics } = parsedConfigFile); + configFileMap.set(configFile, { options, configFileParsingDiagnostics }); + } + } let sourceFiles: ts.SourceFile[] | undefined; if (!fullRecompile) { @@ -132,7 +269,11 @@ function updateWatchCompilerHost( emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...preEmitDiagnostics, + ...configFileParsingDiagnostics, + ...program.getOptionsDiagnostics(), + ...program.getSyntacticDiagnostics(), + ...program.getGlobalDiagnostics(), + ...program.getSemanticDiagnostics(), ...emitDiagnostics, ]); @@ -142,23 +283,10 @@ function updateWatchCompilerHost( // do a full recompile after an error fullRecompile = errors.length > 0; - const watchErrorSummaryDiagnostic: ts.Diagnostic = { - file: undefined, - start: undefined, - length: undefined, - - category: ts.DiagnosticCategory.Message, - code: errors.length === 1 ? 6193 : 6194, - messageText: - errors.length === 1 - ? "Found 1 error. Watching for file changes." - : `Found ${errors.length} errors. Watching for file changes.`, - }; - host.onWatchStatusChange( - watchErrorSummaryDiagnostic, + cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), - builderProgram.getCompilerOptions() + compilerOptions ); }; } diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index e825ba521..180573e2d 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -1,241 +1,178 @@ -import { LuaLibImportKind, LuaTarget } from "../../src"; -import { - findConfigFile, - parseCommandLine, - parseConfigFileContent, -} from "../../src/CommandLineParser"; +import * as ts from "typescript"; +import * as tstl from "../../src"; test.each([ - { args: [""], expected: LuaLibImportKind.Inline }, - { args: ["--luaLibImport", "none"], expected: LuaLibImportKind.None }, - { args: ["--luaLibImport", "always"], expected: LuaLibImportKind.Always }, - { args: ["--luaLibImport", "inline"], expected: LuaLibImportKind.Inline }, - { args: ["--luaLibImport", "require"], expected: LuaLibImportKind.Require }, - { args: ["--luaLibImport", "NoNe"], expected: LuaLibImportKind.None }, + { args: ["--luaLibImport", "none"], expected: tstl.LuaLibImportKind.None }, + { args: ["--luaLibImport", "always"], expected: tstl.LuaLibImportKind.Always }, + { args: ["--luaLibImport", "inline"], expected: tstl.LuaLibImportKind.Inline }, + { args: ["--luaLibImport", "require"], expected: tstl.LuaLibImportKind.Require }, + { args: ["--luaLibImport", "NoNe"], expected: tstl.LuaLibImportKind.None }, ])("CLI parser luaLibImportKind (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.luaLibImport).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaLibImport).toBe(expected); }); test("CLI parser invalid luaLibImportKind", () => { - const result = parseCommandLine(["--luaLibImport", "invalid"]); - expect(result.isValid).toBe(false); + const result = tstl.parseCommandLine(["--luaLibImport", "invalid"]); + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); }); test.each([ - { args: [""], expected: LuaTarget.LuaJIT }, - { args: ["--luaTarget", "5.1"], expected: LuaTarget.Lua51 }, - { args: ["--luaTarget", "5.2"], expected: LuaTarget.Lua52 }, - { args: ["--luaTarget", "jit"], expected: LuaTarget.LuaJIT }, - { args: ["--luaTarget", "JiT"], expected: LuaTarget.LuaJIT }, - { args: ["--luaTarget", "JIT"], expected: LuaTarget.LuaJIT }, - { args: ["--luaTarget", "5.3"], expected: LuaTarget.Lua53 }, + { args: ["--luaTarget", "5.1"], expected: tstl.LuaTarget.Lua51 }, + { args: ["--luaTarget", "5.2"], expected: tstl.LuaTarget.Lua52 }, + { args: ["--luaTarget", "jit"], expected: tstl.LuaTarget.LuaJIT }, + { args: ["--luaTarget", "JiT"], expected: tstl.LuaTarget.LuaJIT }, + { args: ["--luaTarget", "JIT"], expected: tstl.LuaTarget.LuaJIT }, + { args: ["--luaTarget", "5.3"], expected: tstl.LuaTarget.Lua53 }, ])("CLI parser luaTarget (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.luaTarget).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(expected); }); test.each([ - { args: ["-lt", "5.1"], expected: LuaTarget.Lua51 }, - { args: ["-lt", "5.2"], expected: LuaTarget.Lua52 }, - { args: ["-lt", "jit"], expected: LuaTarget.LuaJIT }, - { args: ["-lt", "JIT"], expected: LuaTarget.LuaJIT }, - { args: ["-lt", "5.3"], expected: LuaTarget.Lua53 }, + { args: ["-lt", "5.1"], expected: tstl.LuaTarget.Lua51 }, + { args: ["-lt", "5.2"], expected: tstl.LuaTarget.Lua52 }, + { args: ["-lt", "jit"], expected: tstl.LuaTarget.LuaJIT }, + { args: ["-lt", "JIT"], expected: tstl.LuaTarget.LuaJIT }, + { args: ["-lt", "5.3"], expected: tstl.LuaTarget.Lua53 }, ])("CLI parser luaTarget (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.luaTarget).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(expected); }); test("CLI parser invalid luaTarget", () => { - const result = parseCommandLine(["--luatTarget", "invalid"]); - expect(result.isValid).toBe(false); + const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); + + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); }); test.each([ - { args: [""], expected: false }, { args: ["--noHeader", "true"], expected: true }, { args: ["--noHeader", "false"], expected: false }, { args: ["--noHeader"], expected: true }, { args: ["--noHeader", "--noHoisting"], expected: true }, ])("CLI parser noHeader (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.noHeader).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(expected); }); test.each([ - { args: [""], expected: false }, { args: ["--noHoisting", "true"], expected: true }, { args: ["--noHoisting", "false"], expected: false }, { args: ["--noHoisting"], expected: true }, { args: ["--noHoisting", "--noHeader"], expected: true }, ])("CLI parser noHoisting (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.noHoisting).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHoisting).toBe(expected); }); test.each([ - { args: [""], expected: false }, { args: ["--project", "tsconfig.json"], expected: true }, { args: ["-p", "tsconfig.json"], expected: true }, ])("CLI parser project (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.project !== undefined).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.project !== undefined).toBe(expected); }); test("CLI Parser Multiple Options", () => { const commandLine = "--project tsconfig.json --noHeader --noHoisting -lt 5.3"; - const result = parseCommandLine(commandLine.split(" ")); + const result = tstl.parseCommandLine(commandLine.split(" ")); - if (result.isValid === true) { - expect(result.result.options.project).toBeDefined(); - expect(result.result.options.noHeader).toBe(true); - expect(result.result.options.noHoisting).toBe(true); - expect(result.result.options.luaTarget).toBe(LuaTarget.Lua53); - } else { - expect(result.isValid).toBeTruthy(); - } + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.project).toBe("tsconfig.json"); + expect(result.options.noHeader).toBe(true); + expect(result.options.noHoisting).toBe(true); + expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua53); }); test.each([ - { args: [""], expected: false }, + { args: [""], expected: undefined }, { args: ["--help"], expected: true }, { args: ["-h"], expected: true }, ])("CLI parser project (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.help === true).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(args); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.help).toBe(expected); }); test.each([ - { args: [""], expected: false }, + { args: [""], expected: undefined }, { args: ["--version"], expected: true }, { args: ["-v"], expected: true }, ])("CLI parser project (%p)", ({ args, expected }) => { - const result = parseCommandLine(args); - if (result.isValid === true) { - expect(result.result.options.version === true).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } -}); + const result = tstl.parseCommandLine(args); -test.each([ - { option: "luaTarget", expected: LuaTarget.LuaJIT }, - { option: "noHeader", expected: false }, - { option: "luaLibImport", expected: "inline" }, - { option: "rootDir", expected: process.cwd() }, - { option: "outDir", expected: process.cwd() }, -])("defaultOption (%p)", ({ option, expected }) => { - const parsedCommandLine = parseCommandLine([]); - if (parsedCommandLine.isValid) { - expect(expected).toBe(parsedCommandLine.result.options[option]); - } else { - expect(parsedCommandLine.isValid).toBeTruthy(); - } + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.version).toBe(expected); }); test("ValidLuaTarget", () => { - const parsedCommandLine = parseCommandLine(["--luaTarget", "5.3"]); - if (parsedCommandLine.isValid) { - expect(parsedCommandLine.result.options["luaTarget"]).toBe("5.3"); - } else { - expect(parsedCommandLine.isValid).toBeTruthy(); - } + const result = tstl.parseCommandLine(["--luaTarget", "5.3"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe("5.3"); }); test("InvalidLuaTarget", () => { // Don't check error message because the yargs library messes the message up. - const result = parseCommandLine(["--luaTarget", "42"]); - expect(result.isValid).toBe(false); + const result = tstl.parseCommandLine(["--luaTarget", "42"]); + + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); }); test("InvalidArgumentTSTL", () => { // Don't check error message because the yargs library messes the message up. - const result = parseCommandLine(["--invalidTarget", "test"]); - expect(result.isValid).toBe(false); + const result = tstl.parseCommandLine(["--invalidTarget", "test"]); + + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); }); test("outDir", () => { - const parsedCommandLine = parseCommandLine(["--outDir", "./test"]); + const result = tstl.parseCommandLine(["--outDir", "./test"]); - if (parsedCommandLine.isValid) { - expect(parsedCommandLine.result.options["outDir"]).toBe("./test"); - } else { - expect(parsedCommandLine.isValid).toBeTruthy(); - } + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.outDir).toBe("./test"); }); test("rootDir", () => { - const parsedCommandLine = parseCommandLine(["--rootDir", "./test"]); + const result = tstl.parseCommandLine(["--rootDir", "./test"]); - if (parsedCommandLine.isValid) { - expect(parsedCommandLine.result.options["rootDir"]).toBe("./test"); - expect(parsedCommandLine.result.options["outDir"]).toBe("./test"); - } else { - expect(parsedCommandLine.isValid).toBeTruthy(); - } + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.rootDir).toBe("./test"); + expect(result.options.outDir).toBe("./test"); }); test("outDirAndRooDir", () => { - const parsedCommandLine = parseCommandLine([ - "--outDir", - "./testOut", - "--rootDir", - "./testRoot", - ]); - - if (parsedCommandLine.isValid) { - expect(parsedCommandLine.result.options["outDir"]).toBe("./testOut"); - expect(parsedCommandLine.result.options["rootDir"]).toBe("./testRoot"); - } else { - expect(parsedCommandLine.isValid).toBeTruthy(); - } -}); + const result = tstl.parseCommandLine(["--outDir", "./testOut", "--rootDir", "./testRoot"]); -test("Find config no path", () => { - const result = findConfigFile({ options: {}, fileNames: [], errors: [] }); - expect(result.isValid).toBe(false); + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.outDir).toBe("./testOut"); + expect(result.options.rootDir).toBe("./testRoot"); }); test.each([ - { tsConfig: "{}" }, { tsConfig: `{ noHeader: true }`, expected: true }, { tsConfig: `{ noHeader: "true" }`, expected: true }, { tsConfig: `{ tstl: { noHeader: true } }`, expected: true }, { tsConfig: `{ tstl: { noHeader: "true" } }`, expected: true }, ])("TsConfig noHeader (%p)", ({ tsConfig, expected }) => { - const result = parseConfigFileContent(tsConfig, ""); + const configJson = ts.parseConfigFileTextToJson("", tsConfig); + const parsedJsonConfig = ts.parseJsonConfigFileContent(configJson.config, ts.sys, ""); + const result = tstl.updateParsedConfigFile(parsedJsonConfig); - if (result.isValid) { - expect(result.result.options.noHeader).toBe(expected); - } else { - expect(result.isValid).toBeTruthy(); - } + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(expected); }); diff --git a/test/unit/compiler/configuration/mixed/index.spec.ts b/test/unit/compiler/configuration/mixed/index.spec.ts deleted file mode 100644 index da7577ae0..000000000 --- a/test/unit/compiler/configuration/mixed/index.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind } from "../../../../../src"; -import { parseCommandLine } from "../../../../../src/CommandLineParser"; - -test("tsconfig.json mixed with cmd line args", () => { - const rootPath = __dirname; - const tsConfigPath = path.join(rootPath, "project-tsconfig.json"); - const expectedTsConfig = ts.parseJsonConfigFileContent( - ts.parseConfigFileTextToJson(tsConfigPath, fs.readFileSync(tsConfigPath).toString()).config, - ts.sys, - path.dirname(tsConfigPath), - undefined, - tsConfigPath, - ); - - const parsedArgs = parseCommandLine([ - "-p", - `"${tsConfigPath}"`, - "--luaLibImport", - LuaLibImportKind.Inline, - `${path.join(rootPath, "test.ts")}`, - ]); - - if (parsedArgs.isValid === true) { - expect(parsedArgs.result.options).toEqual({ - ...expectedTsConfig.options, - // Overridden by cmd args (set to "none" in project-tsconfig.json) - luaLibImport: LuaLibImportKind.Inline, - // Only set in tsconfig, TSTL default is "JIT" - luaTarget: "5.1", - // Only present in TSTL dfaults - noHeader: false, - project: tsConfigPath, - noHoisting: false, - sourceMapTraceback: false, - } as CompilerOptions); - } else { - expect(parsedArgs.isValid).toBeTruthy(); - } -}); diff --git a/test/unit/compiler/configuration/mixed/project-tsconfig.json b/test/unit/compiler/configuration/mixed/project-tsconfig.json deleted file mode 100644 index 3fe0b7e03..000000000 --- a/test/unit/compiler/configuration/mixed/project-tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/foo/bar", - "rootDir": "./src/foo/bar" - }, - "luaTarget": "5.1", - "luaLibImport": "none" -} diff --git a/test/util.ts b/test/util.ts index b1bd32a43..483447fb7 100644 --- a/test/util.ts +++ b/test/util.ts @@ -64,7 +64,6 @@ export function transpileStringResult( options: tstl.CompilerOptions = {}, ): tstl.VirtualProgramResult { const optionsWithDefaults = { - luaLibImport: tstl.LuaLibImportKind.Inline, luaTarget: tstl.LuaTarget.Lua53, noHeader: true, skipLibCheck: true, From e7677507e68baea0e24802e21bfadc9a4796dc1c Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 15 Apr 2019 18:13:01 +0500 Subject: [PATCH 06/44] Fix strict issues --- src/CommandLineParser.ts | 11 ++++++----- src/Emit.ts | 5 +++-- src/index.ts | 33 +++++++++++---------------------- src/tstl.ts | 2 +- test/compiler/runner.ts | 4 ++-- test/unit/sourcemaps.spec.ts | 18 +++++++++--------- test/util.ts | 31 +++++++++++++++++++------------ 7 files changed, 51 insertions(+), 53 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 38ff50fff..b086b9a9a 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -224,16 +224,17 @@ function getArgumentValue( } function readValue(value: string | boolean, type: CommandLineOption["type"]): string | boolean { - if (type === "boolean") { - return value === true || value === "true" || value === "t"; - } else if (type === "enum") { - return value.toString().toLowerCase(); + switch (type) { + case "boolean": + return value === true || value === "true" || value === "t"; + case "enum": + return value.toString().toLowerCase(); } } export function parseConfigFileWithSystem( configFileName: string, - commandLineOptions: CompilerOptions, + commandLineOptions?: CompilerOptions, system = ts.sys ): ParsedCommandLine { const { config, error } = ts.readConfigFile(configFileName, system.readFile); diff --git a/src/Emit.ts b/src/Emit.ts index 9da6774d6..40652bf86 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -16,7 +16,8 @@ export function emitTranspiledFiles( options: CompilerOptions, transpiledFiles: Map ): OutputFile[] { - const { rootDir, outDir, outFile, luaLibImport } = options; + // TODO: + const { rootDir = "", outDir = "", outFile, luaLibImport } = options; const files: OutputFile[] = []; for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { @@ -32,7 +33,7 @@ export function emitTranspiledFiles( outPath = outFile; } else { // append to workingDir or outDir - outPath = path.resolve(options.outDir, outFile); + outPath = path.resolve(outDir, outFile); } } else { outPath = trimExt(outPath) + ".lua"; diff --git a/src/index.ts b/src/index.ts index b37786a92..775e11bf2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,7 @@ const libCache: { [key: string]: ts.SourceFile } = {}; /** @internal */ export function createVirtualProgram( input: Record, - options?: CompilerOptions + options: CompilerOptions = {} ): ts.Program { const compilerHost: ts.CompilerHost = { fileExists: () => true, @@ -86,40 +86,29 @@ export function createVirtualProgram( return ts.createProgram(Object.keys(input), options, compilerHost); } -export interface VirtualProgramResult { - file: TranspiledFile; +export interface TranspileStringResult { diagnostics: ts.Diagnostic[]; + file?: TranspiledFile; } export function transpileString( - content: string, + main: string, options: CompilerOptions = {} -): VirtualProgramResult { - const program = createVirtualProgram({ "main.ts": content }, options); - const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); - const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...diagnostics, - ]); - - return { file: transpiledFiles.get("main.ts"), diagnostics: [...allDiagnostics] }; +): TranspileStringResult { + const { diagnostics, transpiledFiles } = transpileVirtualProgram({ "main.ts": main }, options); + return { diagnostics, file: transpiledFiles.get("main.ts") }; } export function transpileVirtualProgram( files: Record, options: CompilerOptions = {} -): VirtualProgramResult { - const mainFileName = Object.keys(files).find(x => /\bmain\.[a-z]+$/.test(x)); - if (mainFileName === undefined) { - throw new Error('Virtual program should have a file named "main"'); - } - +): TranspilationResult { const program = createVirtualProgram(files, options); - const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + const transpileOutput = getTranspileOutput({ program, options }); const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...diagnostics, + ...transpileOutput.diagnostics, ]); - return { file: transpiledFiles.get(mainFileName), diagnostics: [...allDiagnostics] }; + return { ...transpileOutput, diagnostics: [...allDiagnostics] }; } diff --git a/src/tstl.ts b/src/tstl.ts index ea67ed01f..3fc12fdb7 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -283,7 +283,7 @@ function updateWatchCompilationHost( // do a full recompile after an error fullRecompile = errors.length > 0; - host.onWatchStatusChange( + host.onWatchStatusChange!( cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), compilerOptions diff --git a/test/compiler/runner.ts b/test/compiler/runner.ts index e1d33b020..233aeffb7 100644 --- a/test/compiler/runner.ts +++ b/test/compiler/runner.ts @@ -21,8 +21,8 @@ export async function runCli(args: string[]): Promise { const child = forkCli(args); let output = ""; - child.stdout.on("data", data => (output += data)); - child.stderr.on("data", data => (output += data)); + child.stdout!.on("data", data => (output += data)); + child.stderr!.on("data", data => (output += data)); return new Promise(resolve => { child.on("close", exitCode => resolve({ exitCode, output })); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index 524fe15fe..66bd6d1e1 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -47,15 +47,14 @@ test.each([ }, ])("Source map has correct mapping (%p)", async ({ typeScriptSource, assertPatterns }) => { // Act - const { - file: { lua, sourceMap }, - } = util.transpileStringResult(typeScriptSource); + const { file } = util.transpileStringResult(typeScriptSource); // Assert - const consumer = await new SourceMapConsumer(sourceMap); + if (!util.expectToBeDefined(file.lua) || !util.expectToBeDefined(file.sourceMap)) return; + const consumer = await new SourceMapConsumer(file.sourceMap); for (const { luaPattern, typeScriptPattern } of assertPatterns) { - const luaPosition = lineAndColumnOf(lua, luaPattern); + const luaPosition = lineAndColumnOf(file.lua, luaPattern); const mappedPosition = consumer.originalPositionFor(luaPosition); const typescriptPosition = lineAndColumnOf(typeScriptSource, typeScriptPattern); @@ -125,17 +124,18 @@ test("Inline sourcemaps", () => { inlineSourceMap: true, }; - const { lua, sourceMap } = util.transpileStringResult(typeScriptSource, compilerOptions); + const { file } = util.transpileStringResult(typeScriptSource, compilerOptions); + if (!util.expectToBeDefined(file.lua)) return; - const inlineSourceMapMatch = lua.match( + const inlineSourceMapMatch = file.lua.match( /--# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/, ); if (util.expectToBeDefined(inlineSourceMapMatch)) { const inlineSourceMap = Buffer.from(inlineSourceMapMatch[1], "base64").toString(); - expect(sourceMap).toBe(inlineSourceMap); + expect(file.sourceMap).toBe(inlineSourceMap); - expect(util.executeLua(lua)).toBe("foo"); + expect(util.executeLua(file.lua)).toBe("foo"); } }); diff --git a/test/util.ts b/test/util.ts index f3607f796..d33d1222e 100644 --- a/test/util.ts +++ b/test/util.ts @@ -31,8 +31,9 @@ expect.extend({ } // TODO: - expect(executionError).toBeDefined(); - expect(executionError.message).toContain(error.message); + if (expectToBeDefined(executionError)) { + expect(executionError.message).toContain(error.message); + } return { pass: true, message: () => "" }; }, @@ -43,10 +44,8 @@ export function transpileString( options: tstl.CompilerOptions = {}, ignoreDiagnostics = true, ): string { - const { - diagnostics, - file: { lua }, - } = transpileStringResult(str, options); + const { diagnostics, file } = transpileStringResult(str, options); + if (!expectToBeDefined(file) || !expectToBeDefined(file.lua)) return ""; const errors = diagnostics .filter(d => d.category === ts.DiagnosticCategory.Error) @@ -56,13 +55,13 @@ export function transpileString( throw new Error(errors.map(d => d.messageText).join("\n")); } - return lua.trim(); + return file.lua.trim(); } export function transpileStringResult( - input: string | { [filename: string]: string }, + input: string | Record, options: tstl.CompilerOptions = {}, -): tstl.VirtualProgramResult { +): Required { const optionsWithDefaults = { luaTarget: tstl.LuaTarget.Lua53, noHeader: true, @@ -78,9 +77,17 @@ export function transpileStringResult( ...options, }; - return typeof input === "string" - ? tstl.transpileString(input, optionsWithDefaults) - : tstl.transpileVirtualProgram(input, optionsWithDefaults); + const { diagnostics, transpiledFiles } = tstl.transpileVirtualProgram( + typeof input === "string" ? { "main.ts": input } : input, + optionsWithDefaults, + ); + + const mainFileName = [...transpiledFiles.keys()].find(x => /\bmain\.[a-z]+$/.test(x)); + if (mainFileName === undefined) { + throw new Error('Program should have a file named "main"'); + } + + return { diagnostics, file: transpiledFiles.get(mainFileName)! }; } const lualibContent = fs.readFileSync( From c2b8812ca06b50c2f6172f3cbd84d67fd3b830cf Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 15 Apr 2019 22:16:24 +0500 Subject: [PATCH 07/44] Fix rootDir and outDir defaults always being used --- src/CommandLineParser.ts | 17 +---------------- src/Emit.ts | 7 ++++++- test/tslint.json | 2 +- test/unit/commandLineParser.spec.ts | 1 - 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index b086b9a9a..5eef2449b 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -121,22 +121,7 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): } export function parseCommandLine(args: string[]): ParsedCommandLine { - const commandLine = updateParsedCommandLine(ts.parseCommandLine(args), args); - - // TODO: Remove - 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; + return updateParsedCommandLine(ts.parseCommandLine(args), args); } function updateParsedCommandLine( diff --git a/src/Emit.ts b/src/Emit.ts index 40652bf86..c92250450 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -16,8 +16,13 @@ export function emitTranspiledFiles( options: CompilerOptions, transpiledFiles: Map ): OutputFile[] { + let { rootDir, outDir, outFile, luaLibImport } = options; + // TODO: - const { rootDir = "", outDir = "", outFile, luaLibImport } = options; + const configFileName = options.configFilePath as string | undefined; + if (configFileName && rootDir === undefined) rootDir = path.dirname(configFileName); + if (rootDir === undefined) rootDir = process.cwd(); + if (outDir === undefined) outDir = rootDir; const files: OutputFile[] = []; for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { diff --git a/test/tslint.json b/test/tslint.json index e8bb9a2cf..2796d7894 100644 --- a/test/tslint.json +++ b/test/tslint.json @@ -42,7 +42,7 @@ "no-var-keyword": true, "object-literal-shorthand": true, "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], - "prefer-const": true, + "prefer-const": [true, { "destructuring": "all" }], "radix": true, "switch-default": false, "triple-equals": [true, "allow-null-check"], diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 180573e2d..efe3b2a96 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -152,7 +152,6 @@ test("rootDir", () => { expect(result.errors.map(err => err.messageText)).toHaveLength(0); expect(result.options.rootDir).toBe("./test"); - expect(result.options.outDir).toBe("./test"); }); test("outDirAndRooDir", () => { From ff9a233ac34abb1f50d818a4f718d5ccace6f067 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 15 Apr 2019 22:19:09 +0500 Subject: [PATCH 08/44] Remove failing test --- test/compiler/project.spec.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/compiler/project.spec.ts b/test/compiler/project.spec.ts index 9d0f1480e..1d5c92a0d 100644 --- a/test/compiler/project.spec.ts +++ b/test/compiler/project.spec.ts @@ -18,7 +18,7 @@ let filesAfterCompile: string[]; afterEach(() => { // Remove files that were created by the test - const createdFiles = filesAfterCompile.filter(v => existingFiles.indexOf(v) < 0); + const createdFiles = filesAfterCompile.filter(v => !existingFiles.includes(v)); for (const file of createdFiles) { fs.unlinkSync(file); } @@ -37,11 +37,6 @@ test.each([ tsconfig: ".", expectedFiles: ["lualib_bundle.lua", "test_src/test_lib/file.lua", "test_src/main.lua"], }, - { - projectName: "basic", - tsconfig: "test_src/main.ts", - expectedFiles: ["lualib_bundle.lua", "test_src/test_lib/file.lua", "test_src/main.lua"], - }, { projectName: "basic", tsconfig: "tsconfig.outDir.json", From 5ca24e0c8648e90db9323031ec3ef1586f52f660 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 00:07:50 +0500 Subject: [PATCH 09/44] Remove command line parser tests that test standard TS parser features --- test/unit/commandLineParser.spec.ts | 73 +---------------------------- 1 file changed, 2 insertions(+), 71 deletions(-) diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index efe3b2a96..650c9bcd1 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -76,16 +76,6 @@ test.each([ expect(result.options.noHoisting).toBe(expected); }); -test.each([ - { args: ["--project", "tsconfig.json"], expected: true }, - { args: ["-p", "tsconfig.json"], expected: true }, -])("CLI parser project (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.project !== undefined).toBe(expected); -}); - test("CLI Parser Multiple Options", () => { const commandLine = "--project tsconfig.json --noHeader --noHoisting -lt 5.3"; const result = tstl.parseCommandLine(commandLine.split(" ")); @@ -97,71 +87,12 @@ test("CLI Parser Multiple Options", () => { expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua53); }); -test.each([ - { args: [""], expected: undefined }, - { args: ["--help"], expected: true }, - { args: ["-h"], expected: true }, -])("CLI parser project (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.help).toBe(expected); -}); - -test.each([ - { args: [""], expected: undefined }, - { args: ["--version"], expected: true }, - { args: ["-v"], expected: true }, -])("CLI parser project (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.version).toBe(expected); -}); - -test("ValidLuaTarget", () => { - const result = tstl.parseCommandLine(["--luaTarget", "5.3"]); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.luaTarget).toBe("5.3"); -}); - -test("InvalidLuaTarget", () => { - // Don't check error message because the yargs library messes the message up. - const result = tstl.parseCommandLine(["--luaTarget", "42"]); - - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); -}); - -test("InvalidArgumentTSTL", () => { - // Don't check error message because the yargs library messes the message up. - const result = tstl.parseCommandLine(["--invalidTarget", "test"]); +test("CLI parser invalid argument", () => { + const result = tstl.parseCommandLine(["--invalidArgument"]); expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); }); -test("outDir", () => { - const result = tstl.parseCommandLine(["--outDir", "./test"]); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.outDir).toBe("./test"); -}); - -test("rootDir", () => { - const result = tstl.parseCommandLine(["--rootDir", "./test"]); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.rootDir).toBe("./test"); -}); - -test("outDirAndRooDir", () => { - const result = tstl.parseCommandLine(["--outDir", "./testOut", "--rootDir", "./testRoot"]); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.outDir).toBe("./testOut"); - expect(result.options.rootDir).toBe("./testRoot"); -}); - test.each([ { tsConfig: `{ noHeader: true }`, expected: true }, { tsConfig: `{ noHeader: "true" }`, expected: true }, From 021d94013dd618c7f6fa58117a90c4cbf2b7a323 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 01:14:11 +0500 Subject: [PATCH 10/44] Use tsconfig source file during parse to get better diagnostics --- src/CommandLineParser.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 5eef2449b..ec52b4fb9 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -222,11 +222,8 @@ export function parseConfigFileWithSystem( commandLineOptions?: CompilerOptions, system = ts.sys ): ParsedCommandLine { - const { config, error } = ts.readConfigFile(configFileName, system.readFile); - if (error) return { options: {}, fileNames: [], errors: [error] }; - - const parsedConfigFile = ts.parseJsonConfigFileContent( - config, + const parsedConfigFile = ts.parseJsonSourceFileConfigFileContent( + ts.readJsonConfigFile(configFileName, system.readFile), system, path.dirname(configFileName), commandLineOptions, From b99b6b9c9e2a19d8535e876292e7104134720336 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 03:11:47 +0500 Subject: [PATCH 11/44] Parse command line options more similar to typescript --- src/CommandLineParser.ts | 166 ++++++++++++++-------------- src/diagnostics.ts | 5 + test/unit/commandLineParser.spec.ts | 2 - 3 files changed, 91 insertions(+), 82 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index ec52b4fb9..baf33b713 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -50,15 +50,16 @@ const optionDeclarations: Record = { export const version = `Version ${require("../package.json").version}`; -const helpString = - "Syntax: tstl [options] [files...]\n\n" + +const helpString = ` +Syntax: tstl [options] [files...] - "Examples: tstl path/to/file.ts [...]\n" + - " tstl -p path/to/tsconfig.json\n\n" + +Examples: tstl path/to/file.ts [...] + tstl -p path/to/tsconfig.json - "In addition to the options listed below you can also pass options\n" + - "for the typescript compiler (For a list of options use tsc -h).\n" + - "Some tsc options might have no effect."; +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. +`.trim(); export function getHelpString(): string { let result = helpString + "\n\n"; @@ -69,11 +70,11 @@ export function getHelpString(): string { const optionString = aliasStrings.concat(["--" + optionName]).join("|"); - const parameterDescribe = option.type === "enum" ? option.choices.join("|") : option.type; + const optionDescribe = option.type === "enum" ? option.choices.join("|") : option.type; - const spacing = " ".repeat(Math.max(1, 45 - optionString.length - parameterDescribe.length)); + const spacing = " ".repeat(Math.max(1, 45 - optionString.length - optionDescribe.length)); - result += `\n ${optionString} <${parameterDescribe}>${spacing}${option.describe}\n`; + result += `\n ${optionString} <${optionDescribe}>${spacing}${option.describe}\n`; } return result; @@ -84,18 +85,12 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): const option = optionDeclarations[key]; if (!option) continue; - const value = readValue(parsedConfigFile.raw[key], option.type); - if (option.type === "enum" && !option.choices.includes(value as string)) { - parsedConfigFile.errors.push( - diagnostics.argumentForOptionMustBe(key, option.choices.join(", ")) - ); - } else { - // console.warn(`[Deprectated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` - // + `look like { "compilerOptions": { }, "tstl": { } }`); - if (parsedConfigFile.options[key] === undefined) { - parsedConfigFile.options[key] = value; - } - } + // console.warn(`[Deprectated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` + // + `look like { "compilerOptions": { }, "tstl": { } }`); + + const { error, value } = readValue(key, option, parsedConfigFile.raw[key]); + if (error) parsedConfigFile.errors.push(error); + if (parsedConfigFile.options[key] === undefined) parsedConfigFile.options[key] = value; } // Eventually we will only look for the tstl object for tstl options @@ -104,16 +99,9 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): const option = optionDeclarations[key]; if (!option) continue; - const value = readValue(parsedConfigFile.raw.tstl[key], option.type); - if (option.type === "enum" && !option.choices.includes(value as string)) { - parsedConfigFile.errors.push( - diagnostics.argumentForOptionMustBe(key, option.choices.join(", ")) - ); - } else { - if (parsedConfigFile.options[key] === undefined) { - parsedConfigFile.options[key] = value; - } - } + const { error, value } = readValue(key, option, parsedConfigFile.raw.tstl[key]); + if (error) parsedConfigFile.errors.push(error); + if (parsedConfigFile.options[key] === undefined) parsedConfigFile.options[key] = value; } } @@ -146,74 +134,92 @@ function updateParsedCommandLine( if (!args[i].startsWith("-")) continue; const hasTwoDashes = args[i].startsWith("--"); - const parameterValue = args[i].substr(hasTwoDashes ? 2 : 1); - let argumentName = optionDeclarations[parameterValue] && parameterValue; - if (!hasTwoDashes && argumentName === undefined) { + const argumentName = args[i].substr(hasTwoDashes ? 2 : 1); + let optionName = optionDeclarations[argumentName] && argumentName; + if (!hasTwoDashes && optionName === undefined) { for (const key in optionDeclarations) { - if ((optionDeclarations[key].aliases || []).includes(parameterValue)) { - argumentName = key; + if ((optionDeclarations[key].aliases || []).includes(argumentName)) { + optionName = key; break; } } } - if (argumentName !== undefined) { - const argumentResult = getArgumentValue(argumentName, i, args); - if (argumentResult.isValid === true) { - parsedCommandLine.options[argumentName] = argumentResult.result; - // Skip value from being considered as option - i += argumentResult.increment; - } else { - parsedCommandLine.errors.push(argumentResult.error); - } + if (optionName !== undefined) { + const { error, value, increment } = readCommandLineArgument(optionName, args[i + 1]); + if (error) parsedCommandLine.errors.push(error); + parsedCommandLine.options[optionName] = value; + i += increment; } } return parsedCommandLine; } -type ArgumentParseResult = - | { isValid: true; result: string | boolean; increment: number } - | { isValid: false; error: ts.Diagnostic }; - -function getArgumentValue( - argumentName: string, - argumentIndex: number, - args: string[] -): ArgumentParseResult { - const option = optionDeclarations[argumentName]; - const argument = args[argumentIndex + 1]; - - if (option.type === "boolean" && (argument === undefined || argument.startsWith("-"))) { - // Set boolean arguments without supplied value to true - return { isValid: true, result: true, increment: 0 }; - } - - if (argument === undefined) { - return { isValid: false, error: diagnostics.compilerOptionExpectsAnArgument(argumentName) }; - } +interface CommandLineArgument extends ReadValueResult { + increment: number; +} - const value = readValue(argument, option.type); +function readCommandLineArgument(optionName: string, value: any): CommandLineArgument { + const option = optionDeclarations[optionName]; - if (option.type === "enum" && option.choices && !option.choices.includes(value as string)) { + if (option.type === "boolean") { + if (value === "true" || value === "false") { + value = value === "true"; + } else { + // Set boolean arguments without supplied value to true + return { value: true, increment: 0 }; + } + } else if (value === undefined) { return { - isValid: false, - error: diagnostics.argumentForOptionMustBe( - `--${argumentName}`, - option.choices.join(", ") - ), + error: diagnostics.compilerOptionExpectsAnArgument(optionName), + value: undefined, + increment: 0, }; } - return { isValid: true, result: value, increment: 1 }; + return { ...readValue(optionName, option, value), increment: 1 }; +} + +interface ReadValueResult { + error?: ts.Diagnostic; + value: any; } -function readValue(value: string | boolean, type: CommandLineOption["type"]): string | boolean { - switch (type) { - case "boolean": - return value === true || value === "true" || value === "t"; - case "enum": - return value.toString().toLowerCase(); +function readValue(optionName: string, option: CommandLineOption, value: unknown): ReadValueResult { + if (value === null) return { value }; + + switch (option.type) { + case "boolean": { + if (typeof value !== "boolean") { + return { + value: undefined, + error: diagnostics.compilerOptionRequiresAValueOfType(optionName, "boolean"), + }; + } + + return { value }; + } + + case "enum": { + if (typeof value !== "string") { + return { + value: undefined, + error: diagnostics.compilerOptionRequiresAValueOfType(optionName, "string"), + }; + } + + const normalizedValue = value.toLowerCase(); + if (option.choices && !option.choices.includes(normalizedValue)) { + const optionChoices = option.choices.join(", "); + return { + value: undefined, + error: diagnostics.argumentForOptionMustBe(`--${optionName}`, optionChoices), + }; + } + + return { value: normalizedValue }; + } } } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index f8bcc3d5f..b41fd2462 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -24,6 +24,11 @@ const createCommandLineError = ( messageText: getMessage(...args), }); +export const compilerOptionRequiresAValueOfType = createCommandLineError( + 5024, + (name: string, type: string) => `Compiler option '${name}' requires a value of type ${type}.` +); + export const optionProjectCannotBeMixedWithSourceFilesOnACommandLine = createCommandLineError( 5042, () => "Option 'project' cannot be mixed with source files on a command line." diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 650c9bcd1..515988379 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -95,9 +95,7 @@ test("CLI parser invalid argument", () => { test.each([ { tsConfig: `{ noHeader: true }`, expected: true }, - { tsConfig: `{ noHeader: "true" }`, expected: true }, { tsConfig: `{ tstl: { noHeader: true } }`, expected: true }, - { tsConfig: `{ tstl: { noHeader: "true" } }`, expected: true }, ])("TsConfig noHeader (%p)", ({ tsConfig, expected }) => { const configJson = ts.parseConfigFileTextToJson("", tsConfig); const parsedJsonConfig = ts.parseJsonConfigFileContent(configJson.config, ts.sys, ""); From e738b580b4cc5b05c5d148be0df00a29398df8bc Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 19:03:42 +0500 Subject: [PATCH 12/44] Refactor commandLineParser tests --- test/unit/commandLineParser.spec.ts | 218 +++++++++++++++++----------- 1 file changed, 130 insertions(+), 88 deletions(-) diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 515988379..d1e880035 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -1,106 +1,148 @@ import * as ts from "typescript"; import * as tstl from "../../src"; -test.each([ - { args: ["--luaLibImport", "none"], expected: tstl.LuaLibImportKind.None }, - { args: ["--luaLibImport", "always"], expected: tstl.LuaLibImportKind.Always }, - { args: ["--luaLibImport", "inline"], expected: tstl.LuaLibImportKind.Inline }, - { args: ["--luaLibImport", "require"], expected: tstl.LuaLibImportKind.Require }, - { args: ["--luaLibImport", "NoNe"], expected: tstl.LuaLibImportKind.None }, -])("CLI parser luaLibImportKind (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.luaLibImport).toBe(expected); +describe("command line", () => { + test("should support aliases", () => { + const full = tstl.parseCommandLine(["--luaTarget", "5.1"]); + const alias = tstl.parseCommandLine(["-lt", "5.1"]); + expect(full).toEqual(alias); + }); + + test("should support standard typescript options", () => { + const commandLine = "--project tsconfig.json --noHeader -t es3 -lt 5.3"; + const result = tstl.parseCommandLine(commandLine.split(" ")); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options).toEqual({ + project: "tsconfig.json", + noHeader: true, + target: ts.ScriptTarget.ES3, + luaTarget: tstl.LuaTarget.Lua53, + }); + }); + + test("should error on invalid options", () => { + const result = tstl.parseCommandLine(["--invalidArgument"]); + + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + }); + + describe("enum options", () => { + test("should parse enums", () => { + const result = tstl.parseCommandLine(["--luaTarget", "5.1"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); + }); + + test("should be case-insensitive", () => { + for (const value of ["jit", "JiT", "JIT"]) { + const result = tstl.parseCommandLine(["--luaTarget", value]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); + } + }); + + test("should error on invalid value", () => { + const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); + + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + }); + }); + + describe("boolean options", () => { + test.each([true, false])("should parse booleans (%p)", value => { + const result = tstl.parseCommandLine(["--noHeader", value.toString()]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(value); + }); + + test("should be case-sensitive", () => { + const result = tstl.parseCommandLine(["--noHeader", "FALSE"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(true); + expect(result.fileNames).toEqual(["FALSE"]); + }); + + test("should be parsed without a value", () => { + const result = tstl.parseCommandLine(["--noHeader"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(true); + }); + + test("shouldn't parse following arguments as values", () => { + const result = tstl.parseCommandLine(["--noHeader", "--noHoisting"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(true); + expect(result.options.noHoisting).toBe(true); + }); + + test("shouldn't parse following files as values", () => { + const result = tstl.parseCommandLine(["--noHeader", "file.ts"]); + + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(true); + }); + }); }); -test("CLI parser invalid luaLibImportKind", () => { - const result = tstl.parseCommandLine(["--luaLibImport", "invalid"]); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); -}); - -test.each([ - { args: ["--luaTarget", "5.1"], expected: tstl.LuaTarget.Lua51 }, - { args: ["--luaTarget", "5.2"], expected: tstl.LuaTarget.Lua52 }, - { args: ["--luaTarget", "jit"], expected: tstl.LuaTarget.LuaJIT }, - { args: ["--luaTarget", "JiT"], expected: tstl.LuaTarget.LuaJIT }, - { args: ["--luaTarget", "JIT"], expected: tstl.LuaTarget.LuaJIT }, - { args: ["--luaTarget", "5.3"], expected: tstl.LuaTarget.Lua53 }, -])("CLI parser luaTarget (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.luaTarget).toBe(expected); -}); - -test.each([ - { args: ["-lt", "5.1"], expected: tstl.LuaTarget.Lua51 }, - { args: ["-lt", "5.2"], expected: tstl.LuaTarget.Lua52 }, - { args: ["-lt", "jit"], expected: tstl.LuaTarget.LuaJIT }, - { args: ["-lt", "JIT"], expected: tstl.LuaTarget.LuaJIT }, - { args: ["-lt", "5.3"], expected: tstl.LuaTarget.Lua53 }, -])("CLI parser luaTarget (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); - - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.luaTarget).toBe(expected); -}); +describe("tsconfig", () => { + const parseConfigFileContent = (config: any) => { + // Specifying `files` option disables automatic file searching, that includes all files in + // the project, making these tests slow. Empty file list is considered as an error. + config.files = ["src/index.ts"]; + return tstl.updateParsedConfigFile(ts.parseJsonConfigFileContent(config, ts.sys, "")); + }; -test("CLI parser invalid luaTarget", () => { - const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); + test("should support unscoped options", () => { + const unscoped = parseConfigFileContent({ noHeader: true }); + const scoped = parseConfigFileContent({ tstl: { noHeader: true } }); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); -}); + expect(unscoped.options).toEqual(scoped.options); + }); -test.each([ - { args: ["--noHeader", "true"], expected: true }, - { args: ["--noHeader", "false"], expected: false }, - { args: ["--noHeader"], expected: true }, - { args: ["--noHeader", "--noHoisting"], expected: true }, -])("CLI parser noHeader (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); + describe("enum options", () => { + test("should parse enums", () => { + const result = parseConfigFileContent({ tstl: { luaTarget: "5.1" } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.noHeader).toBe(expected); -}); + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); + }); -test.each([ - { args: ["--noHoisting", "true"], expected: true }, - { args: ["--noHoisting", "false"], expected: false }, - { args: ["--noHoisting"], expected: true }, - { args: ["--noHoisting", "--noHeader"], expected: true }, -])("CLI parser noHoisting (%p)", ({ args, expected }) => { - const result = tstl.parseCommandLine(args); + test("should be case-insensitive", () => { + for (const value of ["jit", "JiT", "JIT"]) { + const result = parseConfigFileContent({ tstl: { luaTarget: value } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.noHoisting).toBe(expected); -}); + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); + } + }); -test("CLI Parser Multiple Options", () => { - const commandLine = "--project tsconfig.json --noHeader --noHoisting -lt 5.3"; - const result = tstl.parseCommandLine(commandLine.split(" ")); + test("should error on invalid value", () => { + const result = parseConfigFileContent({ tstl: { luaTarget: "invalid" } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.project).toBe("tsconfig.json"); - expect(result.options.noHeader).toBe(true); - expect(result.options.noHoisting).toBe(true); - expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua53); -}); + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + }); + }); -test("CLI parser invalid argument", () => { - const result = tstl.parseCommandLine(["--invalidArgument"]); + describe("boolean options", () => { + test.each([true, false])("should parse booleans (%p)", value => { + const result = parseConfigFileContent({ tstl: { noHeader: value } }); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); -}); + expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.options.noHeader).toBe(value); + }); -test.each([ - { tsConfig: `{ noHeader: true }`, expected: true }, - { tsConfig: `{ tstl: { noHeader: true } }`, expected: true }, -])("TsConfig noHeader (%p)", ({ tsConfig, expected }) => { - const configJson = ts.parseConfigFileTextToJson("", tsConfig); - const parsedJsonConfig = ts.parseJsonConfigFileContent(configJson.config, ts.sys, ""); - const result = tstl.updateParsedConfigFile(parsedJsonConfig); + test("shouldn't parse strings", () => { + const result = parseConfigFileContent({ tstl: { noHeader: "true" } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); - expect(result.options.noHeader).toBe(expected); + expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + expect(result.options.noHeader).toBeUndefined(); + }); + }); }); From 68c767a03fb4f2c1f0baf1a3dabf5ff64350a99d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 20:39:18 +0500 Subject: [PATCH 13/44] Rename transpileVirtualProgram to transpileVirtualProject --- src/index.ts | 28 ++++++++++++++-------------- test/util.ts | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index 775e11bf2..f9eba091b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -86,20 +86,7 @@ export function createVirtualProgram( return ts.createProgram(Object.keys(input), options, compilerHost); } -export interface TranspileStringResult { - diagnostics: ts.Diagnostic[]; - file?: TranspiledFile; -} - -export function transpileString( - main: string, - options: CompilerOptions = {} -): TranspileStringResult { - const { diagnostics, transpiledFiles } = transpileVirtualProgram({ "main.ts": main }, options); - return { diagnostics, file: transpiledFiles.get("main.ts") }; -} - -export function transpileVirtualProgram( +export function transpileVirtualProject( files: Record, options: CompilerOptions = {} ): TranspilationResult { @@ -112,3 +99,16 @@ export function transpileVirtualProgram( return { ...transpileOutput, diagnostics: [...allDiagnostics] }; } + +export interface TranspileStringResult { + diagnostics: ts.Diagnostic[]; + file?: TranspiledFile; +} + +export function transpileString( + main: string, + options: CompilerOptions = {} +): TranspileStringResult { + const { diagnostics, transpiledFiles } = transpileVirtualProject({ "main.ts": main }, options); + return { diagnostics, file: transpiledFiles.get("main.ts") }; +} diff --git a/test/util.ts b/test/util.ts index d33d1222e..ce89684fc 100644 --- a/test/util.ts +++ b/test/util.ts @@ -77,7 +77,7 @@ export function transpileStringResult( ...options, }; - const { diagnostics, transpiledFiles } = tstl.transpileVirtualProgram( + const { diagnostics, transpiledFiles } = tstl.transpileVirtualProject( typeof input === "string" ? { "main.ts": input } : input, optionsWithDefaults, ); From 000793dfadc060fbf71df05626e821afb6e91bb8 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 20:42:01 +0500 Subject: [PATCH 14/44] Remove export from CLI --- src/tstl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tstl.ts b/src/tstl.ts index 3fc12fdb7..51fc301f3 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -24,7 +24,7 @@ function updateReportDiagnostic(options?: ts.CompilerOptions): void { reportDiagnostic = createDiagnosticReporter(shouldBePretty(options)); } -export function locateConfigFile(commandLine: tstl.ParsedCommandLine): string | undefined { +function locateConfigFile(commandLine: tstl.ParsedCommandLine): string | undefined { const { project } = commandLine.options; if (!project) { if (commandLine.fileNames.length === 0) { From b51b916cba8ed966b406a1d403ea9f9e930b336f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 21:44:43 +0500 Subject: [PATCH 15/44] Add expect(received).toHaveDiagnostics() matcher --- jest.config.js | 1 + test/setup.ts | 58 +++++++++++++++++++++++++++++ test/unit/commandLineParser.spec.ts | 30 +++++++-------- test/util.ts | 42 +-------------------- 4 files changed, 76 insertions(+), 55 deletions(-) create mode 100644 test/setup.ts diff --git a/jest.config.js b/jest.config.js index 7327a27af..deb429cfc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,7 @@ module.exports = { ], watchPathIgnorePatterns: ["/watch\\.ts$"], + setupFilesAfterEnv: ["/test/setup.ts"], testEnvironment: "node", testRunner: "jest-circus/runner", preset: "ts-jest", diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 000000000..8e4e5fbb5 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,58 @@ +import * as ts from "typescript"; +import * as util from "./util"; + +declare global { + namespace jest { + interface Matchers { + toThrowExactError(error: Error): void; + toHaveDiagnostics(): void; + } + } +} + +expect.extend({ + toThrowExactError(callback: () => void, error: Error): jest.CustomMatcherResult { + if (this.isNot) { + return { pass: true, message: () => "Inverted toThrowExactError is not implemented" }; + } + + let executionError: Error | undefined; + try { + callback(); + } catch (err) { + executionError = err; + } + + // TODO: + if (util.expectToBeDefined(executionError)) { + expect(executionError.message).toContain(error.message); + } + + return { pass: true, message: () => "" }; + }, + toHaveDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { + expect(Array.isArray(diagnostics)).toBe(true); + const options = { isNot: this.isNot }; + // @ts-ignore + const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", options); + + const diagnosticMessages = ts.formatDiagnosticsWithColorAndContext(diagnostics, { + getCurrentDirectory: () => "", + getCanonicalFileName: fileName => fileName, + getNewLine: () => "\n", + }); + + return { + pass: diagnostics.length > 0, + message: () => { + return ( + matcherHint + + "\n\n" + + (this.isNot + ? diagnosticMessages + : `Received: ${this.utils.printReceived(diagnostics)}\n`) + ); + }, + }; + }, +}); diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index d1e880035..10c5ae28d 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -12,7 +12,7 @@ describe("command line", () => { const commandLine = "--project tsconfig.json --noHeader -t es3 -lt 5.3"; const result = tstl.parseCommandLine(commandLine.split(" ")); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options).toEqual({ project: "tsconfig.json", noHeader: true, @@ -24,14 +24,14 @@ describe("command line", () => { test("should error on invalid options", () => { const result = tstl.parseCommandLine(["--invalidArgument"]); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + expect(result.errors).toHaveDiagnostics(); }); describe("enum options", () => { test("should parse enums", () => { const result = tstl.parseCommandLine(["--luaTarget", "5.1"]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -39,7 +39,7 @@ describe("command line", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = tstl.parseCommandLine(["--luaTarget", value]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -47,7 +47,7 @@ describe("command line", () => { test("should error on invalid value", () => { const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + expect(result.errors).toHaveDiagnostics(); }); }); @@ -55,14 +55,14 @@ describe("command line", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = tstl.parseCommandLine(["--noHeader", value.toString()]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("should be case-sensitive", () => { const result = tstl.parseCommandLine(["--noHeader", "FALSE"]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.fileNames).toEqual(["FALSE"]); }); @@ -70,14 +70,14 @@ describe("command line", () => { test("should be parsed without a value", () => { const result = tstl.parseCommandLine(["--noHeader"]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); }); test("shouldn't parse following arguments as values", () => { const result = tstl.parseCommandLine(["--noHeader", "--noHoisting"]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.options.noHoisting).toBe(true); }); @@ -85,7 +85,7 @@ describe("command line", () => { test("shouldn't parse following files as values", () => { const result = tstl.parseCommandLine(["--noHeader", "file.ts"]); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); }); }); @@ -110,7 +110,7 @@ describe("tsconfig", () => { test("should parse enums", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "5.1" } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -118,7 +118,7 @@ describe("tsconfig", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = parseConfigFileContent({ tstl: { luaTarget: value } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -126,7 +126,7 @@ describe("tsconfig", () => { test("should error on invalid value", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "invalid" } }); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + expect(result.errors).toHaveDiagnostics(); }); }); @@ -134,14 +134,14 @@ describe("tsconfig", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = parseConfigFileContent({ tstl: { noHeader: value } }); - expect(result.errors.map(err => err.messageText)).toHaveLength(0); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("shouldn't parse strings", () => { const result = parseConfigFileContent({ tstl: { noHeader: "true" } }); - expect(result.errors.map(err => err.messageText)).not.toHaveLength(0); + expect(result.errors).toHaveDiagnostics(); expect(result.options.noHeader).toBeUndefined(); }); }); diff --git a/test/util.ts b/test/util.ts index ce89684fc..e90a6c423 100644 --- a/test/util.ts +++ b/test/util.ts @@ -6,39 +6,6 @@ import * as tstl from "../src"; export const nodeStub = ts.createNode(ts.SyntaxKind.Unknown); -declare global { - namespace jest { - interface Matchers { - toThrowExactError(error: Error): void; - } - } -} - -expect.extend({ - toThrowExactError( - callback: () => void, - error: Error, - ): { pass: boolean; message: () => string } { - if (this.isNot) { - return { pass: true, message: () => "Inverted toThrowExactError is not implemented" }; - } - - let executionError: Error | undefined; - try { - callback(); - } catch (err) { - executionError = err; - } - - // TODO: - if (expectToBeDefined(executionError)) { - expect(executionError.message).toContain(error.message); - } - - return { pass: true, message: () => "" }; - }, -}); - export function transpileString( str: string | { [filename: string]: string }, options: tstl.CompilerOptions = {}, @@ -47,13 +14,8 @@ export function transpileString( const { diagnostics, file } = transpileStringResult(str, options); if (!expectToBeDefined(file) || !expectToBeDefined(file.lua)) return ""; - const errors = diagnostics - .filter(d => d.category === ts.DiagnosticCategory.Error) - .filter(d => (ignoreDiagnostics ? d.code === 0 : true)); - - if (errors.length > 0) { - throw new Error(errors.map(d => d.messageText).join("\n")); - } + const errors = diagnostics.filter(diag => !ignoreDiagnostics || diag.code === 0); + expect(errors).not.toHaveDiagnostics(); return file.lua.trim(); } From 2d02ce1f3828028e0633cb7e100699f1caccd123 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 22:25:23 +0500 Subject: [PATCH 16/44] Parse command line options case-insensitively --- src/CommandLineParser.ts | 106 +++++++++++++--------------- test/unit/commandLineParser.spec.ts | 14 ++++ 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index baf33b713..107a1b430 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -8,8 +8,9 @@ export interface ParsedCommandLine extends ts.ParsedCommandLine { } interface CommandLineOptionBase { - describe: string; + name: string; aliases?: string[]; + describe: string; } interface CommandLineOptionOfEnum extends CommandLineOptionBase { @@ -22,31 +23,36 @@ interface CommandLineOptionOfBoolean extends CommandLineOptionBase { } type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean; -const optionDeclarations: Record = { - luaLibImport: { +const optionDeclarations: CommandLineOption[] = [ + { + name: "luaLibImport", describe: "Specifies how js standard features missing in lua are imported.", type: "enum", choices: Object.values(LuaLibImportKind), }, - luaTarget: { + { + name: "luaTarget", aliases: ["lt"], describe: "Specify Lua target version.", type: "enum", choices: Object.values(LuaTarget), }, - noHeader: { + { + name: "noHeader", describe: "Specify if a header will be added to compiled files.", type: "boolean", }, - noHoisting: { + { + name: "noHoisting", describe: "Disables hoisting.", type: "boolean", }, - sourceMapTraceback: { + { + name: "sourceMapTraceback", describe: "Applies the source map to show source TS files and lines in error tracebacks.", type: "boolean", }, -}; +]; export const version = `Version ${require("../package.json").version}`; @@ -65,16 +71,14 @@ export function getHelpString(): string { let result = helpString + "\n\n"; result += "Options:\n"; - for (const [optionName, option] of Object.entries(optionDeclarations)) { + for (const option of optionDeclarations) { const aliasStrings = (option.aliases || []).map(a => "-" + a); + const optionString = aliasStrings.concat(["--" + option.name]).join("|"); - const optionString = aliasStrings.concat(["--" + optionName]).join("|"); - - const optionDescribe = option.type === "enum" ? option.choices.join("|") : option.type; + const valuesHint = option.type === "enum" ? option.choices.join("|") : option.type; + const spacing = " ".repeat(Math.max(1, 45 - optionString.length - valuesHint.length)); - const spacing = " ".repeat(Math.max(1, 45 - optionString.length - optionDescribe.length)); - - result += `\n ${optionString} <${optionDescribe}>${spacing}${option.describe}\n`; + result += `\n ${optionString} <${valuesHint}>${spacing}${option.describe}\n`; } return result; @@ -82,13 +86,13 @@ export function getHelpString(): string { export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): ParsedCommandLine { for (const key in parsedConfigFile.raw) { - const option = optionDeclarations[key]; + const option = optionDeclarations.find(option => option.name === key); if (!option) continue; // console.warn(`[Deprectated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` // + `look like { "compilerOptions": { }, "tstl": { } }`); - const { error, value } = readValue(key, option, parsedConfigFile.raw[key]); + const { error, value } = readValue(option, parsedConfigFile.raw[key]); if (error) parsedConfigFile.errors.push(error); if (parsedConfigFile.options[key] === undefined) parsedConfigFile.options[key] = value; } @@ -96,10 +100,10 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): // Eventually we will only look for the tstl object for tstl options if (parsedConfigFile.raw.tstl) { for (const key in parsedConfigFile.raw.tstl) { - const option = optionDeclarations[key]; + const option = optionDeclarations.find(option => option.name === key); if (!option) continue; - const { error, value } = readValue(key, option, parsedConfigFile.raw.tstl[key]); + const { error, value } = readValue(option, parsedConfigFile.raw.tstl[key]); if (error) parsedConfigFile.errors.push(error); if (parsedConfigFile.options[key] === undefined) parsedConfigFile.options[key] = value; } @@ -116,39 +120,33 @@ function updateParsedCommandLine( parsedCommandLine: ts.ParsedCommandLine, args: string[] ): ParsedCommandLine { - // Generate a list of valid option names and aliases - const optionNames = Object.keys(optionDeclarations) - .map(n => `--${n}`) - .concat(...Object.values(optionDeclarations).map(o => (o.aliases || []).map(a => `-${a}`))); - - // Ignore errors caused by tstl specific compiler options - const tsInvalidCompilerOptionErrorCode = 5023; - parsedCommandLine.errors = parsedCommandLine.errors.filter(err => { - return !( - err.code === tsInvalidCompilerOptionErrorCode && - optionNames.some(optionName => String(err.messageText).endsWith(`'${optionName}'.`)) - ); - }); - for (let i = 0; i < args.length; i++) { if (!args[i].startsWith("-")) continue; - const hasTwoDashes = args[i].startsWith("--"); - const argumentName = args[i].substr(hasTwoDashes ? 2 : 1); - let optionName = optionDeclarations[argumentName] && argumentName; - if (!hasTwoDashes && optionName === undefined) { - for (const key in optionDeclarations) { - if ((optionDeclarations[key].aliases || []).includes(argumentName)) { - optionName = key; - break; - } + const isShorthand = !args[i].startsWith("--"); + const argumentName = args[i].substr(isShorthand ? 1 : 2); + const option = optionDeclarations.find(option => { + if (option.name.toLowerCase() === argumentName.toLowerCase()) return true; + if (isShorthand && option.aliases) { + return option.aliases.some(a => a.toLowerCase() === argumentName.toLowerCase()); } - } - if (optionName !== undefined) { - const { error, value, increment } = readCommandLineArgument(optionName, args[i + 1]); + return false; + }); + + if (option) { + // Ignore errors caused by tstl specific compiler options + const tsInvalidCompilerOptionErrorCode = 5023; + parsedCommandLine.errors = parsedCommandLine.errors.filter(err => { + return !( + err.code === tsInvalidCompilerOptionErrorCode && + String(err.messageText).endsWith(`'${args[i]}'.`) + ); + }); + + const { error, value, increment } = readCommandLineArgument(option, args[i + 1]); if (error) parsedCommandLine.errors.push(error); - parsedCommandLine.options[optionName] = value; + parsedCommandLine.options[option.name] = value; i += increment; } } @@ -160,9 +158,7 @@ interface CommandLineArgument extends ReadValueResult { increment: number; } -function readCommandLineArgument(optionName: string, value: any): CommandLineArgument { - const option = optionDeclarations[optionName]; - +function readCommandLineArgument(option: CommandLineOption, value: any): CommandLineArgument { if (option.type === "boolean") { if (value === "true" || value === "false") { value = value === "true"; @@ -172,13 +168,13 @@ function readCommandLineArgument(optionName: string, value: any): CommandLineArg } } else if (value === undefined) { return { - error: diagnostics.compilerOptionExpectsAnArgument(optionName), + error: diagnostics.compilerOptionExpectsAnArgument(option.name), value: undefined, increment: 0, }; } - return { ...readValue(optionName, option, value), increment: 1 }; + return { ...readValue(option, value), increment: 1 }; } interface ReadValueResult { @@ -186,7 +182,7 @@ interface ReadValueResult { value: any; } -function readValue(optionName: string, option: CommandLineOption, value: unknown): ReadValueResult { +function readValue(option: CommandLineOption, value: unknown): ReadValueResult { if (value === null) return { value }; switch (option.type) { @@ -194,7 +190,7 @@ function readValue(optionName: string, option: CommandLineOption, value: unknown if (typeof value !== "boolean") { return { value: undefined, - error: diagnostics.compilerOptionRequiresAValueOfType(optionName, "boolean"), + error: diagnostics.compilerOptionRequiresAValueOfType(option.name, "boolean"), }; } @@ -205,7 +201,7 @@ function readValue(optionName: string, option: CommandLineOption, value: unknown if (typeof value !== "string") { return { value: undefined, - error: diagnostics.compilerOptionRequiresAValueOfType(optionName, "string"), + error: diagnostics.compilerOptionRequiresAValueOfType(option.name, "string"), }; } @@ -214,7 +210,7 @@ function readValue(optionName: string, option: CommandLineOption, value: unknown const optionChoices = option.choices.join(", "); return { value: undefined, - error: diagnostics.argumentForOptionMustBe(`--${optionName}`, optionChoices), + error: diagnostics.argumentForOptionMustBe(`--${option.name}`, optionChoices), }; } diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 10c5ae28d..291e9133d 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -27,6 +27,13 @@ describe("command line", () => { expect(result.errors).toHaveDiagnostics(); }); + test("should parse options case-insensitively", () => { + const result = tstl.parseCommandLine(["--NOHEADER"]); + + expect(result.errors).not.toHaveDiagnostics(); + expect(result.options.noHeader).toBe(true); + }); + describe("enum options", () => { test("should parse enums", () => { const result = tstl.parseCommandLine(["--luaTarget", "5.1"]); @@ -106,6 +113,13 @@ describe("tsconfig", () => { expect(unscoped.options).toEqual(scoped.options); }); + test("should parse options case-sensitively", () => { + const result = parseConfigFileContent({ tstl: { NOHEADER: true } }); + + expect(result.options.noHeader).toBeUndefined(); + expect(result.options.NOHEADER).toBeUndefined(); + }); + describe("enum options", () => { test("should parse enums", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "5.1" } }); From 6c36fe0a05522a8711b9acb0f7766abaecadc11d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 22:38:45 +0500 Subject: [PATCH 17/44] Disallow unknown options in "tstl" config object --- src/CommandLineParser.ts | 5 ++++- src/diagnostics.ts | 7 +++++- test/unit/commandLineParser.spec.ts | 33 +++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 107a1b430..7f830f380 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -101,7 +101,10 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): if (parsedConfigFile.raw.tstl) { for (const key in parsedConfigFile.raw.tstl) { const option = optionDeclarations.find(option => option.name === key); - if (!option) continue; + if (!option) { + parsedConfigFile.errors.push(diagnostics.unknownCompilerOption(key)); + continue; + } const { error, value } = readValue(option, parsedConfigFile.raw.tstl[key]); if (error) parsedConfigFile.errors.push(error); diff --git a/src/diagnostics.ts b/src/diagnostics.ts index b41fd2462..01d589eeb 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -15,7 +15,7 @@ export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({ const createCommandLineError = ( code: number, getMessage: (...args: Args) => string -) => (...args: Args) => ({ +) => (...args: Args): ts.Diagnostic => ({ file: undefined, start: undefined, length: undefined, @@ -24,6 +24,11 @@ const createCommandLineError = ( messageText: getMessage(...args), }); +export const unknownCompilerOption = createCommandLineError( + 5023, + (name: string) => `Unknown compiler option '${name}'.` +); + export const compilerOptionRequiresAValueOfType = createCommandLineError( 5024, (name: string, type: string) => `Compiler option '${name}' requires a value of type ${type}.` diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 291e9133d..54164a11d 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -21,14 +21,14 @@ describe("command line", () => { }); }); - test("should error on invalid options", () => { - const result = tstl.parseCommandLine(["--invalidArgument"]); + test("should error on unknown options", () => { + const result = tstl.parseCommandLine(["--unknownOption"]); expect(result.errors).toHaveDiagnostics(); }); test("should parse options case-insensitively", () => { - const result = tstl.parseCommandLine(["--NOHEADER"]); + const result = tstl.parseCommandLine(["--NoHeader"]); expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); @@ -106,18 +106,33 @@ describe("tsconfig", () => { return tstl.updateParsedConfigFile(ts.parseJsonConfigFileContent(config, ts.sys, "")); }; - test("should support unscoped options", () => { - const unscoped = parseConfigFileContent({ noHeader: true }); - const scoped = parseConfigFileContent({ tstl: { noHeader: true } }); + test("should support root-level options", () => { + const rootLevel = parseConfigFileContent({ noHeader: true }); + const namespaced = parseConfigFileContent({ tstl: { noHeader: true } }); - expect(unscoped.options).toEqual(scoped.options); + expect(rootLevel.options).toEqual(namespaced.options); + }); + + test("should allow unknown root-level options", () => { + const result = parseConfigFileContent({ unknownOption: true }); + + expect(result.errors).not.toHaveDiagnostics(); + expect(result.options.unknownOption).toBeUndefined(); + }); + + test("should error on unknown namespaced options", () => { + const result = parseConfigFileContent({ tstl: { unknownOption: true } }); + + expect(result.errors).toHaveDiagnostics(); + expect(result.options.unknownOption).toBeUndefined(); }); test("should parse options case-sensitively", () => { - const result = parseConfigFileContent({ tstl: { NOHEADER: true } }); + const result = parseConfigFileContent({ tstl: { NoHeader: true } }); + expect(result.errors).toHaveDiagnostics(); + expect(result.options.NoHeader).toBeUndefined(); expect(result.options.noHeader).toBeUndefined(); - expect(result.options.NOHEADER).toBeUndefined(); }); describe("enum options", () => { From 64c09623dffdef2954a6c9b20ec523072a0060b0 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 16 Apr 2019 22:53:22 +0500 Subject: [PATCH 18/44] Rename compiler tests to CLI tests --- .prettierignore | 2 +- test/{compiler => cli}/errorreport.spec.ts | 2 +- test/{compiler => cli}/outfile.spec.ts | 2 +- test/{compiler => cli}/project.spec.ts | 2 +- test/{compiler => cli}/projects/baseurl/test_src/main.ts | 0 .../projects/baseurl/test_src/test_lib/nested/lib_file.ts | 0 test/{compiler => cli}/projects/baseurl/tsconfig.json | 0 test/{compiler => cli}/projects/basic/test_src/main.ts | 0 test/{compiler => cli}/projects/basic/test_src/test_lib/file.ts | 0 .../projects/basic/tsconfig.bothDirOptions.json | 0 test/{compiler => cli}/projects/basic/tsconfig.json | 0 test/{compiler => cli}/projects/basic/tsconfig.outDir.json | 0 test/{compiler => cli}/projects/basic/tsconfig.rootDir.json | 0 test/{compiler => cli}/projects/watchmode/tsconfig.json | 0 test/{compiler => cli}/projects/watchmode/watch.ts | 0 test/{compiler/runner.ts => cli/run.ts} | 0 test/{compiler => cli}/testfiles/default_import.ts | 0 test/{compiler => cli}/testfiles/invalid_syntax.ts | 0 test/{compiler => cli}/testfiles/out_file.ts | 0 test/{compiler => cli}/testfiles/watch.ts | 0 test/{compiler => cli}/watchmode.spec.ts | 2 +- test/tsconfig.json | 2 +- 22 files changed, 6 insertions(+), 6 deletions(-) rename test/{compiler => cli}/errorreport.spec.ts (96%) rename test/{compiler => cli}/outfile.spec.ts (96%) rename test/{compiler => cli}/project.spec.ts (98%) rename test/{compiler => cli}/projects/baseurl/test_src/main.ts (100%) rename test/{compiler => cli}/projects/baseurl/test_src/test_lib/nested/lib_file.ts (100%) rename test/{compiler => cli}/projects/baseurl/tsconfig.json (100%) rename test/{compiler => cli}/projects/basic/test_src/main.ts (100%) rename test/{compiler => cli}/projects/basic/test_src/test_lib/file.ts (100%) rename test/{compiler => cli}/projects/basic/tsconfig.bothDirOptions.json (100%) rename test/{compiler => cli}/projects/basic/tsconfig.json (100%) rename test/{compiler => cli}/projects/basic/tsconfig.outDir.json (100%) rename test/{compiler => cli}/projects/basic/tsconfig.rootDir.json (100%) rename test/{compiler => cli}/projects/watchmode/tsconfig.json (100%) rename test/{compiler => cli}/projects/watchmode/watch.ts (100%) rename test/{compiler/runner.ts => cli/run.ts} (100%) rename test/{compiler => cli}/testfiles/default_import.ts (100%) rename test/{compiler => cli}/testfiles/invalid_syntax.ts (100%) rename test/{compiler => cli}/testfiles/out_file.ts (100%) rename test/{compiler => cli}/testfiles/watch.ts (100%) rename test/{compiler => cli}/watchmode.spec.ts (98%) diff --git a/.prettierignore b/.prettierignore index a9b94c668..fbdb2d001 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,6 @@ /dist /coverage -/test/compiler/testfiles/invalid_syntax.ts +/test/cli/testfiles/invalid_syntax.ts /test/translation/transformation/characterEscapeSequence.ts /src diff --git a/test/compiler/errorreport.spec.ts b/test/cli/errorreport.spec.ts similarity index 96% rename from test/compiler/errorreport.spec.ts rename to test/cli/errorreport.spec.ts index 25fb46f75..3c9f7762e 100644 --- a/test/compiler/errorreport.spec.ts +++ b/test/cli/errorreport.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { runCli } from "./runner"; +import { runCli } from "./run"; const srcFilePath = path.resolve(__dirname, "testfiles", "default_import.ts"); const outFilePath = path.resolve(__dirname, "testfiles", "default_import.lua"); diff --git a/test/compiler/outfile.spec.ts b/test/cli/outfile.spec.ts similarity index 96% rename from test/compiler/outfile.spec.ts rename to test/cli/outfile.spec.ts index cc5554d1a..256c5777f 100644 --- a/test/compiler/outfile.spec.ts +++ b/test/cli/outfile.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { runCli } from "./runner"; +import { runCli } from "./run"; const outFileRelPath = "./testfiles/out_file.script"; const outFileAbsPath = path.join(__dirname, outFileRelPath); diff --git a/test/compiler/project.spec.ts b/test/cli/project.spec.ts similarity index 98% rename from test/compiler/project.spec.ts rename to test/cli/project.spec.ts index 1d5c92a0d..f5c4a2f7c 100644 --- a/test/compiler/project.spec.ts +++ b/test/cli/project.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { runCli } from "./runner"; +import { runCli } from "./run"; /** * Find all files inside a dir, recursively. diff --git a/test/compiler/projects/baseurl/test_src/main.ts b/test/cli/projects/baseurl/test_src/main.ts similarity index 100% rename from test/compiler/projects/baseurl/test_src/main.ts rename to test/cli/projects/baseurl/test_src/main.ts diff --git a/test/compiler/projects/baseurl/test_src/test_lib/nested/lib_file.ts b/test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts similarity index 100% rename from test/compiler/projects/baseurl/test_src/test_lib/nested/lib_file.ts rename to test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts diff --git a/test/compiler/projects/baseurl/tsconfig.json b/test/cli/projects/baseurl/tsconfig.json similarity index 100% rename from test/compiler/projects/baseurl/tsconfig.json rename to test/cli/projects/baseurl/tsconfig.json diff --git a/test/compiler/projects/basic/test_src/main.ts b/test/cli/projects/basic/test_src/main.ts similarity index 100% rename from test/compiler/projects/basic/test_src/main.ts rename to test/cli/projects/basic/test_src/main.ts diff --git a/test/compiler/projects/basic/test_src/test_lib/file.ts b/test/cli/projects/basic/test_src/test_lib/file.ts similarity index 100% rename from test/compiler/projects/basic/test_src/test_lib/file.ts rename to test/cli/projects/basic/test_src/test_lib/file.ts diff --git a/test/compiler/projects/basic/tsconfig.bothDirOptions.json b/test/cli/projects/basic/tsconfig.bothDirOptions.json similarity index 100% rename from test/compiler/projects/basic/tsconfig.bothDirOptions.json rename to test/cli/projects/basic/tsconfig.bothDirOptions.json diff --git a/test/compiler/projects/basic/tsconfig.json b/test/cli/projects/basic/tsconfig.json similarity index 100% rename from test/compiler/projects/basic/tsconfig.json rename to test/cli/projects/basic/tsconfig.json diff --git a/test/compiler/projects/basic/tsconfig.outDir.json b/test/cli/projects/basic/tsconfig.outDir.json similarity index 100% rename from test/compiler/projects/basic/tsconfig.outDir.json rename to test/cli/projects/basic/tsconfig.outDir.json diff --git a/test/compiler/projects/basic/tsconfig.rootDir.json b/test/cli/projects/basic/tsconfig.rootDir.json similarity index 100% rename from test/compiler/projects/basic/tsconfig.rootDir.json rename to test/cli/projects/basic/tsconfig.rootDir.json diff --git a/test/compiler/projects/watchmode/tsconfig.json b/test/cli/projects/watchmode/tsconfig.json similarity index 100% rename from test/compiler/projects/watchmode/tsconfig.json rename to test/cli/projects/watchmode/tsconfig.json diff --git a/test/compiler/projects/watchmode/watch.ts b/test/cli/projects/watchmode/watch.ts similarity index 100% rename from test/compiler/projects/watchmode/watch.ts rename to test/cli/projects/watchmode/watch.ts diff --git a/test/compiler/runner.ts b/test/cli/run.ts similarity index 100% rename from test/compiler/runner.ts rename to test/cli/run.ts diff --git a/test/compiler/testfiles/default_import.ts b/test/cli/testfiles/default_import.ts similarity index 100% rename from test/compiler/testfiles/default_import.ts rename to test/cli/testfiles/default_import.ts diff --git a/test/compiler/testfiles/invalid_syntax.ts b/test/cli/testfiles/invalid_syntax.ts similarity index 100% rename from test/compiler/testfiles/invalid_syntax.ts rename to test/cli/testfiles/invalid_syntax.ts diff --git a/test/compiler/testfiles/out_file.ts b/test/cli/testfiles/out_file.ts similarity index 100% rename from test/compiler/testfiles/out_file.ts rename to test/cli/testfiles/out_file.ts diff --git a/test/compiler/testfiles/watch.ts b/test/cli/testfiles/watch.ts similarity index 100% rename from test/compiler/testfiles/watch.ts rename to test/cli/testfiles/watch.ts diff --git a/test/compiler/watchmode.spec.ts b/test/cli/watchmode.spec.ts similarity index 98% rename from test/compiler/watchmode.spec.ts rename to test/cli/watchmode.spec.ts index 9cacad298..dad077670 100644 --- a/test/compiler/watchmode.spec.ts +++ b/test/cli/watchmode.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { forkCli } from "./runner"; +import { forkCli } from "./run"; let testsCleanup: Array<() => void> = []; afterEach(() => { diff --git a/test/tsconfig.json b/test/tsconfig.json index 666a6006e..f4d391bfb 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,5 +9,5 @@ "noEmit": true, "module": "commonjs" }, - "exclude": ["translation/transformation", "compiler/projects", "compiler/testfiles"] + "exclude": ["translation/transformation", "cli/projects", "cli/testfiles"] } From 9cc5be755df85b59205f789303eb0376dc29e2a2 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 17 Apr 2019 20:03:26 +0500 Subject: [PATCH 19/44] Refactor CLI tests --- jest.config.js | 2 +- test/cli/errorreport.spec.ts | 33 ------- test/cli/errors.spec.ts | 27 +++++ test/cli/errors/error.ts | 2 + test/cli/outfile.spec.ts | 45 --------- test/cli/project.spec.ts | 99 ------------------- test/cli/projects/baseurl/test_src/main.ts | 5 - .../test_src/test_lib/nested/lib_file.ts | 3 - test/cli/projects/baseurl/tsconfig.json | 10 -- test/cli/projects/basic/test_src/main.ts | 3 - .../projects/basic/test_src/test_lib/file.ts | 1 - .../basic/tsconfig.bothDirOptions.json | 7 -- test/cli/projects/basic/tsconfig.json | 7 -- test/cli/projects/basic/tsconfig.outDir.json | 6 -- test/cli/projects/basic/tsconfig.rootDir.json | 6 -- test/cli/projects/watchmode/tsconfig.json | 7 -- test/cli/projects/watchmode/watch.ts | 1 - test/cli/run.ts | 3 +- test/cli/testfiles/default_import.ts | 1 - test/cli/testfiles/invalid_syntax.ts | 1 - test/cli/testfiles/out_file.ts | 1 - test/cli/testfiles/watch.ts | 1 - test/cli/watch.spec.ts | 67 +++++++++++++ test/cli/watch/tsconfig.json | 1 + test/cli/watch/watch.ts | 1 + test/cli/watchmode.spec.ts | 65 ------------ test/tsconfig.json | 2 +- 27 files changed, 102 insertions(+), 305 deletions(-) delete mode 100644 test/cli/errorreport.spec.ts create mode 100644 test/cli/errors.spec.ts create mode 100644 test/cli/errors/error.ts delete mode 100644 test/cli/outfile.spec.ts delete mode 100644 test/cli/project.spec.ts delete mode 100644 test/cli/projects/baseurl/test_src/main.ts delete mode 100644 test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts delete mode 100644 test/cli/projects/baseurl/tsconfig.json delete mode 100644 test/cli/projects/basic/test_src/main.ts delete mode 100644 test/cli/projects/basic/test_src/test_lib/file.ts delete mode 100644 test/cli/projects/basic/tsconfig.bothDirOptions.json delete mode 100644 test/cli/projects/basic/tsconfig.json delete mode 100644 test/cli/projects/basic/tsconfig.outDir.json delete mode 100644 test/cli/projects/basic/tsconfig.rootDir.json delete mode 100644 test/cli/projects/watchmode/tsconfig.json delete mode 100644 test/cli/projects/watchmode/watch.ts delete mode 100644 test/cli/testfiles/default_import.ts delete mode 100644 test/cli/testfiles/invalid_syntax.ts delete mode 100644 test/cli/testfiles/out_file.ts delete mode 100644 test/cli/testfiles/watch.ts create mode 100644 test/cli/watch.spec.ts create mode 100644 test/cli/watch/tsconfig.json create mode 100644 test/cli/watch/watch.ts delete mode 100644 test/cli/watchmode.spec.ts diff --git a/jest.config.js b/jest.config.js index deb429cfc..5a98e2b3d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,7 +9,7 @@ module.exports = { // https://github.com/facebook/jest/issues/5274 "!/src/tstl.ts", ], - watchPathIgnorePatterns: ["/watch\\.ts$"], + watchPathIgnorePatterns: ["cli/watch/[^/]+$"], setupFilesAfterEnv: ["/test/setup.ts"], testEnvironment: "node", diff --git a/test/cli/errorreport.spec.ts b/test/cli/errorreport.spec.ts deleted file mode 100644 index 3c9f7762e..000000000 --- a/test/cli/errorreport.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { runCli } from "./run"; - -const srcFilePath = path.resolve(__dirname, "testfiles", "default_import.ts"); -const outFilePath = path.resolve(__dirname, "testfiles", "default_import.lua"); - -afterEach(() => { - try { - fs.unlinkSync(outFilePath); - } catch (err) { - if (err.code !== "ENOENT") throw err; - } -}); - -test("Compile project", async () => { - const { exitCode, output } = await runCli([ - srcFilePath, - "--outDir", - ".", - "--rootDir", - ".", - "--types", - "node", - ]); - - expect(exitCode).toBe(2); - expect(fs.existsSync(outFilePath)).toBe(true); - expect(output).toContain("Cannot find module './default_export'."); - expect(output).toContain( - "Default Imports are not supported, please use named imports instead!", - ); -}); diff --git a/test/cli/errors.spec.ts b/test/cli/errors.spec.ts new file mode 100644 index 000000000..8ff45ce14 --- /dev/null +++ b/test/cli/errors.spec.ts @@ -0,0 +1,27 @@ +import * as fs from "fs"; +import * as path from "path"; +import { runCli } from "./run"; + +const srcFilePath = path.resolve(__dirname, "errors", "error.ts"); +const outFilePath = path.resolve(__dirname, "errors", "error.lua"); +const errorMessage = "Unable to convert function with no 'this' parameter to function with 'this'."; + +afterEach(() => { + if (fs.existsSync(outFilePath)) fs.unlinkSync(outFilePath); +}); + +test("should report errors", async () => { + const { exitCode, output } = await runCli([srcFilePath]); + + expect(output).toContain(errorMessage); + expect(exitCode).toBe(2); + expect(fs.existsSync(outFilePath)).toBe(true); +}); + +test("shouldn't emit files with --noEmitOnError", async () => { + const { exitCode, output } = await runCli(["--noEmitOnError", srcFilePath]); + + expect(output).toContain(errorMessage); + expect(exitCode).toBe(1); + expect(fs.existsSync(outFilePath)).toBe(false); +}); diff --git a/test/cli/errors/error.ts b/test/cli/errors/error.ts new file mode 100644 index 000000000..6b4c5320c --- /dev/null +++ b/test/cli/errors/error.ts @@ -0,0 +1,2 @@ +const foo: (this: void) => void = () => {}; +const bar: () => void = foo; diff --git a/test/cli/outfile.spec.ts b/test/cli/outfile.spec.ts deleted file mode 100644 index 256c5777f..000000000 --- a/test/cli/outfile.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { runCli } from "./run"; - -const outFileRelPath = "./testfiles/out_file.script"; -const outFileAbsPath = path.join(__dirname, outFileRelPath); - -afterEach(() => { - try { - fs.unlinkSync(outFileAbsPath); - } catch (err) { - if (err.code !== "ENOENT") throw err; - } -}); - -test("Outfile absoulte path", async () => { - const { exitCode } = await runCli([ - "--types", - "node", - "--skipLibCheck", - "--outFile", - outFileAbsPath, - path.join(__dirname, "./testfiles/out_file.ts"), - ]); - - expect(exitCode).toBe(0); - expect(fs.existsSync(outFileAbsPath)).toBe(true); -}); - -test("Outfile relative path", async () => { - const { exitCode, output } = await runCli([ - "--types", - "node", - "--skipLibCheck", - "--outDir", - __dirname, - "--outFile", - outFileRelPath, - path.join(__dirname, "./testfiles/out_file.ts"), - ]); - - expect(output).not.toContain("error TS"); - expect(exitCode).toBe(0); - expect(fs.existsSync(outFileAbsPath)).toBe(true); -}); diff --git a/test/cli/project.spec.ts b/test/cli/project.spec.ts deleted file mode 100644 index f5c4a2f7c..000000000 --- a/test/cli/project.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { runCli } from "./run"; - -/** - * Find all files inside a dir, recursively. - */ -function getAllFiles(dir: string): string[] { - return fs.readdirSync(dir).reduce((files: string[], file) => { - const name = path.join(dir, file); - const isDirectory = fs.statSync(name).isDirectory(); - return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name]; - }, []); -} - -let existingFiles: string[]; -let filesAfterCompile: string[]; - -afterEach(() => { - // Remove files that were created by the test - const createdFiles = filesAfterCompile.filter(v => !existingFiles.includes(v)); - for (const file of createdFiles) { - fs.unlinkSync(file); - } - existingFiles = []; - filesAfterCompile = []; -}); - -test.each([ - { - projectName: "basic", - tsconfig: "tsconfig.json", - expectedFiles: ["lualib_bundle.lua", "test_src/test_lib/file.lua", "test_src/main.lua"], - }, - { - projectName: "basic", - tsconfig: ".", - expectedFiles: ["lualib_bundle.lua", "test_src/test_lib/file.lua", "test_src/main.lua"], - }, - { - projectName: "basic", - tsconfig: "tsconfig.outDir.json", - expectedFiles: [ - "out_dir/lualib_bundle.lua", - "out_dir/test_src/test_lib/file.lua", - "out_dir/test_src/main.lua", - ], - }, - { - projectName: "basic", - tsconfig: "tsconfig.rootDir.json", - expectedFiles: [ - "test_src/lualib_bundle.lua", - "test_src/test_lib/file.lua", - "test_src/main.lua", - ], - }, - { - projectName: "basic", - tsconfig: "tsconfig.bothDirOptions.json", - expectedFiles: [ - "out_dir/lualib_bundle.lua", - "out_dir/test_lib/file.lua", - "out_dir/main.lua", - ], - }, - { - projectName: "baseurl", - tsconfig: "tsconfig.json", - expectedFiles: [ - "out_dir/lualib_bundle.lua", - "out_dir/test_src/test_lib/nested/lib_file.lua", - "out_dir/test_src/main.lua", - ], - }, -])("Compile project (%p)", async ({ projectName, tsconfig, expectedFiles }) => { - const relPathToProject = path.join("projects", projectName); - - // Setup we cant do this in beforeEach because we need the projectname - existingFiles = getAllFiles(path.resolve(__dirname, relPathToProject)); - filesAfterCompile = []; - - const tsconfigPath = path.resolve(__dirname, relPathToProject, tsconfig); - - const { exitCode, output } = await runCli(["-p", tsconfigPath]); - - expect(output).not.toContain("error TS"); - expect(exitCode).toBe(0); - - filesAfterCompile = getAllFiles(path.resolve(__dirname, relPathToProject)); - expectedFiles = expectedFiles.map(relPath => - path.resolve(__dirname, relPathToProject, relPath), - ); - expectedFiles.push(...existingFiles); - - for (const existingFile of filesAfterCompile) { - expect(expectedFiles).toContain(existingFile); - } -}); diff --git a/test/cli/projects/baseurl/test_src/main.ts b/test/cli/projects/baseurl/test_src/main.ts deleted file mode 100644 index 62afd2ad1..000000000 --- a/test/cli/projects/baseurl/test_src/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as test from "nested/lib_file"; - -let hello = "12"; - -hello = "13"; diff --git a/test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts b/test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts deleted file mode 100644 index 4248a042b..000000000 --- a/test/cli/projects/baseurl/test_src/test_lib/nested/lib_file.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function test() { - return 1; -} diff --git a/test/cli/projects/baseurl/tsconfig.json b/test/cli/projects/baseurl/tsconfig.json deleted file mode 100644 index 299ae7cf8..000000000 --- a/test/cli/projects/baseurl/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "luaTarget": "JIT", - "compilerOptions": { - "outDir": "./out_dir", - "rootDir": ".", - "baseUrl": "./test_src/test_lib", - "types": [], - "skipLibCheck": true - } -} diff --git a/test/cli/projects/basic/test_src/main.ts b/test/cli/projects/basic/test_src/main.ts deleted file mode 100644 index add53cc9a..000000000 --- a/test/cli/projects/basic/test_src/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -function main() { - const main = 10; -} diff --git a/test/cli/projects/basic/test_src/test_lib/file.ts b/test/cli/projects/basic/test_src/test_lib/file.ts deleted file mode 100644 index 4ac2b8fe0..000000000 --- a/test/cli/projects/basic/test_src/test_lib/file.ts +++ /dev/null @@ -1 +0,0 @@ -const foo = true; diff --git a/test/cli/projects/basic/tsconfig.bothDirOptions.json b/test/cli/projects/basic/tsconfig.bothDirOptions.json deleted file mode 100644 index b21881ae5..000000000 --- a/test/cli/projects/basic/tsconfig.bothDirOptions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "out_dir", - "rootDir": "test_src" - } -} diff --git a/test/cli/projects/basic/tsconfig.json b/test/cli/projects/basic/tsconfig.json deleted file mode 100644 index ec7262ddf..000000000 --- a/test/cli/projects/basic/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "luaTarget": "JIT", - "compilerOptions": { - "types": [], - "skipLibCheck": true - } -} diff --git a/test/cli/projects/basic/tsconfig.outDir.json b/test/cli/projects/basic/tsconfig.outDir.json deleted file mode 100644 index cd2de0d24..000000000 --- a/test/cli/projects/basic/tsconfig.outDir.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "out_dir" - } -} diff --git a/test/cli/projects/basic/tsconfig.rootDir.json b/test/cli/projects/basic/tsconfig.rootDir.json deleted file mode 100644 index d9506e7ee..000000000 --- a/test/cli/projects/basic/tsconfig.rootDir.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "rootDir": "test_src" - } -} diff --git a/test/cli/projects/watchmode/tsconfig.json b/test/cli/projects/watchmode/tsconfig.json deleted file mode 100644 index ec7262ddf..000000000 --- a/test/cli/projects/watchmode/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "luaTarget": "JIT", - "compilerOptions": { - "types": [], - "skipLibCheck": true - } -} diff --git a/test/cli/projects/watchmode/watch.ts b/test/cli/projects/watchmode/watch.ts deleted file mode 100644 index fa2edc21a..000000000 --- a/test/cli/projects/watchmode/watch.ts +++ /dev/null @@ -1 +0,0 @@ -class MyTest {} diff --git a/test/cli/run.ts b/test/cli/run.ts index 233aeffb7..59d2e9263 100644 --- a/test/cli/run.ts +++ b/test/cli/run.ts @@ -5,8 +5,9 @@ jest.setTimeout(20000); const cliPath = path.join(__dirname, "../../src/tstl.ts"); +const defaultArgs = ["--skipLibCheck", "--types", "node"]; export function forkCli(args: string[]): ChildProcess { - return fork(cliPath, args, { + return fork(cliPath, [...defaultArgs, ...args], { stdio: "pipe", execArgv: ["--require", "ts-node/register/transpile-only"], }); diff --git a/test/cli/testfiles/default_import.ts b/test/cli/testfiles/default_import.ts deleted file mode 100644 index 9f80d14ac..000000000 --- a/test/cli/testfiles/default_import.ts +++ /dev/null @@ -1 +0,0 @@ -import Test from "./default_export"; diff --git a/test/cli/testfiles/invalid_syntax.ts b/test/cli/testfiles/invalid_syntax.ts deleted file mode 100644 index 6d38d3866..000000000 --- a/test/cli/testfiles/invalid_syntax.ts +++ /dev/null @@ -1 +0,0 @@ -const variable = () => {} => {}; diff --git a/test/cli/testfiles/out_file.ts b/test/cli/testfiles/out_file.ts deleted file mode 100644 index ef22a69f1..000000000 --- a/test/cli/testfiles/out_file.ts +++ /dev/null @@ -1 +0,0 @@ -class Test {} diff --git a/test/cli/testfiles/watch.ts b/test/cli/testfiles/watch.ts deleted file mode 100644 index fa2edc21a..000000000 --- a/test/cli/testfiles/watch.ts +++ /dev/null @@ -1 +0,0 @@ -class MyTest {} diff --git a/test/cli/watch.spec.ts b/test/cli/watch.spec.ts new file mode 100644 index 000000000..661978c44 --- /dev/null +++ b/test/cli/watch.spec.ts @@ -0,0 +1,67 @@ +import * as fs from "fs"; +import * as path from "path"; +import { forkCli } from "./run"; +import { ChildProcess } from "child_process"; + +let testsCleanup: Array<() => void> = []; +afterEach(() => { + testsCleanup.forEach(x => x()); + testsCleanup = []; +}); + +function waitForFileExists(filePath: string): Promise { + return new Promise(resolve => { + const intervalTimerId = setInterval(() => { + if (fs.existsSync(filePath)) { + clearInterval(intervalTimerId); + resolve(); + } + }, 100); + + testsCleanup.push(() => clearInterval(intervalTimerId)); + }); +} + +function forkWatchProcess(args: string[]): void { + const child = forkCli(["--watch", ...args]); + testsCleanup.push(() => child.kill()); +} + +const watchedFile = path.join(__dirname, "./watch/watch.ts"); +const watchedFileOut = watchedFile.replace(".ts", ".lua"); + +afterEach(() => { + if (fs.existsSync(watchedFileOut)) fs.unlinkSync(watchedFileOut); +}); + +async function compileChangeAndCompare(filePath: string, content: string): Promise { + await waitForFileExists(watchedFileOut); + const initialResultLua = fs.readFileSync(watchedFileOut, "utf8"); + + fs.unlinkSync(watchedFileOut); + + const originalContent = fs.readFileSync(filePath, "utf8"); + fs.writeFileSync(filePath, content); + testsCleanup.push(() => fs.writeFileSync(filePath, originalContent)); + + await waitForFileExists(watchedFileOut); + const updatedResultLua = fs.readFileSync(watchedFileOut, "utf8"); + + expect(initialResultLua).not.toEqual(updatedResultLua); +} + +test("should watch single file", async () => { + forkWatchProcess([path.join(__dirname, "./watch/watch.ts")]); + await compileChangeAndCompare(watchedFile, "const value = 1;"); +}); + +test("should watch project", async () => { + forkWatchProcess(["--project", path.join(__dirname, "./watch")]); + await compileChangeAndCompare(watchedFile, "const value = 1;"); +}); + +test("should watch config file", async () => { + const configFilePath = path.join(__dirname, "./watch/tsconfig.json"); + forkWatchProcess(["--project", configFilePath]); + await compileChangeAndCompare(configFilePath, '{ "tstl": { "luaTarget": "5.3" } }'); +}); diff --git a/test/cli/watch/tsconfig.json b/test/cli/watch/tsconfig.json new file mode 100644 index 000000000..922b8de4c --- /dev/null +++ b/test/cli/watch/tsconfig.json @@ -0,0 +1 @@ +{ "tstl": { "luaTarget": "JIT" } } diff --git a/test/cli/watch/watch.ts b/test/cli/watch/watch.ts new file mode 100644 index 000000000..45813bdf9 --- /dev/null +++ b/test/cli/watch/watch.ts @@ -0,0 +1 @@ +const value = 0 & 0; diff --git a/test/cli/watchmode.spec.ts b/test/cli/watchmode.spec.ts deleted file mode 100644 index dad077670..000000000 --- a/test/cli/watchmode.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { forkCli } from "./run"; - -let testsCleanup: Array<() => void> = []; -afterEach(() => { - testsCleanup.forEach(x => x()); - testsCleanup = []; -}); - -function waitForFileExists(filePath: string): Promise { - return new Promise(resolve => { - const intervalTimerId = setInterval(() => { - if (fs.existsSync(filePath)) { - clearInterval(intervalTimerId); - resolve(); - } - }, 100); - - testsCleanup.push(() => clearInterval(intervalTimerId)); - }); -} - -test.each([ - { - args: [ - "--types", - "node", - "--skipLibCheck", - "-w", - path.join(__dirname, "./testfiles/watch.ts"), - ], - fileToChange: path.join(__dirname, "./testfiles/watch.ts"), - }, - { - args: ["-w", "-p", path.join(__dirname, "./projects/watchmode/")], - fileToChange: path.join(__dirname, "./projects/watchmode/watch.ts"), - }, -])("Watch single File (%p)", async ({ args, fileToChange }) => { - const fileToChangeOut = fileToChange.replace(".ts", ".lua"); - const originalTS = fs.readFileSync(fileToChange, "utf-8"); - - const child = forkCli(args); - - testsCleanup.push(() => { - try { - fs.unlinkSync(fileToChangeOut); - } catch (err) { - if (err.code !== "ENOENT") throw err; - } - fs.writeFileSync(fileToChange, originalTS); - child.kill(); - }); - - await waitForFileExists(fileToChangeOut); - const initialResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); - - fs.unlinkSync(fileToChangeOut); - fs.writeFileSync(fileToChange, "class MyTest2 {}"); - - await waitForFileExists(fileToChangeOut); - const updatedResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); - - expect(initialResultLua).not.toEqual(updatedResultLua); -}); diff --git a/test/tsconfig.json b/test/tsconfig.json index f4d391bfb..9a8fec86e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,5 +9,5 @@ "noEmit": true, "module": "commonjs" }, - "exclude": ["translation/transformation", "cli/projects", "cli/testfiles"] + "exclude": ["translation/transformation", "cli/watch", "cli/errors"] } From 52b5c509f152c01654072d701026c6a311984b58 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 21 Apr 2019 03:51:08 +0500 Subject: [PATCH 20/44] Add transpile tests --- .prettierignore | 1 - src/Emit.ts | 29 +++++++++---- src/Transpile.ts | 1 - test/cli/watch.spec.ts | 1 - .../__snapshots__/directories.spec.ts.snap | 41 +++++++++++++++++++ test/transpile/directories.spec.ts | 33 +++++++++++++++ .../baseurl/src/lib/nested/file.ts | 3 ++ .../transpile/directories/baseurl/src/main.ts | 3 ++ .../directories/basic/src/lib/file.ts | 1 + test/transpile/directories/basic/src/main.ts | 3 ++ test/transpile/outFile.spec.ts | 32 +++++++++++++++ test/transpile/outFile/index.ts | 1 + test/transpile/run.ts | 35 ++++++++++++++++ test/tsconfig.json | 8 +++- 14 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 test/transpile/__snapshots__/directories.spec.ts.snap create mode 100644 test/transpile/directories.spec.ts create mode 100644 test/transpile/directories/baseurl/src/lib/nested/file.ts create mode 100644 test/transpile/directories/baseurl/src/main.ts create mode 100644 test/transpile/directories/basic/src/lib/file.ts create mode 100644 test/transpile/directories/basic/src/main.ts create mode 100644 test/transpile/outFile.spec.ts create mode 100644 test/transpile/outFile/index.ts create mode 100644 test/transpile/run.ts diff --git a/.prettierignore b/.prettierignore index fbdb2d001..fef924d04 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ /dist /coverage -/test/cli/testfiles/invalid_syntax.ts /test/translation/transformation/characterEscapeSequence.ts /src diff --git a/src/Emit.ts b/src/Emit.ts index c92250450..d04903f79 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -3,8 +3,8 @@ import * as path from "path"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { TranspiledFile } from "./Transpile"; -const trimExt = (filePath: string) => - path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); +const trimExt = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); +const normalizeSlashes = (filePath: string) => filePath.replace(/\\/g, "/"); export interface OutputFile { name: string; @@ -18,11 +18,15 @@ export function emitTranspiledFiles( ): OutputFile[] { let { rootDir, outDir, outFile, luaLibImport } = options; - // TODO: - const configFileName = options.configFilePath as string | undefined; - if (configFileName && rootDir === undefined) rootDir = path.dirname(configFileName); - if (rootDir === undefined) rootDir = process.cwd(); - if (outDir === undefined) outDir = rootDir; + if (rootDir === undefined) { + const configFileName = options.configFilePath as string | undefined; + // TODO: Use getCommonSourceDirectory + rootDir = configFileName ? path.dirname(configFileName) : process.cwd(); + } + + if (outDir === undefined) { + outDir = rootDir; + } const files: OutputFile[] = []; for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { @@ -44,6 +48,8 @@ export function emitTranspiledFiles( outPath = trimExt(outPath) + ".lua"; } + outPath = normalizeSlashes(outPath); + if (lua !== undefined) { files.push({ name: outPath, text: lua }); } @@ -69,8 +75,13 @@ export function emitTranspiledFiles( ); } - const outPath = path.join(outDir, "lualib_bundle.lua"); - files.push({ name: outPath, text: lualibContent }); + let outPath = path.resolve(path.join(rootDir, "lualib_bundle.lua")); + if (outDir !== rootDir) { + const relativeSourcePath = path.resolve(outPath).replace(path.resolve(rootDir), ""); + outPath = path.join(outDir, relativeSourcePath); + } + + files.push({ name: normalizeSlashes(outPath), text: lualibContent }); } return files; diff --git a/src/Transpile.ts b/src/Transpile.ts index 9fadaf451..63db60260 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -112,7 +112,6 @@ export function getTranspileOutput({ updateTranspiledFile(sourceFile.fileName, { lua, sourceMap }); } } catch (err) { - /* istanbul ignore if: Testing it would require to add a bug/exception to our code */ if (!(err instanceof TranspileError)) throw err; diagnostics.push({ diff --git a/test/cli/watch.spec.ts b/test/cli/watch.spec.ts index 661978c44..d1274f865 100644 --- a/test/cli/watch.spec.ts +++ b/test/cli/watch.spec.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import * as path from "path"; import { forkCli } from "./run"; -import { ChildProcess } from "child_process"; let testsCleanup: Array<() => void> = []; afterEach(() => { diff --git a/test/transpile/__snapshots__/directories.spec.ts.snap b/test/transpile/__snapshots__/directories.spec.ts.snap new file mode 100644 index 000000000..952fd709b --- /dev/null +++ b/test/transpile/__snapshots__/directories.spec.ts.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should be able to resolve ({"name": "baseurl", "options": [Object]}) 1`] = ` +Array [ + "directories/baseurl/out/lualib_bundle.lua", + "directories/baseurl/out/src/lib/nested/file.lua", + "directories/baseurl/out/src/main.lua", +] +`; + +exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = ` +Array [ + "directories/basic/lualib_bundle.lua", + "directories/basic/src/lib/file.lua", + "directories/basic/src/main.lua", +] +`; + +exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 2`] = ` +Array [ + "directories/basic/out/lualib_bundle.lua", + "directories/basic/out/src/lib/file.lua", + "directories/basic/out/src/main.lua", +] +`; + +exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 3`] = ` +Array [ + "directories/basic/src/lib/file.lua", + "directories/basic/src/lualib_bundle.lua", + "directories/basic/src/main.lua", +] +`; + +exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 4`] = ` +Array [ + "directories/basic/out/lib/file.lua", + "directories/basic/out/lualib_bundle.lua", + "directories/basic/out/main.lua", +] +`; diff --git a/test/transpile/directories.spec.ts b/test/transpile/directories.spec.ts new file mode 100644 index 000000000..877e58f50 --- /dev/null +++ b/test/transpile/directories.spec.ts @@ -0,0 +1,33 @@ +import * as path from "path"; +import * as ts from "typescript"; +import * as tstl from "../../src"; +import { buildVirtualProject } from "./run"; + +interface DirectoryTestCase { + name: string; + options: tstl.CompilerOptions; +} + +test.each([ + { name: "basic", options: {} }, + { name: "basic", options: { outDir: "out" } }, + { name: "basic", options: { rootDir: "src" } }, + { name: "basic", options: { rootDir: "src", outDir: "out" } }, + { name: "baseurl", options: { baseUrl: "./src/lib", rootDir: ".", outDir: "./out" } }, +])("should be able to resolve (%p)", async ({ name, options: compilerOptions }) => { + const projectPath = path.join(__dirname, "directories", name); + jest.spyOn(process, "cwd").mockReturnValue(projectPath); + + const config = { + compilerOptions: { ...compilerOptions, types: [], skipLibCheck: true }, + tstl: { luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.Always }, + }; + + const { fileNames, options } = tstl.updateParsedConfigFile( + ts.parseJsonConfigFileContent(config, ts.sys, projectPath), + ); + + const { diagnostics, emittedFiles } = buildVirtualProject(fileNames, options); + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles).toMatchSnapshot(); +}); diff --git a/test/transpile/directories/baseurl/src/lib/nested/file.ts b/test/transpile/directories/baseurl/src/lib/nested/file.ts new file mode 100644 index 000000000..4248a042b --- /dev/null +++ b/test/transpile/directories/baseurl/src/lib/nested/file.ts @@ -0,0 +1,3 @@ +export function test() { + return 1; +} diff --git a/test/transpile/directories/baseurl/src/main.ts b/test/transpile/directories/baseurl/src/main.ts new file mode 100644 index 000000000..1665f4e08 --- /dev/null +++ b/test/transpile/directories/baseurl/src/main.ts @@ -0,0 +1,3 @@ +import { test } from "nested/file"; + +test(); diff --git a/test/transpile/directories/basic/src/lib/file.ts b/test/transpile/directories/basic/src/lib/file.ts new file mode 100644 index 000000000..4ac2b8fe0 --- /dev/null +++ b/test/transpile/directories/basic/src/lib/file.ts @@ -0,0 +1 @@ +const foo = true; diff --git a/test/transpile/directories/basic/src/main.ts b/test/transpile/directories/basic/src/main.ts new file mode 100644 index 000000000..add53cc9a --- /dev/null +++ b/test/transpile/directories/basic/src/main.ts @@ -0,0 +1,3 @@ +function main() { + const main = 10; +} diff --git a/test/transpile/outFile.spec.ts b/test/transpile/outFile.spec.ts new file mode 100644 index 000000000..e3d516236 --- /dev/null +++ b/test/transpile/outFile.spec.ts @@ -0,0 +1,32 @@ +import * as path from "path"; +import { buildVirtualProject } from "./run"; + +const inputFilePath = path.join(__dirname, "outFile/index.ts"); +test("should support absolute outFile", () => { + const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { + outFile: path.join(__dirname, "output.script"), + }); + + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles).toEqual(["output.script"]); +}); + +test("should support relative outFile", () => { + jest.spyOn(process, "cwd").mockReturnValue(__dirname); + const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { + outFile: "output.script", + }); + + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles).toEqual(["output.script"]); +}); + +test("should support outFile with declaration", () => { + const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { + outFile: path.join(__dirname, "output.script"), + declaration: true, + }); + + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); +}); diff --git a/test/transpile/outFile/index.ts b/test/transpile/outFile/index.ts new file mode 100644 index 000000000..ef22a69f1 --- /dev/null +++ b/test/transpile/outFile/index.ts @@ -0,0 +1 @@ +class Test {} diff --git a/test/transpile/run.ts b/test/transpile/run.ts new file mode 100644 index 000000000..ed8712877 --- /dev/null +++ b/test/transpile/run.ts @@ -0,0 +1,35 @@ +import * as ts from "typescript"; +import * as tstl from "../../src"; +import * as path from "path"; + +interface BuildVirtualProjectResult { + diagnostics: ts.Diagnostic[]; + emitResult: tstl.OutputFile[]; + emittedFiles: string[]; +} + +export function buildVirtualProject( + rootNames: string[], + options: tstl.CompilerOptions, +): BuildVirtualProjectResult { + options.skipLibCheck = true; + options.types = []; + const program = ts.createProgram({ rootNames, options }); + + const { transpiledFiles, diagnostics: emitDiagnostics } = tstl.getTranspileOutput({ + program, + options, + }); + + const diagnostics = ts.sortAndDeduplicateDiagnostics([ + ...ts.getPreEmitDiagnostics(program), + ...emitDiagnostics, + ]); + + const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + const emittedFiles = emitResult + .map(result => path.relative(__dirname, result.name).replace(/\\/g, "/")) + .sort(); + + return { diagnostics: [...diagnostics], emitResult, emittedFiles }; +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 9a8fec86e..684b6c260 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,5 +9,11 @@ "noEmit": true, "module": "commonjs" }, - "exclude": ["translation/transformation", "cli/watch", "cli/errors"] + "exclude": [ + "translation/transformation", + "cli/errors", + "cli/watch", + "transpile/directories", + "transpile/outFile" + ] } From 28b5b52d9f20f0f8bf1bbaec644442bd4fc1eb13 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 21 Apr 2019 19:11:56 +0500 Subject: [PATCH 21/44] Fix invalid behavior with relative outFile and outDir --- src/Emit.ts | 28 +++++++++------------------- test/transpile/outFile.spec.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index d04903f79..16af6318b 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -18,32 +18,23 @@ export function emitTranspiledFiles( ): OutputFile[] { let { rootDir, outDir, outFile, luaLibImport } = options; - if (rootDir === undefined) { - const configFileName = options.configFilePath as string | undefined; - // TODO: Use getCommonSourceDirectory - rootDir = configFileName ? path.dirname(configFileName) : process.cwd(); - } + const configFileName = options.configFilePath as string | undefined; + // TODO: Use getCommonSourceDirectory + const baseDir = configFileName ? path.dirname(configFileName) : process.cwd(); - if (outDir === undefined) { - outDir = rootDir; - } + rootDir = rootDir || baseDir; + outDir = outDir ? path.resolve(baseDir, outDir) : rootDir; const files: OutputFile[] = []; for (const [fileName, { lua, sourceMap, declaration, declarationMap }] of transpiledFiles) { let outPath = fileName; if (outDir !== rootDir) { - const relativeSourcePath = path.resolve(fileName).replace(path.resolve(rootDir), ""); - outPath = path.join(outDir, relativeSourcePath); + outPath = path.resolve(outDir, path.relative(rootDir, fileName)); } // change extension or rename to outFile if (outFile) { - if (path.isAbsolute(outFile)) { - outPath = outFile; - } else { - // append to workingDir or outDir - outPath = path.resolve(outDir, outFile); - } + outPath = path.isAbsolute(outFile) ? outFile : path.resolve(baseDir, outFile); } else { outPath = trimExt(outPath) + ".lua"; } @@ -75,10 +66,9 @@ export function emitTranspiledFiles( ); } - let outPath = path.resolve(path.join(rootDir, "lualib_bundle.lua")); + let outPath = path.resolve(rootDir, "lualib_bundle.lua"); if (outDir !== rootDir) { - const relativeSourcePath = path.resolve(outPath).replace(path.resolve(rootDir), ""); - outPath = path.join(outDir, relativeSourcePath); + outPath = path.join(outDir, path.relative(rootDir, outPath)); } files.push({ name: normalizeSlashes(outPath), text: lualibContent }); diff --git a/test/transpile/outFile.spec.ts b/test/transpile/outFile.spec.ts index e3d516236..5a2e9c399 100644 --- a/test/transpile/outFile.spec.ts +++ b/test/transpile/outFile.spec.ts @@ -30,3 +30,15 @@ test("should support outFile with declaration", () => { expect(diagnostics).not.toHaveDiagnostics(); expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); }); + +test("should resolve outFile relative to base directory", () => { + jest.spyOn(process, "cwd").mockReturnValue(__dirname); + const { diagnostics, emittedFiles, emitResult } = buildVirtualProject([inputFilePath], { + outFile: "output.script", + outDir: "out", + declaration: true, + }); + + expect(diagnostics).not.toHaveDiagnostics(); + expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); +}); From c211764808084d34570ee44f508afc19e0468b5d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 21 Apr 2019 20:13:29 +0500 Subject: [PATCH 22/44] Rename getTranspileOutput to getTranspilationResult --- src/Transpile.ts | 6 +++--- src/index.ts | 21 ++++++++++++--------- src/tstl.ts | 6 +++--- test/transpile/run.ts | 6 +++--- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 63db60260..ea21acaf0 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -46,7 +46,7 @@ export interface TranspilationResult { transpiledFiles: Map; } -export interface GetTranspileOutputOptions { +export interface GetTranspilationResultOptions { program: ts.Program; options: CompilerOptions; customTransformers?: ts.CustomTransformers; @@ -55,14 +55,14 @@ export interface GetTranspileOutputOptions { transformer?: LuaTransformer; } -export function getTranspileOutput({ +export function getTranspilationResult({ program, options, customTransformers = {}, sourceFiles: targetSourceFiles, printer = new LuaPrinter(options), transformer = new LuaTransformer(program, options), -}: GetTranspileOutputOptions): TranspilationResult { +}: GetTranspilationResultOptions): TranspilationResult { const { noEmit, emitDeclarationOnly, noEmitOnError } = options; const diagnostics: ts.Diagnostic[] = []; diff --git a/src/index.ts b/src/index.ts index f9eba091b..b283f5deb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as ts from "typescript"; import { parseConfigFileWithSystem } from "./CommandLineParser"; import { CompilerOptions } from "./CompilerOptions"; -import { getTranspileOutput, TranspilationResult, TranspiledFile } from "./Transpile"; +import { getTranspilationResult, TranspilationResult, TranspiledFile } from "./Transpile"; export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./CommandLineParser"; export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; @@ -19,14 +19,17 @@ export function transpileFiles( options: CompilerOptions = {} ): TranspilationResult { const program = ts.createProgram(rootNames, options); - const { diagnostics, transpiledFiles } = getTranspileOutput({ program, options }); + const { transpiledFiles, diagnostics: transpileDiagnostics } = getTranspilationResult({ + program, + options, + }); - const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...diagnostics, + ...transpileDiagnostics, ]); - return { transpiledFiles, diagnostics: [...allDiagnostics] }; + return { transpiledFiles, diagnostics: [...diagnostics] }; } export function transpileProject(fileName: string, options?: CompilerOptions): TranspilationResult { @@ -91,13 +94,13 @@ export function transpileVirtualProject( options: CompilerOptions = {} ): TranspilationResult { const program = createVirtualProgram(files, options); - const transpileOutput = getTranspileOutput({ program, options }); - const allDiagnostics = ts.sortAndDeduplicateDiagnostics([ + const result = getTranspilationResult({ program, options }); + const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...transpileOutput.diagnostics, + ...result.diagnostics, ]); - return { ...transpileOutput, diagnostics: [...allDiagnostics] }; + return { ...result, diagnostics: [...diagnostics] }; } export interface TranspileStringResult { diff --git a/src/tstl.ts b/src/tstl.ts index 51fc301f3..bbeb20166 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -147,14 +147,14 @@ function performCompilation( configFileParsingDiagnostics, }); - const { transpiledFiles, diagnostics: emitDiagnostics } = tstl.getTranspileOutput({ + const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ program, options, }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...emitDiagnostics, + ...transpileDiagnostics, ]); const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); @@ -259,7 +259,7 @@ function updateWatchCompilationHost( } } - const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.getTranspileOutput({ + const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.getTranspilationResult({ program, options, sourceFiles, diff --git a/test/transpile/run.ts b/test/transpile/run.ts index ed8712877..d6bd8badb 100644 --- a/test/transpile/run.ts +++ b/test/transpile/run.ts @@ -14,16 +14,16 @@ export function buildVirtualProject( ): BuildVirtualProjectResult { options.skipLibCheck = true; options.types = []; - const program = ts.createProgram({ rootNames, options }); - const { transpiledFiles, diagnostics: emitDiagnostics } = tstl.getTranspileOutput({ + const program = ts.createProgram({ rootNames, options }); + const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ program, options, }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), - ...emitDiagnostics, + ...transpileDiagnostics, ]); const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); From 65e51834f24b8161a9a30089c98d849c67b35d9f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 00:48:12 +0500 Subject: [PATCH 23/44] Remove `options` argument from `getTranspilationResult` --- src/Transpile.ts | 29 ++++++++++++++--------------- src/index.ts | 3 +-- src/tstl.ts | 29 +++++++++++------------------ test/transpile/run.ts | 1 - 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index ea21acaf0..5d41a68ba 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -48,7 +48,6 @@ export interface TranspilationResult { export interface GetTranspilationResultOptions { program: ts.Program; - options: CompilerOptions; customTransformers?: ts.CustomTransformers; sourceFiles?: ts.SourceFile[]; printer?: LuaPrinter; @@ -57,13 +56,14 @@ export interface GetTranspilationResultOptions { export function getTranspilationResult({ program, - options, customTransformers = {}, sourceFiles: targetSourceFiles, - printer = new LuaPrinter(options), - transformer = new LuaTransformer(program, options), + printer, + transformer, }: GetTranspilationResultOptions): TranspilationResult { - const { noEmit, emitDeclarationOnly, noEmitOnError } = options; + const options = program.getCompilerOptions(); + printer = printer || new LuaPrinter(options); + transformer = transformer || new LuaTransformer(program, options); const diagnostics: ts.Diagnostic[] = []; const transpiledFiles = new Map(); @@ -75,7 +75,7 @@ export function getTranspilationResult({ } }; - if (noEmitOnError) { + if (options.noEmitOnError) { const preEmitDiagnostics = [ ...program.getOptionsDiagnostics(), ...program.getGlobalDiagnostics(), @@ -102,9 +102,9 @@ export function getTranspilationResult({ const processSourceFile = (sourceFile: ts.SourceFile) => { try { - const [luaAST, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); - if (!noEmit && !emitDeclarationOnly) { - const [lua, sourceMap] = printer.print( + const [luaAST, lualibFeatureSet] = transformer!.transformSourceFile(sourceFile); + if (!options.noEmit && !options.emitDeclarationOnly) { + const [lua, sourceMap] = printer!.print( luaAST, lualibFeatureSet, sourceFile.fileName @@ -146,13 +146,12 @@ export function getTranspilationResult({ const isEmittableJsonFile = (sourceFile: ts.SourceFile) => sourceFile.flags & ts.NodeFlags.JsonFile && - !emitDeclarationOnly && + !options.emitDeclarationOnly && !program.isSourceFileFromExternalLibrary(sourceFile); // We always have to emit to get transformer diagnostics - const programOptions = program.getCompilerOptions(); - const programNoEmit = programOptions.noEmit; - programOptions.noEmit = false; + const oldNoEmit = options.noEmit; + options.noEmit = false; if (targetSourceFiles) { for (const sourceFile of targetSourceFiles) { @@ -177,9 +176,9 @@ export function getTranspilationResult({ .forEach(processSourceFile); } - programOptions.noEmit = programNoEmit; + options.noEmit = oldNoEmit; - if (noEmit || (noEmitOnError && diagnostics.length > 0)) { + if (options.noEmit || (options.noEmitOnError && diagnostics.length > 0)) { transpiledFiles.clear(); } diff --git a/src/index.ts b/src/index.ts index b283f5deb..1325ca385 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,6 @@ export function transpileFiles( const program = ts.createProgram(rootNames, options); const { transpiledFiles, diagnostics: transpileDiagnostics } = getTranspilationResult({ program, - options, }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ @@ -94,7 +93,7 @@ export function transpileVirtualProject( options: CompilerOptions = {} ): TranspilationResult { const program = createVirtualProgram(files, options); - const result = getTranspilationResult({ program, options }); + const result = getTranspilationResult({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...result.diagnostics, diff --git a/src/tstl.ts b/src/tstl.ts index bbeb20166..4d64bbe67 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -149,7 +149,6 @@ function performCompilation( const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ program, - options, }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ @@ -205,30 +204,22 @@ function createWatchOfFilesAndCompilerOptions( ts.createWatchProgram(watchCompilerHost); } -interface ConfigFileSnapshot { - options: tstl.CompilerOptions; - configFileParsingDiagnostics: ts.Diagnostic[]; -} - function updateWatchCompilationHost( host: ts.WatchCompilerHost, optionsToExtend: tstl.CompilerOptions ): void { let fullRecompile = true; - const configFileMap = new WeakMap(); + const configFileMap = new WeakMap(); host.afterProgramCreate = builderProgram => { const program = builderProgram.getProgram(); - const compilerOptions = builderProgram.getCompilerOptions(); + const options = builderProgram.getCompilerOptions(); - let options = optionsToExtend; let configFileParsingDiagnostics: ts.Diagnostic[] = []; - const configFile = compilerOptions.configFile as ts.TsConfigSourceFile | undefined; - const configFilePath = compilerOptions.configFilePath as string | undefined; + const configFile = options.configFile as ts.TsConfigSourceFile | undefined; + const configFilePath = options.configFilePath as string | undefined; if (configFile && configFilePath) { - if (configFileMap.has(configFile)) { - ({ options, configFileParsingDiagnostics } = configFileMap.get(configFile)!); - } else { + if (!configFileMap.has(configFile)) { const parsedConfigFile = CommandLineParser.updateParsedConfigFile( ts.parseJsonSourceFileConfigFileContent( configFile, @@ -239,9 +230,12 @@ function updateWatchCompilationHost( ) ); - ({ options, errors: configFileParsingDiagnostics } = parsedConfigFile); - configFileMap.set(configFile, { options, configFileParsingDiagnostics }); + configFileMap.set(configFile, parsedConfigFile); } + + const parsedConfigFile = configFileMap.get(configFile)!; + Object.assign(options, parsedConfigFile.options); + configFileParsingDiagnostics = parsedConfigFile.errors; } let sourceFiles: ts.SourceFile[] | undefined; @@ -261,7 +255,6 @@ function updateWatchCompilationHost( const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.getTranspilationResult({ program, - options, sourceFiles, }); @@ -286,7 +279,7 @@ function updateWatchCompilationHost( host.onWatchStatusChange!( cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), - compilerOptions + options ); }; } diff --git a/test/transpile/run.ts b/test/transpile/run.ts index d6bd8badb..675adad07 100644 --- a/test/transpile/run.ts +++ b/test/transpile/run.ts @@ -18,7 +18,6 @@ export function buildVirtualProject( const program = ts.createProgram({ rootNames, options }); const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ program, - options, }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ From 07c23dc492fd83191b88b1d6458f23416ad2d8ba Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 01:02:16 +0500 Subject: [PATCH 24/44] Rename getTranspilationResult to transpile --- src/Transpile.ts | 8 ++++---- src/index.ts | 14 ++++++-------- src/tstl.ts | 6 ++---- test/transpile/run.ts | 4 +--- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 5d41a68ba..81b4d9e1d 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -41,12 +41,12 @@ export interface TranspiledFile { declarationMap?: string; } -export interface TranspilationResult { +export interface TranspileResult { diagnostics: ts.Diagnostic[]; transpiledFiles: Map; } -export interface GetTranspilationResultOptions { +export interface TranspileOptions { program: ts.Program; customTransformers?: ts.CustomTransformers; sourceFiles?: ts.SourceFile[]; @@ -54,13 +54,13 @@ export interface GetTranspilationResultOptions { transformer?: LuaTransformer; } -export function getTranspilationResult({ +export function transpile({ program, customTransformers = {}, sourceFiles: targetSourceFiles, printer, transformer, -}: GetTranspilationResultOptions): TranspilationResult { +}: TranspileOptions): TranspileResult { const options = program.getCompilerOptions(); printer = printer || new LuaPrinter(options); transformer = transformer || new LuaTransformer(program, options); diff --git a/src/index.ts b/src/index.ts index 1325ca385..4d157d1a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as ts from "typescript"; import { parseConfigFileWithSystem } from "./CommandLineParser"; import { CompilerOptions } from "./CompilerOptions"; -import { getTranspilationResult, TranspilationResult, TranspiledFile } from "./Transpile"; +import { transpile, TranspileResult, TranspiledFile } from "./Transpile"; export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./CommandLineParser"; export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; @@ -17,11 +17,9 @@ export * from "./Transpile"; export function transpileFiles( rootNames: string[], options: CompilerOptions = {} -): TranspilationResult { +): TranspileResult { const program = ts.createProgram(rootNames, options); - const { transpiledFiles, diagnostics: transpileDiagnostics } = getTranspilationResult({ - program, - }); + const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), @@ -31,7 +29,7 @@ export function transpileFiles( return { transpiledFiles, diagnostics: [...diagnostics] }; } -export function transpileProject(fileName: string, options?: CompilerOptions): TranspilationResult { +export function transpileProject(fileName: string, options?: CompilerOptions): TranspileResult { const parseResult = parseConfigFileWithSystem(fileName, options); if (parseResult.errors.length > 0) { return { diagnostics: parseResult.errors, transpiledFiles: new Map() }; @@ -91,9 +89,9 @@ export function createVirtualProgram( export function transpileVirtualProject( files: Record, options: CompilerOptions = {} -): TranspilationResult { +): TranspileResult { const program = createVirtualProgram(files, options); - const result = getTranspilationResult({ program }); + const result = transpile({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...result.diagnostics, diff --git a/src/tstl.ts b/src/tstl.ts index 4d64bbe67..9bf8c0b72 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -147,9 +147,7 @@ function performCompilation( configFileParsingDiagnostics, }); - const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ - program, - }); + const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.transpile({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), @@ -253,7 +251,7 @@ function updateWatchCompilationHost( } } - const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.getTranspilationResult({ + const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.transpile({ program, sourceFiles, }); diff --git a/test/transpile/run.ts b/test/transpile/run.ts index 675adad07..dac84badb 100644 --- a/test/transpile/run.ts +++ b/test/transpile/run.ts @@ -16,9 +16,7 @@ export function buildVirtualProject( options.types = []; const program = ts.createProgram({ rootNames, options }); - const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.getTranspilationResult({ - program, - }); + const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.transpile({ program }); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), From 2fc099e7b58751594e7cad77eb480bed2bdec8a5 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 01:07:22 +0500 Subject: [PATCH 25/44] Extract luaLibImport to a variable --- src/LuaPrinter.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 7af2e2078..ba9678a9e 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -10,7 +10,6 @@ import { TSHelper as tsHelper } from "./TSHelper"; type SourceChunk = string | SourceNode; export class LuaPrinter { - /* tslint:disable:object-literal-sort-keys */ private static operatorMap: {[key in tstl.Operator]: string} = { [tstl.SyntaxKind.AdditionOperator]: "+", [tstl.SyntaxKind.SubractionOperator]: "-", @@ -38,7 +37,6 @@ export class LuaPrinter { [tstl.SyntaxKind.BitwiseLeftShiftOperator]: "<<", [tstl.SyntaxKind.BitwiseNotOperator]: "~", }; - /* tslint:enable:object-literal-sort-keys */ private options: CompilerOptions; private currentIndent: string; @@ -122,18 +120,15 @@ export class LuaPrinter { } if (luaLibFeatures) { + const luaLibImport = this.options.luaLibImport || LuaLibImportKind.Inline; // Require lualib bundle - if ((this.options.luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) - || this.options.luaLibImport === LuaLibImportKind.Always) + if ((luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) + || luaLibImport === LuaLibImportKind.Always) { header += `require("lualib_bundle");\n`; } // Inline lualib features - else if ( - (this.options.luaLibImport === undefined || - this.options.luaLibImport === LuaLibImportKind.Inline) && - luaLibFeatures.size > 0 - ) { + else if (luaLibImport === LuaLibImportKind.Inline && luaLibFeatures.size > 0) { header += "-- Lua Library inline imports\n"; header += LuaLib.loadFeatures(luaLibFeatures); } From e072bb04cf49a42f75d6b974cb3d067164757741 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 03:12:00 +0500 Subject: [PATCH 26/44] Add command line parsing integration tests --- test/unit/commandLineParser.spec.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 54164a11d..3f29dc6ff 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -96,6 +96,32 @@ describe("command line", () => { expect(result.options.noHeader).toBe(true); }); }); + + describe("integration", () => { + test.each<[string, string, tstl.CompilerOptions]>([ + ["noHeader", "false", { noHeader: false }], + ["noHeader", "true", { noHeader: true }], + ["noHoisting", "false", { noHoisting: false }], + ["noHoisting", "true", { noHoisting: true }], + ["sourceMapTraceback", "false", { sourceMapTraceback: false }], + ["sourceMapTraceback", "true", { sourceMapTraceback: true }], + + ["luaLibImport", "none", { luaLibImport: tstl.LuaLibImportKind.None }], + ["luaLibImport", "always", { luaLibImport: tstl.LuaLibImportKind.Always }], + ["luaLibImport", "inline", { luaLibImport: tstl.LuaLibImportKind.Inline }], + ["luaLibImport", "require", { luaLibImport: tstl.LuaLibImportKind.Require }], + + ["luaTarget", "5.1", { luaTarget: tstl.LuaTarget.Lua51 }], + ["luaTarget", "5.2", { luaTarget: tstl.LuaTarget.Lua52 }], + ["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }], + ["luaTarget", "jit", { luaTarget: tstl.LuaTarget.LuaJIT }], + ])("--%s %s", (optionName, value, expected) => { + const result = tstl.parseCommandLine([`--${optionName}`, value]); + + expect(result.errors).not.toHaveDiagnostics(); + expect(result.options).toEqual(expected); + }); + }); }); describe("tsconfig", () => { From 4045b47d7bf00dd3d952d46b6dc50b7e7c2898de Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 04:54:23 +0500 Subject: [PATCH 27/44] Make diagnostics use node's source file instead of transformed one --- src/Transpile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 81b4d9e1d..46842fcce 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -117,7 +117,7 @@ export function transpile({ diagnostics.push({ category: ts.DiagnosticCategory.Error, code: 0, - file: sourceFile, + file: err.node.getSourceFile(), start: err.node.getStart(), length: err.node.getWidth(), messageText: err.message, From 1b62fd912513f02f13679f5721b6ad7f70a89998 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 04:58:34 +0500 Subject: [PATCH 28/44] Cast getCompilerOptions calls to custom compiler options --- src/Transpile.ts | 2 +- src/tstl.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 46842fcce..1d65a13ef 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -61,7 +61,7 @@ export function transpile({ printer, transformer, }: TranspileOptions): TranspileResult { - const options = program.getCompilerOptions(); + const options = program.getCompilerOptions() as CompilerOptions; printer = printer || new LuaPrinter(options); transformer = transformer || new LuaTransformer(program, options); diff --git a/src/tstl.ts b/src/tstl.ts index 9bf8c0b72..67e3c0571 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -211,7 +211,7 @@ function updateWatchCompilationHost( host.afterProgramCreate = builderProgram => { const program = builderProgram.getProgram(); - const options = builderProgram.getCompilerOptions(); + const options = builderProgram.getCompilerOptions() as tstl.CompilerOptions; let configFileParsingDiagnostics: ts.Diagnostic[] = []; const configFile = options.configFile as ts.TsConfigSourceFile | undefined; From 946f16a721e4990f5b1da7b6c97487e8a4e47499 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 06:51:03 +0500 Subject: [PATCH 29/44] Export TranspileError and all LuaTransformer exports from package index --- src/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4d157d1a7..35cf3ed9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,16 +3,17 @@ import * as path from "path"; import * as ts from "typescript"; import { parseConfigFileWithSystem } from "./CommandLineParser"; import { CompilerOptions } from "./CompilerOptions"; -import { transpile, TranspileResult, TranspiledFile } from "./Transpile"; +import { transpile, TranspiledFile, TranspileResult } from "./Transpile"; export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./CommandLineParser"; -export { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; +export * from "./CompilerOptions"; export * from "./Emit"; export * from "./LuaAST"; export { LuaLibFeature } from "./LuaLib"; -export { LuaPrinter } from "./LuaPrinter"; -export { LuaTransformer } from "./LuaTransformer"; +export * from "./LuaPrinter"; +export * from "./LuaTransformer"; export * from "./Transpile"; +export * from "./TranspileError"; export function transpileFiles( rootNames: string[], From 4c7645778718f1fcd1084f3fae6f7dd7d553dd83 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 07:05:23 +0500 Subject: [PATCH 30/44] Remove options argument from LuaTransformer --- src/LuaTransformer.ts | 10 ++++------ src/Transpile.ts | 9 ++++----- test/util.ts | 7 ++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 4b236a734..ae3ad412b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -43,12 +43,11 @@ export class LuaTransformer { "not", "or", "repeat", "return", "self", "then", "until", "while", ]); - private isStrict = true; + private isStrict: boolean; private luaTarget: LuaTarget; private checker: ts.TypeChecker; protected options: CompilerOptions; - protected program: ts.Program; private isModule = false; @@ -69,17 +68,16 @@ export class LuaTransformer { private readonly typeValidationCache: Map> = new Map>(); - public constructor(program: ts.Program, options: CompilerOptions) { + public constructor(protected program: ts.Program) { this.checker = program.getTypeChecker(); - this.options = options; - this.program = program; + this.options = program.getCompilerOptions(); this.isStrict = this.options.alwaysStrict !== undefined || (this.options.strict !== undefined && this.options.alwaysStrict !== false) || (this.isModule && this.options.target !== undefined && this.options.target >= ts.ScriptTarget.ES2015); - this.luaTarget = options.luaTarget || LuaTarget.LuaJIT; + this.luaTarget = this.options.luaTarget || LuaTarget.LuaJIT; this.setupState(); } diff --git a/src/Transpile.ts b/src/Transpile.ts index 1d65a13ef..b9146bb59 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -48,22 +48,21 @@ export interface TranspileResult { export interface TranspileOptions { program: ts.Program; - customTransformers?: ts.CustomTransformers; sourceFiles?: ts.SourceFile[]; - printer?: LuaPrinter; + customTransformers?: ts.CustomTransformers; transformer?: LuaTransformer; + printer?: LuaPrinter; } export function transpile({ program, - customTransformers = {}, sourceFiles: targetSourceFiles, + customTransformers = {}, + transformer = new LuaTransformer(program), printer, - transformer, }: TranspileOptions): TranspileResult { const options = program.getCompilerOptions() as CompilerOptions; printer = printer || new LuaPrinter(options); - transformer = transformer || new LuaTransformer(program, options); const diagnostics: ts.Diagnostic[] = []; const transpiledFiles = new Map(); diff --git a/test/util.ts b/test/util.ts index e90a6c423..9c06cbfef 100644 --- a/test/util.ts +++ b/test/util.ts @@ -93,11 +93,8 @@ export function executeLua(luaStr: string, withLib = true): any { } // Get a mock transformer to use for testing -export function makeTestTransformer( - target: tstl.LuaTarget = tstl.LuaTarget.Lua53, -): tstl.LuaTransformer { - const options = { luaTarget: target }; - return new tstl.LuaTransformer(ts.createProgram([], options), options); +export function makeTestTransformer(luaTarget = tstl.LuaTarget.Lua53): tstl.LuaTransformer { + return new tstl.LuaTransformer(ts.createProgram([], { luaTarget })); } export function transpileAndExecute( From 6a595b15ef34236fce8a8bab6110a82b794be5c3 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 15:58:44 +0500 Subject: [PATCH 31/44] Rename sourceFile variable --- src/Transpile.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index b9146bb59..f35a7475f 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -5,7 +5,7 @@ import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; function getCustomTransformers( - options: CompilerOptions, + program: ts.Program, customTransformers: ts.CustomTransformers, onSourceFile: (sourceFile: ts.SourceFile) => void ): ts.CustomTransformers { @@ -129,7 +129,7 @@ export function transpile({ } }; - const transformers = getCustomTransformers(options, customTransformers, processSourceFile); + const transformers = getCustomTransformers(program, customTransformers, processSourceFile); const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { for (const sourceFile of sourceFiles) { @@ -143,23 +143,22 @@ export function transpile({ } }; - const isEmittableJsonFile = (sourceFile: ts.SourceFile) => - sourceFile.flags & ts.NodeFlags.JsonFile && + const isEmittableJsonFile = (file: ts.SourceFile) => + file.flags & ts.NodeFlags.JsonFile && !options.emitDeclarationOnly && - !program.isSourceFileFromExternalLibrary(sourceFile); + !program.isSourceFileFromExternalLibrary(file); // We always have to emit to get transformer diagnostics const oldNoEmit = options.noEmit; options.noEmit = false; if (targetSourceFiles) { - for (const sourceFile of targetSourceFiles) { - if (isEmittableJsonFile(sourceFile)) { - processSourceFile(sourceFile); + for (const file of targetSourceFiles) { + if (isEmittableJsonFile(file)) { + processSourceFile(file); } else { diagnostics.push( - ...program.emit(sourceFile, writeFile, undefined, false, transformers) - .diagnostics + ...program.emit(file, writeFile, undefined, false, transformers).diagnostics ); } } From e821d7f71278424d4f6645fd07d852964f82ebdf Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 23:21:03 +0500 Subject: [PATCH 32/44] Set printer default during destructuring --- src/Transpile.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index f35a7475f..0fca6706a 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -59,10 +59,9 @@ export function transpile({ sourceFiles: targetSourceFiles, customTransformers = {}, transformer = new LuaTransformer(program), - printer, + printer = new LuaPrinter(program.getCompilerOptions()), }: TranspileOptions): TranspileResult { const options = program.getCompilerOptions() as CompilerOptions; - printer = printer || new LuaPrinter(options); const diagnostics: ts.Diagnostic[] = []; const transpiledFiles = new Map(); @@ -101,9 +100,9 @@ export function transpile({ const processSourceFile = (sourceFile: ts.SourceFile) => { try { - const [luaAST, lualibFeatureSet] = transformer!.transformSourceFile(sourceFile); + const [luaAST, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); if (!options.noEmit && !options.emitDeclarationOnly) { - const [lua, sourceMap] = printer!.print( + const [lua, sourceMap] = printer.print( luaAST, lualibFeatureSet, sourceFile.fileName From 4982c446e97164f0e9b84661e89902be043b10fb Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 22 Apr 2019 23:21:51 +0500 Subject: [PATCH 33/44] Rename --- src/Transpile.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 0fca6706a..8cd9c2c19 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -142,10 +142,10 @@ export function transpile({ } }; - const isEmittableJsonFile = (file: ts.SourceFile) => - file.flags & ts.NodeFlags.JsonFile && + const isEmittableJsonFile = (sourceFile: ts.SourceFile) => + sourceFile.flags & ts.NodeFlags.JsonFile && !options.emitDeclarationOnly && - !program.isSourceFileFromExternalLibrary(file); + !program.isSourceFileFromExternalLibrary(sourceFile); // We always have to emit to get transformer diagnostics const oldNoEmit = options.noEmit; From 280ed35e04ee425e523e1db493e81d0a3968941d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 28 Apr 2019 01:50:57 +0500 Subject: [PATCH 34/44] Use emitTranspiledFiles in transpileFiles and transpileProject --- build_lualib.ts | 3 +-- src/index.ts | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/build_lualib.ts b/build_lualib.ts index 89903dc7f..cca39f055 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -16,8 +16,7 @@ const options: tstl.CompilerOptions = { }; // TODO: Check diagnostics -const { transpiledFiles } = tstl.transpileFiles(glob.sync("./src/lualib/**/*.ts"), options); -const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); +const { emitResult } = tstl.transpileFiles(glob.sync("./src/lualib/**/*.ts"), options); emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const bundlePath = path.join(__dirname, "./dist/lualib/lualib_bundle.lua"); diff --git a/src/index.ts b/src/index.ts index 35cf3ed9b..283fbee02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import * as path from "path"; import * as ts from "typescript"; import { parseConfigFileWithSystem } from "./CommandLineParser"; import { CompilerOptions } from "./CompilerOptions"; +import { emitTranspiledFiles, OutputFile } from "./Emit"; import { transpile, TranspiledFile, TranspileResult } from "./Transpile"; export { parseCommandLine, ParsedCommandLine, updateParsedConfigFile } from "./CommandLineParser"; @@ -15,25 +16,34 @@ export * from "./LuaTransformer"; export * from "./Transpile"; export * from "./TranspileError"; +export interface TranspileFilesResult { + diagnostics: ts.Diagnostic[]; + emitResult: OutputFile[]; +} + export function transpileFiles( rootNames: string[], options: CompilerOptions = {} -): TranspileResult { +): TranspileFilesResult { const program = ts.createProgram(rootNames, options); const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program }); + const emitResult = emitTranspiledFiles(program.getCompilerOptions(), transpiledFiles); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), ...transpileDiagnostics, ]); - return { transpiledFiles, diagnostics: [...diagnostics] }; + return { diagnostics: [...diagnostics], emitResult }; } -export function transpileProject(fileName: string, options?: CompilerOptions): TranspileResult { - const parseResult = parseConfigFileWithSystem(fileName, options); +export function transpileProject( + fileName: string, + optionsToExtend?: CompilerOptions +): TranspileFilesResult { + const parseResult = parseConfigFileWithSystem(fileName, optionsToExtend); if (parseResult.errors.length > 0) { - return { diagnostics: parseResult.errors, transpiledFiles: new Map() }; + return { diagnostics: parseResult.errors, emitResult: [] }; } return transpileFiles(parseResult.fileNames, parseResult.options); From 55acfd7875edc3048e56963beed2db03edd6c819 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 28 Apr 2019 01:56:49 +0500 Subject: [PATCH 35/44] Use transpileFiles in transpile tests runner --- test/transpile/run.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/transpile/run.ts b/test/transpile/run.ts index dac84badb..57bbc8f6b 100644 --- a/test/transpile/run.ts +++ b/test/transpile/run.ts @@ -15,18 +15,10 @@ export function buildVirtualProject( options.skipLibCheck = true; options.types = []; - const program = ts.createProgram({ rootNames, options }); - const { transpiledFiles, diagnostics: transpileDiagnostics } = tstl.transpile({ program }); - - const diagnostics = ts.sortAndDeduplicateDiagnostics([ - ...ts.getPreEmitDiagnostics(program), - ...transpileDiagnostics, - ]); - - const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + const { diagnostics, emitResult } = tstl.transpileFiles(rootNames, options); const emittedFiles = emitResult .map(result => path.relative(__dirname, result.name).replace(/\\/g, "/")) .sort(); - return { diagnostics: [...diagnostics], emitResult, emittedFiles }; + return { diagnostics, emitResult, emittedFiles }; } From 7f8a198fa8a2af2c7a3171d9ed883a9c721e5de2 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 29 Apr 2019 02:14:00 +0500 Subject: [PATCH 36/44] Fix typos --- src/CommandLineParser.ts | 2 +- src/LuaAST.ts | 42 ++++++++++++++++++++-------------------- src/LuaPrinter.ts | 10 +++++----- src/LuaTransformer.ts | 36 +++++++++++++++++----------------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 7f830f380..22586ebec 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -89,7 +89,7 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): const option = optionDeclarations.find(option => option.name === key); if (!option) continue; - // console.warn(`[Deprectated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` + // console.warn(`[Deprecated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` // + `look like { "compilerOptions": { }, "tstl": { } }`); const { error, value } = readValue(option, parsedConfigFile.raw[key]); diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 6925fc885..d9983979e 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -1,8 +1,8 @@ // Simplified Lua AST based roughly on http://lua-users.org/wiki/MetaLuaAbstractSyntaxTree, // https://www.lua.org/manual/5.3/manual.html (9 – The Complete Syntax of Lua) and the TS AST implementation -// We can ellide a lot of nodes especially tokens and keyowords -// becasue we dont create the AST from text +// We can elide a lot of nodes especially tokens and keywords +// because we dont create the AST from text import * as ts from "typescript"; @@ -41,14 +41,14 @@ export enum SyntaxKind { TableIndexExpression, // Operators // Arithmetic - AdditionOperator, // Maybe use abreviations for those add, sub, mul ... - SubractionOperator, + AdditionOperator, // Maybe use abbreviations for those add, sub, mul ... + SubtractionOperator, MultiplicationOperator, DivisionOperator, FloorDivisionOperator, ModuloOperator, PowerOperator, - NegationOperator, // Unaray minus + NegationOperator, // Unary minus // Concat ConcatOperator, // Length @@ -87,7 +87,7 @@ export type BinaryBitwiseOperator = SyntaxKind.BitwiseAndOperator | SyntaxKind.B | SyntaxKind.BitwiseLeftShiftOperator; export type BinaryOperator = - SyntaxKind.AdditionOperator | SyntaxKind.SubractionOperator | SyntaxKind.MultiplicationOperator + SyntaxKind.AdditionOperator | SyntaxKind.SubtractionOperator | SyntaxKind.MultiplicationOperator | SyntaxKind.DivisionOperator | SyntaxKind.FloorDivisionOperator | SyntaxKind.ModuloOperator | SyntaxKind.PowerOperator | SyntaxKind.ConcatOperator | SyntaxKind.EqualityOperator | SyntaxKind.InequalityOperator | SyntaxKind.LessThanOperator | SyntaxKind.LessEqualOperator @@ -288,7 +288,7 @@ export function createAssignmentStatement( export interface IfStatement extends Statement { kind: SyntaxKind.IfStatement; - condtion: Expression; + condition: Expression; ifBlock: Block; elseBlock?: Block | IfStatement; } @@ -298,7 +298,7 @@ export function isIfStatement(node: Node): node is IfStatement { } export function createIfStatement( - condtion: Expression, + condition: Expression, ifBlock: Block, elseBlock?: Block | IfStatement, tsOriginal?: ts.Node, @@ -306,8 +306,8 @@ export function createIfStatement( ): IfStatement { const statement = createNode(SyntaxKind.IfStatement, tsOriginal, parent) as IfStatement; - setParent(condtion, statement); - statement.condtion = condtion; + setParent(condition, statement); + statement.condition = condition; setParent(ifBlock, statement); statement.ifBlock = ifBlock; setParent(ifBlock, statement); @@ -326,7 +326,7 @@ export function isIterationStatement(node: Node): node is IterationStatement { export interface WhileStatement extends IterationStatement { kind: SyntaxKind.WhileStatement; - condtion: Expression; + condition: Expression; } export function isWhileStatement(node: Node): node is WhileStatement { @@ -335,7 +335,7 @@ export function isWhileStatement(node: Node): node is WhileStatement { export function createWhileStatement( body: Block, - condtion: Expression, + condition: Expression, tsOriginal?: ts.Node, parent?: Node ): WhileStatement @@ -343,14 +343,14 @@ export function createWhileStatement( const statement = createNode(SyntaxKind.WhileStatement, tsOriginal, parent) as WhileStatement; setParent(body, statement); statement.body = body; - setParent(condtion, statement); - statement.condtion = condtion; + setParent(condition, statement); + statement.condition = condition; return statement; } export interface RepeatStatement extends IterationStatement { kind: SyntaxKind.RepeatStatement; - condtion: Expression; + condition: Expression; } export function isRepeatStatement(node: Node): node is RepeatStatement { @@ -359,7 +359,7 @@ export function isRepeatStatement(node: Node): node is RepeatStatement { export function createRepeatStatement( body: Block, - condtion: Expression, + condition: Expression, tsOriginal?: ts.Node, parent?: Node ): RepeatStatement @@ -367,8 +367,8 @@ export function createRepeatStatement( const statement = createNode(SyntaxKind.RepeatStatement, tsOriginal, parent) as RepeatStatement; setParent(body, statement); statement.body = body; - setParent(condtion, statement); - statement.condtion = condtion; + setParent(condition, statement); + statement.condition = condition; return statement; } @@ -745,7 +745,7 @@ export function createBinaryExpression( export interface ParenthesizedExpression extends Expression { kind: SyntaxKind.ParenthesizedExpression; - innerEpxression: Expression; + innerExpression: Expression; } export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { @@ -760,7 +760,7 @@ export function createParenthesizedExpression( { const expression = createNode(SyntaxKind.ParenthesizedExpression, tsOriginal, parent) as ParenthesizedExpression; setParent(innerExpression, expression); - expression.innerEpxression = innerExpression; + expression.innerExpression = innerExpression; return expression; } @@ -845,7 +845,7 @@ export function cloneIdentifier(identifier: Identifier, tsOriginal?: ts.Node): I return createIdentifier(identifier.text, tsOriginal, identifier.symbolId); } -export function createAnnonymousIdentifier(tsOriginal?: ts.Node, parent?: Node): Identifier { +export function createAnonymousIdentifier(tsOriginal?: ts.Node, parent?: Node): Identifier { const expression = createNode(SyntaxKind.Identifier, tsOriginal, parent) as Identifier; expression.text = "____"; return expression; diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index ba9678a9e..40b05d8a2 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -12,7 +12,7 @@ type SourceChunk = string | SourceNode; export class LuaPrinter { private static operatorMap: {[key in tstl.Operator]: string} = { [tstl.SyntaxKind.AdditionOperator]: "+", - [tstl.SyntaxKind.SubractionOperator]: "-", + [tstl.SyntaxKind.SubtractionOperator]: "-", [tstl.SyntaxKind.MultiplicationOperator]: "*", [tstl.SyntaxKind.DivisionOperator]: "/", [tstl.SyntaxKind.FloorDivisionOperator]: "//", @@ -310,7 +310,7 @@ export class LuaPrinter { const prefix = isElseIf ? "elseif" : "if"; - chunks.push(this.indent(prefix + " "), this.printExpression(statement.condtion), " then\n"); + chunks.push(this.indent(prefix + " "), this.printExpression(statement.condition), " then\n"); this.pushIndent(); chunks.push(this.printBlock(statement.ifBlock)); @@ -336,7 +336,7 @@ export class LuaPrinter { private printWhileStatement(statement: tstl.WhileStatement): SourceNode { const chunks: SourceChunk[] = []; - chunks.push(this.indent("while "), this.printExpression(statement.condtion), " do\n"); + chunks.push(this.indent("while "), this.printExpression(statement.condition), " do\n"); this.pushIndent(); chunks.push(this.printBlock(statement.body)); @@ -356,7 +356,7 @@ export class LuaPrinter { chunks.push(this.printBlock(statement.body)); this.popIndent(); - chunks.push(this.indent("until "), this.printExpression(statement.condtion)); + chunks.push(this.indent("until "), this.printExpression(statement.condition)); return this.concatNodes(...chunks); } @@ -611,7 +611,7 @@ export class LuaPrinter { } private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode { - return this.createSourceNode(expression, ["(", this.printExpression(expression.innerEpxression), ")"]); + return this.createSourceNode(expression, ["(", this.printExpression(expression.innerExpression), ")"]); } private printCallExpression(expression: tstl.CallExpression): SourceNode { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ae3ad412b..c847186d3 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -943,7 +943,7 @@ export class LuaTransformer { } private transformClassInstanceFields( - classDeclarataion: ts.ClassLikeDeclaration, + classDeclaration: ts.ClassLikeDeclaration, instanceFields: ts.PropertyDeclaration[] ): tstl.Statement[] { @@ -964,8 +964,8 @@ export class LuaTransformer { statements.push(assignClassField); } - const getOverrides = classDeclarataion.members.filter(m => - tsHelper.isGetAccessorOverride(m, classDeclarataion, this.checker) + const getOverrides = classDeclaration.members.filter(m => + tsHelper.isGetAccessorOverride(m, classDeclaration, this.checker) ) as ts.GetAccessorDeclaration[]; for (const getter of getOverrides) { @@ -1018,7 +1018,7 @@ export class LuaTransformer { const declarationName = this.transformIdentifier(declaration.name as ts.Identifier); if (declaration.initializer) { // self.declarationName = declarationName or initializer - const assignement = tstl.createAssignmentStatement( + const assignment = tstl.createAssignmentStatement( tstl.createTableIndexExpression( this.createSelfIdentifier(), tstl.createStringLiteral(declarationName.text) ), @@ -1028,17 +1028,17 @@ export class LuaTransformer { tstl.SyntaxKind.OrOperator ) ); - bodyWithFieldInitializers.push(assignement); + bodyWithFieldInitializers.push(assignment); } else { // self.declarationName = declarationName - const assignement = tstl.createAssignmentStatement( + const assignment = tstl.createAssignmentStatement( tstl.createTableIndexExpression( this.createSelfIdentifier(), tstl.createStringLiteral(declarationName.text) ), declarationName ); - bodyWithFieldInitializers.push(assignement); + bodyWithFieldInitializers.push(assignment); } } @@ -1647,7 +1647,7 @@ export class LuaTransformer { //function(____, ...) const nextFunctionDeclaration = tstl.createFunctionExpression( tstl.createBlock(nextBody), - [tstl.createAnnonymousIdentifier()], + [tstl.createAnonymousIdentifier()], tstl.createDotsLiteral()); //____it = {next = function(____, ...)} @@ -1781,7 +1781,7 @@ export class LuaTransformer { table = this.transformIdentifier(statement.initializer); } else { // Contain the expression in a temporary variable - table = tstl.createAnnonymousIdentifier(); + table = tstl.createAnonymousIdentifier(); if (statement.initializer) { statements.push(tstl.createVariableDeclarationStatement( table, this.transformExpression(statement.initializer))); @@ -1800,7 +1800,7 @@ export class LuaTransformer { ? this.filterUndefinedAndCast( statement.name.elements.map(e => this.transformArrayBindingElement(e)), tstl.isIdentifier) - : tstl.createAnnonymousIdentifier(statement.name); + : tstl.createAnonymousIdentifier(statement.name); // Don't unpack TupleReturn decorated functions if (statement.initializer) { @@ -1902,7 +1902,7 @@ export class LuaTransformer { if (!ts.isCallLikeExpression(expression)) { // Assign expression statements to dummy to make sure they're legal lua return tstl.createVariableDeclarationStatement( - tstl.createAnnonymousIdentifier(), + tstl.createAnonymousIdentifier(), this.transformExpression(expression) ); } @@ -2632,7 +2632,7 @@ export class LuaTransformer { // Destructuring assignment const left = expression.left.elements.length > 0 ? expression.left.elements.map(e => this.transformExpression(e)) - : [tstl.createAnnonymousIdentifier(expression.left)]; + : [tstl.createAnonymousIdentifier(expression.left)]; let right: tstl.Expression[]; if (ts.isArrayLiteralExpression(expression.right)) { if (expression.right.elements.length > 0) { @@ -2680,7 +2680,7 @@ export class LuaTransformer { // (function() local ${tmps} = ${right}; ${left} = ${tmps}; return {${tmps}} end)() const left = expression.left.elements.length > 0 ? expression.left.elements.map(e => this.transformExpression(e)) - : [tstl.createAnnonymousIdentifier(expression.left)]; + : [tstl.createAnonymousIdentifier(expression.left)]; let right: tstl.Expression[]; if (ts.isArrayLiteralExpression(expression.right)) { right = expression.right.elements.length > 0 @@ -2876,7 +2876,7 @@ export class LuaTransformer { case ts.SyntaxKind.BarBarToken: return tstl.SyntaxKind.OrOperator; case ts.SyntaxKind.MinusToken: - return tstl.SyntaxKind.SubractionOperator; + return tstl.SyntaxKind.SubtractionOperator; case ts.SyntaxKind.PlusToken: if (ts.isBinaryExpression(node)) { // Check is we need to use string concat operator @@ -2917,7 +2917,7 @@ export class LuaTransformer { public transformClassExpression(expression: ts.ClassExpression): ExpressionVisitResult { const className = expression.name !== undefined ? this.transformIdentifier(expression.name) - : tstl.createAnnonymousIdentifier(); + : tstl.createAnonymousIdentifier(); const classDeclaration = this.transformClassDeclaration(expression, className); return this.createImmediatelyInvokedFunctionExpression( @@ -3240,7 +3240,7 @@ export class LuaTransformer { if (ts.isArrowFunction(node)) { // dummy context for arrow functions with parameters if (node.parameters.length > 0) { - context = tstl.createAnnonymousIdentifier(); + context = tstl.createAnonymousIdentifier(); } } else { // self context @@ -3869,7 +3869,7 @@ export class LuaTransformer { ) ), tstl.createNumericLiteral(1), - tstl.SyntaxKind.SubractionOperator, + tstl.SyntaxKind.SubtractionOperator, node ) ); @@ -4351,7 +4351,7 @@ export class LuaTransformer { if (expression.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword) { return tstl.createIdentifier("nil"); // TODO this is a hack that allows use to keep Identifier // as return time as changing that would break a lot of stuff. - // But this should be changed to retun tstl.createNilLiteral() + // But this should be changed to return tstl.createNilLiteral() // at some point. } From 96e0895b5238ebda13c59e91e2e6e1110ccb6215 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 30 Apr 2019 02:30:10 +0500 Subject: [PATCH 37/44] Deprecate root-level options --- src/CommandLineParser.ts | 17 ++++++++++------- src/diagnostics.ts | 15 +++++++++++++++ src/tstl.ts | 4 +++- test/unit/commandLineParser.spec.ts | 5 ++++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 22586ebec..76d5c12ee 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -85,20 +85,23 @@ export function getHelpString(): string { } export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): ParsedCommandLine { + let hasRootLevelOptions = false; for (const key in parsedConfigFile.raw) { const option = optionDeclarations.find(option => option.name === key); if (!option) continue; - // console.warn(`[Deprecated] TSTL options are moving to the luaConfig object. Adjust your tsconfig to ` - // + `look like { "compilerOptions": { }, "tstl": { } }`); - - const { error, value } = readValue(option, parsedConfigFile.raw[key]); - if (error) parsedConfigFile.errors.push(error); - if (parsedConfigFile.options[key] === undefined) parsedConfigFile.options[key] = value; + if (parsedConfigFile.raw.tstl === undefined) parsedConfigFile.raw.tstl = {}; + parsedConfigFile.raw.tstl[key] = parsedConfigFile.raw[key]; + hasRootLevelOptions = true; } - // Eventually we will only look for the tstl object for tstl options if (parsedConfigFile.raw.tstl) { + if (hasRootLevelOptions) { + parsedConfigFile.errors.push( + diagnostics.tstlOptionsAreMovingToTheTstlObject(parsedConfigFile.raw.tstl) + ); + } + for (const key in parsedConfigFile.raw.tstl) { const option = optionDeclarations.find(option => option.name === key); if (!option) { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 01d589eeb..38a777924 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,5 +1,20 @@ import * as ts from "typescript"; +export const tstlOptionsAreMovingToTheTstlObject = (tstl: Record) => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Warning, + code: 0, + messageText: + 'TSTL options are moving to the "tstl" object. Adjust your tsconfig to look like\n' + + JSON.stringify({ tstl }, undefined, 4) + .split("\n") + .slice(1, -1) + .map(line => line.slice(4)) + .join("\n"), +}); + export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({ file: undefined, start: undefined, diff --git a/src/tstl.ts b/src/tstl.ts index 67e3c0571..2eab5caac 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -80,7 +80,9 @@ function executeCommandLine(args: string[]): void { return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); } - if (commandLine.errors.length > 0) { + // TODO: ParsedCommandLine.errors isn't meant to contain warnings. Once root-level options + // support would be dropped it should be changed to `commandLine.errors.length > 0`. + if (commandLine.errors.some(e => e.category === ts.DiagnosticCategory.Error)) { commandLine.errors.forEach(reportDiagnostic); return ts.sys.exit(ts.ExitStatus.DiagnosticsPresent_OutputsSkipped); } diff --git a/test/unit/commandLineParser.spec.ts b/test/unit/commandLineParser.spec.ts index 3f29dc6ff..8572f7c58 100644 --- a/test/unit/commandLineParser.spec.ts +++ b/test/unit/commandLineParser.spec.ts @@ -132,10 +132,13 @@ describe("tsconfig", () => { return tstl.updateParsedConfigFile(ts.parseJsonConfigFileContent(config, ts.sys, "")); }; - test("should support root-level options", () => { + test("should support deprecated root-level options", () => { const rootLevel = parseConfigFileContent({ noHeader: true }); const namespaced = parseConfigFileContent({ tstl: { noHeader: true } }); + expect(rootLevel.errors).toEqual([ + expect.objectContaining({ category: ts.DiagnosticCategory.Warning }), + ]); expect(rootLevel.options).toEqual(namespaced.options); }); From f8ef95ffbf6b4d5e751575b668719ec2fde3795f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 30 Apr 2019 02:37:07 +0500 Subject: [PATCH 38/44] Move TranspileError diagnostic to diagnostics.ts --- src/Transpile.ts | 10 ++-------- src/diagnostics.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 8cd9c2c19..190d0c5b5 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -1,5 +1,6 @@ import * as ts from "typescript"; import { CompilerOptions } from "./CompilerOptions"; +import { transpileError } from './diagnostics'; import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; @@ -112,14 +113,7 @@ export function transpile({ } catch (err) { if (!(err instanceof TranspileError)) throw err; - diagnostics.push({ - category: ts.DiagnosticCategory.Error, - code: 0, - file: err.node.getSourceFile(), - start: err.node.getStart(), - length: err.node.getWidth(), - messageText: err.message, - }); + diagnostics.push(transpileError(err)); updateTranspiledFile(sourceFile.fileName, { lua: `error(${JSON.stringify(err.message)})\n`, diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 38a777924..cce2df0df 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,4 +1,14 @@ import * as ts from "typescript"; +import { TranspileError } from './TranspileError'; + +export const transpileError = (error: TranspileError) => ({ + file: error.node.getSourceFile(), + start: error.node.getStart(), + length: error.node.getWidth(), + category: ts.DiagnosticCategory.Error, + code: 0, + messageText: error.message, +}); export const tstlOptionsAreMovingToTheTstlObject = (tstl: Record) => ({ file: undefined, From f0e82ce3e1df617f1952c3be76fbc3d2f8a4cf18 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 30 Apr 2019 02:41:08 +0500 Subject: [PATCH 39/44] Add source to custom diagnostics --- src/diagnostics.ts | 4 +++- test/util.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/diagnostics.ts b/src/diagnostics.ts index cce2df0df..82f915aa6 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { TranspileError } from './TranspileError'; +import { TranspileError } from "./TranspileError"; export const transpileError = (error: TranspileError) => ({ file: error.node.getSourceFile(), @@ -7,6 +7,7 @@ export const transpileError = (error: TranspileError) => ({ length: error.node.getWidth(), category: ts.DiagnosticCategory.Error, code: 0, + source: "typescript-to-lua", messageText: error.message, }); @@ -16,6 +17,7 @@ export const tstlOptionsAreMovingToTheTstlObject = (tstl: Record) = length: undefined, category: ts.DiagnosticCategory.Warning, code: 0, + source: "typescript-to-lua", messageText: 'TSTL options are moving to the "tstl" object. Adjust your tsconfig to look like\n' + JSON.stringify({ tstl }, undefined, 4) diff --git a/test/util.ts b/test/util.ts index 9c06cbfef..b43f09dc5 100644 --- a/test/util.ts +++ b/test/util.ts @@ -14,7 +14,7 @@ export function transpileString( const { diagnostics, file } = transpileStringResult(str, options); if (!expectToBeDefined(file) || !expectToBeDefined(file.lua)) return ""; - const errors = diagnostics.filter(diag => !ignoreDiagnostics || diag.code === 0); + const errors = diagnostics.filter(d => !ignoreDiagnostics || d.source === "typescript-to-lua"); expect(errors).not.toHaveDiagnostics(); return file.lua.trim(); From f78e0d04690c5325fcf0e9b5033e797713058d19 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 30 Apr 2019 02:47:27 +0500 Subject: [PATCH 40/44] Show custom diagnostics as `TSTL` in CLI --- src/tstl.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tstl.ts b/src/tstl.ts index 2eab5caac..ea6489c3f 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -6,7 +6,14 @@ import * as CommandLineParser from "./CommandLineParser"; import * as cliDiagnostics from "./diagnostics"; function createDiagnosticReporter(pretty: boolean): ts.DiagnosticReporter { - return (ts as any).createDiagnosticReporter(ts.sys, pretty); + const reporter: ts.DiagnosticReporter = (ts as any).createDiagnosticReporter(ts.sys, pretty); + return diagnostic => { + if (diagnostic.source === "typescript-to-lua") { + diagnostic = { ...diagnostic, code: ("TL" + diagnostic.code) as any }; + } + + reporter(diagnostic); + }; } function createWatchStatusReporter(options?: ts.CompilerOptions): ts.WatchStatusReporter { From dc53f260b98bceda610cc5d40b982206ae50170e Mon Sep 17 00:00:00 2001 From: ark120202 Date: Tue, 30 Apr 2019 10:57:37 +0500 Subject: [PATCH 41/44] Fix quotes --- src/Transpile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index 190d0c5b5..b14fcc780 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; import { CompilerOptions } from "./CompilerOptions"; -import { transpileError } from './diagnostics'; +import { transpileError } from "./diagnostics"; import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; From 8378cdfd3fc995966b1637bb10519c0f8489a043 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 2 May 2019 02:49:28 +0500 Subject: [PATCH 42/44] Always import tstl as a namespace in tests --- test/translation/transformation.spec.ts | 4 +- test/unit/assignmentDestructuring.spec.ts | 14 ++--- test/unit/conditionals.spec.ts | 6 +- test/unit/expressions.spec.ts | 44 ++++++++----- test/unit/loops.spec.ts | 76 ++++++++++++----------- test/unit/modules.spec.ts | 25 ++++---- test/unit/sourcemaps.spec.ts | 11 ++-- test/unit/spreadElement.spec.ts | 22 +++++-- 8 files changed, 116 insertions(+), 86 deletions(-) diff --git a/test/translation/transformation.spec.ts b/test/translation/transformation.spec.ts index 99f18c31c..d7467a78f 100644 --- a/test/translation/transformation.spec.ts +++ b/test/translation/transformation.spec.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -import { LuaLibImportKind } from "../../src"; +import * as tstl from "../../src"; import * as util from "../util"; const fixturesPath = path.join(__dirname, "./transformation"); @@ -11,6 +11,6 @@ const fixtures = fs .map(f => [path.parse(f).name, fs.readFileSync(path.join(fixturesPath, f), "utf8")]); test.each(fixtures)("Transformation (%s)", (_name, content) => { - const result = util.transpileString(content, { luaLibImport: LuaLibImportKind.Require }); + const result = util.transpileString(content, { luaLibImport: tstl.LuaLibImportKind.Require }); expect(result).toMatchSnapshot(); }); diff --git a/test/unit/assignmentDestructuring.spec.ts b/test/unit/assignmentDestructuring.spec.ts index 6473f5f3b..0aeca594d 100644 --- a/test/unit/assignmentDestructuring.spec.ts +++ b/test/unit/assignmentDestructuring.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src"; +import * as tstl from "../../src"; import * as util from "../util"; const assignmentDestruturingTs = ` @@ -7,24 +7,24 @@ const assignmentDestruturingTs = ` test("Assignment destructuring [5.1]", () => { const lua = util.transpileString(assignmentDestruturingTs, { - luaTarget: LuaTarget.Lua51, - luaLibImport: LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.Lua51, + luaLibImport: tstl.LuaLibImportKind.None, }); expect(lua).toBe(`local a, b = unpack(myFunc())`); }); test("Assignment destructuring [5.2]", () => { const lua = util.transpileString(assignmentDestruturingTs, { - luaTarget: LuaTarget.Lua52, - luaLibImport: LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.Lua52, + luaLibImport: tstl.LuaLibImportKind.None, }); expect(lua).toBe(`local a, b = table.unpack(myFunc())`); }); test("Assignment destructuring [JIT]", () => { const lua = util.transpileString(assignmentDestruturingTs, { - luaTarget: LuaTarget.LuaJIT, - luaLibImport: LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.LuaJIT, + luaLibImport: tstl.LuaLibImportKind.None, }); expect(lua).toBe(`local a, b = unpack(myFunc())`); }); diff --git a/test/unit/conditionals.spec.ts b/test/unit/conditionals.spec.ts index d1aac1a4a..d09d54a7f 100644 --- a/test/unit/conditionals.spec.ts +++ b/test/unit/conditionals.spec.ts @@ -1,4 +1,4 @@ -import { LuaTarget } from "../../src"; +import * as tstl from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; @@ -373,8 +373,8 @@ test("switch dead code after return", () => { test("switch not allowed in 5.1", () => { expect(() => - util.transpileString(`switch ("abc") {}`, { luaTarget: LuaTarget.Lua51 }), + util.transpileString(`switch ("abc") {}`, { luaTarget: tstl.LuaTarget.Lua51 }), ).toThrowExactError( - TSTLErrors.UnsupportedForTarget("Switch statements", LuaTarget.Lua51, util.nodeStub), + TSTLErrors.UnsupportedForTarget("Switch statements", tstl.LuaTarget.Lua51, util.nodeStub), ); }); diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index acc29362f..28f758a98 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { LuaLibImportKind, LuaTarget } from "../../src"; +import * as tstl from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; @@ -99,8 +99,8 @@ test.each([ // Bit operations not supported in 5.1, expect an exception expect(() => util.transpileString(input, { - luaTarget: LuaTarget.Lua51, - luaLibImport: LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.Lua51, + luaLibImport: tstl.LuaLibImportKind.None, }), ).toThrow(); }); @@ -120,7 +120,7 @@ test.each([ { input: "a>>>b", lua: "local ____ = bit.rshift(a, b)" }, { input: "a>>>=b", lua: "a = bit.rshift(a, b)" }, ])("Bitop [JIT] (%p)", ({ input, lua }) => { - const options = { luaTarget: LuaTarget.LuaJIT, luaLibImport: LuaLibImportKind.None }; + const options = { luaTarget: tstl.LuaTarget.LuaJIT, luaLibImport: tstl.LuaLibImportKind.None }; expect(util.transpileString(input, options)).toBe(lua); }); @@ -139,7 +139,7 @@ test.each([ { input: "a>>>b", lua: "local ____ = bit32.rshift(a, b)" }, { input: "a>>>=b", lua: "a = bit32.rshift(a, b)" }, ])("Bitop [5.2] (%p)", ({ input, lua }) => { - const options = { luaTarget: LuaTarget.Lua52, luaLibImport: LuaLibImportKind.None }; + const options = { luaTarget: tstl.LuaTarget.Lua52, luaLibImport: tstl.LuaLibImportKind.None }; expect(util.transpileString(input, options)).toBe(lua); }); @@ -156,15 +156,15 @@ test.each([ { input: "a>>>b", lua: "local ____ = a >> b" }, { input: "a>>>=b", lua: "a = a >> b" }, ])("Bitop [5.3] (%p)", ({ input, lua }) => { - const options = { luaTarget: LuaTarget.Lua53, luaLibImport: LuaLibImportKind.None }; + const options = { luaTarget: tstl.LuaTarget.Lua53, luaLibImport: tstl.LuaLibImportKind.None }; expect(util.transpileString(input, options)).toBe(lua); }); test.each(["a>>b", "a>>=b"])("Unsupported bitop 5.3 (%p)", input => { expect(() => util.transpileString(input, { - luaTarget: LuaTarget.Lua53, - luaLibImport: LuaLibImportKind.None, + luaTarget: tstl.LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.None, }), ).toThrowExactError( TSTLErrors.UnsupportedKind( @@ -232,12 +232,20 @@ test.each([ }, { input: "true ? undefined : true", options: { strictNullChecks: true } }, { input: "true ? null : true", options: { strictNullChecks: true } }, - { input: "true ? false : true", expected: false, options: { luaTarget: LuaTarget.Lua51 } }, - { input: "false ? false : true", expected: true, options: { luaTarget: LuaTarget.Lua51 } }, - { input: "true ? undefined : true", options: { luaTarget: LuaTarget.Lua51 } }, - { input: "true ? false : true", expected: false, options: { luaTarget: LuaTarget.LuaJIT } }, - { input: "false ? false : true", expected: true, options: { luaTarget: LuaTarget.LuaJIT } }, - { input: "true ? undefined : true", options: { luaTarget: LuaTarget.LuaJIT } }, + { input: "true ? false : true", expected: false, options: { luaTarget: tstl.LuaTarget.Lua51 } }, + { input: "false ? false : true", expected: true, options: { luaTarget: tstl.LuaTarget.Lua51 } }, + { input: "true ? undefined : true", options: { luaTarget: tstl.LuaTarget.Lua51 } }, + { + input: "true ? false : true", + expected: false, + options: { luaTarget: tstl.LuaTarget.LuaJIT }, + }, + { + input: "false ? false : true", + expected: true, + options: { luaTarget: tstl.LuaTarget.LuaJIT }, + }, + { input: "true ? undefined : true", options: { luaTarget: tstl.LuaTarget.LuaJIT } }, ])("Ternary operator (%p)", ({ input, expected, options }) => { const result = util.transpileAndExecute( `const literalValue = 'literal'; @@ -487,7 +495,7 @@ test("Incompatible fromCodePoint expression error", () => { expect(() => util.transpileString("const abc = String.fromCodePoint(123);")).toThrowExactError( TSTLErrors.UnsupportedForTarget( "string property fromCodePoint", - LuaTarget.Lua53, + tstl.LuaTarget.Lua53, util.nodeStub, ), ); @@ -495,7 +503,11 @@ test("Incompatible fromCodePoint expression error", () => { test("Unknown string expression error", () => { expect(() => util.transpileString("const abc = String.abcd();")).toThrowExactError( - TSTLErrors.UnsupportedForTarget("string property abcd", LuaTarget.Lua53, util.nodeStub), + TSTLErrors.UnsupportedForTarget( + "string property abcd", + tstl.LuaTarget.Lua53, + util.nodeStub, + ), ); }); diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 71b0dc480..ddf9de97d 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -1,5 +1,5 @@ import * as ts from "typescript"; -import { LuaLibImportKind, LuaTarget } from "../../src"; +import * as tstl from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; @@ -418,8 +418,8 @@ test("forof with iterator", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -444,8 +444,8 @@ test("forof with iterator and existing variable", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -469,8 +469,8 @@ test("forof destructuring with iterator", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -496,8 +496,8 @@ test("forof destructuring with iterator and existing variables", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -518,8 +518,8 @@ test("forof lua iterator", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -540,8 +540,8 @@ test("forof array lua iterator", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -563,8 +563,8 @@ test("forof lua iterator with existing variable", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -585,8 +585,8 @@ test("forof lua iterator destructuring", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -609,8 +609,8 @@ test("forof lua iterator destructuring with existing variables", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -634,8 +634,8 @@ test("forof lua iterator tuple-return", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -661,8 +661,8 @@ test("forof lua iterator tuple-return with existing variables", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -678,8 +678,8 @@ test("forof lua iterator tuple-return single variable", () => { for (let x of luaIter()) {} `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; expect(() => util.transpileString(code, compilerOptions)).toThrowExactError( @@ -697,8 +697,8 @@ test("forof lua iterator tuple-return single existing variable", () => { for (x of luaIter()) {} `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; expect(() => util.transpileString(code, compilerOptions)).toThrowExactError( @@ -725,8 +725,8 @@ test("forof forwarded lua iterator", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -754,8 +754,8 @@ test("forof forwarded lua iterator with tupleReturn", () => { return result; `; const compilerOptions = { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.Lua53, target: ts.ScriptTarget.ES2015, }; const result = util.transpileAndExecute(code, compilerOptions); @@ -769,13 +769,17 @@ test.each([ "for (let a in b) { continue; }", "for (let a of b) { continue; }", ])("loop continue in different lua versions (%p)", loop => { - const lua51 = { luaTarget: LuaTarget.Lua51 }; - const lua52 = { luaTarget: LuaTarget.Lua52 }; - const lua53 = { luaTarget: LuaTarget.Lua53 }; - const luajit = { luaTarget: LuaTarget.LuaJIT }; + const lua51 = { luaTarget: tstl.LuaTarget.Lua51 }; + const lua52 = { luaTarget: tstl.LuaTarget.Lua52 }; + const lua53 = { luaTarget: tstl.LuaTarget.Lua53 }; + const luajit = { luaTarget: tstl.LuaTarget.LuaJIT }; expect(() => util.transpileString(loop, lua51)).toThrowExactError( - TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, ts.createContinue()), + TSTLErrors.UnsupportedForTarget( + "Continue statement", + tstl.LuaTarget.Lua51, + ts.createContinue(), + ), ); expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); expect(util.transpileString(loop, lua53).indexOf("::__continue1::") !== -1).toBe(true); diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index bca4abae6..679e6e615 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src"; +import * as tstl from "../../src"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; @@ -10,8 +10,8 @@ test("defaultImport", () => { test("lualibRequire", () => { const lua = util.transpileString(`let a = b instanceof c;`, { - luaLibImport: LuaLibImportKind.Require, - luaTarget: LuaTarget.LuaJIT, + luaLibImport: tstl.LuaLibImportKind.Require, + luaTarget: tstl.LuaTarget.LuaJIT, }); expect(lua.startsWith(`require("lualib_bundle")`)); @@ -19,8 +19,8 @@ test("lualibRequire", () => { test("lualibRequireAlways", () => { const lua = util.transpileString(``, { - luaLibImport: LuaLibImportKind.Always, - luaTarget: LuaTarget.LuaJIT, + luaLibImport: tstl.LuaLibImportKind.Always, + luaTarget: tstl.LuaTarget.LuaJIT, }); expect(lua).toBe(`require("lualib_bundle");`); @@ -37,14 +37,15 @@ test("Non-exported module", () => { expect(result).toBe(3); }); -test.each([LuaLibImportKind.Inline, LuaLibImportKind.None, LuaLibImportKind.Require])( - "LuaLib no uses? No code (%p)", - impKind => { - const lua = util.transpileString(``, { luaLibImport: impKind }); +test.each([ + tstl.LuaLibImportKind.Inline, + tstl.LuaLibImportKind.None, + tstl.LuaLibImportKind.Require, +])("LuaLib no uses? No code (%p)", luaLibImport => { + const lua = util.transpileString(``, { luaLibImport }); - expect(lua).toBe(``); - }, -); + expect(lua).toBe(``); +}); test("Nested module with dot in name", () => { const code = `module a.b { diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index 66bd6d1e1..905e3dc05 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -1,5 +1,5 @@ import { Position, SourceMapConsumer } from "source-map"; -import { LuaLibImportKind, CompilerOptions } from "../../src"; +import * as tstl from "../../src"; import * as util from "../util"; test.each([ @@ -72,7 +72,10 @@ test("sourceMapTraceback saves sourcemap in _G", () => { } return JSONStringify(_G.__TS__sourcemap);`; - const options = { sourceMapTraceback: true, luaLibImport: LuaLibImportKind.Inline }; + const options: tstl.CompilerOptions = { + sourceMapTraceback: true, + luaLibImport: tstl.LuaLibImportKind.Inline, + }; // Act const transpiledLua = util.transpileString(typeScriptSource, options); @@ -120,9 +123,7 @@ test("Inline sourcemaps", () => { } return abc();`; - const compilerOptions: CompilerOptions = { - inlineSourceMap: true, - }; + const compilerOptions: tstl.CompilerOptions = { inlineSourceMap: true }; const { file } = util.transpileStringResult(typeScriptSource, compilerOptions); if (!util.expectToBeDefined(file.lua)) return; diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 93fd0a369..dfcb345d1 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -1,4 +1,4 @@ -import { LuaLibImportKind, LuaTarget } from "../../src"; +import * as tstl from "../../src"; import * as util from "../util"; test.each([{ inp: [] }, { inp: [1, 2, 3] }, { inp: [1, "test", 3] }])( @@ -13,25 +13,37 @@ test.each([{ inp: [] }, { inp: [1, 2, 3] }, { inp: [1, "test", 3] }])( test("Spread Element Lua 5.1", () => { // Cant test functional because our VM doesn't run on 5.1 - const options = { luaTarget: LuaTarget.Lua51, luaLibImport: LuaLibImportKind.None }; + const options: tstl.CompilerOptions = { + luaTarget: tstl.LuaTarget.Lua51, + luaLibImport: tstl.LuaLibImportKind.None, + }; const lua = util.transpileString(`[].push(...${JSON.stringify([1, 2, 3])});`, options); expect(lua).toBe("__TS__ArrayPush({}, unpack({\n 1,\n 2,\n 3,\n}))"); }); test("Spread Element Lua 5.2", () => { - const options = { luaTarget: LuaTarget.Lua52, luaLibImport: LuaLibImportKind.None }; + const options: tstl.CompilerOptions = { + luaTarget: tstl.LuaTarget.Lua52, + luaLibImport: tstl.LuaLibImportKind.None, + }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); expect(lua).toBe("local ____ = {table.unpack({\n 0,\n 1,\n 2,\n})}"); }); test("Spread Element Lua 5.3", () => { - const options = { luaTarget: LuaTarget.Lua53, luaLibImport: LuaLibImportKind.None }; + const options: tstl.CompilerOptions = { + luaTarget: tstl.LuaTarget.Lua53, + luaLibImport: tstl.LuaLibImportKind.None, + }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); expect(lua).toBe("local ____ = {table.unpack({\n 0,\n 1,\n 2,\n})}"); }); test("Spread Element Lua JIT", () => { - const options = { luaTarget: LuaTarget.LuaJIT, luaLibImport: LuaLibImportKind.None }; + const options: tstl.CompilerOptions = { + luaTarget: tstl.LuaTarget.LuaJIT, + luaLibImport: tstl.LuaLibImportKind.None, + }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); expect(lua).toBe("local ____ = {unpack({\n 0,\n 1,\n 2,\n})}"); }); From d5b8e65e2c8a414fc96e3231cba5c3cc7f082bd4 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 2 May 2019 04:17:52 +0500 Subject: [PATCH 43/44] Simplify tstl object formatting in deprecation warning --- src/diagnostics.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 82f915aa6..ddc8611c4 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -20,11 +20,7 @@ export const tstlOptionsAreMovingToTheTstlObject = (tstl: Record) = source: "typescript-to-lua", messageText: 'TSTL options are moving to the "tstl" object. Adjust your tsconfig to look like\n' + - JSON.stringify({ tstl }, undefined, 4) - .split("\n") - .slice(1, -1) - .map(line => line.slice(4)) - .join("\n"), + `"tstl": ${JSON.stringify(tstl, undefined, 4)}`, }); export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({ From e8b89cdabfa774df47075d3b7d7e7da1efb47d22 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 2 May 2019 11:35:00 +0500 Subject: [PATCH 44/44] Add Lua AST to TranspiledFile interface --- src/Transpile.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Transpile.ts b/src/Transpile.ts index b14fcc780..ee59af81d 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -1,6 +1,7 @@ import * as ts from "typescript"; import { CompilerOptions } from "./CompilerOptions"; import { transpileError } from "./diagnostics"; +import { Block } from './LuaAST'; import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; @@ -36,6 +37,7 @@ function getCustomTransformers( } export interface TranspiledFile { + luaAst?: Block; lua?: string; sourceMap?: string; declaration?: string; @@ -101,14 +103,14 @@ export function transpile({ const processSourceFile = (sourceFile: ts.SourceFile) => { try { - const [luaAST, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); + const [luaAst, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); if (!options.noEmit && !options.emitDeclarationOnly) { const [lua, sourceMap] = printer.print( - luaAST, + luaAst, lualibFeatureSet, sourceFile.fileName ); - updateTranspiledFile(sourceFile.fileName, { lua, sourceMap }); + updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap }); } } catch (err) { if (!(err instanceof TranspileError)) throw err;