From 0cabb00b343f4acfee112eaaa4ea737383b494c2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 14 Dec 2018 15:13:19 -0800 Subject: [PATCH 01/21] Use watch factory instead of direct host functions in tsbuild to provide detailed information on watch invokations --- src/compiler/tsbuild.ts | 59 +++++++++++++++++++++++----------- src/compiler/watch.ts | 49 +++++++++++++++++++--------- src/compiler/watchUtilities.ts | 8 ++--- src/server/editorServices.ts | 19 ++--------- src/server/project.ts | 4 +-- src/server/utilities.ts | 11 +++++++ 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f1e194e307d4f..8b06070a925e5 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -443,6 +443,7 @@ namespace ts { let nextProjectToBuild = 0; let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; + const { watchFile, watchFilePath, watchDirectory } = createWatchFactory(host, options); // Watches for the solution const allWatchedWildcardDirectories = createFileMap>(toPath); @@ -542,9 +543,16 @@ namespace ts { function watchConfigFile(resolved: ResolvedConfigFileName) { if (options.watch && !allWatchedConfigFiles.hasKey(resolved)) { - allWatchedConfigFiles.setValue(resolved, hostWithWatch.watchFile(resolved, () => { + allWatchedConfigFiles.setValue(resolved, watchFile( + hostWithWatch, + resolved, + () => { invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full); - })); + }, + PollingInterval.High, + WatchType.ConfigFile, + resolved + )); } } @@ -554,20 +562,27 @@ namespace ts { getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolved), createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), (dir, flags) => { - return hostWithWatch.watchDirectory(dir, fileOrDirectory => { - const fileOrDirectoryPath = toPath(fileOrDirectory); - if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { - // writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); - return; - } - - if (isOutputFile(fileOrDirectory, parsed)) { - // writeLog(`${fileOrDirectory} is output file`); - return; - } - - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial); - }, !!(flags & WatchDirectoryFlags.Recursive)); + return watchDirectory( + hostWithWatch, + dir, + fileOrDirectory => { + const fileOrDirectoryPath = toPath(fileOrDirectory); + if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { + // writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); + return; + } + + if (isOutputFile(fileOrDirectory, parsed)) { + // writeLog(`${fileOrDirectory} is output file`); + return; + } + + invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial); + }, + flags, + WatchType.WildcardDirectory, + resolved + ); } ); } @@ -578,9 +593,15 @@ namespace ts { getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolved), arrayToMap(parsed.fileNames, toPath), { - createNewValue: (_key, input) => hostWithWatch.watchFile(input, () => { - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None); - }), + createNewValue: (path, input) => watchFilePath( + hostWithWatch, + input, + () => invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None), + PollingInterval.Low, + path as Path, + WatchType.SourceFile, + resolved + ), onDeleteValue: closeFileWatcher, } ); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 7a5c12cae4e6c..4112271307c8f 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -194,6 +194,30 @@ namespace ts { }; } + export const enum WatchType { + ConfigFile = "Config file", + SourceFile = "Source file", + MissingFile = "Missing file", + WildcardDirectory = "Wild card directory", + FailedLookupLocations = "Failed Lookup Locations", + TypeRoots = "Type roots" + } + + interface WatchFactory extends ts.WatchFactory { + watchLogLevel: WatchLogLevel; + writeLog: (s: string) => void; + } + + export function createWatchFactory(host: { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) { + const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : + options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None; + const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop; + const result = getWatchFactory(watchLogLevel, writeLog) as WatchFactory; + result.watchLogLevel = watchLogLevel; + result.writeLog = writeLog; + return result; + } + /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ @@ -224,7 +248,7 @@ namespace ts { watchDirectory, setTimeout, clearTimeout, - trace: s => system.write(s), + trace: s => system.write(s + system.newLine), onWatchStatusChange, createDirectory: path => system.createDirectory(path), writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), @@ -517,17 +541,12 @@ namespace ts { newLine = updateNewLine(); } - const trace = host.trace && ((s: string) => { host.trace!(s + newLine); }); - const watchLogLevel = trace ? compilerOptions.extendedDiagnostics ? WatchLogLevel.Verbose : - compilerOptions.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None; - const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? trace! : noop; // TODO: GH#18217 - const { watchFile, watchFilePath, watchDirectory } = getWatchFactory(watchLogLevel, writeLog); - + const { watchFile, watchFilePath, watchDirectory, watchLogLevel, writeLog } = createWatchFactory(host, compilerOptions); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`); if (configFileName) { - watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, "Config file"); + watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile); } const compilerHost: CompilerHost & ResolutionCacheHost = { @@ -543,7 +562,7 @@ namespace ts { getNewLine: () => newLine, fileExists, readFile, - trace, + 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)), @@ -553,8 +572,8 @@ namespace ts { // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, - watchDirectoryOfFailedLookupLocation: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, "Failed Lookup Locations"), - watchTypeRootsDirectory: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, "Type roots"), + 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: () => { @@ -719,7 +738,7 @@ namespace ts { (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!(hostSourceFile as FilePresentOnHost).fileWatcher) { - (hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, "Source file"); + (hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); } } else { @@ -733,7 +752,7 @@ namespace ts { else { if (sourceFile) { sourceFile.version = initialVersion.toString(); - const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, "Source file"); + const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); sourceFilesCache.set(path, { sourceFile, version: initialVersion, fileWatcher }); } else { @@ -907,7 +926,7 @@ namespace ts { } function watchMissingFilePath(missingFilePath: Path) { - return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, "Missing file"); + return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, WatchType.MissingFile); } function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { @@ -971,7 +990,7 @@ namespace ts { } }, flags, - "Wild card directories" + WatchType.WildcardDirectory ); } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 8fa04e52da3f3..9bf242e07e82c 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -343,10 +343,10 @@ namespace ts { export interface WatchDirectoryHost { watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; } - export type WatchFile = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; + export type WatchFile = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1: X, detailInfo2?: Y) => FileWatcher; export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; - export type WatchFilePath = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; - export type WatchDirectory = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; + export type WatchFilePath = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher; + export type WatchDirectory = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1: X, detailInfo2?: Y) => FileWatcher; export interface WatchFactory { watchFile: WatchFile; @@ -444,7 +444,7 @@ namespace ts { } function getWatchInfo(file: string, flags: T, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo | undefined) { - return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo1}`; + return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`; } export function closeFileWatcherOf(objWithWatcher: T) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3050c9762313e..7256e46d5778a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -332,19 +332,6 @@ namespace ts.server { } } - /* @internal */ - export const enum WatchType { - ConfigFilePath = "Config file for the program", - MissingFilePath = "Missing file from program", - WildcardDirectories = "Wild card directory", - ClosedScriptInfo = "Closed Script info", - ConfigFileForInferredRoot = "Config file for the inferred project root", - FailedLookupLocation = "Directory of Failed lookup locations in module resolution", - TypeRoots = "Type root directory", - NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", - MissingSourceMapFile = "Missing source map file" - } - const enum ConfigFileWatcherStatus { ReloadingFiles = "Reloading configured projects for files", ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files", @@ -1035,7 +1022,7 @@ namespace ts.server { } }, flags, - WatchType.WildcardDirectories, + WatchType.WildcardDirectory, project ); } @@ -1338,7 +1325,7 @@ namespace ts.server { watches.push(WatchType.ConfigFileForInferredRoot); } if (this.configuredProjects.has(canonicalConfigFilePath)) { - watches.push(WatchType.ConfigFilePath); + watches.push(WatchType.ConfigFile); } this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } @@ -1705,7 +1692,7 @@ namespace ts.server { configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind), PollingInterval.High, - WatchType.ConfigFilePath, + WatchType.ConfigFile, project ); this.configuredProjects.set(project.canonicalConfigFilePath, project); diff --git a/src/server/project.ts b/src/server/project.ts index e3069e1d183eb..b296668e6ae4c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -428,7 +428,7 @@ namespace ts.server { directory, cb, flags, - WatchType.FailedLookupLocation, + WatchType.FailedLookupLocations, this ); } @@ -989,7 +989,7 @@ namespace ts.server { } }, PollingInterval.Medium, - WatchType.MissingFilePath, + WatchType.MissingFile, this ); return fileWatcher; diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 15b217822c0b6..04ce9f4e4200a 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -217,3 +217,14 @@ namespace ts.server { return indentStr + JSON.stringify(json); } } + +/* @internal */ +namespace ts { + // Additional tsserver specific watch information + export const enum WatchType { + ClosedScriptInfo = "Closed Script info", + ConfigFileForInferredRoot = "Config file for the inferred project root", + NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", + MissingSourceMapFile = "Missing source map file", + } +} From 9e05abcfd3f8bb3d6775144ede807daceab2e321 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 14 Dec 2018 16:51:18 -0800 Subject: [PATCH 02/21] Make BuilderProgram as Program --- src/compiler/builder.ts | 85 +++++-------------- src/compiler/core.ts | 4 + src/compiler/program.ts | 2 +- src/compiler/tsbuild.ts | 3 +- src/compiler/watch.ts | 17 +--- .../reference/api/tsserverlibrary.d.ts | 34 +------- tests/baselines/reference/api/typescript.d.ts | 34 +------- 7 files changed, 30 insertions(+), 149 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 637e77c545f60..ce12373960a8d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -411,21 +411,13 @@ namespace ts { oldProgram = undefined; oldState = undefined; - const result: BuilderProgram = { - getState: () => state, - getProgram: () => state.program, - getCompilerOptions: () => state.program.getCompilerOptions(), - getSourceFile: fileName => state.program.getSourceFile(fileName), - getSourceFiles: () => state.program.getSourceFiles(), - getOptionsDiagnostics: cancellationToken => state.program.getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => state.program.getGlobalDiagnostics(cancellationToken), - getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics || state.program.getConfigFileParsingDiagnostics(), - getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics, - emit, - getAllDependencies: sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile), - getCurrentDirectory: () => state.program.getCurrentDirectory() - }; + const result = createRedirectObject(state.program) as BuilderProgram; + result.getState = () => state; + result.getProgram = () => state.program; + result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile); + result.getConfigFileParsingDiagnostics = () => configFileParsingDiagnostics; + result.getSemanticDiagnostics = getSemanticDiagnostics; + result.emit = emit; if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { (result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; @@ -595,45 +587,20 @@ namespace ts { /** * Builder to manage the program state changes */ - export interface BuilderProgram { + export interface BuilderProgram extends Program { /*@internal*/ getState(): BuilderProgramState; /** * Returns current program */ getProgram(): Program; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): ReadonlyArray; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): ReadonlyArray; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + + // These two are same signatures but because the doc comments are useful they are retained + /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here @@ -655,10 +622,6 @@ namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; } /** @@ -710,22 +673,14 @@ namespace ts { export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray): BuilderProgram; export function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram { - const { newProgram: program } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return { - // Only return program, all other methods are not implemented - getProgram: () => program, - getState: notImplemented, - getCompilerOptions: notImplemented, - getSourceFile: notImplemented, - getSourceFiles: notImplemented, - getOptionsDiagnostics: notImplemented, - getGlobalDiagnostics: notImplemented, - getConfigFileParsingDiagnostics: notImplemented, - getSyntacticDiagnostics: notImplemented, - getSemanticDiagnostics: notImplemented, - emit: notImplemented, - getAllDependencies: notImplemented, - getCurrentDirectory: notImplemented - }; + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); + const builderProgram = createRedirectObject(newProgram) as BuilderProgram; + builderProgram.getState = notImplemented; + builderProgram.getProgram = () => newProgram; + builderProgram.getAllDependencies = notImplemented; + + // Always return latest config file diagnostics + builderProgram.getConfigFileParsingDiagnostics = () => newConfigFileParsingDiagnostics; + return builderProgram; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cb9e385b3667a..76bf4a314b6fb 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1370,6 +1370,10 @@ namespace ts { return result; } + export function createRedirectObject(redirectTarget: T): T { + return Object.create(redirectTarget); + } + export function extend(first: T1, second: T2): T1 & T2 { const result: T1 & T2 = {}; for (const id in second) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 080b17b8cbe0d..b388e70353ea6 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2180,7 +2180,7 @@ namespace ts { } function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { - const redirect: SourceFile = Object.create(redirectTarget); + const redirect = createRedirectObject(redirectTarget); redirect.fileName = fileName; redirect.path = path; redirect.resolvedPath = resolvedPath; diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 8b06070a925e5..47d8106a457de 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1065,8 +1065,9 @@ namespace ts { // Don't emit anything in the presence of syntactic errors or options diagnostics const syntaxDiagnostics = [ - ...program.getOptionsDiagnostics(), ...program.getConfigFileParsingDiagnostics(), + ...program.getOptionsDiagnostics(), + ...program.getGlobalDiagnostics(), ...program.getSyntacticDiagnostics()]; if (syntaxDiagnostics.length) { return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic"); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 4112271307c8f..2fbda3fde8a5d 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -88,21 +88,6 @@ namespace ts { return result; } - /** - * Program structure needed to emit the files and report diagnostics - */ - export interface ProgramToEmitFilesAndReportErrors { - getCurrentDirectory(): string; - getCompilerOptions(): CompilerOptions; - getSourceFiles(): ReadonlyArray; - getSyntacticDiagnostics(): ReadonlyArray; - getOptionsDiagnostics(): ReadonlyArray; - getGlobalDiagnostics(): ReadonlyArray; - getSemanticDiagnostics(): ReadonlyArray; - getConfigFileParsingDiagnostics(): ReadonlyArray; - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult; - } - export type ReportEmitErrorSummary = (errorCount: number) => void; export function getErrorCountForSummary(diagnostics: ReadonlyArray) { @@ -124,7 +109,7 @@ namespace ts { /** * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options */ - export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { + export function emitFilesAndReportErrors(program: Program, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { // First get and report any syntactic errors. const diagnostics = program.getConfigFileParsingDiagnostics().slice(); const configFileParsingDiagnosticsLength = diagnostics.length; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 91f492ddd1d52..d5b2c020e7441 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4245,39 +4245,11 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BuilderProgram { + interface BuilderProgram extends Program { /** * Returns current program */ getProgram(): Program; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): ReadonlyArray; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): ReadonlyArray; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ @@ -4303,10 +4275,6 @@ declare namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0e693f698f253..3d6e4feb106c2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4245,39 +4245,11 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BuilderProgram { + interface BuilderProgram extends Program { /** * Returns current program */ getProgram(): Program; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): ReadonlyArray; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): ReadonlyArray; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ @@ -4303,10 +4275,6 @@ declare namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files From 48baa42d655aa1fdea1ab3dcb9ee3a7e15b7de91 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 18 Dec 2018 16:12:37 -0800 Subject: [PATCH 03/21] Make SolutionBuilder handle BuilderProgram in preparation to handle incremental builds --- src/compiler/core.ts | 8 + src/compiler/program.ts | 91 ++++--- src/compiler/tsbuild.ts | 81 +++--- src/compiler/types.ts | 2 +- src/compiler/watch.ts | 242 +++++++++--------- src/harness/fakes.ts | 4 +- .../unittests/config/projectReferences.ts | 2 +- src/testRunner/unittests/tsbuild.ts | 2 +- src/tsc/tsc.ts | 18 +- .../reference/api/tsserverlibrary.d.ts | 10 +- tests/baselines/reference/api/typescript.d.ts | 10 +- 11 files changed, 251 insertions(+), 219 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 76bf4a314b6fb..17f6c6f4fc340 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1391,6 +1391,14 @@ namespace ts { return result; } + export function copyProperities(first: T1, second: T2) { + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (first as any)[id] = second[id]; + } + } + } + export interface MultiMap extends Map { /** * Adds the value to an array of values associated with the key, and returns the array. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b388e70353ea6..6c132ad417d8e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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(); const fileExistsCache = createMap(); const directoryExistsCache = createMap(); @@ -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)); @@ -3101,18 +3108,28 @@ namespace ts { } } + interface CompilerHostLike { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + readDirectory?(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, 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 }; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 47d8106a457de..bc0a2f8768062 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -321,7 +321,7 @@ namespace ts { return fileExtensionIs(fileName, Extension.Dts); } - export interface SolutionBuilderHostBase extends CompilerHost { + export interface SolutionBuilderHostBase extends ProgramHost { 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 extends SolutionBuilderHostBase { reportErrorSummary?: ReportEmitErrorSummary; } - export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { + export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, 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(system: System, createProgram: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) { + const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase; 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(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { + const host = createSolutionBuilderHostBase(system, createProgram || createAbstractBuilder as any as CreateProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost; 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(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { + const host = createSolutionBuilderHostBase(system, createProgram || createSemanticDiagnosticsBuilderProgram as any as CreateProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; 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, defaultOptions: BuildOptions): SolutionBuilder; - export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; - export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { - const hostWithWatch = host as SolutionBuilderWithWatchHost; + export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; + export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { + const hostWithWatch = host as SolutionBuilderWithWatchHost; 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>(toPath); @@ -919,7 +919,7 @@ namespace ts { } function reportErrorSummary() { - if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) { + if (options.watch || (host as SolutionBuilderHost).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).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 savedReadFileWithCache = readFileWithCache; + const savedGetSourceFile = compilerHost.getSourceFile; + const { originalReadFile, originalFileExists, originalDirectoryExists, - originalCreateDirectory, originalWriteFile, originalGetSourceFile, + originalCreateDirectory, originalWriteFile, getSourceFileWithCache, readFileWithCache: newReadFileWithCache - } = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true); - const savedReadFileWithCache = readFileWithCache; + } = 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)); } /** diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b70b4e4d3d087..de444eea885f2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2827,7 +2827,7 @@ namespace ts { fileName: string, data: string, writeByteOrderMark: boolean, - onError: ((message: string) => void) | undefined, + onError?: (message: string) => void, sourceFiles?: ReadonlyArray, ) => void; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 2fbda3fde8a5d..a29d54ac98105 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -203,23 +203,81 @@ namespace ts { return result; } + export function createCompilerHostFromProgramHost(host: ProgramHost, 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(system = sys, createProgram: CreateProgram | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost { - if (!createProgram) { - createProgram = createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; - } - + export function createProgramHost(system: System, createProgram: CreateProgram): ProgramHost { + 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(system = sys, createProgram: CreateProgram | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost { + const writeFileName = (s: string) => system.write(s + system.newLine); + const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram) as WatchCompilerHost; + 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 = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray | 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 extends WatchHost { - // TODO: GH#18217 Optional methods are frequently asserted - + export interface ProgramHost { /** * Used to create the program when need for program creation or recreation detected */ createProgram: CreateProgram; - /** 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 { + export interface ProgramHost { // 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 extends ProgramHost, 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); - } - } - } } } diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index d25211d36d3a6..6119dd153b9c6 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -375,7 +375,9 @@ namespace fakes { } } - export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { + export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { + createProgram = ts.createAbstractBuilder; + diagnostics: ts.Diagnostic[] = []; reportDiagnostic(diagnostic: ts.Diagnostic) { diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index 266b016c681bf..6c8863314fa05 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -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, diff --git a/src/testRunner/unittests/tsbuild.ts b/src/testRunner/unittests/tsbuild.ts index bf628e10598d9..4a3f259cc3452 100644 --- a/src/testRunner/unittests/tsbuild.ts +++ b/src/testRunner/unittests/tsbuild.ts @@ -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"); }); }); diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 9d363dc471538..a9d961a14d6c3 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -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) { - const compileUsingBuilder = watchCompilerHost.createProgram; - watchCompilerHost.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => { + function updateCreateProgram(host: { createProgram: CreateProgram; }) { + 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) { + updateCreateProgram(watchCompilerHost); const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217 watchCompilerHost.afterProgramCreate = builderProgram => { emitFilesUsingBuilder(builderProgram); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d5b2c020e7441..3b1e45b0bfa45 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -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) => void; + type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray) => void; class OperationCanceledException { } interface CancellationToken { @@ -4332,13 +4332,11 @@ declare namespace ts { /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; } - interface WatchCompilerHost extends WatchHost { + interface ProgramHost { /** * Used to create the program when need for program creation or recreation detected */ createProgram: CreateProgram; - /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(program: T): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -4372,6 +4370,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 extends ProgramHost, WatchHost { + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(program: T): void; + } /** * Host to create watch with root files and options */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3d6e4feb106c2..4e1b769378e51 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -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) => void; + type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray) => void; class OperationCanceledException { } interface CancellationToken { @@ -4332,13 +4332,11 @@ declare namespace ts { /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; } - interface WatchCompilerHost extends WatchHost { + interface ProgramHost { /** * Used to create the program when need for program creation or recreation detected */ createProgram: CreateProgram; - /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(program: T): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -4372,6 +4370,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 extends ProgramHost, WatchHost { + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(program: T): void; + } /** * Host to create watch with root files and options */ From 56a76d8b62de56d65559c6391312c96e1b24d635 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 19 Dec 2018 13:44:47 -0800 Subject: [PATCH 04/21] Revert BuilderProgram to be redirected object to Program in preparation to set Program in state to undefined for storing. --- src/compiler/builder.ts | 72 +++++++++++++++---- src/compiler/tsbuild.ts | 2 +- src/compiler/watch.ts | 17 ++++- src/tsc/tsc.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 38 +++++++++- tests/baselines/reference/api/typescript.d.ts | 38 +++++++++- 6 files changed, 150 insertions(+), 19 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ce12373960a8d..14db0b367e277 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -411,11 +411,9 @@ namespace ts { oldProgram = undefined; oldState = undefined; - const result = createRedirectObject(state.program) as BuilderProgram; + const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); result.getState = () => state; - result.getProgram = () => state.program; result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile); - result.getConfigFileParsingDiagnostics = () => configFileParsingDiagnostics; result.getSemanticDiagnostics = getSemanticDiagnostics; result.emit = emit; @@ -563,6 +561,25 @@ namespace ts { return diagnostics || emptyArray; } } + + export function createRedirectedBuilderProgram(state: { program: Program; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { + return { + getState: notImplemented, + getProgram: () => state.program, + getCompilerOptions: () => state.program.getCompilerOptions(), + getSourceFile: fileName => state.program.getSourceFile(fileName), + getSourceFiles: () => state.program.getSourceFiles(), + getOptionsDiagnostics: cancellationToken => state.program.getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => state.program.getGlobalDiagnostics(cancellationToken), + getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, + getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => state.program.getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => state.program.getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => state.program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + getAllDependencies: notImplemented, + getCurrentDirectory: () => state.program.getCurrentDirectory() + }; + } } namespace ts { @@ -587,20 +604,50 @@ namespace ts { /** * Builder to manage the program state changes */ - export interface BuilderProgram extends Program { + export interface BuilderProgram { /*@internal*/ getState(): BuilderProgramState; /** * Returns current program */ getProgram(): Program; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ getAllDependencies(sourceFile: SourceFile): ReadonlyArray; - // These two are same signatures but because the doc comments are useful they are retained - /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here @@ -622,6 +669,10 @@ namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; } /** @@ -674,13 +725,6 @@ namespace ts { export function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram { const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - const builderProgram = createRedirectObject(newProgram) as BuilderProgram; - builderProgram.getState = notImplemented; - builderProgram.getProgram = () => newProgram; - builderProgram.getAllDependencies = notImplemented; - - // Always return latest config file diagnostics - builderProgram.getConfigFileParsingDiagnostics = () => newConfigFileParsingDiagnostics; - return builderProgram; + return createRedirectedBuilderProgram({ program: newProgram }, newConfigFileParsingDiagnostics); } } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index bc0a2f8768062..ca3108616a63a 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -331,7 +331,7 @@ 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 - afterProgramEmitAndDiagnostics?(program: Program): void; + afterProgramEmitAndDiagnostics?(program: T): void; } export interface SolutionBuilderHost extends SolutionBuilderHostBase { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index a29d54ac98105..cc6d1236fed6f 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -106,10 +106,25 @@ namespace ts { return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}`; } + /** + * Program structure needed to emit the files and report diagnostics + */ + export interface ProgramToEmitFilesAndReportErrors { + getCurrentDirectory(): string; + getCompilerOptions(): CompilerOptions; + getSourceFiles(): ReadonlyArray; + getSyntacticDiagnostics(): ReadonlyArray; + getOptionsDiagnostics(): ReadonlyArray; + getGlobalDiagnostics(): ReadonlyArray; + getSemanticDiagnostics(): ReadonlyArray; + getConfigFileParsingDiagnostics(): ReadonlyArray; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult; + } + /** * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options */ - export function emitFilesAndReportErrors(program: Program, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { + export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { // First get and report any syntactic errors. const diagnostics = program.getConfigFileParsingDiagnostics().slice(); const configFileParsingDiagnosticsLength = diagnostics.length; diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index a9d961a14d6c3..cafd06ca4175d 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -209,7 +209,7 @@ namespace ts { createSolutionBuilderWithWatchHost(sys, createSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) : createSolutionBuilderHost(sys, createAbstractBuilder, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions)); updateCreateProgram(buildHost); - buildHost.afterProgramEmitAndDiagnostics = reportStatistics; + buildHost.afterProgramEmitAndDiagnostics = (program: BuilderProgram) => reportStatistics(program.getProgram()); const builder = createSolutionBuilder(buildHost, projects, buildOptions); if (buildOptions.clean) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3b1e45b0bfa45..aee6b01eaff40 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4245,11 +4245,43 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BuilderProgram extends Program { + interface BuilderProgram { /** * Returns current program */ getProgram(): Program; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ @@ -4275,6 +4307,10 @@ declare namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4e1b769378e51..ca4177d87cc77 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4245,11 +4245,43 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BuilderProgram extends Program { + interface BuilderProgram { /** * Returns current program */ getProgram(): Program; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ @@ -4275,6 +4307,10 @@ declare namespace ts { * in that order would be used to write the files */ emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files From 69193d9c20223d0feb5f5d8d475ddceb580842ca Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 19 Dec 2018 14:19:05 -0800 Subject: [PATCH 05/21] Add method to release held Program in BuilderProgram --- src/compiler/builder.ts | 79 +++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 14db0b367e277..b0bd840c61077 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -49,7 +49,11 @@ namespace ts { /** * program corresponding to this state */ - program: Program; + program: Program | undefined; + /** + * compilerOptions for the program + */ + compilerOptions: CompilerOptions; } function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined): boolean { @@ -64,13 +68,14 @@ namespace ts { const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState; state.program = newProgram; const compilerOptions = newProgram.getCompilerOptions(); + state.compilerOptions = compilerOptions; if (!compilerOptions.outFile && !compilerOptions.out) { state.semanticDiagnosticsPerFile = createMap>(); } state.changedFilesSet = createMap(); const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); - const oldCompilerOptions = useOldState ? oldState!.program.getCompilerOptions() : undefined; + const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); if (useOldState) { @@ -109,7 +114,7 @@ namespace ts { state.changedFilesSet.set(sourceFilePath, true); } else if (canCopySemanticDiagnostics) { - const sourceFile = state.program.getSourceFileByPath(sourceFilePath as Path)!; + const sourceFile = newProgram.getSourceFileByPath(sourceFilePath as Path)!; if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; } if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; } @@ -179,10 +184,11 @@ namespace ts { // With --out or --outFile all outputs go into single file // so operations are performed directly on program, return program - const compilerOptions = state.program.getCompilerOptions(); + const program = Debug.assertDefined(state.program); + const compilerOptions = program.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(!state.semanticDiagnosticsPerFile); - return state.program; + return program; } // Get next batch of affected files @@ -190,7 +196,7 @@ namespace ts { if (state.exportedModulesMap) { state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap(); } - state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); state.currentChangedFilePath = nextKey.value as Path; state.affectedFilesIndex = 0; state.seenAffectedFiles = state.seenAffectedFiles || createMap(); @@ -209,9 +215,10 @@ namespace ts { // Clean lib file diagnostics if its all files excluding default files to emit if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles && !state.cleanedDiagnosticsOfLibFiles) { state.cleanedDiagnosticsOfLibFiles = true; - const options = state.program.getCompilerOptions(); - if (forEach(state.program.getSourceFiles(), f => - state.program.isSourceFileDefaultLibrary(f) && + const program = Debug.assertDefined(state.program); + const options = program.getCompilerOptions(); + if (forEach(program.getSourceFiles(), f => + program.isSourceFileDefaultLibrary(f) && !skipTypeChecking(f, options) && removeSemanticDiagnosticsOf(state, f.path) )) { @@ -336,7 +343,7 @@ namespace ts { } // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = state.program.getSemanticDiagnostics(sourceFile, cancellationToken); + const diagnostics = Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); state.semanticDiagnosticsPerFile!.set(path, diagnostics); return diagnostics; } @@ -370,7 +377,7 @@ namespace ts { rootNames: newProgramOrRootNames, options: hostOrOptions as CompilerOptions, host: oldProgramOrHost as CompilerHost, - oldProgram: oldProgram && oldProgram.getProgram(), + oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), configFileParsingDiagnostics, projectReferences }); @@ -413,7 +420,7 @@ namespace ts { const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); result.getState = () => state; - result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile); + result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile); result.getSemanticDiagnostics = getSemanticDiagnostics; result.emit = emit; @@ -445,7 +452,7 @@ namespace ts { state, // When whole program is affected, do emit only once (eg when --out or --outFile is specified) // Otherwise just affected file - state.program.emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers), + Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers), affected ); } @@ -486,7 +493,7 @@ namespace ts { }; } } - return state.program.emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + return Debug.assertDefined(state.program).emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); } /** @@ -534,11 +541,11 @@ namespace ts { */ function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); - const compilerOptions = state.program.getCompilerOptions(); + const compilerOptions = Debug.assertDefined(state.program).getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(!state.semanticDiagnosticsPerFile); // We dont need to cache the diagnostics just return them from program - return state.program.getSemanticDiagnostics(sourceFile, cancellationToken); + return Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); } if (sourceFile) { @@ -555,29 +562,31 @@ namespace ts { } let diagnostics: Diagnostic[] | undefined; - for (const sourceFile of state.program.getSourceFiles()) { + for (const sourceFile of Debug.assertDefined(state.program).getSourceFiles()) { diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } } - export function createRedirectedBuilderProgram(state: { program: Program; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { + export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { return { getState: notImplemented, - getProgram: () => state.program, - getCompilerOptions: () => state.program.getCompilerOptions(), - getSourceFile: fileName => state.program.getSourceFile(fileName), - getSourceFiles: () => state.program.getSourceFiles(), - getOptionsDiagnostics: cancellationToken => state.program.getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => state.program.getGlobalDiagnostics(cancellationToken), + getProgram: () => Debug.assertDefined(state.program), + getProgramOrUndefined: () => state.program, + releaseProgram: () => state.program = undefined, + getCompilerOptions: () => state.compilerOptions, + getSourceFile: fileName => Debug.assertDefined(state.program).getSourceFile(fileName), + getSourceFiles: () => Debug.assertDefined(state.program).getSourceFiles(), + getOptionsDiagnostics: cancellationToken => Debug.assertDefined(state.program).getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => Debug.assertDefined(state.program).getGlobalDiagnostics(cancellationToken), getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, - getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken), - getDeclarationDiagnostics: (sourceFile, cancellationToken) => state.program.getDeclarationDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics: (sourceFile, cancellationToken) => state.program.getSemanticDiagnostics(sourceFile, cancellationToken), - emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => state.program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => Debug.assertDefined(state.program).emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), getAllDependencies: notImplemented, - getCurrentDirectory: () => state.program.getCurrentDirectory() + getCurrentDirectory: () => Debug.assertDefined(state.program).getCurrentDirectory() }; } } @@ -611,6 +620,16 @@ namespace ts { * Returns current program */ getProgram(): Program; + /** + * Returns current program that could be undefined if the program was released + */ + /*@internal*/ + getProgramOrUndefined(): Program | undefined; + /** + * Releases reference to the program, making all the other operations that need program to fail. + */ + /*@internal*/ + releaseProgram(): void; /** * Get compiler options of the program */ @@ -725,6 +744,6 @@ namespace ts { export function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram { const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return createRedirectedBuilderProgram({ program: newProgram }, newConfigFileParsingDiagnostics); + return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics); } } From 47f51060e9c2deb555b49a9e071538de4aff2a08 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 19 Dec 2018 15:24:36 -0800 Subject: [PATCH 06/21] Use oldProgram to create the new Program. This helps in storing the semantic diagnostics --- src/compiler/builder.ts | 2 +- src/compiler/builderState.ts | 4 +-- src/compiler/sys.ts | 19 ++++++++------- src/compiler/tsbuild.ts | 47 ++++++++++++++++++++++++++++++------ 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index b0bd840c61077..28a85f7432020 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -410,7 +410,7 @@ namespace ts { /** * Computing hash to for signature verification */ - const computeHash = host.createHash || identity; + const computeHash = host.createHash || generateDjb2Hash; const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); // To ensure that we arent storing any references to old program or new program without state diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 7c7bebde9f9be..0462beada9e7b 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -505,14 +505,14 @@ namespace ts.BuilderState { // Start with the paths this file was referenced by seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.path); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); while (queue.length > 0) { const currentPath = queue.pop()!; if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; seenFileNamesMap.set(currentPath, currentSourceFile); if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217 - queue.push(...getReferencedByPaths(state, currentPath)); + queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 8ee1e4571b367..35e9a9ec35fd0 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -2,6 +2,16 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number): declare function clearTimeout(handle: any): void; namespace ts { + /** + * djb2 hashing algorithm + * http://www.cse.yorku.ca/~oz/hash.html + */ + /* @internal */ + export function generateDjb2Hash(data: string): string { + const chars = data.split("").map(str => str.charCodeAt(0)); + return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`; + } + /** * Set a high stack trace limit to provide more information in case of an error. * Called for command-line and server use cases. @@ -1115,15 +1125,6 @@ namespace ts { } } - /** - * djb2 hashing algorithm - * http://www.cse.yorku.ca/~oz/hash.html - */ - function generateDjb2Hash(data: string): string { - const chars = data.split("").map(str => str.charCodeAt(0)); - return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`; - } - function createMD5HashUsingNativeCrypto(data: string): string { const hash = _crypto!.createHash("md5"); hash.update(data); diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index ca3108616a63a..b833b7744e95a 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -434,8 +434,12 @@ namespace ts { let readFileWithCache = (f: string) => host.readFile(f); let projectCompilerOptions = baseCompilerOptions; const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions); + const originalGetSourceFile = compilerHost.getSourceFile; + const computeHash = host.createHash || generateDjb2Hash; + updateGetSourceFile(); // Watch state + const builderPrograms = createFileMap(toPath); const diagnostics = createFileMap>(toPath); const projectPendingBuild = createFileMap(toPath); const projectErrorsReported = createFileMap(toPath); @@ -493,6 +497,29 @@ namespace ts { clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf)); clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher)); clearMap(allWatchedConfigFiles, closeFileWatcher); + if (!options.watch) { + builderPrograms.clear(); + } + updateGetSourceFile(); + } + + function updateGetSourceFile() { + if (options.watch) { + if (compilerHost.getSourceFile === originalGetSourceFile) { + compilerHost.getSourceFile = (...args) => { + const result = originalGetSourceFile.call(compilerHost, ...args); + if (result && options.watch) { + result.version = computeHash.call(host, result.text); + } + return result; + }; + } + } + else { + if (compilerHost.getSourceFile !== originalGetSourceFile) { + compilerHost.getSourceFile = originalGetSourceFile; + } + } } function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { @@ -1057,7 +1084,7 @@ namespace ts { configFile.fileNames, configFile.options, compilerHost, - /*oldProgram*/ undefined, + builderPrograms.getValue(proj), configFile.errors, configFile.projectReferences ); @@ -1123,22 +1150,28 @@ namespace ts { }; diagnostics.removeKey(proj); projectStatus.setValue(proj, status); - if (host.afterProgramEmitAndDiagnostics) { - host.afterProgramEmitAndDiagnostics(program); - } + afterProgramCreate(proj, program); return resultFlags; function buildErrors(diagnostics: ReadonlyArray, errorFlags: BuildResultFlags, errorType: string) { resultFlags |= errorFlags; reportAndStoreErrors(proj, diagnostics); projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); - if (host.afterProgramEmitAndDiagnostics) { - host.afterProgramEmitAndDiagnostics(program); - } + afterProgramCreate(proj, program); return resultFlags; } } + function afterProgramCreate(proj: ResolvedConfigFileName, program: T) { + if (host.afterProgramEmitAndDiagnostics) { + host.afterProgramEmitAndDiagnostics(program); + } + if (options.watch) { + program.releaseProgram(); + builderPrograms.setValue(proj, program); + } + } + function updateOutputTimestamps(proj: ParsedCommandLine) { if (options.dry) { return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!); From f1949bbae824fb22dad38efc9e6c6b929945301f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 19 Dec 2018 17:08:36 -0800 Subject: [PATCH 07/21] Use emit builder to emit only changed files. --- src/compiler/builder.ts | 89 +++++++++++++++++++++++++++++++++++------ src/compiler/tsbuild.ts | 3 +- src/tsc/tsc.ts | 3 +- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 28a85f7432020..df3b056227f69 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -54,6 +54,18 @@ namespace ts { * compilerOptions for the program */ compilerOptions: CompilerOptions; + /** + * Files pending to be emitted + */ + affectedFilesPendingEmit: ReadonlyArray | undefined; + /** + * Current index to retrieve pending affected file + */ + affectedFilesPendingEmitIndex: number | undefined; + /** + * Already seen affected files + */ + seenEmittedFiles: Map | undefined; } function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined): boolean { @@ -89,6 +101,10 @@ namespace ts { // Copy old state's changed files set copyEntries(oldState!.changedFilesSet, state.changedFilesSet); + if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) { + state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit; + state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; + } } // Update changed files and copy semantic diagnostics if we can @@ -203,6 +219,27 @@ namespace ts { } } + /** + * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet + */ + function getNextAffectedFilePendingEmit(state: BuilderProgramState): SourceFile | undefined { + const { affectedFilesPendingEmit } = state; + if (affectedFilesPendingEmit) { + const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap()); + for (let affectedFilesIndex = state.affectedFilesPendingEmitIndex!; affectedFilesIndex < affectedFilesPendingEmit.length; affectedFilesIndex++) { + const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[affectedFilesIndex]); + if (affectedFile && !seenEmittedFiles.has(affectedFile.path)) { + // emit this file + state.affectedFilesPendingEmitIndex = affectedFilesIndex; + return affectedFile; + } + } + state.affectedFilesPendingEmit = undefined; + state.affectedFilesPendingEmitIndex = undefined; + } + return undefined; + } + /** * Remove the semantic diagnostics cached from old state for affected File and the files that are referencing modules that export entities from affected file */ @@ -312,21 +349,26 @@ namespace ts { * This is called after completing operation on the next affected file. * The operations here are postponed to ensure that cancellation during the iteration is handled correctly */ - function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program) { + function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean) { if (affected === state.program) { state.changedFilesSet.clear(); } else { state.seenAffectedFiles!.set((affected as SourceFile).path, true); - state.affectedFilesIndex!++; + if (isPendingEmit) { + state.affectedFilesPendingEmitIndex!++; + } + else { + state.affectedFilesIndex!++; + } } } /** * Returns the result with affected file */ - function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { - doneWithAffectedFile(state, affected); + function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean): AffectedFileResult { + doneWithAffectedFile(state, affected, isPendingEmit); return { result, affected }; } @@ -442,10 +484,20 @@ namespace ts { * in that order would be used to write the files */ function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { - const affected = getNextAffectedFile(state, cancellationToken, computeHash); + let affected = getNextAffectedFile(state, cancellationToken, computeHash); + let isPendingEmitFile = false; if (!affected) { + affected = getNextAffectedFilePendingEmit(state); // Done - return undefined; + if (!affected) { + return undefined; + } + isPendingEmitFile = true; + } + + // Mark seen emitted files if there are pending files to be emitted + if (state.affectedFilesPendingEmit && state.program !== affected) { + (state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, true); } return toAffectedFileResult( @@ -453,7 +505,8 @@ namespace ts { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) // Otherwise just affected file Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers), - affected + affected, + isPendingEmitFile ); } @@ -552,12 +605,22 @@ namespace ts { return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); } - if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { - // When semantic builder asks for diagnostics of the whole program, - // ensure that all the affected files are handled - let affected: SourceFile | Program | undefined; - while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) { - doneWithAffectedFile(state, affected); + // When semantic builder asks for diagnostics of the whole program, + // ensure that all the affected files are handled + let affected: SourceFile | Program | undefined; + let affectedFilesPendingEmit: Path[] | undefined; + while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) { + if (affected !== state.program && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (affectedFilesPendingEmit || (affectedFilesPendingEmit = [])).push((affected as SourceFile).path); + } + doneWithAffectedFile(state, affected); + } + + // In case of emit builder, cache the files to be emitted + if (affectedFilesPendingEmit) { + state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); + if (state.affectedFilesPendingEmitIndex === undefined) { + state.affectedFilesPendingEmitIndex = 0; } } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index b833b7744e95a..78cdf676d2f06 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -389,9 +389,8 @@ namespace ts { return host; } - // TODO: we should use emit and semantic diagnostics builder but that needs to handle errors little differently so handle it later export function createSolutionBuilderWithWatchHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { - const host = createSolutionBuilderHostBase(system, createProgram || createSemanticDiagnosticsBuilderProgram as any as CreateProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; + const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; const watchHost = createWatchHost(system, reportWatchStatus); copyProperities(host, watchHost); return host; diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index cafd06ca4175d..88c3cdedfd939 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -204,9 +204,8 @@ namespace ts { reportWatchModeWithoutSysSupport(); } - // TODO: change this to host if watch => watchHost otherwiue without watch const buildHost = buildOptions.watch ? - createSolutionBuilderWithWatchHost(sys, createSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) : + createSolutionBuilderWithWatchHost(sys, createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) : createSolutionBuilderHost(sys, createAbstractBuilder, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions)); updateCreateProgram(buildHost); buildHost.afterProgramEmitAndDiagnostics = (program: BuilderProgram) => reportStatistics(program.getProgram()); From 7b290fdbd4e892379c3ab3ed46d7b41f15a31a2b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 20 Dec 2018 11:33:11 -0800 Subject: [PATCH 08/21] Update the timestamps of outputs that dont need to be written because of incremental build This ensures that after `tsbuild` after incremental build of `tsbuild -w` doesnt result in unnecessary rebuilds --- src/compiler/diagnosticMessages.json | 4 + src/compiler/tsbuild.ts | 97 ++++++++-- src/harness/fakes.ts | 3 + src/testRunner/unittests/tsbuild.ts | 59 +++--- src/testRunner/unittests/tsbuildWatchMode.ts | 181 +++++++++++-------- 5 files changed, 226 insertions(+), 118 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b0dfb85ce6d25..b6fec29ca8e6c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3945,6 +3945,10 @@ "category": "Error", "code": 6370 }, + "Updating unchanged output timestamps of project '{0}'...": { + "category": "Message", + "code": 6371 + }, "The expected type comes from property '{0}' which is declared here on type '{1}'": { "category": "Message", diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 78cdf676d2f06..ae5ddfcccf31f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -119,7 +119,7 @@ namespace ts { newestDeclarationFileContentChangedTime?: Date; newestOutputFileTime?: Date; newestOutputFileName?: string; - oldestOutputFileName?: string; + oldestOutputFileName: string; } /** @@ -332,6 +332,9 @@ 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 afterProgramEmitAndDiagnostics?(program: T): void; + + // For testing + now?(): Date; } export interface SolutionBuilderHost extends SolutionBuilderHostBase { @@ -991,16 +994,40 @@ namespace ts { return; } + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + // Fake build + updateOutputTimestamps(proj); + return; + } + const buildResult = buildSingleProject(resolved); - const dependencyGraph = getGlobalDependencyGraph(); - const referencingProjects = dependencyGraph.referencingProjectsMap.getValue(resolved); + if (buildResult & BuildResultFlags.AnyErrors) return; + + const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph(); + const referencingProjects = referencingProjectsMap.getValue(resolved); if (!referencingProjects) return; + // Always use build order to queue projects - for (const project of dependencyGraph.buildQueue) { + for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) { + const project = buildQueue[index]; const prepend = referencingProjects.getValue(project); - // If the project is referenced with prepend, always build downstream projectm, - // otherwise queue it only if declaration output changed - if (prepend || (prepend !== undefined && !(buildResult & BuildResultFlags.DeclarationOutputUnchanged))) { + if (prepend !== undefined) { + // If the project is referenced with prepend, always build downstream project, + // If declaration output is changed changed, build the project + // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps + const status = projectStatus.getValue(project); + if (prepend || !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { + if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes)) { + projectStatus.setValue(project, { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: status.oldestOutputFileName, + newerProjectName: resolved + }); + } + } + else if (status && status.type === UpToDateStatusType.UpToDate) { + status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; + } addProjToQueue(project); } } @@ -1110,6 +1137,7 @@ namespace ts { let declDiagnostics: Diagnostic[] | undefined; const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); const outputFiles: OutputFile[] = []; + // TODO:: handle declaration diagnostics in incremental build. emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { @@ -1118,6 +1146,7 @@ namespace ts { // Actual Emit const emitterDiagnostics = createDiagnosticCollection(); + const emittedOutputs = createFileMap(toPath as ToPath); outputFiles.forEach(({ name, text, writeByteOrderMark }) => { let priorChangeTime: Date | undefined; if (!anyDtsChanged && isDeclarationFile(name)) { @@ -1131,6 +1160,7 @@ namespace ts { } } + emittedOutputs.setValue(name, true); writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); if (priorChangeTime !== undefined) { newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); @@ -1143,9 +1173,13 @@ namespace ts { return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit"); } + // Update time stamps for rest of the outputs + newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + const status: UpToDateStatus = { type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime + newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile) }; diagnostics.removeKey(proj); projectStatus.setValue(proj, status); @@ -1175,23 +1209,34 @@ namespace ts { if (options.dry) { return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!); } + const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); + projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + } - if (options.verbose) { - reportStatus(Diagnostics.Updating_output_timestamps_of_project_0, proj.options.configFilePath!); - } - - const now = new Date(); + function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { const outputs = getAllProjectOutputs(proj); - let priorNewestUpdateTime = minimumDate; - for (const file of outputs) { - if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + if (!skipOutputs || outputs.length !== skipOutputs.getSize()) { + if (options.verbose) { + reportStatus(verboseMessage, proj.options.configFilePath!); } + const now = host.now ? host.now() : new Date(); + for (const file of outputs) { + if (skipOutputs && skipOutputs.hasKey(file)) { + continue; + } + + if (isDeclarationFile(file)) { + priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + } - host.setModifiedTime(file, now); + host.setModifiedTime(file, now); + if (proj.options.listEmittedFiles) { + writeFileName(`TSFILE: ${file}`); + } + } } - projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + return priorNewestUpdateTime; } function getFilesToClean(): string[] { @@ -1368,6 +1413,20 @@ namespace ts { } } + function getFirstProjectOutput(project: ParsedCommandLine): string { + if (project.options.outFile || project.options.out) { + return first(getOutFileOutputs(project)); + } + + for (const inputFile of project.fileNames) { + const outputs = getOutputFileNames(inputFile, project); + if (outputs.length) { + return first(outputs); + } + } + return Debug.fail(`project ${project.options.configFilePath} expected to have atleast one output`); + } + export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { switch (status.type) { case UpToDateStatusType.OutOfDateWithSelf: diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 6119dd153b9c6..e8c8161705252 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -377,6 +377,9 @@ namespace fakes { export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { createProgram = ts.createAbstractBuilder; + now() { + return new Date(this.sys.vfs.time()); + } diagnostics: ts.Diagnostic[] = []; diff --git a/src/testRunner/unittests/tsbuild.ts b/src/testRunner/unittests/tsbuild.ts index 4a3f259cc3452..ba94072b230ac 100644 --- a/src/testRunner/unittests/tsbuild.ts +++ b/src/testRunner/unittests/tsbuild.ts @@ -234,39 +234,46 @@ namespace ts { // Update a timestamp in the middle project tick(); touch(fs, "/src/logic/index.ts"); + const originalWriteFile = fs.writeFileSync; + const writtenFiles = createMap(); + fs.writeFileSync = (path, data, encoding) => { + writtenFiles.set(path, true); + originalWriteFile.call(fs, path, data, encoding); + }; // Because we haven't reset the build context, the builder should assume there's nothing to do right now const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic")); assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); + verifyInvalidation(/*expectedToWriteTests*/ false); // Rebuild this project - tick(); - builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); - // The file should be updated - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - - // Does not build tests or core because there is no change in declaration file - tick(); - builder.buildInvalidatedProject(); - assert.isBelow(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"); - - // Rebuild this project - tick(); fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} export class cNew {}`); - builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); - // The file should be updated - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - - // Build downstream projects should update 'tests', but not 'core' - tick(); - builder.buildInvalidatedProject(); - 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"); + verifyInvalidation(/*expectedToWriteTests*/ true); + + function verifyInvalidation(expectedToWriteTests: boolean) { + // Rebuild this project + tick(); + builder.invalidateProject("/src/logic"); + builder.buildInvalidatedProject(); + // The file should be updated + assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); + assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); + assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); + assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); + writtenFiles.clear(); + + // Build downstream projects should update 'tests', but not 'core' + tick(); + builder.buildInvalidatedProject(); + if (expectedToWriteTests) { + assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); + } + else { + assert.equal(writtenFiles.size, 0, "Should not write any new files"); + } + assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); + assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + } }); }); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 72da8dbf85edb..bdc8fe1be86b9 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -2,18 +2,37 @@ namespace ts.tscWatch { import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath; import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; + type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map; }; + + function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray, params?: TestFSWithWatch.TestServerHostCreationParameters) { + const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem; + const originalWriteFile = host.writeFile; + host.writtenFiles = createMap(); + host.writeFile = (fileName, content) => { + originalWriteFile.call(host, fileName, content); + const path = host.toFullPath(fileName); + host.writtenFiles.set(path, true); + }; + return host; + } + export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { const host = createSolutionBuilderWithWatchHost(system); return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true }); } - function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { + function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions); solutionBuilder.buildAllProjects(); solutionBuilder.startWatching(); return solutionBuilder; } + type OutputFileStamp = [string, Date | undefined, boolean]; + function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp { + return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp; + } + describe("unittests:: tsbuild-watch program updates", () => { const project = "sample1"; const enum SubProject { @@ -61,12 +80,11 @@ namespace ts.tscWatch { return [`${file}.js`, `${file}.d.ts`]; } - type OutputFileStamp = [string, Date | undefined]; - function getOutputStamps(host: WatchedSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { - return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp); + function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { + return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host)); } - function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] { + function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] { const result = [ ...getOutputStamps(host, SubProject.core, "anotherModule"), ...getOutputStamps(host, SubProject.core, "index"), @@ -76,18 +94,21 @@ namespace ts.tscWatch { if (additionalFiles) { additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension))); } + host.writtenFiles.clear(); return result; } - function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) { + function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: ReadonlyArray, modifiedTimeStampFiles: ReadonlyArray) { for (let i = 0; i < oldTimeStamps.length; i++) { const actual = actualStamps[i]; const old = oldTimeStamps[i]; - if (contains(changedFiles, actual[0])) { - assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} expected to written`); + const expectedIsChanged = contains(changedFiles, actual[0]); + assert.equal(actual[2], contains(changedFiles, actual[0]), `Expected ${actual[0]} to be written.`); + if (expectedIsChanged || contains(modifiedTimeStampFiles, actual[0])) { + assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} file expected to have newer modified time because it is expected to ${expectedIsChanged ? "be changed" : "have modified time stamp"}`); } else { - assert.equal(actual[1], old[1], `${actual[0]} expected to not change`); + assert.equal(actual[1], old[1], `${actual[0]} expected to not change or have timestamp modified.`); } } } @@ -101,7 +122,7 @@ namespace ts.tscWatch { const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)]; function createSolutionInWatchMode(allFiles: ReadonlyArray, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) { - const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], defaultOptions); verifyWatches(host); checkOutputErrorsInitial(host, emptyArray, disableConsoleClears); @@ -112,7 +133,7 @@ namespace ts.tscWatch { return host; } - function verifyWatches(host: WatchedSystem) { + function verifyWatches(host: TsBuildWatchSystem) { checkWatchedFiles(host, testProjectExpectedWatchedFiles); checkWatchedDirectories(host, emptyArray, /*recursive*/ false); checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true); @@ -134,30 +155,50 @@ namespace ts.tscWatch { const host = createSolutionInWatchMode(allFiles); return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches }; - function verifyChangeWithFile(fileName: string, content: string) { + function verifyChangeWithFile(fileName: string, content: string, local?: boolean) { const outputFileStamps = getOutputFileStamps(host, additionalFiles); host.writeFile(fileName, content); - verifyChangeAfterTimeout(outputFileStamps); + verifyChangeAfterTimeout(outputFileStamps, local); } - function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) { + function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[], local?: boolean) { host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really - ...getOutputFileNames(SubProject.core, "index"), - ...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray) - ]); - host.checkTimeoutQueueLengthAndRun(1); // Builds logic + verifyChangedFiles( + changedCore, + outputFileStamps, + additionalFiles ? + getOutputFileNames(SubProject.core, newFileWithoutExtension) : + getOutputFileNames(SubProject.core, "index"), // Written files are new file or core index file thats changed + [ + ...getOutputFileNames(SubProject.core, "anotherModule"), + ...(additionalFiles ? getOutputFileNames(SubProject.core, "index") : emptyArray) + ] + ); + host.checkTimeoutQueueLengthAndRun(1); // Builds logic or updates timestamps const changedLogic = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedLogic, changedCore, [ - ...getOutputFileNames(SubProject.logic, "index") // Again these need not be written - ]); + verifyChangedFiles( + changedLogic, + changedCore, + additionalFiles || local ? + emptyArray : + getOutputFileNames(SubProject.logic, "index"), + additionalFiles || local ? + getOutputFileNames(SubProject.logic, "index") : + emptyArray + ); host.checkTimeoutQueueLengthAndRun(1); // Builds tests const changedTests = getOutputFileStamps(host, additionalFiles); - verifyChangedFiles(changedTests, changedLogic, [ - ...getOutputFileNames(SubProject.tests, "index") // Again these need not be written - ]); + verifyChangedFiles( + changedTests, + changedLogic, + additionalFiles || local ? + emptyArray : + getOutputFileNames(SubProject.tests, "index"), + additionalFiles || local ? + getOutputFileNames(SubProject.tests, "index") : + emptyArray + ); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(); @@ -193,19 +234,9 @@ export class someClass2 { }`); }); it("non local change does not start build of referencing projects", () => { - const host = createSolutionInWatchMode(allFiles); - const outputFileStamps = getOutputFileStamps(host); - host.writeFile(core[1].path, `${core[1].content} -function foo() { }`); - host.checkTimeoutQueueLengthAndRun(1); // Builds core - const changedCore = getOutputFileStamps(host); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really - ...getOutputFileNames(SubProject.core, "index"), - ]); - host.checkTimeoutQueueLength(0); - checkOutputErrorsIncremental(host, emptyArray); - verifyWatches(host); + const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges(); + verifyChangeWithFile(core[1].path, `${core[1].content} +function foo() { }`, /*local*/ true); }); it("builds when new file is added, and its subsequent updates", () => { @@ -242,7 +273,7 @@ export class someClass2 { }`); it("watches config files that are not present", () => { const allFiles = [libFile, ...core, logic[1], ...tests]; - const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); checkWatchedFiles(host, [core[0], core[1], core[2]!, logic[0], ...tests].map(f => f.path.toLowerCase())); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary) checkWatchedDirectories(host, emptyArray, /*recursive*/ false); @@ -268,14 +299,10 @@ export class someClass2 { }`); host.writeFile(logic[0].path, logic[0].content); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(host); - verifyChangedFiles(changedLogic, initial, [ - ...getOutputFileNames(SubProject.logic, "index") - ]); + verifyChangedFiles(changedLogic, initial, getOutputFileNames(SubProject.logic, "index"), emptyArray); host.checkTimeoutQueueLengthAndRun(1); // Builds tests const changedTests = getOutputFileStamps(host); - verifyChangedFiles(changedTests, changedLogic, [ - ...getOutputFileNames(SubProject.tests, "index") - ]); + verifyChangedFiles(changedTests, changedLogic, getOutputFileNames(SubProject.tests, "index"), emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(host); @@ -305,7 +332,7 @@ export class someClass2 { }`); }; const projectFiles = [coreTsConfig, coreIndex, logicTsConfig, logicIndex]; - const host = createWatchedSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation }); + const host = createTsBuildWatchSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.logic}`]); verifyWatches(); checkOutputErrorsInitial(host, emptyArray); @@ -318,6 +345,7 @@ export class someClass2 { }`); verifyChangeInCore(`${coreIndex.content} function myFunc() { return 10; }`); + // TODO:: local change does not build logic.js because builder doesnt find any changes in input files to generate output // Make local change to function bar verifyChangeInCore(`${coreIndex.content} function myFunc() { return 100; }`); @@ -328,14 +356,20 @@ function myFunc() { return 100; }`); host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(); - verifyChangedFiles(changedCore, outputFileStamps, [ - ...getOutputFileNames(SubProject.core, "index") - ]); + verifyChangedFiles( + changedCore, + outputFileStamps, + getOutputFileNames(SubProject.core, "index"), + emptyArray + ); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(); - verifyChangedFiles(changedLogic, changedCore, [ - ...getOutputFileNames(SubProject.logic, "index") - ]); + verifyChangedFiles( + changedLogic, + changedCore, + getOutputFileNames(SubProject.logic, "index"), + emptyArray + ); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, emptyArray); verifyWatches(); @@ -346,6 +380,7 @@ function myFunc() { return 100; }`); ...getOutputStamps(host, SubProject.core, "index"), ...getOutputStamps(host, SubProject.logic, "index"), ]; + host.writtenFiles.clear(); return result; } @@ -389,7 +424,7 @@ createSomeObject().message;` }; const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; - const host = createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); + const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); createSolutionBuilderWithWatch(host, ["App"]); checkOutputErrorsInitial(host, emptyArray); @@ -418,7 +453,7 @@ let y: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); // Builds logic const changedLogic = getOutputFileStamps(host); - verifyChangedFiles(changedLogic, outputFileStamps, emptyArray); + verifyChangedFiles(changedLogic, outputFileStamps, emptyArray, emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, [ `sample1/logic/index.ts(8,5): error TS2322: Type '10' is not assignable to type 'string'.\n` @@ -429,7 +464,7 @@ let x: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); // Builds core const changedCore = getOutputFileStamps(host); - verifyChangedFiles(changedCore, changedLogic, emptyArray); + verifyChangedFiles(changedCore, changedLogic, emptyArray, emptyArray); host.checkTimeoutQueueLength(0); checkOutputErrorsIncremental(host, [ `sample1/core/index.ts(5,5): error TS2322: Type '10' is not assignable to type 'string'.\n`, @@ -448,7 +483,7 @@ let x: string = 10;`); describe("tsc-watch and tsserver works with project references", () => { describe("invoking when references are already built", () => { - function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, expectedWatchedDirectories?: ReadonlyArray) { + function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, expectedWatchedDirectories?: ReadonlyArray) { checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false); checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true); @@ -457,9 +492,9 @@ let x: string = 10;`); function createSolutionOfProject(allFiles: ReadonlyArray, currentDirectory: string, solutionBuilderconfig: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project - const host = createWatchedSystem(allFiles, { currentDirectory }); + const host = createTsBuildWatchSystem(allFiles, { currentDirectory }); const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {}); solutionBuilder.buildAllProjects(); const outputFileStamps = getOutputFileStamps(host); @@ -474,7 +509,7 @@ let x: string = 10;`); currentDirectory: string, solutionBuilderconfig: string, watchConfig: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps); @@ -489,7 +524,7 @@ let x: string = 10;`); currentDirectory: string, solutionBuilderconfig: string, openFileName: string, - getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray) { // Build the composite project const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps); @@ -527,12 +562,12 @@ let x: string = 10;`); return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps); } - function verifyWatches(host: WatchedSystem, withTsserver?: boolean) { + function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) { verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles.filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles, expectedWatchedDirectoriesRecursive); } function verifyScenario( - edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedFilesAfterEdit: ReadonlyArray ) { it("with tsc-watch", () => { @@ -635,7 +670,7 @@ export function gfoo() { } function verifyWatchState( - host: WatchedSystem, + host: TsBuildWatchSystem, watch: Watch, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, @@ -722,20 +757,20 @@ export function gfoo() { return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps); } - function getOutputFileStamps(host: WatchedSystem) { - return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp); + function getOutputFileStamps(host: TsBuildWatchSystem) { + return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host)); } - function verifyProgram(host: WatchedSystem, watch: Watch) { + function verifyProgram(host: TsBuildWatchSystem, watch: Watch) { verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories); } - function verifyProject(host: WatchedSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray) { + function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray) { verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos); } function verifyServerState( - host: WatchedSystem, + host: TsBuildWatchSystem, service: projectSystem.TestProjectService, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, @@ -755,13 +790,13 @@ export function gfoo() { } function verifyScenario( - edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedEditErrors: ReadonlyArray, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray, dependencies: ReadonlyArray<[string, ReadonlyArray]>, - revert?: (host: WatchedSystem) => void, + revert?: (host: TsBuildWatchSystem) => void, orphanInfosAfterEdit?: ReadonlyArray, orphanInfosAfterRevert?: ReadonlyArray) { it("with tsc-watch", () => { @@ -980,8 +1015,8 @@ export function gfoo() { [refs.path, [refs.path]], [cTsFile.path, [cTsFile.path, refs.path, bDts]] ]; - function getOutputFileStamps(host: WatchedSystem) { - return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp); + function getOutputFileStamps(host: TsBuildWatchSystem) { + return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host)); } const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps); verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies); From 0d9038c30a42f20e7c22eb0c6c5ca3ed00e21eca Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Dec 2018 17:22:17 -0800 Subject: [PATCH 09/21] Handle prepend in incremental build. Always emit when program uses project reference with prepend since it cant tell changes in js/map files --- src/compiler/builder.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index df3b056227f69..ef017e2ae97b4 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -66,6 +66,10 @@ namespace ts { * Already seen affected files */ seenEmittedFiles: Map | undefined; + /** + * true if program has been emitted + */ + programEmitComplete?: true; } function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined): boolean { @@ -352,6 +356,7 @@ namespace ts { function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean) { if (affected === state.program) { state.changedFilesSet.clear(); + state.programEmitComplete = true; } else { state.seenAffectedFiles!.set((affected as SourceFile).path, true); @@ -487,12 +492,22 @@ namespace ts { let affected = getNextAffectedFile(state, cancellationToken, computeHash); let isPendingEmitFile = false; if (!affected) { - affected = getNextAffectedFilePendingEmit(state); - // Done - if (!affected) { - return undefined; + if (!state.compilerOptions.out && !state.compilerOptions.outFile) { + affected = getNextAffectedFilePendingEmit(state); + if (!affected) { + return undefined; + } + isPendingEmitFile = true; + } + else { + const program = Debug.assertDefined(state.program); + // Check if program uses any prepend project references, if thats the case we cant track of the js files of those, so emit even though there are no changes + if (state.programEmitComplete || !some(program.getProjectReferences(), ref => !!ref.prepend)) { + state.programEmitComplete = true; + return undefined; + } + affected = program; } - isPendingEmitFile = true; } // Mark seen emitted files if there are pending files to be emitted From b360ff770af33ea9fbad4bd90cdc79516fa9bd5f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Dec 2018 11:13:54 -0800 Subject: [PATCH 10/21] Write the tests for incremental build and declaration emit errors handling These will fail since its still TODO --- src/testRunner/unittests/tsbuildWatchMode.ts | 107 +++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index bdc8fe1be86b9..083061cf96ce9 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -479,6 +479,113 @@ let x: string = 10;`); it("when preserveWatchOutput is passed on command line", () => { verifyIncrementalErrors({ preserveWatchOutput: true, watch: true }, /*disabledConsoleClear*/ true); }); + + describe("when declaration emit errors are present", () => { + const solution = "solution"; + const subProject = "app"; + const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`; + const fileWithError: File = { + path: `${subProjectLocation}/fileWithError.ts`, + content: `export var myClassWithError = class { + tags() { } + private p = 12 + };` + }; + const fileWithFixedError: File = { + path: fileWithError.path, + content: fileWithError.content.replace("private p = 12", "") + }; + const fileWithoutError: File = { + path: `${subProjectLocation}/fileWithoutError.ts`, + content: `export class myClass { }` + }; + const tsconfig: File = { + path: `${subProjectLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; + const expectedDtsEmitErrors = [ + `${subProject}/fileWithError.ts(1,12): error TS4094: Property 'p' of exported class expression may not be private or protected.\n` + ]; + const outputs = [ + changeExtension(fileWithError.path, Extension.Js), + changeExtension(fileWithError.path, Extension.Dts), + changeExtension(fileWithoutError.path, Extension.Js), + changeExtension(fileWithoutError.path, Extension.Dts) + ]; + + function verifyDtsErrors(host: TsBuildWatchSystem, isIncremental: boolean, expectedErrors: ReadonlyArray) { + (isIncremental ? checkOutputErrorsIncremental : checkOutputErrorsInitial)(host, expectedErrors); + outputs.forEach(f => assert.equal(host.fileExists(f), !expectedErrors.length, `Expected file ${f} to ${!expectedErrors.length ? "exist" : "not exist"}`)); + } + + function createSolutionWithWatch(withFixedError?: true) { + const files = [libFile, withFixedError ? fileWithFixedError : fileWithError, fileWithoutError, tsconfig]; + const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${solution}` }); + createSolutionBuilderWithWatch(host, [subProject]); + verifyDtsErrors(host, /*isIncremental*/ false, withFixedError ? emptyArray : expectedDtsEmitErrors); + return host; + } + + function incrementalBuild(host: TsBuildWatchSystem) { + host.checkTimeoutQueueLengthAndRun(1); // Build the app + host.checkTimeoutQueueLength(0); + } + + function fixError(host: TsBuildWatchSystem) { + // Fix error + host.writeFile(fileWithError.path, fileWithFixedError.content); + host.writtenFiles.clear(); + incrementalBuild(host); + verifyDtsErrors(host, /*isIncremental*/ true, emptyArray); + } + + it("when fixing error files all files are emitted", () => { + const host = createSolutionWithWatch(); + fixError(host); + }); + + it("when file with no error changes, declaration errors are reported", () => { + const host = createSolutionWithWatch(); + host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")); + incrementalBuild(host); + verifyDtsErrors(host, /*isIncremental*/ true, expectedDtsEmitErrors); + }); + + describe("when reporting errors on introducing error", () => { + function createSolutionWithIncrementalError() { + const host = createSolutionWithWatch(/*withFixedError*/ true); + host.writeFile(fileWithError.path, fileWithError.content); + host.writtenFiles.clear(); + + incrementalBuild(host); + checkOutputErrorsIncremental(host, expectedDtsEmitErrors); + assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`); + return host; + } + + function verifyWrittenFile(host: TsBuildWatchSystem, f: string) { + assert.isTrue(host.writtenFiles.has(host.toFullPath(f)), `Expected to write ${f}: ${arrayFrom(host.writtenFiles.keys())}`); + } + + it("when fixing errors only changed file is emitted", () => { + const host = createSolutionWithIncrementalError(); + fixError(host); + assert.equal(host.writtenFiles.size, 2, `Expected to write only changed files: ${arrayFrom(host.writtenFiles.keys())}`); + verifyWrittenFile(host, outputs[0]); + verifyWrittenFile(host, outputs[1]); + }); + + it("when file with no error changes, declaration errors are reported", () => { + const host = createSolutionWithIncrementalError(); + host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")); + host.writtenFiles.clear(); + + incrementalBuild(host); + checkOutputErrorsIncremental(host, expectedDtsEmitErrors); + assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`); + }); + }); + }); }); describe("tsc-watch and tsserver works with project references", () => { From 69abc124944ddd7fa9b8c64384be5538757fc536 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Dec 2018 12:07:59 -0800 Subject: [PATCH 11/21] Handle declaration emit errors in tsbuild mode by backing up builder state This helps us revert to state where we pretend as if emit is not done (since we do not do emit if there are errors) --- src/compiler/builder.ts | 53 +++++++++++++++++++++++++++++++++++- src/compiler/builderState.ts | 37 +++++++++++++++++++++---- src/compiler/tsbuild.ts | 12 ++++---- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ef017e2ae97b4..a4c9dd0cc8433 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -154,6 +154,38 @@ namespace ts { return state; } + /** + * Releases program and other related not needed properties + */ + function releaseCache(state: BuilderProgramState) { + BuilderState.releaseCache(state); + state.program = undefined; + } + + /** + * Creates a clone of the state + */ + function cloneBuilderProgramState(state: Readonly): BuilderProgramState { + const newState = BuilderState.clone(state) as BuilderProgramState; + newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile); + newState.changedFilesSet = cloneMap(state.changedFilesSet); + newState.affectedFiles = state.affectedFiles; + newState.affectedFilesIndex = state.affectedFilesIndex; + newState.currentChangedFilePath = state.currentChangedFilePath; + newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures); + newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap); + newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles); + newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; + newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState); + newState.program = state.program; + newState.compilerOptions = state.compilerOptions; + newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit; + newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; + newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles); + newState.programEmitComplete = state.programEmitComplete; + return newState; + } + /** * Verifies that source file is ok to be used in calls that arent handled by next */ @@ -458,7 +490,8 @@ namespace ts { * Computing hash to for signature verification */ const computeHash = host.createHash || generateDjb2Hash; - const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); + let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); + let backupState: BuilderProgramState | undefined; // To ensure that we arent storing any references to old program or new program without state newProgram = undefined!; // TODO: GH#18217 @@ -467,9 +500,21 @@ namespace ts { const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); result.getState = () => state; + result.backupCurrentState = () => { + Debug.assert(backupState === undefined); + backupState = cloneBuilderProgramState(state); + }; + result.useBackupState = () => { + state = Debug.assertDefined(backupState); + backupState = undefined; + }; result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile); result.getSemanticDiagnostics = getSemanticDiagnostics; result.emit = emit; + result.releaseProgram = () => { + releaseCache(state); + backupState = undefined; + }; if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { (result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; @@ -650,6 +695,8 @@ namespace ts { export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { return { getState: notImplemented, + backupCurrentState: noop, + useBackupState: noop, getProgram: () => Debug.assertDefined(state.program), getProgramOrUndefined: () => state.program, releaseProgram: () => state.program = undefined, @@ -694,6 +741,10 @@ namespace ts { export interface BuilderProgram { /*@internal*/ getState(): BuilderProgramState; + /*@internal*/ + backupCurrentState(): void; + /*@internal*/ + useBackupState(): void; /** * Returns current program */ diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 0462beada9e7b..552f46c378dba 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -50,11 +50,15 @@ namespace ts { /** * Cache of all files excluding default library file for the current program */ - allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + allFilesExcludingDefaultLibraryFile?: ReadonlyArray; /** * Cache of all the file names */ - allFileNames: ReadonlyArray | undefined; + allFileNames?: ReadonlyArray; + } + + export function cloneMapOrUndefined(map: ReadonlyMap | undefined) { + return map ? cloneMap(map) : undefined; } } @@ -230,9 +234,32 @@ namespace ts.BuilderState { fileInfos, referencedMap, exportedModulesMap, - hasCalledUpdateShapeSignature, - allFilesExcludingDefaultLibraryFile: undefined, - allFileNames: undefined + hasCalledUpdateShapeSignature + }; + } + + /** + * Releases needed properties + */ + export function releaseCache(state: BuilderState) { + state.allFilesExcludingDefaultLibraryFile = undefined; + state.allFileNames = undefined; + } + + /** + * Creates a clone of the state + */ + export function clone(state: Readonly): BuilderState { + const fileInfos = createMap(); + state.fileInfos.forEach((value, key) => { + fileInfos.set(key, { ...value }); + }); + // Dont need to backup allFiles info since its cache anyway + return { + fileInfos, + referencedMap: cloneMapOrUndefined(state.referencedMap), + exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap), + hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature), }; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index ae5ddfcccf31f..fa84b4bd9bbf6 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -382,8 +382,6 @@ namespace ts { host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system); return host; - - // TODO after program create } export function createSolutionBuilderHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { @@ -499,9 +497,7 @@ namespace ts { clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf)); clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher)); clearMap(allWatchedConfigFiles, closeFileWatcher); - if (!options.watch) { - builderPrograms.clear(); - } + builderPrograms.clear(); updateGetSourceFile(); } @@ -576,7 +572,7 @@ namespace ts { hostWithWatch, resolved, () => { - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full); + invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full); }, PollingInterval.High, WatchType.ConfigFile, @@ -1132,15 +1128,17 @@ namespace ts { return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic"); } + // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly + program.backupCurrentState(); let newestDeclarationFileContentChangedTime = minimumDate; let anyDtsChanged = false; let declDiagnostics: Diagnostic[] | undefined; const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); const outputFiles: OutputFile[] = []; - // TODO:: handle declaration diagnostics in incremental build. emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { + program.useBackupState(); return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file"); } From 42484b504edadaaf1ef91c307974b643d3ee9eb2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 27 Dec 2018 10:36:18 -0800 Subject: [PATCH 12/21] Use DirectoryStructureHost for fileExists and readFile --- src/compiler/program.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6c132ad417d8e..7dd57a07b9f00 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3121,12 +3121,12 @@ namespace ts { /* @internal */ export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { return { - fileExists: f => host.fileExists(f), + fileExists: f => directoryStructureHost.fileExists(f), 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), + readFile: f => directoryStructureHost.readFile(f), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), getCurrentDirectory: () => host.getCurrentDirectory(), onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || (() => undefined), From abc861862ab7c08b07216ba2c4ede7ccefc1cc60 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 10 Jan 2019 15:18:02 -0800 Subject: [PATCH 13/21] Fix typo --- src/compiler/core.ts | 2 +- src/compiler/tsbuild.ts | 2 +- src/compiler/watch.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 17f6c6f4fc340..74ac9e93047a1 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1391,7 +1391,7 @@ namespace ts { return result; } - export function copyProperities(first: T1, second: T2) { + export function copyProperties(first: T1, second: T2) { for (const id in second) { if (hasOwnProperty.call(second, id)) { (first as any)[id] = second[id]; diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index fa84b4bd9bbf6..8c1c93eeb0b26 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -393,7 +393,7 @@ namespace ts { export function createSolutionBuilderWithWatchHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; const watchHost = createWatchHost(system, reportWatchStatus); - copyProperities(host, watchHost); + copyProperties(host, watchHost); return host; } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index cc6d1236fed6f..b92289635d301 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -317,7 +317,7 @@ namespace ts { function createWatchCompilerHost(system = sys, createProgram: CreateProgram | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost { const writeFileName = (s: string) => system.write(s + system.newLine); const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram) as WatchCompilerHost; - copyProperities(result, createWatchHost(system, reportWatchStatus)); + copyProperties(result, createWatchHost(system, reportWatchStatus)); result.afterProgramCreate = builderProgram => { const compilerOptions = builderProgram.getCompilerOptions(); const newLine = getNewLineCharacter(compilerOptions, () => system.newLine); From c909becdd5eba949cf48b4c0f8d89c783384d6a9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Jan 2019 12:40:50 -0800 Subject: [PATCH 14/21] Rename indexing variable --- src/compiler/builder.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 0db924913f4b1..89577cfd68a29 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -265,11 +265,11 @@ namespace ts { const { affectedFilesPendingEmit } = state; if (affectedFilesPendingEmit) { const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap()); - for (let affectedFilesIndex = state.affectedFilesPendingEmitIndex!; affectedFilesIndex < affectedFilesPendingEmit.length; affectedFilesIndex++) { - const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[affectedFilesIndex]); + for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { + const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); if (affectedFile && !seenEmittedFiles.has(affectedFile.path)) { // emit this file - state.affectedFilesPendingEmitIndex = affectedFilesIndex; + state.affectedFilesPendingEmitIndex = i; return affectedFile; } } @@ -695,6 +695,10 @@ namespace ts { // In case of emit builder, cache the files to be emitted if (affectedFilesPendingEmit) { state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); + // affectedFilesPendingEmitIndex === undefined + // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files + // so start from 0 as array would be affectedFilesPendingEmit + // else, continue to iterate from existing index, the current set is appended to existing files if (state.affectedFilesPendingEmitIndex === undefined) { state.affectedFilesPendingEmitIndex = 0; } From 39435887939e65650fffc14e19d80d9046b429a8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Jan 2019 12:48:22 -0800 Subject: [PATCH 15/21] CompilerHostLikeForCache rename --- src/compiler/program.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index fbe9a6c508966..8b66d82fa8143 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -203,7 +203,7 @@ namespace ts { return compilerHost; } - interface ComplierHostLikeForCache { + interface CompilerHostLikeForCache { fileExists(fileName: string): boolean; readFile(fileName: string, encoding?: string): string | undefined; directoryExists?(directory: string): boolean; @@ -213,7 +213,7 @@ namespace ts { /*@internal*/ export function changeCompilerHostLikeToUseCache( - host: ComplierHostLikeForCache, + host: CompilerHostLikeForCache, toPath: (fileName: string) => Path, getSourceFile?: CompilerHost["getSourceFile"] ) { From ff97d86cfabd038275a479c82887d6416302e81b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Jan 2019 13:40:54 -0800 Subject: [PATCH 16/21] Fix typo --- src/compiler/tsbuild.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 8c1c93eeb0b26..60c61f2e62cef 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1008,8 +1008,8 @@ namespace ts { const project = buildQueue[index]; const prepend = referencingProjects.getValue(project); if (prepend !== undefined) { - // If the project is referenced with prepend, always build downstream project, - // If declaration output is changed changed, build the project + // If the project is referenced with prepend, always build downstream projects, + // If declaration output is changed, build the project // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps const status = projectStatus.getValue(project); if (prepend || !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { From e745fca4133b62ff8e0d8f6024c6a4c8c3b9cf1f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Jan 2019 14:35:05 -0800 Subject: [PATCH 17/21] Fix typo --- src/compiler/tsbuild.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 60c61f2e62cef..4395b3029440f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1422,7 +1422,7 @@ namespace ts { return first(outputs); } } - return Debug.fail(`project ${project.options.configFilePath} expected to have atleast one output`); + return Debug.fail(`project ${project.options.configFilePath} expected to have at least one output`); } export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { From 520e33fa513a8c5ce17d2e9db390f1f9c241d555 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Jan 2019 15:09:34 -0800 Subject: [PATCH 18/21] PR feedback --- src/compiler/watch.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index b92289635d301..6dbed25954701 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -203,17 +203,14 @@ namespace ts { TypeRoots = "Type roots" } - interface WatchFactory extends ts.WatchFactory { - watchLogLevel: WatchLogLevel; + interface WatchFactory extends ts.WatchFactory { writeLog: (s: string) => void; } export function createWatchFactory(host: { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) { - const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : - options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None; + const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None; const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop; const result = getWatchFactory(watchLogLevel, writeLog) as WatchFactory; - result.watchLogLevel = watchLogLevel; result.writeLog = writeLog; return result; } @@ -590,7 +587,7 @@ namespace ts { newLine = updateNewLine(); } - const { watchFile, watchFilePath, watchDirectory, watchLogLevel, writeLog } = createWatchFactory(host, compilerOptions); + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`); @@ -685,11 +682,9 @@ namespace ts { function createNewProgram(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) { // Compile the program - if (watchLogLevel !== WatchLogLevel.None) { - writeLog("CreatingProgramWith::"); - writeLog(` roots: ${JSON.stringify(rootFileNames)}`); - writeLog(` options: ${JSON.stringify(compilerOptions)}`); - } + writeLog("CreatingProgramWith::"); + writeLog(` roots: ${JSON.stringify(rootFileNames)}`); + writeLog(` options: ${JSON.stringify(compilerOptions)}`); const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; hasChangedCompilerOptions = false; From 9f3b77a8bd2bfd27e380763cce3f80018348ff68 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 17 Jan 2019 09:21:49 -0800 Subject: [PATCH 19/21] PR feedback --- src/compiler/builder.ts | 24 +++++++------ src/compiler/core.ts | 8 ++--- src/compiler/program.ts | 2 +- src/compiler/tsbuild.ts | 8 ++--- src/compiler/types.ts | 1 - src/compiler/watch.ts | 35 ++++++++++--------- .../reference/api/tsserverlibrary.d.ts | 1 - tests/baselines/reference/api/typescript.d.ts | 1 - 8 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 89577cfd68a29..e8a2bae05135d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -717,22 +717,26 @@ namespace ts { getState: notImplemented, backupCurrentState: noop, useBackupState: noop, - getProgram: () => Debug.assertDefined(state.program), + getProgram, getProgramOrUndefined: () => state.program, releaseProgram: () => state.program = undefined, getCompilerOptions: () => state.compilerOptions, - getSourceFile: fileName => Debug.assertDefined(state.program).getSourceFile(fileName), - getSourceFiles: () => Debug.assertDefined(state.program).getSourceFiles(), - getOptionsDiagnostics: cancellationToken => Debug.assertDefined(state.program).getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => Debug.assertDefined(state.program).getGlobalDiagnostics(cancellationToken), + getSourceFile: fileName => getProgram().getSourceFile(fileName), + getSourceFiles: () => getProgram().getSourceFiles(), + getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, - getSyntacticDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getSyntacticDiagnostics(sourceFile, cancellationToken), - getDeclarationDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getDeclarationDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics: (sourceFile, cancellationToken) => Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken), - emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => Debug.assertDefined(state.program).emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), getAllDependencies: notImplemented, - getCurrentDirectory: () => Debug.assertDefined(state.program).getCurrentDirectory() + getCurrentDirectory: () => getProgram().getCurrentDirectory() }; + + function getProgram() { + return Debug.assertDefined(state.program); + } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 74ac9e93047a1..d4f5fe9664afa 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1370,10 +1370,6 @@ namespace ts { return result; } - export function createRedirectObject(redirectTarget: T): T { - return Object.create(redirectTarget); - } - export function extend(first: T1, second: T2): T1 & T2 { const result: T1 & T2 = {}; for (const id in second) { @@ -1399,6 +1395,10 @@ namespace ts { } } + export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { + return fn ? fn.bind(obj) : undefined; + } + export interface MultiMap extends Map { /** * Adds the value to an array of values associated with the key, and returns the array. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8b66d82fa8143..0eff6998d8d9f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2187,7 +2187,7 @@ namespace ts { } function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { - const redirect = createRedirectObject(redirectTarget); + const redirect = Object.create(redirectTarget); redirect.fileName = fileName; redirect.path = path; redirect.resolvedPath = resolvedPath; diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 4395b3029440f..61b3c610f5aaf 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -447,7 +447,7 @@ namespace ts { let nextProjectToBuild = 0; let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; - const { watchFile, watchFilePath, watchDirectory } = createWatchFactory(host, options); + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); // Watches for the solution const allWatchedWildcardDirectories = createFileMap>(toPath); @@ -593,12 +593,12 @@ namespace ts { fileOrDirectory => { const fileOrDirectoryPath = toPath(fileOrDirectory); if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { - // writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); + writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); return; } if (isOutputFile(fileOrDirectory, parsed)) { - // writeLog(`${fileOrDirectory} is output file`); + writeLog(`${fileOrDirectory} is output file`); return; } @@ -991,7 +991,7 @@ namespace ts { } if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { - // Fake build + // Fake that files have been built by updating output file stamps updateOutputTimestamps(proj); return; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 852437942f196..ceec8d60cf2aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5000,7 +5000,6 @@ namespace ts { getDefaultLibLocation?(): string; writeFile: WriteFileCallback; getCurrentDirectory(): string; - getDirectories(path: string): string[]; getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 6dbed25954701..827a3279b3bd5 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -187,10 +187,10 @@ namespace ts { const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system); return { onWatchStatusChange, - watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher, - watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher, - setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop, - clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop + watchFile: maybeBind(system, system.watchFile) || (() => noopFileWatcher), + watchDirectory: maybeBind(system, system.watchDirectory) || (() => noopFileWatcher), + setTimeout: maybeBind(system, system.setTimeout) || noop, + clearTimeout: maybeBind(system, system.clearTimeout) || noop }; } @@ -217,6 +217,7 @@ namespace ts { export function createCompilerHostFromProgramHost(host: ProgramHost, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost { const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); + const hostGetNewLine = memoize(() => host.getNewLine()); return { getSourceFile: (fileName, languageVersion, onError) => { let text: string | undefined; @@ -235,22 +236,22 @@ namespace ts { return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; }, - getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation!()), + getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation), getDefaultLibFileName: options => host.getDefaultLibFileName(options), writeFile, getCurrentDirectory: memoize(() => host.getCurrentDirectory()), useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames), - getNewLine: memoize(() => getNewLineCharacter(getCompilerOptions(), () => host.getNewLine())), + getNewLine: () => getNewLineCharacter(getCompilerOptions(), hostGetNewLine), 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), + trace: maybeBind(host, host.trace), + directoryExists: maybeBind(directoryStructureHost, directoryStructureHost.directoryExists), + getDirectories: maybeBind(directoryStructureHost, directoryStructureHost.getDirectories), + realpath: maybeBind(host, host.realpath), + getEnvironmentVariable: maybeBind(host, host.getEnvironmentVariable) || (() => ""), + createHash: maybeBind(host, host.createHash), + readDirectory: maybeBind(host, host.readDirectory), }; function ensureDirectoriesExist(directoryPath: string) { @@ -297,13 +298,13 @@ namespace ts { directoryExists: path => system.directoryExists(path), getDirectories: path => system.getDirectories(path), 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)), + realpath: maybeBind(system, system.realpath), + getEnvironmentVariable: maybeBind(system, system.getEnvironmentVariable), trace: s => system.write(s + system.newLine), 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)), + createHash: maybeBind(system, system.createHash), createProgram }; } @@ -758,7 +759,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.call(compilerHost, fileName, languageVersion, onError); + const sourceFile = getNewSourceFile(fileName, languageVersion, onError); if (hostSourceFile) { if (shouldCreateNewSourceFile) { hostSourceFile.version++; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index aee6b01eaff40..4801d83de319a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2689,7 +2689,6 @@ declare namespace ts { getDefaultLibLocation?(): string; writeFile: WriteFileCallback; getCurrentDirectory(): string; - getDirectories(path: string): string[]; getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ca4177d87cc77..2e347d5a53a83 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2689,7 +2689,6 @@ declare namespace ts { getDefaultLibLocation?(): string; writeFile: WriteFileCallback; getCurrentDirectory(): string; - getDirectories(path: string): string[]; getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; From dbae2cba478c1acffc45adb517db562d6bc764a4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 17 Jan 2019 11:54:43 -0800 Subject: [PATCH 20/21] add missing type annotation --- src/compiler/program.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0eff6998d8d9f..14042a89e9458 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2187,7 +2187,7 @@ namespace ts { } function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { - const redirect = Object.create(redirectTarget); + const redirect: SourceFile = Object.create(redirectTarget); redirect.fileName = fileName; redirect.path = path; redirect.resolvedPath = resolvedPath; From 900d6f7c9042a686cebc2c50343bfa50ca66b04f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 17 Jan 2019 12:29:23 -0800 Subject: [PATCH 21/21] renames --- src/compiler/builder.ts | 12 ++++++------ src/compiler/tsbuild.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index e8a2bae05135d..44ae6a299d897 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -516,11 +516,11 @@ namespace ts { const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); result.getState = () => state; - result.backupCurrentState = () => { + result.backupState = () => { Debug.assert(backupState === undefined); backupState = cloneBuilderProgramState(state); }; - result.useBackupState = () => { + result.restoreState = () => { state = Debug.assertDefined(backupState); backupState = undefined; }; @@ -715,8 +715,8 @@ namespace ts { export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { return { getState: notImplemented, - backupCurrentState: noop, - useBackupState: noop, + backupState: noop, + restoreState: noop, getProgram, getProgramOrUndefined: () => state.program, releaseProgram: () => state.program = undefined, @@ -766,9 +766,9 @@ namespace ts { /*@internal*/ getState(): BuilderProgramState; /*@internal*/ - backupCurrentState(): void; + backupState(): void; /*@internal*/ - useBackupState(): void; + restoreState(): void; /** * Returns current program */ diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 61b3c610f5aaf..01c4801b106ed 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1129,7 +1129,7 @@ namespace ts { } // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly - program.backupCurrentState(); + program.backupState(); let newestDeclarationFileContentChangedTime = minimumDate; let anyDtsChanged = false; let declDiagnostics: Diagnostic[] | undefined; @@ -1138,7 +1138,7 @@ namespace ts { emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { - program.useBackupState(); + program.restoreState(); return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file"); }