Dramatic simplifications from using TypeScript-provided machinery which appeared sometime between when this code was originally written and now

This commit is contained in:
Tony Garnock-Jones 2023-02-14 11:32:21 +01:00
parent 958cbae171
commit 14702c66b9
1 changed files with 41 additions and 75 deletions

View File

@ -50,35 +50,17 @@ export function main(argv: string[]) {
.argv; .argv;
if (options.watch) { if (options.watch) {
function run() { if (options.clear) clearScreen(options);
const toWatch = new ToWatch(); runBuildOnce(options);
console.log((options.clear ? '\x1b[2J\x1b[H' : '\n') + (new Date()) + ': Running build');
runBuildOnce(options, toWatch);
const watchers: Array<ts.FileWatcher> = [];
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();
} else { } else {
ts.sys.exit(runBuildOnce(options) ? 0 : 1); 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 { function finalSlash(s: string): string {
if (s[s.length - 1] !== '/') s = s + '/'; if (s[s.length - 1] !== '/') s = s + '/';
return s; return s;
@ -90,15 +72,7 @@ const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: f => f, getCanonicalFileName: f => f,
}; };
class ToWatch { function runBuildOnce(options: CommandLineArguments): boolean {
files: Set<string> = new Set();
directories: Set<string> = new Set();
}
function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) {
let problemCount = 0;
let hasErrors = false;
const syndicateInfo: Map<string, SyndicateInfo> = new Map(); const syndicateInfo: Map<string, SyndicateInfo> = new Map();
function createProgram(commandLineOptions: CommandLineArguments): ts.CreateProgram<any> function createProgram(commandLineOptions: CommandLineArguments): ts.CreateProgram<any>
@ -138,7 +112,6 @@ function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) {
languageVersion: ts.ScriptTarget, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void), onError?: ((message: string) => void),
shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined => { shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined => {
toWatch.files.add(fileName);
if ((rootNames?.indexOf(fileName) ?? -1) !== -1) { if ((rootNames?.indexOf(fileName) ?? -1) !== -1) {
try { try {
const inputText = host.readFile(fileName); const inputText = host.readFile(fileName);
@ -220,63 +193,56 @@ function runBuildOnce(options: CommandLineArguments, toWatch = new ToWatch()) {
return; return;
if (!syntheticSourceFiles.has(d.file.fileName)) { if (!syntheticSourceFiles.has(d.file.fileName)) {
syntheticSourceFiles.set( const sf = ts.createSourceFile(d.file.fileName,
d.file.fileName, info.originalSource,
ts.createSourceFile(d.file.fileName, info.languageVersion,
info.originalSource, false,
info.languageVersion, ts.ScriptKind.Unknown);
false, (sf as any).resolvedPath = d.file.fileName;
ts.ScriptKind.Unknown)); syntheticSourceFiles.set(d.file.fileName, sf);
} }
d.file = syntheticSourceFiles.get(d.file.fileName); 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) { function reportDiagnostic(d: ts.Diagnostic) {
if (d.category === ts.DiagnosticCategory.Error) problemCount++;
fixupDiagnostic(d); 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()); console.log(ts.formatDiagnosticsWithColorAndContext([d], formatDiagnosticsHost).trimEnd());
} }
function reportErrorSummary(n: number) { const getCustomTransformers = (_project: string) => ({ before: [fixSourceMap] });
if (n > 0) {
console.error(`\n - ${n} errors reported`); const sbh = ts.createSolutionBuilderWithWatchHost(ts.sys,
hasErrors = true; createProgram(options),
} reportDiagnostic,
reportDiagnostic,
reportDiagnostic);
if (sbh.getCustomTransformers) {
console.warn('SolutionBuilderHost already has getCustomTransformers');
} }
sbh.getCustomTransformers = getCustomTransformers;
const sbh = ts.createSolutionBuilderHost(ts.sys, const sb = ts.createSolutionBuilderWithWatch(sbh, ['.'], {
createProgram(options),
reportDiagnostic,
reportDiagnostic,
reportErrorSummary);
const sb = ts.createSolutionBuilder(sbh, ['.'], {
verbose: options.verbose, 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 switch (exitStatus) {
// ts.ParsedCommandline objects to prime our set of watched case ts.ExitStatus.Success:
// files/directories, in case all the projects are up-to-date and case ts.ExitStatus.DiagnosticsPresent_OutputsGenerated:
// our createProgram function is never called. return true;
//
(((sb as any).getAllParsedConfigs?.() ?? []) as Array<ts.ParsedCommandLine>).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));
});
while (project !== void 0) { case ts.ExitStatus.DiagnosticsPresent_OutputsSkipped:
project.done(void 0, void 0, { case ts.ExitStatus.InvalidProject_OutputsSkipped:
before: [fixSourceMap] case ts.ExitStatus.ProjectReferenceCycle_OutputsSkipped:
}); return false;
project = sb.getNextInvalidatedProject();
default:
((_: never) => { throw new Error("Unexpected ts.ExitStatus: " + _) })(exitStatus);
} }
return (problemCount === 0) && !hasErrors;
} }