14
import process from "process";
15
import * as readline from "readline";
16
import ts from "typescript";
17
+import yargs from "yargs";
18
import { hideBin } from "yargs/helpers";
18
-import yargs from "yargs/yargs";
19
import { TestResult } from "./compiler-worker";
20
import * as compiler from "./compiler-worker.js";
21
26
}
27
28
const argv: {
29
- sync?: boolean;
30
- disableWorkerThreads?: boolean;
31
- watch?: boolean;
32
- update?: boolean;
33
-} = yargs(hideBin(process.argv)).argv as any;
29
+ sync: boolean;
30
+ workerThreads: boolean;
31
+ watch: boolean;
32
+ update: boolean;
33
+} = yargs
34
+ .boolean("sync")
35
+ .describe(
36
+ "sync",
37
+ "Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false."
38
+ )
39
+ .default("sync", false)
40
+ .boolean("worker-threads")
41
+ .describe(
42
+ "worker-threads",
43
+ "Run compiler in worker threads (instead of subprocesses). Defaults to true."
44
+ )
45
+ .default("worker-threads", true)
46
+ .boolean("watch")
47
+ .describe("watch", "Run in watch mode. Defaults to false (single run).")
48
+ .default("watch", false)
49
+ .boolean("update") // Test mode by default, opt-in to update
50
+ .describe(
51
+ "update",
52
+ "Run in update mode. Update mode only affects the first run, subsequent runs (in watch mode) require typing `u` to update. Defaults to false."
53
+ )
54
+ .default("update", false)
55
+ .help("help")
56
+ .strict()
57
+ .parseSync(hideBin(process.argv));
58
35
-// Parallel by default
59
const PARALLEL = !argv.sync;
37
-
38
-// Enable worker threads by default
39
-const ENABLE_WORKER_THREADS = !argv.disableWorkerThreads;
40
-
41
-// Single-run by default, opt-in to watch mode
42
-const WATCH = !!argv.watch;
43
-
44
-// Test mode by default, opt-in to update
45
-// NOTE: update mode only affects the first run, subsequent runs (in watch mode)
46
-// require typing `u` to update
47
-const UPDATE = !!argv.update;
48
-
60
+const ENABLE_WORKER_THREADS = argv.workerThreads;
61
+const WATCH = argv.watch;
62
+const UPDATE = argv.update;
63
const WORKER_PATH = require.resolve("./compiler-worker.js");
64
const COMPILER_PATH = path.join(
65
process.cwd(),
198
199
function watchSrc(
200
onStart: () => void,
187
- onComplete: () => void
201
+ onComplete: (isSuccess: boolean) => void
202
): ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> {
203
const configPath = ts.findConfigFile(
204
/*searchPath*/ "./",
214
{},
215
ts.sys,
216
createProgram,
203
- reportDiagnostic,
204
- reportWatchStatusChanged
217
+ () => {}, // we manually report errors in afterProgramCreate
218
+ () => {} // we manually report watch status
219
);
220
221
const origCreateProgram = host.createProgram;
226
const origPostProgramCreate = host.afterProgramCreate;
227
host.afterProgramCreate = (program) => {
228
origPostProgramCreate!(program);
215
- onComplete();
229
+
230
+ // syntactic diagnostics refer to javascript syntax
231
+ const errors = program
232
+ .getSyntacticDiagnostics()
233
+ .filter((diag) => diag.category === ts.DiagnosticCategory.Error);
234
+ // semantic diagnostics refer to typescript semantics
235
+ errors.push(
236
+ ...program
237
+ .getSemanticDiagnostics()
238
+ .filter((diag) => diag.category === ts.DiagnosticCategory.Error)
239
+ );
240
+
241
+ if (errors.length > 0) {
242
+ for (const diagnostic of errors) {
243
+ let fileLoc: string;
244
+ if (diagnostic.file) {
245
+ // https://github.com/microsoft/TypeScript/blob/ddd5084659c423f4003d2176e12d879b6a5bcf30/src/compiler/program.ts#L663-L674
246
+ const { line, character } = ts.getLineAndCharacterOfPosition(
247
+ diagnostic.file,
248
+ diagnostic.start!
249
+ );
250
+ const fileName = path.relative(
251
+ ts.sys.getCurrentDirectory(),
252
+ diagnostic.file.fileName
253
+ );
254
+ fileLoc = `${fileName}:${line + 1}:${character + 1} - `;
255
+ } else {
256
+ fileLoc = "";
257
+ }
258
+ console.error(
259
+ `${fileLoc}error TS${diagnostic.code}:`,
260
+ ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
261
+ );
262
+ }
263
+ console.error(
264
+ `Compilation failed (${errors.length} ${
265
+ errors.length > 1 ? "errors" : "error"
266
+ }).\n`
267
+ );
268
+ }
269
+
270
+ const isSuccess = errors.length === 0;
271
+ onComplete(isSuccess);
272
};
273
274
// `createWatchProgram` creates an initial program, watches files, and updates
276
return ts.createWatchProgram(host);
277
}
278
223
-const formatHost = {
224
- getCanonicalFileName: (path: string) => path,
225
- getCurrentDirectory: ts.sys.getCurrentDirectory,
226
- getNewLine: () => ts.sys.newLine,
227
-};
228
-
229
-// Gets called if TS reported any errors with the source.
230
-// TODO: wire this up, if there are errors we should just report them and
231
-// probably not run tests.
232
-function reportDiagnostic(diagnostic: ts.Diagnostic): void {
233
- console.error(
234
- "Error",
235
- diagnostic.code,
236
- ":",
237
- ts.flattenDiagnosticMessageText(
238
- diagnostic.messageText,
239
- formatHost.getNewLine()
240
- )
241
- );
242
-}
243
-
244
-/**
245
- * Prints a diagnostic every time the watch status changes.
246
- * This is mainly for messages like "Starting compilation" or "Compilation completed".
247
- */
248
-function reportWatchStatusChanged(diagnostic: ts.Diagnostic): void {
249
- // console.info(ts.formatDiagnostic(diagnostic, formatHost));
250
-}
251
-
279
enum Mode {
280
Test = "Test",
281
Update = "Update",
285
* Runs the compiler in watch or single-execution mode
286
*/
287
async function main(): Promise<void> {
261
- // Monotonically increasing integer to describe the 'version' of the compiler.
262
- // This is passed to `compile()` (from compiler-worker) when compiling, so
263
- // that the worker knows when it has to reset its module cache and when its
264
- // safe to use a cached compiler version
265
- let compilerVersion = 0;
266
-
288
if (WATCH) {
289
+ // Monotonically increasing integer to describe the 'version' of the compiler.
290
+ // This is passed to `compile()` (from compiler-worker) when compiling, so
291
+ // that the worker knows when it has to reset its module cache and when its
292
+ // safe to use a cached compiler version
293
+ let compilerVersion = 0;
294
+ let isCompilerValid = false;
295
+
296
function onStart() {
297
// Notify the user when compilation starts but don't clear the screen yet
270
- console.log("Compiling...");
298
+ console.log("\nCompiling...");
299
}
300
301
// Callback to re-run tests after some change
302
async function onChange({ mode }: { mode: Mode }) {
275
- const start = performance.now();
276
- console.clear();
277
- console.log("Running tests...");
278
- const results = await run(compilerVersion);
279
- console.clear();
280
- if (mode === Mode.Update) {
281
- update(results);
303
+ if (isCompilerValid) {
304
+ const start = performance.now();
305
+ console.clear();
306
+ console.log("Running tests...");
307
+ const results = await run(compilerVersion);
308
+ console.clear();
309
+ if (mode === Mode.Update) {
310
+ update(results);
311
+ } else {
312
+ report(results);
313
+ }
314
+ const end = performance.now();
315
+ console.log(`Completed in ${Math.floor(end - start)} ms`);
316
} else {
283
- report(results);
317
+ console.error(
318
+ `${mode}: Found errors in Forget source code, skipping test fixtures.`
319
+ );
320
}
285
- const end = performance.now();
286
- console.log(`Completed in ${end - start} ms`);
321
+ console.log(
322
+ "\nWaiting for input or file changes...\n" +
323
+ "u - update fixtures\n" +
324
+ "q - quit\n" +
325
+ "[any] - rerun tests\n"
326
+ );
327
}
328
329
// Run TS in incremental watch mode
290
- const _tsWatch = watchSrc(onStart, () => {
330
+ const _tsWatch = watchSrc(onStart, (isSuccess) => {
331
// Bump the compiler version after a build finishes
332
// and re-run tests
293
- compilerVersion++;
333
+ if (isSuccess) {
334
+ compilerVersion++;
335
+ }
336
+ isCompilerValid = isSuccess;
337
onChange({ mode: Mode.Test });
338
});
339
374
null;
375
tsWatch = watchSrc(
376
() => {},
334
- async () => {
335
- const results = await run(compilerVersion);
336
- if (UPDATE) {
337
- update(results);
377
+ async (isSuccess: boolean) => {
378
+ if (isSuccess) {
379
+ const results = await run(0);
380
+ if (UPDATE) {
381
+ update(results);
382
+ } else {
383
+ report(results);
384
+ }
385
} else {
339
- report(results);
386
+ console.error(
387
+ "Found errors in Forget source code, skipping test fixtures."
388
+ );
389
}
390
if (tsWatch != null) {
391
tsWatch.close();
392
}
393
await worker.end();
345
- process.exit();
394
+ process.exit(isSuccess ? 0 : -1);
395
}
396
);
397
}