Use builder to emit the files from the tsc.js

This commit is contained in:
Sheetal Nandi 2017-07-24 16:57:49 -07:00
parent 6237b221da
commit 9b18f7b61c
3 changed files with 156 additions and 115 deletions

View File

@ -6,36 +6,28 @@ namespace ts {
emitSkipped: boolean;
}
export interface EmitOutputDetailed extends EmitOutput {
diagnostics: Diagnostic[];
sourceMaps: SourceMapData[];
emittedSourceFiles: SourceFile[];
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
export interface Builder {
export interface Builder<T extends EmitOutput> {
/**
* This is the callback when file infos in the builder are updated
*/
onProgramUpdateGraph(program: Program): void;
getFilesAffectedBy(program: Program, path: Path): string[];
emitFile(program: Program, path: Path): EmitOutput;
emitFile(program: Program, path: Path): T | EmitOutput;
clear(): void;
}
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
const outputFiles: OutputFile[] = [];
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
return {
outputFiles,
emitSkipped: emitOutput.emitSkipped
};
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
}
}
interface EmitHandler {
addScriptInfo(program: Program, sourceFile: SourceFile): void;
removeScriptInfo(path: Path): void;
@ -46,12 +38,49 @@ namespace ts {
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
}
export function createBuilder(
export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed {
return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles,
cancellationToken, customTransformers) as EmitOutputDetailed;
}
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles,
cancellationToken, customTransformers);
}
function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed {
const outputFiles: OutputFile[] = [];
let emittedSourceFiles: SourceFile[];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
if (!isDetailed) {
return { outputFiles, emitSkipped: emitResult.emitSkipped };
}
return {
outputFiles,
emitSkipped: emitResult.emitSkipped,
diagnostics: emitResult.diagnostics,
sourceMaps: emitResult.sourceMaps,
emittedSourceFiles
};
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
if (isDetailed) {
emittedSourceFiles = concatenate(emittedSourceFiles, sourceFiles);
}
}
}
export function createBuilder<T extends EmitOutput>(
getCanonicalFileName: (fileName: string) => string,
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => EmitOutput,
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T,
computeHash: (data: string) => string,
shouldEmitFile: (sourceFile: SourceFile) => boolean
): Builder {
): Builder<T> {
let isModuleEmit: boolean | undefined;
// Last checked shape signature for the file info
let fileInfos: Map<string>;
@ -108,7 +137,7 @@ namespace ts {
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
}
function emitFile(program: Program, path: Path): EmitOutput {
function emitFile(program: Program, path: Path): T | EmitOutput {
ensureProgramGraph(program);
if (!fileInfos || !fileInfos.has(path)) {
return { outputFiles: [], emitSkipped: true };

View File

@ -2,8 +2,9 @@
/// <reference path="commandLineParser.ts"/>
namespace ts {
export interface SourceFile {
fileWatcher?: FileWatcher;
export interface CompilerHost {
/** If this is the emit based on the graph builder, use it to emit */
emitWithBuilder?(program: Program): EmitResult;
}
interface Statistic {
@ -215,16 +216,17 @@ namespace ts {
function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>) {
let program: Program;
let needsReload: boolean;
let missingFilesMap: Map<FileWatcher>;
let configFileWatcher: FileWatcher;
let watchedWildCardDirectories: Map<WildCardDirectoryWatchers>;
let timerToUpdateProgram: any;
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
let configFileWatcher: FileWatcher; // watcher for the config file
let watchedWildCardDirectories: Map<WildCardDirectoryWatchers>; // map of watchers for the wild card directories in the config file
let timerToUpdateProgram: any; // timer callback to recompile the program
let compilerOptions: CompilerOptions;
let rootFileNames: string[];
const sourceFilesCache = createMap<HostFileInfo | string>();
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
let changedFilePaths: Path[] = [];
let host: System;
if (configFileName) {
@ -241,6 +243,9 @@ namespace ts {
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
// There is no extra check needed since we can just rely on the program to decide emit
const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true);
if (compilerOptions.pretty) {
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
}
@ -268,85 +273,6 @@ namespace ts {
}
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
const existingDirectories = createMap<boolean>();
function directoryExists(directoryPath: string): boolean {
if (existingDirectories.has(directoryPath)) {
return true;
}
if (host.directoryExists(directoryPath)) {
existingDirectories.set(directoryPath, true);
return true;
}
return false;
}
function ensureDirectoriesExist(directoryPath: string) {
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
const parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
host.createDirectory(directoryPath);
}
}
type OutputFingerprint = {
hash: string;
byteOrderMark: boolean;
mtime: Date;
};
let outputFingerprints: Map<OutputFingerprint>;
function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void {
if (!outputFingerprints) {
outputFingerprints = createMap<OutputFingerprint>();
}
const hash = host.createHash(data);
const mtimeBefore = host.getModifiedTime(fileName);
if (mtimeBefore) {
const fingerprint = outputFingerprints.get(fileName);
// If output has not been changed, and the file has no external modification
if (fingerprint &&
fingerprint.byteOrderMark === writeByteOrderMark &&
fingerprint.hash === hash &&
fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
return;
}
}
host.writeFile(fileName, data, writeByteOrderMark);
const mtimeAfter = host.getModifiedTime(fileName);
outputFingerprints.set(fileName, {
hash,
byteOrderMark: writeByteOrderMark,
mtime: mtimeAfter
});
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) {
try {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
//if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) {
writeFileIfUpdated(fileName, data, writeByteOrderMark);
//}
//else {
//host.writeFile(fileName, data, writeByteOrderMark);
//}
performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
}
catch (e) {
if (onError) {
onError(e.message);
}
}
}
const newLine = getNewLineCharacter(options);
const realpath = host.realpath && ((path: string) => host.realpath(path));
@ -355,7 +281,7 @@ namespace ts {
getSourceFileByPath: getVersionedSourceFileByPath,
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile,
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames,
getCanonicalFileName,
@ -367,7 +293,8 @@ namespace ts {
getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => host.getDirectories(path),
realpath,
onReleaseOldSourceFile
onReleaseOldSourceFile,
emitWithBuilder
};
// TODO: cache module resolution
@ -379,6 +306,81 @@ namespace ts {
// return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile);
// };
//}
function ensureDirectoriesExist(directoryPath: string) {
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) {
const parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
host.createDirectory(directoryPath);
}
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
try {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
host.writeFile(fileName, data, writeByteOrderMark);
performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
}
catch (e) {
return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e);
}
}
function emitWithBuilder(program: Program): EmitResult {
builder.onProgramUpdateGraph(program);
const filesPendingToEmit = changedFilePaths;
changedFilePaths = [];
const seenFiles = createMap<true>();
let emitSkipped: boolean;
let diagnostics: Diagnostic[];
const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined;
let sourceMaps: SourceMapData[];
while (filesPendingToEmit.length) {
const filePath = filesPendingToEmit.pop();
const affectedFiles = builder.getFilesAffectedBy(program, filePath);
for (const file of affectedFiles) {
if (!seenFiles.has(file)) {
seenFiles.set(file, true);
const sourceFile = program.getSourceFile(file);
if (sourceFile) {
writeFiles(<EmitOutputDetailed>builder.emitFile(program, sourceFile.path));
}
}
}
}
return { emitSkipped, diagnostics, emittedFiles, sourceMaps };
function writeFiles(emitOutput: EmitOutputDetailed) {
if (emitOutput.emitSkipped) {
emitSkipped = true;
}
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
// If it emitted more than one source files, just mark all those source files as seen
if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) {
for (const file of emitOutput.emittedSourceFiles) {
seenFiles.set(file.fileName, true);
}
}
for (const outputFile of emitOutput.outputFiles) {
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
if (error) {
(diagnostics || (diagnostics = [])).push(error);
}
if (emittedFiles) {
emittedFiles.push(outputFile.name);
}
}
}
}
}
function fileExists(fileName: string) {
@ -408,6 +410,7 @@ namespace ts {
// Create new source file if requested or the versions dont match
if (!hostSourceFile) {
changedFilePaths.push(path);
const sourceFile = getSourceFile(fileName, languageVersion, onError);
if (sourceFile) {
sourceFile.version = "0";
@ -420,6 +423,7 @@ namespace ts {
return sourceFile;
}
else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
changedFilePaths.push(path);
if (shouldCreateNewSourceFile) {
hostSourceFile.version++;
}
@ -652,6 +656,10 @@ namespace ts {
host.write(s);
}
}
function computeHash(data: string) {
return sys.createHash ? sys.createHash(data) : data;
}
}
interface CachedSystem extends System {
@ -806,16 +814,20 @@ namespace ts {
}
}
// TODO: in watch mode to emit only affected files
// Otherwise, emit and report any errors we ran into.
const emitOutput = program.emit();
// Emit and report any errors we ran into.
let emitOutput: EmitResult;
if (compilerHost.emitWithBuilder) {
emitOutput = compilerHost.emitWithBuilder(program);
}
else {
// Emit whole program
emitOutput = program.emit();
}
diagnostics = diagnostics.concat(emitOutput.diagnostics);
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost);
reportEmittedFiles(emitOutput.emittedFiles);
if (emitOutput.emitSkipped && diagnostics.length > 0) {
// If the emitter didn't emit anything, then pass that value along.
return ExitStatus.DiagnosticsPresent_OutputsSkipped;

View File

@ -130,7 +130,7 @@ namespace ts.server {
/*@internal*/
lsHost: LSHost;
builder: Builder;
builder: Builder<EmitOutput>;
/**
* Set of files names that were updated since the last call to getChangesSinceVersion.
*/