Skip to content

Commit 688fe72

Browse files
authored
Merge pull request #175 from Perryvw/feature/watch-mode
Added experimental watch mode
2 parents d63ce03 + f0c67c1 commit 688fe72

8 files changed

Lines changed: 170 additions & 41 deletions

File tree

src/Compiler.ts

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,91 @@ import { LuaLibImportKind, LuaTarget, LuaTranspiler } from "./Transpiler";
1111

1212
export function compile(argv: string[]): void {
1313
const commandLine = parseCommandLine(argv);
14-
compileFilesWithOptions(commandLine.fileNames, commandLine.options);
14+
/* istanbul ignore if: tested in test/compiler/watchmode.spec with subproccess */
15+
if (commandLine.options.watch) {
16+
watchWithOptions(commandLine.fileNames, commandLine.options);
17+
} else {
18+
compileFilesWithOptions(commandLine.fileNames, commandLine.options);
19+
}
1520
}
1621

17-
export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void {
18-
if (!options.luaTarget) {
19-
options.luaTarget = LuaTarget.LuaJIT;
22+
/* istanbul ignore next: tested in test/compiler/watchmode.spec with subproccess */
23+
export function watchWithOptions(fileNames: string[], options: CompilerOptions): void {
24+
let host: ts.WatchCompilerHost<ts.SemanticDiagnosticsBuilderProgram>;
25+
let config = false;
26+
if (options.project) {
27+
config = true;
28+
host = ts.createWatchCompilerHost(
29+
options.project,
30+
options,
31+
ts.sys,
32+
ts.createSemanticDiagnosticsBuilderProgram
33+
);
34+
} else {
35+
host = ts.createWatchCompilerHost(
36+
fileNames,
37+
options,
38+
ts.sys,
39+
ts.createSemanticDiagnosticsBuilderProgram
40+
);
2041
}
2142

43+
host.afterProgramCreate = program => {
44+
const status = emitFilesAndReportErrors(program.getProgram());
45+
const errorDiagnostic: ts.Diagnostic = {
46+
category: undefined,
47+
code: 6194,
48+
file: undefined,
49+
length: 0,
50+
messageText: "Found 0 errors. Watching for file changes.",
51+
start: 0,
52+
};
53+
if (status !== 0) {
54+
errorDiagnostic.messageText = "Found Errors. Watching for file changes.";
55+
errorDiagnostic.code = 6193;
56+
}
57+
host.onWatchStatusChange(
58+
errorDiagnostic,
59+
host.getNewLine(),
60+
program.getCompilerOptions()
61+
);
62+
};
63+
64+
if (config) {
65+
ts.createWatchProgram(
66+
host as ts.WatchCompilerHostOfConfigFile<ts.SemanticDiagnosticsBuilderProgram>);
67+
} else {
68+
ts.createWatchProgram(
69+
host as ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.SemanticDiagnosticsBuilderProgram>);
70+
}
71+
}
72+
73+
export function compileFilesWithOptions(fileNames: string[], options: CompilerOptions): void {
2274
const program = ts.createProgram(fileNames, options);
2375

76+
emitFilesAndReportErrors(program);
77+
}
78+
79+
function emitFilesAndReportErrors(program: ts.Program): number {
80+
const options = program.getCompilerOptions() as CompilerOptions;
81+
2482
const checker = program.getTypeChecker();
2583

2684
// Get all diagnostics, ignore unsupported extension
2785
const diagnostics = ts.getPreEmitDiagnostics(program).filter(diag => diag.code !== 6054);
28-
diagnostics.forEach(diagnostic => {
29-
if (diagnostic.file) {
30-
const { line, character } =
31-
diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
32-
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
33-
console.log(
34-
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
35-
);
36-
} else {
37-
console.log(
38-
`${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`
39-
);
40-
}
41-
});
86+
diagnostics.forEach(reportDiagnostic);
4287

4388
// If there are errors dont emit
4489
if (diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error).length > 0) {
45-
console.log("Stopping compilation process because of errors.");
46-
process.exit(1);
90+
if (!options.watch) {
91+
process.exit(1);
92+
} else {
93+
return 1;
94+
}
4795
}
4896

4997
program.getSourceFiles().forEach(sourceFile => {
98+
5099
if (!sourceFile.isDeclarationFile) {
51100
try {
52101
const rootDir = options.rootDir;
@@ -101,16 +150,15 @@ export function compileFilesWithOptions(fileNames: string[], options: CompilerOp
101150
path.join(options.outDir, "lualib_bundle.lua")
102151
);
103152
}
153+
154+
return 0;
104155
}
105156

106157
export function createTranspiler(checker: ts.TypeChecker,
107158
options: ts.CompilerOptions,
108159
sourceFile: ts.SourceFile): LuaTranspiler {
109160
let luaTargetTranspiler: LuaTranspiler;
110161
switch (options.luaTarget) {
111-
case LuaTarget.LuaJIT:
112-
luaTargetTranspiler = new LuaTranspilerJIT(checker, options, sourceFile);
113-
break;
114162
case LuaTarget.Lua51:
115163
luaTargetTranspiler = new LuaTranspiler51(checker, options, sourceFile);
116164
break;
@@ -121,9 +169,24 @@ export function createTranspiler(checker: ts.TypeChecker,
121169
luaTargetTranspiler = new LuaTranspiler53(checker, options, sourceFile);
122170
break;
123171
default:
124-
// should not happen
125-
throw Error("No luaTarget Specified please ensure a target is set!");
172+
luaTargetTranspiler = new LuaTranspilerJIT(checker, options, sourceFile);
173+
break;
126174
}
127175

128176
return luaTargetTranspiler;
129177
}
178+
179+
function reportDiagnostic(diagnostic: ts.Diagnostic): void {
180+
if (diagnostic.file) {
181+
const { line, character } =
182+
diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
183+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
184+
console.log(
185+
`${diagnostic.code}: ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
186+
);
187+
} else {
188+
console.log(
189+
`${diagnostic.code}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`
190+
);
191+
}
192+
}

src/Transpiler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export abstract class LuaTranspiler {
8787
this.classStack = [];
8888
this.exportStack = [];
8989
this.luaLibFeatureSet = new Set<LuaLibFeature>();
90+
91+
if (!this.options.luaTarget) {
92+
this.options.luaTarget = LuaTarget.LuaJIT;
93+
}
9094
}
9195

9296
public pushIndent(): void {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"luaTarget": "JIT"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class MyTest {}

test/compiler/testfiles/watch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class MyTest {}

test/compiler/watcher_proccess.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { compile } from "../../src/Compiler";
2+
3+
process.on("message", args => {
4+
compile(args);
5+
});

test/compiler/watchmode.spec.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { AsyncTest, Expect, Setup, TestCase, Timeout } from "alsatian";
2+
import { fork } from "child_process";
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
6+
export class CompilerWatchModeTest {
7+
8+
@TestCase(["-w", path.join(__dirname, "./testfiles/watch.ts")],
9+
path.join(__dirname, "./testfiles/watch.ts"))
10+
@TestCase(["-w", "-p", path.join(__dirname, "./projects/watchmode/")],
11+
path.join(__dirname, "./projects/watchmode/watch.ts"))
12+
@AsyncTest("Watch single File")
13+
@Timeout(16000)
14+
public async testSingle(args: string[], fileToChange: string): Promise<void> {
15+
fileToChange = fileToChange;
16+
const fileToChangeOut = fileToChange.replace(".ts", ".lua");
17+
18+
const child = fork(path.join(__dirname, "watcher_proccess.ts"));
19+
child.send(args);
20+
21+
await this.waitForFileExists(fileToChangeOut, 9000)
22+
.catch(err => console.error(err));
23+
24+
Expect(fs.existsSync(fileToChangeOut)).toBe(true);
25+
26+
const initialResultLua = fs.readFileSync(fileToChangeOut);
27+
const originalTS = fs.readFileSync(fileToChange);
28+
29+
fs.unlinkSync(fileToChangeOut);
30+
31+
fs.writeFileSync(fileToChange, "class MyTest2 {}");
32+
33+
await this.waitForFileExists(fileToChangeOut, 5000)
34+
.catch(err => console.error(err));
35+
36+
const updatedResultLua = fs.readFileSync(fileToChangeOut).toString();
37+
38+
Expect(initialResultLua).not.toEqual(updatedResultLua);
39+
40+
fs.writeFileSync(fileToChange, originalTS);
41+
42+
fs.unlinkSync(fileToChangeOut);
43+
44+
child.kill();
45+
}
46+
47+
private waitForFileExists(filepath: string, timeout: number = 3000): Promise<void> {
48+
const interval = 200;
49+
return new Promise((resolve, reject) => {
50+
const intervalTimerId = setInterval(
51+
() => {
52+
if (fs.existsSync(filepath)) {
53+
clearTimeout(timeoutId);
54+
clearInterval(intervalTimerId);
55+
resolve();
56+
}
57+
},
58+
interval);
59+
60+
const timeoutId = setTimeout(
61+
() => {
62+
clearInterval(intervalTimerId);
63+
reject(new Error("Wating for file timed out!"));
64+
},
65+
timeout);
66+
});
67+
}
68+
}

test/unit/compiler.spec.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)