Skip to content

Commit 9b18f7b

Browse files
committed
Use builder to emit the files from the tsc.js
1 parent 6237b22 commit 9b18f7b

3 files changed

Lines changed: 156 additions & 115 deletions

File tree

src/compiler/builder.ts

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,28 @@ namespace ts {
66
emitSkipped: boolean;
77
}
88

9+
export interface EmitOutputDetailed extends EmitOutput {
10+
diagnostics: Diagnostic[];
11+
sourceMaps: SourceMapData[];
12+
emittedSourceFiles: SourceFile[];
13+
}
14+
915
export interface OutputFile {
1016
name: string;
1117
writeByteOrderMark: boolean;
1218
text: string;
1319
}
1420

15-
export interface Builder {
21+
export interface Builder<T extends EmitOutput> {
1622
/**
1723
* This is the callback when file infos in the builder are updated
1824
*/
1925
onProgramUpdateGraph(program: Program): void;
2026
getFilesAffectedBy(program: Program, path: Path): string[];
21-
emitFile(program: Program, path: Path): EmitOutput;
27+
emitFile(program: Program, path: Path): T | EmitOutput;
2228
clear(): void;
2329
}
2430

25-
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean,
26-
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
27-
const outputFiles: OutputFile[] = [];
28-
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
29-
return {
30-
outputFiles,
31-
emitSkipped: emitOutput.emitSkipped
32-
};
33-
34-
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
35-
outputFiles.push({ name: fileName, writeByteOrderMark, text });
36-
}
37-
}
38-
3931
interface EmitHandler {
4032
addScriptInfo(program: Program, sourceFile: SourceFile): void;
4133
removeScriptInfo(path: Path): void;
@@ -46,12 +38,49 @@ namespace ts {
4638
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
4739
}
4840

49-
export function createBuilder(
41+
export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
42+
cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed {
43+
return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles,
44+
cancellationToken, customTransformers) as EmitOutputDetailed;
45+
}
46+
47+
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
48+
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
49+
return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles,
50+
cancellationToken, customTransformers);
51+
}
52+
53+
function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
54+
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed {
55+
const outputFiles: OutputFile[] = [];
56+
let emittedSourceFiles: SourceFile[];
57+
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
58+
if (!isDetailed) {
59+
return { outputFiles, emitSkipped: emitResult.emitSkipped };
60+
}
61+
62+
return {
63+
outputFiles,
64+
emitSkipped: emitResult.emitSkipped,
65+
diagnostics: emitResult.diagnostics,
66+
sourceMaps: emitResult.sourceMaps,
67+
emittedSourceFiles
68+
};
69+
70+
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) {
71+
outputFiles.push({ name: fileName, writeByteOrderMark, text });
72+
if (isDetailed) {
73+
emittedSourceFiles = concatenate(emittedSourceFiles, sourceFiles);
74+
}
75+
}
76+
}
77+
78+
export function createBuilder<T extends EmitOutput>(
5079
getCanonicalFileName: (fileName: string) => string,
51-
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => EmitOutput,
80+
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T,
5281
computeHash: (data: string) => string,
5382
shouldEmitFile: (sourceFile: SourceFile) => boolean
54-
): Builder {
83+
): Builder<T> {
5584
let isModuleEmit: boolean | undefined;
5685
// Last checked shape signature for the file info
5786
let fileInfos: Map<string>;
@@ -108,7 +137,7 @@ namespace ts {
108137
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
109138
}
110139

111-
function emitFile(program: Program, path: Path): EmitOutput {
140+
function emitFile(program: Program, path: Path): T | EmitOutput {
112141
ensureProgramGraph(program);
113142
if (!fileInfos || !fileInfos.has(path)) {
114143
return { outputFiles: [], emitSkipped: true };

src/compiler/tsc.ts

Lines changed: 106 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
/// <reference path="commandLineParser.ts"/>
33

44
namespace ts {
5-
export interface SourceFile {
6-
fileWatcher?: FileWatcher;
5+
export interface CompilerHost {
6+
/** If this is the emit based on the graph builder, use it to emit */
7+
emitWithBuilder?(program: Program): EmitResult;
78
}
89

910
interface Statistic {
@@ -215,16 +216,17 @@ namespace ts {
215216

216217
function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>) {
217218
let program: Program;
218-
let needsReload: boolean;
219-
let missingFilesMap: Map<FileWatcher>;
220-
let configFileWatcher: FileWatcher;
221-
let watchedWildCardDirectories: Map<WildCardDirectoryWatchers>;
222-
let timerToUpdateProgram: any;
219+
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
220+
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
221+
let configFileWatcher: FileWatcher; // watcher for the config file
222+
let watchedWildCardDirectories: Map<WildCardDirectoryWatchers>; // map of watchers for the wild card directories in the config file
223+
let timerToUpdateProgram: any; // timer callback to recompile the program
223224

224225
let compilerOptions: CompilerOptions;
225226
let rootFileNames: string[];
226227

227-
const sourceFilesCache = createMap<HostFileInfo | string>();
228+
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
229+
let changedFilePaths: Path[] = [];
228230

229231
let host: System;
230232
if (configFileName) {
@@ -241,6 +243,9 @@ namespace ts {
241243
const currentDirectory = host.getCurrentDirectory();
242244
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
243245

246+
// There is no extra check needed since we can just rely on the program to decide emit
247+
const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true);
248+
244249
if (compilerOptions.pretty) {
245250
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
246251
}
@@ -268,85 +273,6 @@ namespace ts {
268273
}
269274

270275
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
271-
const existingDirectories = createMap<boolean>();
272-
function directoryExists(directoryPath: string): boolean {
273-
if (existingDirectories.has(directoryPath)) {
274-
return true;
275-
}
276-
if (host.directoryExists(directoryPath)) {
277-
existingDirectories.set(directoryPath, true);
278-
return true;
279-
}
280-
return false;
281-
}
282-
283-
function ensureDirectoriesExist(directoryPath: string) {
284-
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
285-
const parentDirectory = getDirectoryPath(directoryPath);
286-
ensureDirectoriesExist(parentDirectory);
287-
host.createDirectory(directoryPath);
288-
}
289-
}
290-
291-
type OutputFingerprint = {
292-
hash: string;
293-
byteOrderMark: boolean;
294-
mtime: Date;
295-
};
296-
let outputFingerprints: Map<OutputFingerprint>;
297-
298-
function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void {
299-
if (!outputFingerprints) {
300-
outputFingerprints = createMap<OutputFingerprint>();
301-
}
302-
303-
const hash = host.createHash(data);
304-
const mtimeBefore = host.getModifiedTime(fileName);
305-
306-
if (mtimeBefore) {
307-
const fingerprint = outputFingerprints.get(fileName);
308-
// If output has not been changed, and the file has no external modification
309-
if (fingerprint &&
310-
fingerprint.byteOrderMark === writeByteOrderMark &&
311-
fingerprint.hash === hash &&
312-
fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
313-
return;
314-
}
315-
}
316-
317-
host.writeFile(fileName, data, writeByteOrderMark);
318-
319-
const mtimeAfter = host.getModifiedTime(fileName);
320-
321-
outputFingerprints.set(fileName, {
322-
hash,
323-
byteOrderMark: writeByteOrderMark,
324-
mtime: mtimeAfter
325-
});
326-
}
327-
328-
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) {
329-
try {
330-
performance.mark("beforeIOWrite");
331-
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
332-
333-
//if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) {
334-
writeFileIfUpdated(fileName, data, writeByteOrderMark);
335-
//}
336-
//else {
337-
//host.writeFile(fileName, data, writeByteOrderMark);
338-
//}
339-
340-
performance.mark("afterIOWrite");
341-
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
342-
}
343-
catch (e) {
344-
if (onError) {
345-
onError(e.message);
346-
}
347-
}
348-
}
349-
350276
const newLine = getNewLineCharacter(options);
351277
const realpath = host.realpath && ((path: string) => host.realpath(path));
352278

@@ -355,7 +281,7 @@ namespace ts {
355281
getSourceFileByPath: getVersionedSourceFileByPath,
356282
getDefaultLibLocation,
357283
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
358-
writeFile,
284+
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
359285
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
360286
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames,
361287
getCanonicalFileName,
@@ -367,7 +293,8 @@ namespace ts {
367293
getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "",
368294
getDirectories: (path: string) => host.getDirectories(path),
369295
realpath,
370-
onReleaseOldSourceFile
296+
onReleaseOldSourceFile,
297+
emitWithBuilder
371298
};
372299

373300
// TODO: cache module resolution
@@ -379,6 +306,81 @@ namespace ts {
379306
// return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile);
380307
// };
381308
//}
309+
310+
function ensureDirectoriesExist(directoryPath: string) {
311+
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) {
312+
const parentDirectory = getDirectoryPath(directoryPath);
313+
ensureDirectoriesExist(parentDirectory);
314+
host.createDirectory(directoryPath);
315+
}
316+
}
317+
318+
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
319+
try {
320+
performance.mark("beforeIOWrite");
321+
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
322+
323+
host.writeFile(fileName, data, writeByteOrderMark);
324+
325+
performance.mark("afterIOWrite");
326+
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
327+
}
328+
catch (e) {
329+
return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e);
330+
}
331+
}
332+
333+
function emitWithBuilder(program: Program): EmitResult {
334+
builder.onProgramUpdateGraph(program);
335+
const filesPendingToEmit = changedFilePaths;
336+
changedFilePaths = [];
337+
338+
const seenFiles = createMap<true>();
339+
340+
let emitSkipped: boolean;
341+
let diagnostics: Diagnostic[];
342+
const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined;
343+
let sourceMaps: SourceMapData[];
344+
while (filesPendingToEmit.length) {
345+
const filePath = filesPendingToEmit.pop();
346+
const affectedFiles = builder.getFilesAffectedBy(program, filePath);
347+
for (const file of affectedFiles) {
348+
if (!seenFiles.has(file)) {
349+
seenFiles.set(file, true);
350+
const sourceFile = program.getSourceFile(file);
351+
if (sourceFile) {
352+
writeFiles(<EmitOutputDetailed>builder.emitFile(program, sourceFile.path));
353+
}
354+
}
355+
}
356+
}
357+
358+
return { emitSkipped, diagnostics, emittedFiles, sourceMaps };
359+
360+
function writeFiles(emitOutput: EmitOutputDetailed) {
361+
if (emitOutput.emitSkipped) {
362+
emitSkipped = true;
363+
}
364+
365+
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
366+
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
367+
// If it emitted more than one source files, just mark all those source files as seen
368+
if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) {
369+
for (const file of emitOutput.emittedSourceFiles) {
370+
seenFiles.set(file.fileName, true);
371+
}
372+
}
373+
for (const outputFile of emitOutput.outputFiles) {
374+
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
375+
if (error) {
376+
(diagnostics || (diagnostics = [])).push(error);
377+
}
378+
if (emittedFiles) {
379+
emittedFiles.push(outputFile.name);
380+
}
381+
}
382+
}
383+
}
382384
}
383385

384386
function fileExists(fileName: string) {
@@ -408,6 +410,7 @@ namespace ts {
408410

409411
// Create new source file if requested or the versions dont match
410412
if (!hostSourceFile) {
413+
changedFilePaths.push(path);
411414
const sourceFile = getSourceFile(fileName, languageVersion, onError);
412415
if (sourceFile) {
413416
sourceFile.version = "0";
@@ -420,6 +423,7 @@ namespace ts {
420423
return sourceFile;
421424
}
422425
else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
426+
changedFilePaths.push(path);
423427
if (shouldCreateNewSourceFile) {
424428
hostSourceFile.version++;
425429
}
@@ -652,6 +656,10 @@ namespace ts {
652656
host.write(s);
653657
}
654658
}
659+
660+
function computeHash(data: string) {
661+
return sys.createHash ? sys.createHash(data) : data;
662+
}
655663
}
656664

657665
interface CachedSystem extends System {
@@ -806,16 +814,20 @@ namespace ts {
806814
}
807815
}
808816

809-
// TODO: in watch mode to emit only affected files
810-
811-
// Otherwise, emit and report any errors we ran into.
812-
const emitOutput = program.emit();
817+
// Emit and report any errors we ran into.
818+
let emitOutput: EmitResult;
819+
if (compilerHost.emitWithBuilder) {
820+
emitOutput = compilerHost.emitWithBuilder(program);
821+
}
822+
else {
823+
// Emit whole program
824+
emitOutput = program.emit();
825+
}
813826
diagnostics = diagnostics.concat(emitOutput.diagnostics);
814827

815828
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost);
816829

817830
reportEmittedFiles(emitOutput.emittedFiles);
818-
819831
if (emitOutput.emitSkipped && diagnostics.length > 0) {
820832
// If the emitter didn't emit anything, then pass that value along.
821833
return ExitStatus.DiagnosticsPresent_OutputsSkipped;

0 commit comments

Comments
 (0)