diff --git a/packages/tsc/src/tsc.ts b/packages/tsc/src/tsc.ts index 8ad356f..22957b8 100644 --- a/packages/tsc/src/tsc.ts +++ b/packages/tsc/src/tsc.ts @@ -50,35 +50,17 @@ export function main(argv: string[]) { .argv; if (options.watch) { - function run() { - const toWatch = new ToWatch(); - console.log((options.clear ? '\x1b[2J\x1b[H' : '\n') + (new Date()) + ': Running build'); - runBuildOnce(options, toWatch); - const watchers: Array = []; - let rebuildTriggered = false; - const cb = () => { - if (!rebuildTriggered) { - rebuildTriggered = true; - watchers.forEach(w => w.close()); - queueMicrotask(run); - } - }; - toWatch.files.forEach(f => { - const w = ts.sys.watchFile?.(f, cb); - if (w) watchers.push(w); - }); - toWatch.directories.forEach(d => { - const w = ts.sys.watchDirectory?.(d, cb, true); - if (w) watchers.push(w); - }); - console.log('\n' + (new Date()) + ': Waiting for changes to input files'); - } - run(); + if (options.clear) clearScreen(options); + runBuildOnce(options); } else { ts.sys.exit(runBuildOnce(options) ? 0 : 1); } } +function clearScreen(options: CommandLineArguments) { + process.stdout.write(options.clear ? '\x1b[2J\x1b[H' : '\n'); +} + function finalSlash(s: string): string { if (s[s.length - 1] !== '/') s = s + '/'; return s; @@ -90,15 +72,7 @@ const formatDiagnosticsHost: ts.FormatDiagnosticsHost = { getCanonicalFileName: f => f, }; -class ToWatch { - files: Set = new Set(); - directories: Set = new Set(); -} - -function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) { - let problemCount = 0; - let hasErrors = false; - +function runBuildOnce(options: CommandLineArguments): boolean { const syndicateInfo: Map = new Map(); function createProgram(commandLineOptions: CommandLineArguments): ts.CreateProgram @@ -138,7 +112,6 @@ function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) { languageVersion: ts.ScriptTarget, onError?: ((message: string) => void), shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined => { - toWatch.files.add(fileName); if ((rootNames?.indexOf(fileName) ?? -1) !== -1) { try { const inputText = host.readFile(fileName); @@ -220,63 +193,56 @@ function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) { return; if (!syntheticSourceFiles.has(d.file.fileName)) { - syntheticSourceFiles.set( - d.file.fileName, - ts.createSourceFile(d.file.fileName, - info.originalSource, - info.languageVersion, - false, - ts.ScriptKind.Unknown)); + const sf = ts.createSourceFile(d.file.fileName, + info.originalSource, + info.languageVersion, + false, + ts.ScriptKind.Unknown); + (sf as any).resolvedPath = d.file.fileName; + syntheticSourceFiles.set(d.file.fileName, sf); } d.file = syntheticSourceFiles.get(d.file.fileName); - const p = info.targetToSourceMap.get(d.start)!; - d.start = p.firstItem.start.pos + p.offset; } } function reportDiagnostic(d: ts.Diagnostic) { - if (d.category === ts.DiagnosticCategory.Error) problemCount++; fixupDiagnostic(d); + // Sneakily access ts.screenStartingMessageCodes, internally used to decide whether to + // clear the screen or not. + const shouldClear = ((ts as any).screenStartingMessageCodes ?? []).indexOf(d.code) !== -1; + if (shouldClear) clearScreen(options); console.log(ts.formatDiagnosticsWithColorAndContext([d], formatDiagnosticsHost).trimEnd()); } - function reportErrorSummary(n: number) { - if (n > 0) { - console.error(`\n - ${n} errors reported`); - hasErrors = true; - } + const getCustomTransformers = (_project: string) => ({ before: [fixSourceMap] }); + + const sbh = ts.createSolutionBuilderWithWatchHost(ts.sys, + createProgram(options), + reportDiagnostic, + reportDiagnostic, + reportDiagnostic); + if (sbh.getCustomTransformers) { + console.warn('SolutionBuilderHost already has getCustomTransformers'); } + sbh.getCustomTransformers = getCustomTransformers; - const sbh = ts.createSolutionBuilderHost(ts.sys, - createProgram(options), - reportDiagnostic, - reportDiagnostic, - reportErrorSummary); - - const sb = ts.createSolutionBuilder(sbh, ['.'], { + const sb = ts.createSolutionBuilderWithWatch(sbh, ['.'], { verbose: options.verbose, }); - let project = sb.getNextInvalidatedProject(); + const exitStatus = sb.build(void 0, void 0, void 0, getCustomTransformers); - // Sneakily get into secret members of the ts.SolutionBuilder and - // ts.ParsedCommandline objects to prime our set of watched - // files/directories, in case all the projects are up-to-date and - // our createProgram function is never called. - // - (((sb as any).getAllParsedConfigs?.() ?? []) as Array).forEach(c => { - const f = (c.options as any).configFilePath; - if (f) toWatch.files.add(f); - c.fileNames.forEach(f => toWatch.files.add(f)); - Object.keys(c.wildcardDirectories ?? {}).forEach(d => toWatch.directories.add(d)); - }); + switch (exitStatus) { + case ts.ExitStatus.Success: + case ts.ExitStatus.DiagnosticsPresent_OutputsGenerated: + return true; - while (project !== void 0) { - project.done(void 0, void 0, { - before: [fixSourceMap] - }); - project = sb.getNextInvalidatedProject(); + case ts.ExitStatus.DiagnosticsPresent_OutputsSkipped: + case ts.ExitStatus.InvalidProject_OutputsSkipped: + case ts.ExitStatus.ProjectReferenceCycle_OutputsSkipped: + return false; + + default: + ((_: never) => { throw new Error("Unexpected ts.ExitStatus: " + _) })(exitStatus); } - - return (problemCount === 0) && !hasErrors; }