From e8ff78959b87951b4a9182c33c3c7fbc9f527785 Mon Sep 17 00:00:00 2001 From: Lolleko Date: Sun, 19 Aug 2018 16:04:47 +0200 Subject: [PATCH 1/6] Added experimental watch mode --- src/Compiler.ts | 103 ++++++++++++++++++++++++++++++++++++---------- src/Transpiler.ts | 4 ++ 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/Compiler.ts b/src/Compiler.ts index 319226a96..ee29bd4c8 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -11,42 +11,86 @@ import { LuaLibImportKind, LuaTarget, LuaTranspiler, TranspileError } from "./Tr export function compile(argv: string[]): void { const commandLine = parseCommandLine(argv); - compileFilesWithOptions(commandLine.fileNames, commandLine.options); + if (commandLine.options.watch) { + watchWithOptions(commandLine.fileNames, commandLine.options); + } else { + compileFilesWithOptions(commandLine.fileNames, commandLine.options); + } } -export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void { - if (!options.luaTarget) { - options.luaTarget = LuaTarget.LuaJIT; +export function watchWithOptions(fileNames: string[], options: CompilerOptions): void { + let host: ts.WatchCompilerHost; + if (options.project) { + host = ts.createWatchCompilerHost( + options.project, + options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram + ); + } else { + host = ts.createWatchCompilerHost( + fileNames, + options, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram + ); } + const errorDiagnostic: ts.Diagnostic = { + category: undefined, + code: 0, + file: undefined, + length: 0, + messageText: "", + start: 0, + }; + + host.afterProgramCreate = program => { + const status = emitFilesAndReportErrors(program.getProgram()); + let messageText = "Found 0 errors. Watching for file changes."; + let code = 6194; + if (status !== 0) { + messageText = "Found Errors. Watching for file changes."; + code = 6193; + } + errorDiagnostic.messageText = messageText; + errorDiagnostic.code = code; + host.onWatchStatusChange( + errorDiagnostic, + host.getNewLine(), + program.getCompilerOptions() + ); + }; + + ts.createWatchProgram(host); +} + +export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void { const program = ts.createProgram(fileNames, options); + emitFilesAndReportErrors(program); +} + +function emitFilesAndReportErrors(program: ts.Program): number { + const options = program.getCompilerOptions() as CompilerOptions; + const checker = program.getTypeChecker(); // Get all diagnostics, ignore unsupported extension const diagnostics = ts.getPreEmitDiagnostics(program).filter(diag => diag.code !== 6054); - diagnostics.forEach(diagnostic => { - if (diagnostic.file) { - const { line, character } = - diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); - const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - console.log( - `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` - ); - } else { - console.log( - `${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}` - ); - } - }); + diagnostics.forEach(reportDiagnostic); // If there are errors dont emit if (diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error).length > 0) { - console.log("Stopping compilation process because of errors."); - process.exit(1); + if (!options.watch) { + process.exit(1); + } else { + return 1; + } } program.getSourceFiles().forEach(sourceFile => { + if (!sourceFile.isDeclarationFile) { try { const rootDir = options.rootDir; @@ -57,7 +101,7 @@ export function compileFilesWithOptions(fileNames: string[], options: CompilerOp let outPath = sourceFile.fileName; if (options.outDir !== options.rootDir) { const relativeSourcePath = path.resolve(sourceFile.fileName) - .replace(path.resolve(rootDir), ""); + .replace(path.resolve(rootDir), ""); outPath = path.join(options.outDir, relativeSourcePath); } @@ -101,6 +145,8 @@ export function compileFilesWithOptions(fileNames: string[], options: CompilerOp path.join(options.outDir, "lualib_bundle.lua") ); } + + return 0; } export function createTranspiler(checker: ts.TypeChecker, @@ -127,3 +173,18 @@ export function createTranspiler(checker: ts.TypeChecker, return luaTargetTranspiler; } + +function 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/Transpiler.ts b/src/Transpiler.ts index 5108276de..58c99ab44 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -90,6 +90,10 @@ export abstract class LuaTranspiler { this.classStack = []; this.exportStack = []; this.luaLibFeatureSet = new Set(); + + if (!this.options.luaTarget) { + this.options.luaTarget = LuaTarget.LuaJIT; + } } public pushIndent(): void { From 165935f5b1b429006063854c0baacba9ccc50ec1 Mon Sep 17 00:00:00 2001 From: Lolleko Date: Sun, 19 Aug 2018 16:18:47 +0200 Subject: [PATCH 2/6] Fixed host type issue --- src/Compiler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Compiler.ts b/src/Compiler.ts index ee29bd4c8..caf307ccd 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -20,7 +20,9 @@ export function compile(argv: string[]): void { 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, @@ -62,7 +64,13 @@ export function watchWithOptions(fileNames: string[], options: CompilerOptions): ); }; - ts.createWatchProgram(host); + if (config) { + ts.createWatchProgram( + host as ts.WatchCompilerHostOfConfigFile); + } else { + ts.createWatchProgram( + host as ts.WatchCompilerHostOfFilesAndCompilerOptions); + } } export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void { From 99b42b6b3542f58b5c43f607b42fc75f28d7b2bf Mon Sep 17 00:00:00 2001 From: Lolleko Date: Sun, 19 Aug 2018 16:44:24 +0200 Subject: [PATCH 3/6] Fixed Tests --- src/Compiler.ts | 7 ++----- test/unit/compiler.spec.ts | 16 ---------------- 2 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 test/unit/compiler.spec.ts diff --git a/src/Compiler.ts b/src/Compiler.ts index caf307ccd..89df93aa0 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -162,9 +162,6 @@ export function createTranspiler(checker: ts.TypeChecker, sourceFile: ts.SourceFile): LuaTranspiler { let luaTargetTranspiler: LuaTranspiler; switch (options.luaTarget) { - case LuaTarget.LuaJIT: - luaTargetTranspiler = new LuaTranspilerJIT(checker, options, sourceFile); - break; case LuaTarget.Lua51: luaTargetTranspiler = new LuaTranspiler51(checker, options, sourceFile); break; @@ -175,8 +172,8 @@ export function createTranspiler(checker: ts.TypeChecker, luaTargetTranspiler = new LuaTranspiler53(checker, options, sourceFile); break; default: - // should not happen - throw Error("No luaTarget Specified please ensure a target is set!"); + luaTargetTranspiler = new LuaTranspilerJIT(checker, options, sourceFile); + break; } return luaTargetTranspiler; diff --git a/test/unit/compiler.spec.ts b/test/unit/compiler.spec.ts deleted file mode 100644 index d96dcfc00..000000000 --- a/test/unit/compiler.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Expect, Test } from "alsatian"; - -import { createTranspiler } from "../../src/Compiler"; - -import * as ts from "typescript"; - -export class CompilerTests { - - @Test("Throw if no luaTarget specified") - public validLuaTarget() { - Expect(() => { - createTranspiler(({} as ts.TypeChecker), ({} as ts.CompilerOptions), ({} as ts.SourceFile)); - }).toThrowError(Error, "No luaTarget Specified please ensure a target is set!"); - } - -} From ec5f397711e65df0eda8855b47df24764d3d6c76 Mon Sep 17 00:00:00 2001 From: lolleko Date: Mon, 20 Aug 2018 15:40:29 +0200 Subject: [PATCH 4/6] Added test --- test/compiler/testfiles/watch_single.ts | 1 + test/compiler/watcher_proccess.ts | 5 ++ test/compiler/watchmode.spec.ts | 71 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 test/compiler/testfiles/watch_single.ts create mode 100644 test/compiler/watcher_proccess.ts create mode 100644 test/compiler/watchmode.spec.ts diff --git a/test/compiler/testfiles/watch_single.ts b/test/compiler/testfiles/watch_single.ts new file mode 100644 index 000000000..4e2a15260 --- /dev/null +++ b/test/compiler/testfiles/watch_single.ts @@ -0,0 +1 @@ +class MyTest {} \ No newline at end of file diff --git a/test/compiler/watcher_proccess.ts b/test/compiler/watcher_proccess.ts new file mode 100644 index 000000000..ecdf7e360 --- /dev/null +++ b/test/compiler/watcher_proccess.ts @@ -0,0 +1,5 @@ +import { compile } from "../../src/Compiler"; + +process.on("message", args => { + compile(args); +}); \ No newline at end of file diff --git a/test/compiler/watchmode.spec.ts b/test/compiler/watchmode.spec.ts new file mode 100644 index 000000000..a73cac60f --- /dev/null +++ b/test/compiler/watchmode.spec.ts @@ -0,0 +1,71 @@ +import { AsyncTest, Expect, Setup, Timeout } from "alsatian"; +import { fork } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; + +export class CompilerWatchModeTest { + + private singleFilePath: string; + private singleFilePathOut: string; + + @AsyncTest("Watch single File") + @Timeout(10000) + public async testSingle(): Promise { + // spawn watcher in different thread, that way we can just terminate it after test are completed + const child = fork(path.join(__dirname, "watcher_proccess.ts")); + child.send(["-w", this.singleFilePath]); + + await this.waitForFileExists(this.singleFilePathOut, 4000) + .catch(err => console.error(err)); + + Expect(fs.existsSync(this.singleFilePathOut)).toBe(true); + + const initialResultLua = fs.readFileSync(this.singleFilePathOut); + const originalTS = fs.readFileSync(this.singleFilePath); + + fs.unlinkSync(this.singleFilePathOut); + + fs.writeFileSync(this.singleFilePath, "class MyTest2 {}"); + + await this.waitForFileExists(this.singleFilePathOut) + .catch(err => console.error(err)); + + const updatedResultLua = fs.readFileSync(this.singleFilePathOut).toString(); + + Expect(initialResultLua).not.toEqual(updatedResultLua); + + fs.writeFileSync(this.singleFilePath, originalTS); + + fs.unlinkSync(this.singleFilePathOut); + + child.kill(); + } + + @Setup + private setup(): void { + this.singleFilePath = path.join(__dirname, "./testfiles/watch_single.ts"); + this.singleFilePathOut = path.join(__dirname, "./testfiles/watch_single.lua"); + } + + private waitForFileExists(filepath: string, timeout: number = 3000): Promise { + const interval = 200; + return new Promise((resolve, reject) => { + const intervalTimerId = setInterval( + () => { + if (fs.existsSync(filepath)) { + clearTimeout(timeoutId); + clearInterval(intervalTimerId); + resolve(); + } + }, + interval); + + const timeoutId = setTimeout( + () => { + clearInterval(intervalTimerId); + reject(new Error("Wating for file timed out!")); + }, + timeout); + }); + } +} From fcfb68029ab20f6a49b03aad317fd0ce735c0109 Mon Sep 17 00:00:00 2001 From: lolleko Date: Mon, 20 Aug 2018 16:56:58 +0200 Subject: [PATCH 5/6] Improved test & coverage ignore --- src/Compiler.ts | 2 + .../compiler/projects/watchmode/tsconfig.json | 3 ++ .../watchmode/watch.ts} | 0 test/compiler/testfiles/watch.ts | 1 + test/compiler/watcher_proccess.ts | 2 +- test/compiler/watchmode.spec.ts | 45 +++++++++---------- 6 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 test/compiler/projects/watchmode/tsconfig.json rename test/compiler/{testfiles/watch_single.ts => projects/watchmode/watch.ts} (100%) create mode 100644 test/compiler/testfiles/watch.ts diff --git a/src/Compiler.ts b/src/Compiler.ts index 89df93aa0..8f38fbb76 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -11,6 +11,7 @@ import { LuaLibImportKind, LuaTarget, LuaTranspiler, TranspileError } from "./Tr export function compile(argv: string[]): void { const commandLine = parseCommandLine(argv); + /* istanbul ignore if: tested in test/compiler/watchmode.spec with subproccess */ if (commandLine.options.watch) { watchWithOptions(commandLine.fileNames, commandLine.options); } else { @@ -18,6 +19,7 @@ export function compile(argv: string[]): void { } } +/* 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; diff --git a/test/compiler/projects/watchmode/tsconfig.json b/test/compiler/projects/watchmode/tsconfig.json new file mode 100644 index 000000000..e5a835e99 --- /dev/null +++ b/test/compiler/projects/watchmode/tsconfig.json @@ -0,0 +1,3 @@ +{ + "luaTarget": "JIT" +} diff --git a/test/compiler/testfiles/watch_single.ts b/test/compiler/projects/watchmode/watch.ts similarity index 100% rename from test/compiler/testfiles/watch_single.ts rename to test/compiler/projects/watchmode/watch.ts diff --git a/test/compiler/testfiles/watch.ts b/test/compiler/testfiles/watch.ts new file mode 100644 index 000000000..4e2a15260 --- /dev/null +++ b/test/compiler/testfiles/watch.ts @@ -0,0 +1 @@ +class MyTest {} \ No newline at end of file diff --git a/test/compiler/watcher_proccess.ts b/test/compiler/watcher_proccess.ts index ecdf7e360..25b4bd344 100644 --- a/test/compiler/watcher_proccess.ts +++ b/test/compiler/watcher_proccess.ts @@ -2,4 +2,4 @@ import { compile } from "../../src/Compiler"; process.on("message", args => { compile(args); -}); \ No newline at end of file +}); diff --git a/test/compiler/watchmode.spec.ts b/test/compiler/watchmode.spec.ts index a73cac60f..c943b2779 100644 --- a/test/compiler/watchmode.spec.ts +++ b/test/compiler/watchmode.spec.ts @@ -1,52 +1,49 @@ -import { AsyncTest, Expect, Setup, Timeout } from "alsatian"; +import { AsyncTest, Expect, Setup, TestCase, Timeout } from "alsatian"; import { fork } from "child_process"; import * as fs from "fs"; import * as path from "path"; export class CompilerWatchModeTest { - private singleFilePath: string; - private singleFilePathOut: string; - + @TestCase(["-w", path.join(__dirname, "./testfiles/watch.ts")], + path.join(__dirname, "./testfiles/watch.ts")) + @TestCase(["-w", "-p", path.join(__dirname, "./projects/watchmode/")], + path.join(__dirname, "./projects/watchmode/watch.ts")) @AsyncTest("Watch single File") - @Timeout(10000) - public async testSingle(): Promise { - // spawn watcher in different thread, that way we can just terminate it after test are completed + @Timeout(16000) + public async testSingle(args: string[], fileToChange: string): Promise { + fileToChange = fileToChange; + const fileToChangeOut = fileToChange.replace(".ts", ".lua"); + const child = fork(path.join(__dirname, "watcher_proccess.ts")); - child.send(["-w", this.singleFilePath]); + child.send(args); - await this.waitForFileExists(this.singleFilePathOut, 4000) + await this.waitForFileExists(fileToChangeOut, 9000) .catch(err => console.error(err)); - Expect(fs.existsSync(this.singleFilePathOut)).toBe(true); + Expect(fs.existsSync(fileToChangeOut)).toBe(true); - const initialResultLua = fs.readFileSync(this.singleFilePathOut); - const originalTS = fs.readFileSync(this.singleFilePath); + const initialResultLua = fs.readFileSync(fileToChangeOut); + const originalTS = fs.readFileSync(fileToChange); - fs.unlinkSync(this.singleFilePathOut); + fs.unlinkSync(fileToChangeOut); - fs.writeFileSync(this.singleFilePath, "class MyTest2 {}"); + fs.writeFileSync(fileToChange, "class MyTest2 {}"); - await this.waitForFileExists(this.singleFilePathOut) + await this.waitForFileExists(fileToChangeOut, 5000) .catch(err => console.error(err)); - const updatedResultLua = fs.readFileSync(this.singleFilePathOut).toString(); + const updatedResultLua = fs.readFileSync(fileToChangeOut).toString(); Expect(initialResultLua).not.toEqual(updatedResultLua); - fs.writeFileSync(this.singleFilePath, originalTS); + fs.writeFileSync(fileToChange, originalTS); - fs.unlinkSync(this.singleFilePathOut); + fs.unlinkSync(fileToChangeOut); child.kill(); } - @Setup - private setup(): void { - this.singleFilePath = path.join(__dirname, "./testfiles/watch_single.ts"); - this.singleFilePathOut = path.join(__dirname, "./testfiles/watch_single.lua"); - } - private waitForFileExists(filepath: string, timeout: number = 3000): Promise { const interval = 200; return new Promise((resolve, reject) => { From f0c67c1ab6851e5edcef3620e414d10a3d305662 Mon Sep 17 00:00:00 2001 From: lolleko Date: Mon, 20 Aug 2018 22:31:53 +0200 Subject: [PATCH 6/6] Imporved error instantiation --- src/Compiler.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Compiler.ts b/src/Compiler.ts index 8f38fbb76..411e1ba00 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -40,25 +40,20 @@ export function watchWithOptions(fileNames: string[], options: CompilerOptions): ); } - const errorDiagnostic: ts.Diagnostic = { - category: undefined, - code: 0, - file: undefined, - length: 0, - messageText: "", - start: 0, - }; - host.afterProgramCreate = program => { const status = emitFilesAndReportErrors(program.getProgram()); - let messageText = "Found 0 errors. Watching for file changes."; - let code = 6194; + 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) { - messageText = "Found Errors. Watching for file changes."; - code = 6193; + errorDiagnostic.messageText = "Found Errors. Watching for file changes."; + errorDiagnostic.code = 6193; } - errorDiagnostic.messageText = messageText; - errorDiagnostic.code = code; host.onWatchStatusChange( errorDiagnostic, host.getNewLine(), @@ -111,7 +106,7 @@ function emitFilesAndReportErrors(program: ts.Program): number { let outPath = sourceFile.fileName; if (options.outDir !== options.rootDir) { const relativeSourcePath = path.resolve(sourceFile.fileName) - .replace(path.resolve(rootDir), ""); + .replace(path.resolve(rootDir), ""); outPath = path.join(options.outDir, relativeSourcePath); }