Make SolutionBuilder handle BuilderProgram in preparation to handle incremental builds

This commit is contained in:
Sheetal Nandi 2018-12-18 16:12:37 -08:00
parent 9fcfa28c40
commit 5d6ecb5436
11 changed files with 252 additions and 220 deletions

View File

@ -1391,6 +1391,14 @@ namespace ts {
return result;
}
export function copyProperities<T1 extends T2, T2>(first: T1, second: T2) {
for (const id in second) {
if (hasOwnProperty.call(second, id)) {
(first as any)[id] = second[id];
}
}
}
export interface MultiMap<T> extends Map<T[]> {
/**
* Adds the value to an array of values associated with the key, and returns the array.

View File

@ -69,6 +69,7 @@ namespace ts {
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
return createCompilerHostWorker(options, setParentNodes);
}
/*@internal*/
// TODO(shkamat): update this after reworking ts build API
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
@ -93,7 +94,6 @@ namespace ts {
}
text = "";
}
return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined;
}
@ -203,18 +203,25 @@ namespace ts {
return compilerHost;
}
interface ComplierHostLikeForCache {
fileExists(fileName: string): boolean;
readFile(fileName: string, encoding?: string): string | undefined;
directoryExists?(directory: string): boolean;
createDirectory?(directory: string): void;
writeFile?: WriteFileCallback;
}
/*@internal*/
export function changeCompilerHostToUseCache(
host: CompilerHost,
export function changeCompilerHostLikeToUseCache(
host: ComplierHostLikeForCache,
toPath: (fileName: string) => Path,
useCacheForSourceFile: boolean
getSourceFile?: CompilerHost["getSourceFile"]
) {
const originalReadFile = host.readFile;
const originalFileExists = host.fileExists;
const originalDirectoryExists = host.directoryExists;
const originalCreateDirectory = host.createDirectory;
const originalWriteFile = host.writeFile;
const originalGetSourceFile = host.getSourceFile;
const readFileCache = createMap<string | false>();
const fileExistsCache = createMap<boolean>();
const directoryExistsCache = createMap<boolean>();
@ -242,19 +249,17 @@ namespace ts {
return setReadFileCache(key, fileName);
};
if (useCacheForSourceFile) {
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
const key = toPath(fileName);
const value = sourceFileCache.get(key);
if (value) return value;
const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
const key = toPath(fileName);
const value = sourceFileCache.get(key);
if (value) return value;
const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
sourceFileCache.set(key, sourceFile);
}
return sourceFile;
};
}
const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
sourceFileCache.set(key, sourceFile);
}
return sourceFile;
} : undefined;
// fileExists for any kind of extension
host.fileExists = fileName => {
@ -265,23 +270,25 @@ namespace ts {
fileExistsCache.set(key, !!newValue);
return newValue;
};
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
const key = toPath(fileName);
fileExistsCache.delete(key);
if (originalWriteFile) {
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
const key = toPath(fileName);
fileExistsCache.delete(key);
const value = readFileCache.get(key);
if (value && value !== data) {
readFileCache.delete(key);
sourceFileCache.delete(key);
}
else if (useCacheForSourceFile) {
const sourceFile = sourceFileCache.get(key);
if (sourceFile && sourceFile.text !== data) {
const value = readFileCache.get(key);
if (value && value !== data) {
readFileCache.delete(key);
sourceFileCache.delete(key);
}
}
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
};
else if (getSourceFileWithCache) {
const sourceFile = sourceFileCache.get(key);
if (sourceFile && sourceFile.text !== data) {
sourceFileCache.delete(key);
}
}
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
};
}
// directoryExists
if (originalDirectoryExists && originalCreateDirectory) {
@ -306,7 +313,7 @@ namespace ts {
originalDirectoryExists,
originalCreateDirectory,
originalWriteFile,
originalGetSourceFile,
getSourceFileWithCache,
readFileWithCache
};
}
@ -735,7 +742,7 @@ namespace ts {
performance.mark("beforeProgram");
const host = createProgramOptions.host || createCompilerHost(options);
const configParsingHost = parseConfigHostFromCompilerHost(host);
const configParsingHost = parseConfigHostFromCompilerHostLike(host);
let skipDefaultLib = options.noLib;
const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options));
@ -3104,18 +3111,28 @@ namespace ts {
}
}
interface CompilerHostLike {
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
fileExists(fileName: string): boolean;
readFile(fileName: string): string | undefined;
readDirectory?(rootDir: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, depth?: number): string[];
trace?(s: string): void;
onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter;
}
/* @internal */
export function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigFileHost {
export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost {
return {
fileExists: f => host.fileExists(f),
readDirectory(root, extensions, excludes, includes, depth) {
Debug.assertDefined(host.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
return host.readDirectory!(root, extensions, excludes, includes, depth);
Debug.assertDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
return directoryStructureHost.readDirectory!(root, extensions, excludes, includes, depth);
},
readFile: f => host.readFile(f),
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
getCurrentDirectory: () => host.getCurrentDirectory(),
onUnRecoverableConfigFileDiagnostic: () => undefined,
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || (() => undefined),
trace: host.trace ? (s) => host.trace!(s) : undefined
};
}

View File

@ -321,7 +321,7 @@ namespace ts {
return fileExtensionIs(fileName, Extension.Dts);
}
export interface SolutionBuilderHostBase extends CompilerHost {
export interface SolutionBuilderHostBase<T extends BuilderProgram> extends ProgramHost<T> {
getModifiedTime(fileName: string): Date | undefined;
setModifiedTime(fileName: string, date: Date): void;
deleteFile(fileName: string): void;
@ -331,15 +331,14 @@ namespace ts {
// TODO: To do better with watch mode and normal build mode api that creates program and emits files
// This currently helps enable --diagnostics and --extendedDiagnostics
beforeCreateProgram?(options: CompilerOptions): void;
afterProgramEmitAndDiagnostics?(program: Program): void;
}
export interface SolutionBuilderHost extends SolutionBuilderHostBase {
export interface SolutionBuilderHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T> {
reportErrorSummary?: ReportEmitErrorSummary;
}
export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost {
export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost {
}
export interface SolutionBuilder {
@ -372,30 +371,29 @@ namespace ts {
};
}
function createSolutionBuilderHostBase(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
const host = createCompilerHostWorker({}, /*setParentNodes*/ undefined, system) as SolutionBuilderHostBase;
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system);
host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system);
return host;
// TODO after program create
}
export function createSolutionBuilderHost(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost;
export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
const host = createSolutionBuilderHostBase(system, createProgram || createAbstractBuilder as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
host.reportErrorSummary = reportErrorSummary;
return host;
}
export function createSolutionBuilderWithWatchHost(system?: System, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost;
// TODO: we should use emit and semantic diagnostics builder but that needs to handle errors little differently so handle it later
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = SemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
const host = createSolutionBuilderHostBase(system, createProgram || createSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
const watchHost = createWatchHost(system, reportWatchStatus);
host.onWatchStatusChange = watchHost.onWatchStatusChange;
host.watchFile = watchHost.watchFile;
host.watchDirectory = watchHost.watchDirectory;
host.setTimeout = watchHost.setTimeout;
host.clearTimeout = watchHost.clearTimeout;
copyProperities(host, watchHost);
return host;
}
@ -413,13 +411,13 @@ namespace ts {
* TODO: use SolutionBuilderWithWatchHost => watchedSolution
* use SolutionBuilderHost => Solution
*/
export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
const hostWithWatch = host as SolutionBuilderWithWatchHost;
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
const hostWithWatch = host as SolutionBuilderWithWatchHost<T>;
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
const parseConfigFileHost = parseConfigHostFromCompilerHost(host);
const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host);
// State of the solution
let options = defaultOptions;
@ -434,6 +432,8 @@ namespace ts {
let globalDependencyGraph: DependencyGraph | undefined;
const writeFileName = (s: string) => host.trace && host.trace(s);
let readFileWithCache = (f: string) => host.readFile(f);
let projectCompilerOptions = baseCompilerOptions;
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
// Watch state
const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
@ -919,7 +919,7 @@ namespace ts {
}
function reportErrorSummary() {
if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) {
if (options.watch || (host as SolutionBuilderHost<T>).reportErrorSummary) {
// Report errors from the other projects
getGlobalDependencyGraph().buildQueue.forEach(project => {
if (!projectErrorsReported.hasKey(project)) {
@ -932,7 +932,7 @@ namespace ts {
reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors);
}
else {
(host as SolutionBuilderHost).reportErrorSummary!(totalErrors);
(host as SolutionBuilderHost<T>).reportErrorSummary!(totalErrors);
}
}
}
@ -1051,17 +1051,17 @@ namespace ts {
return BuildResultFlags.None;
}
const programOptions: CreateProgramOptions = {
projectReferences: configFile.projectReferences,
host,
rootNames: configFile.fileNames,
options: configFile.options,
configFileParsingDiagnostics: configFile.errors
};
if (host.beforeCreateProgram) {
host.beforeCreateProgram(options);
}
const program = createProgram(programOptions);
// TODO: handle resolve module name to cache result in project reference redirect
projectCompilerOptions = configFile.options;
const program = host.createProgram(
configFile.fileNames,
configFile.options,
compilerHost,
/*oldProgram*/ undefined,
configFile.errors,
configFile.projectReferences
);
projectCompilerOptions = baseCompilerOptions;
// Don't emit anything in the presence of syntactic errors or options diagnostics
const syntaxDiagnostics = [
@ -1105,7 +1105,7 @@ namespace ts {
}
}
writeFile(host, emitterDiagnostics, name, text, writeByteOrderMark);
writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
if (priorChangeTime !== undefined) {
newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime);
unchangedOutputs.setValue(name, priorChangeTime);
@ -1209,12 +1209,15 @@ namespace ts {
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
// TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api
// Override readFile for json files and output .d.ts to cache the text
const { originalReadFile, originalFileExists, originalDirectoryExists,
originalCreateDirectory, originalWriteFile, originalGetSourceFile,
readFileWithCache: newReadFileWithCache
} = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true);
const savedReadFileWithCache = readFileWithCache;
const savedGetSourceFile = compilerHost.getSourceFile;
const { originalReadFile, originalFileExists, originalDirectoryExists,
originalCreateDirectory, originalWriteFile, getSourceFileWithCache,
readFileWithCache: newReadFileWithCache
} = changeCompilerHostLikeToUseCache(host, toPath, (...args) => savedGetSourceFile.call(compilerHost, ...args));
readFileWithCache = newReadFileWithCache;
compilerHost.getSourceFile = getSourceFileWithCache!;
const graph = getGlobalDependencyGraph();
reportBuildQueue(graph);
@ -1271,8 +1274,8 @@ namespace ts {
host.directoryExists = originalDirectoryExists;
host.createDirectory = originalCreateDirectory;
host.writeFile = originalWriteFile;
compilerHost.getSourceFile = savedGetSourceFile;
readFileWithCache = savedReadFileWithCache;
host.getSourceFile = originalGetSourceFile;
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
}
@ -1300,7 +1303,7 @@ namespace ts {
}
function relName(path: string): string {
return convertToRelativePath(path, host.getCurrentDirectory(), f => host.getCanonicalFileName(f));
return convertToRelativePath(path, host.getCurrentDirectory(), f => compilerHost.getCanonicalFileName(f));
}
/**

View File

@ -2827,7 +2827,7 @@ namespace ts {
fileName: string,
data: string,
writeByteOrderMark: boolean,
onError: ((message: string) => void) | undefined,
onError?: (message: string) => void,
sourceFiles?: ReadonlyArray<SourceFile>,
) => void;

View File

@ -203,23 +203,81 @@ namespace ts {
return result;
}
export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
return {
getSourceFile: (fileName, languageVersion, onError) => {
let text: string | undefined;
try {
performance.mark("beforeIORead");
text = host.readFile(fileName, getCompilerOptions().charset);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
catch (e) {
if (onError) {
onError(e.message);
}
text = "";
}
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
},
getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation!()),
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
writeFile,
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
getNewLine: memoize(() => getNewLineCharacter(getCompilerOptions(), () => host.getNewLine())),
fileExists: f => host.fileExists(f),
readFile: f => host.readFile(f),
trace: host.trace && (s => host.trace!(s)),
directoryExists: directoryStructureHost.directoryExists && (path => directoryStructureHost.directoryExists!(path)),
getDirectories: (directoryStructureHost.getDirectories && ((path: string) => directoryStructureHost.getDirectories!(path)))!, // TODO: GH#18217
realpath: host.realpath && (s => host.realpath!(s)),
getEnvironmentVariable: host.getEnvironmentVariable ? (name => host.getEnvironmentVariable!(name)) : (() => ""),
createHash: host.createHash && (data => host.createHash!(data)),
readDirectory: (path, extensions, exclude, include, depth?) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
};
function ensureDirectoriesExist(directoryPath: string) {
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) {
const parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
if (host.createDirectory) host.createDirectory(directoryPath);
}
}
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
try {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
host.writeFile!(fileName, text, writeByteOrderMark);
performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
}
catch (e) {
if (onError) {
onError(e.message);
}
}
}
}
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*/
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
if (!createProgram) {
createProgram = createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>;
}
export function createProgramHost<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>): ProgramHost<T> {
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
let host: DirectoryStructureHost = system;
host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames;
const writeFileName = (s: string) => system.write(s + system.newLine);
const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus);
return {
useCaseSensitiveFileNames,
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getNewLine: () => system.newLine,
getCurrentDirectory: () => system.getCurrentDirectory(),
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
fileExists: path => system.fileExists(path),
@ -229,25 +287,23 @@ namespace ts {
readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth),
realpath: system.realpath && (path => system.realpath!(path)),
getEnvironmentVariable: system.getEnvironmentVariable && (name => system.getEnvironmentVariable(name)),
watchFile,
watchDirectory,
setTimeout,
clearTimeout,
trace: s => system.write(s + system.newLine),
onWatchStatusChange,
createDirectory: path => system.createDirectory(path),
writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
onCachedDirectoryStructureHostCreate: cacheHost => host = cacheHost || system,
createHash: system.createHash && (s => system.createHash!(s)),
createProgram,
afterProgramCreate: emitFilesAndReportErrorUsingBuilder
createProgram
};
}
function getDefaultLibLocation() {
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
}
function emitFilesAndReportErrorUsingBuilder(builderProgram: BuilderProgram) {
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*/
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
const writeFileName = (s: string) => system.write(s + system.newLine);
const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>) as WatchCompilerHost<T>;
copyProperities(result, createWatchHost(system, reportWatchStatus));
result.afterProgramCreate = builderProgram => {
const compilerOptions = builderProgram.getCompilerOptions();
const newLine = getNewLineCharacter(compilerOptions, () => system.newLine);
@ -255,13 +311,14 @@ namespace ts {
builderProgram,
reportDiagnostic,
writeFileName,
errorCount => onWatchStatusChange!(
errorCount => result.onWatchStatusChange!(
createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(errorCount), errorCount),
newLine,
compilerOptions
)
);
}
};
return result;
}
/**
@ -300,6 +357,7 @@ namespace ts {
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference> | undefined) => T;
/** Host that has watch functionality used in --watch mode */
export interface WatchHost {
/** If provided, called with Diagnostic message that informs about change in watch status */
@ -314,19 +372,11 @@ namespace ts {
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
export interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
// TODO: GH#18217 Optional methods are frequently asserted
export interface ProgramHost<T extends BuilderProgram> {
/**
* Used to create the program when need for program creation or recreation detected
*/
createProgram: CreateProgram<T>;
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
// Only for testing
/*@internal*/
maxNumberOfFilesToIterateForInvalidation?: number;
// Sub set of compiler host methods to read and generate new program
useCaseSensitiveFileNames(): boolean;
@ -366,16 +416,25 @@ namespace ts {
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
}
/** Internal interface used to wire emit through same host */
/*@internal*/
export interface WatchCompilerHost<T extends BuilderProgram> {
export interface ProgramHost<T extends BuilderProgram> {
// TODO: GH#18217 Optional methods are frequently asserted
createDirectory?(path: string): void;
writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void;
}
export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
// Only for testing
/*@internal*/
maxNumberOfFilesToIterateForInvalidation?: number;
}
/**
* Host to create watch with root files and options
*/
@ -488,8 +547,6 @@ namespace ts {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
const currentDirectory = host.getCurrentDirectory();
const getCurrentDirectory = () => currentDirectory;
const readFile: (path: string, encoding?: string) => string | undefined = (path, encoding) => host.readFile(path, encoding);
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host;
let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host;
let configFileSpecs: ConfigFileSpecs;
@ -502,15 +559,7 @@ namespace ts {
host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost);
}
const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host;
const parseConfigFileHost: ParseConfigFileHost = {
useCaseSensitiveFileNames,
readDirectory: (path, extensions, exclude, include, depth) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
fileExists: path => host.fileExists(path),
readFile,
getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic,
trace: host.trace ? s => host.trace!(s) : undefined
};
const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost);
// From tsc we want to get already parsed result and hence check for rootFileNames
let newLine = updateNewLine();
@ -534,42 +583,29 @@ namespace ts {
watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
}
const compilerHost: CompilerHost & ResolutionCacheHost = {
// Members for CompilerHost
getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile),
getSourceFileByPath: getVersionedSourceFileByPath,
getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation!()),
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
writeFile,
getCurrentDirectory,
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine,
fileExists,
readFile,
trace: host.trace && (s => host.trace!(s)),
directoryExists: directoryStructureHost.directoryExists && (path => directoryStructureHost.directoryExists!(path)),
getDirectories: (directoryStructureHost.getDirectories && ((path: string) => directoryStructureHost.getDirectories!(path)))!, // TODO: GH#18217
realpath: host.realpath && (s => host.realpath!(s)),
getEnvironmentVariable: host.getEnvironmentVariable ? (name => host.getEnvironmentVariable!(name)) : (() => ""),
onReleaseOldSourceFile,
createHash: host.createHash && (data => host.createHash!(data)),
// Members for ResolutionCacheHost
toPath,
getCompilationSettings: () => compilerOptions,
watchDirectoryOfFailedLookupLocation: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations),
watchTypeRootsDirectory: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots),
getCachedDirectoryStructureHost: () => cachedDirectoryStructureHost,
onInvalidatedResolution: scheduleProgramUpdate,
onChangedAutomaticTypeDirectiveNames: () => {
hasChangedAutomaticTypeDirectiveNames = true;
scheduleProgramUpdate();
},
maxNumberOfFilesToIterateForInvalidation: host.maxNumberOfFilesToIterateForInvalidation,
getCurrentProgram,
writeLog,
readDirectory: (path, extensions, exclude, include, depth?) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
// Members for CompilerHost
const getNewSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args);
compilerHost.getSourceFileByPath = getVersionedSourceFileByPath;
compilerHost.getNewLine = () => newLine;
compilerHost.fileExists = fileExists;
compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile;
// Members for ResolutionCacheHost
compilerHost.toPath = toPath;
compilerHost.getCompilationSettings = () => compilerOptions;
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots);
compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
compilerHost.onChangedAutomaticTypeDirectiveNames = () => {
hasChangedAutomaticTypeDirectiveNames = true;
scheduleProgramUpdate();
};
compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation;
compilerHost.getCurrentProgram = getCurrentProgram;
compilerHost.writeLog = writeLog;
// Cache for the module resolution
const resolutionCache = createResolutionCache(compilerHost, configFileName ?
getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
@ -712,7 +748,7 @@ namespace ts {
// Create new source file if requested or the versions dont match
if (!hostSourceFile || shouldCreateNewSourceFile || !isFilePresentOnHost(hostSourceFile) || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
const sourceFile = getNewSourceFile();
const sourceFile = getNewSourceFile.call(compilerHost, fileName, languageVersion, onError);
if (hostSourceFile) {
if (shouldCreateNewSourceFile) {
hostSourceFile.version++;
@ -747,23 +783,6 @@ namespace ts {
return sourceFile;
}
return hostSourceFile.sourceFile;
function getNewSourceFile() {
let text: string | undefined;
try {
performance.mark("beforeIORead");
text = host.readFile(fileName, compilerOptions.charset);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
catch (e) {
if (onError) {
onError(e.message);
}
}
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
}
}
function nextSourceFileVersion(path: Path) {
@ -978,30 +997,5 @@ namespace ts {
WatchType.WildcardDirectory
);
}
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, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
try {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
host.writeFile!(fileName, text, writeByteOrderMark);
performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
}
catch (e) {
if (onError) {
onError(e.message);
}
}
}
}
}

View File

@ -375,7 +375,9 @@ namespace fakes {
}
}
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost {
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
createProgram = ts.createAbstractBuilder;
diagnostics: ts.Diagnostic[] = [];
reportDiagnostic(diagnostic: ts.Diagnostic) {

View File

@ -85,7 +85,7 @@ namespace ts {
// We shouldn't have any errors about invalid tsconfig files in these tests
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHost(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
file.options.configFilePath = entryPointConfigFileName;
const prog = createProgram({
rootNames: file.fileNames,

View File

@ -265,7 +265,7 @@ export class cNew {}`);
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildInvalidatedProject();
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
});
});

View File

@ -206,9 +206,9 @@ namespace ts {
// TODO: change this to host if watch => watchHost otherwiue without watch
const buildHost = buildOptions.watch ?
createSolutionBuilderWithWatchHost(sys, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
createSolutionBuilderHost(sys, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions));
buildHost.beforeCreateProgram = enableStatistics;
createSolutionBuilderWithWatchHost(sys, createSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
createSolutionBuilderHost(sys, createAbstractBuilder, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions));
updateCreateProgram(buildHost);
buildHost.afterProgramEmitAndDiagnostics = reportStatistics;
const builder = createSolutionBuilder(buildHost, projects, buildOptions);
@ -234,7 +234,7 @@ namespace ts {
const host = createCompilerHost(options);
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
changeCompilerHostToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName), /*useCacheForSourceFile*/ false);
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
enableStatistics(options);
const programOptions: CreateProgramOptions = {
@ -255,15 +255,19 @@ namespace ts {
return sys.exit(exitStatus);
}
function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
const compileUsingBuilder = watchCompilerHost.createProgram;
watchCompilerHost.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
function updateCreateProgram<T extends BuilderProgram>(host: { createProgram: CreateProgram<T>; }) {
const compileUsingBuilder = host.createProgram;
host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram));
if (options !== undefined) {
enableStatistics(options);
}
return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
};
}
function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
updateCreateProgram(watchCompilerHost);
const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217
watchCompilerHost.afterProgramCreate = builderProgram => {
emitFilesUsingBuilder(builderProgram);

View File

@ -1778,7 +1778,7 @@ declare namespace ts {
type ResolvedConfigFileName = string & {
_isResolvedConfigFileName: never;
};
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles?: ReadonlyArray<SourceFile>) => void;
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
class OperationCanceledException {
}
interface CancellationToken {
@ -4333,13 +4333,11 @@ declare namespace ts {
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
interface ProgramHost<T extends BuilderProgram> {
/**
* Used to create the program when need for program creation or recreation detected
*/
createProgram: CreateProgram<T>;
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
getCurrentDirectory(): string;
@ -4373,6 +4371,10 @@ declare namespace ts {
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
}
interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
}
/**
* Host to create watch with root files and options
*/

View File

@ -1778,7 +1778,7 @@ declare namespace ts {
type ResolvedConfigFileName = string & {
_isResolvedConfigFileName: never;
};
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles?: ReadonlyArray<SourceFile>) => void;
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
class OperationCanceledException {
}
interface CancellationToken {
@ -4333,13 +4333,11 @@ declare namespace ts {
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
interface ProgramHost<T extends BuilderProgram> {
/**
* Used to create the program when need for program creation or recreation detected
*/
createProgram: CreateProgram<T>;
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
getCurrentDirectory(): string;
@ -4373,6 +4371,10 @@ declare namespace ts {
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
}
interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
}
/**
* Host to create watch with root files and options
*/