diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts
index 06615b3a97f51..44ae6a299d897 100644
--- a/src/compiler/builder.ts
+++ b/src/compiler/builder.ts
@@ -49,7 +49,27 @@ namespace ts {
         /**
          * program corresponding to this state
          */
-        program: Program;
+        program: Program | undefined;
+        /**
+         * compilerOptions for the program
+         */
+        compilerOptions: CompilerOptions;
+        /**
+         * Files pending to be emitted
+         */
+        affectedFilesPendingEmit: ReadonlyArray<Path> | undefined;
+        /**
+         * Current index to retrieve pending affected file
+         */
+        affectedFilesPendingEmitIndex: number | undefined;
+        /**
+         * Already seen affected files
+         */
+        seenEmittedFiles: Map<true> | undefined;
+        /**
+         * true if program has been emitted
+         */
+        programEmitComplete?: true;
     }
 
     function hasSameKeys<T, U>(map1: ReadonlyMap<T> | undefined, map2: ReadonlyMap<U> | undefined): boolean {
@@ -64,6 +84,7 @@ namespace ts {
         const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState;
         state.program = newProgram;
         const compilerOptions = newProgram.getCompilerOptions();
+        state.compilerOptions = compilerOptions;
         // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
         // With --isolatedModules, emitting changed file doesnt emit dependent files so we cant know of dependent files to retrieve errors so dont cache the errors
         if (!compilerOptions.outFile && !compilerOptions.out && !compilerOptions.isolatedModules) {
@@ -72,7 +93,7 @@ namespace ts {
         state.changedFilesSet = createMap<true>();
 
         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) {
@@ -87,6 +108,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
@@ -112,7 +137,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; }
@@ -132,6 +157,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>): 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
      */
@@ -182,10 +239,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
@@ -193,13 +251,34 @@ namespace ts {
             if (state.exportedModulesMap) {
                 state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap<BuilderState.ReferencedSet | false>();
             }
-            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<true>();
         }
     }
 
+    /**
+     * 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 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 = i;
+                    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
      */
@@ -212,9 +291,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)
             )) {
@@ -317,21 +397,27 @@ 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();
+            state.programEmitComplete = true;
         }
         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<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
-        doneWithAffectedFile(state, affected);
+    function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean): AffectedFileResult<T> {
+        doneWithAffectedFile(state, affected, isPendingEmit);
         return { result, affected };
     }
 
@@ -350,7 +436,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);
         if (state.semanticDiagnosticsPerFile) {
             state.semanticDiagnosticsPerFile.set(path, diagnostics);
         }
@@ -386,7 +472,7 @@ namespace ts {
                 rootNames: newProgramOrRootNames,
                 options: hostOrOptions as CompilerOptions,
                 host: oldProgramOrHost as CompilerHost,
-                oldProgram: oldProgram && oldProgram.getProgram(),
+                oldProgram: oldProgram && oldProgram.getProgramOrUndefined(),
                 configFileParsingDiagnostics,
                 projectReferences
             });
@@ -419,28 +505,31 @@ namespace ts {
         /**
          * Computing hash to for signature verification
          */
-        const computeHash = host.createHash || identity;
-        const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
+        const computeHash = host.createHash || generateDjb2Hash;
+        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
         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 = createRedirectedBuilderProgram(state, configFileParsingDiagnostics);
+        result.getState = () => state;
+        result.backupState = () => {
+            Debug.assert(backupState === undefined);
+            backupState = cloneBuilderProgramState(state);
+        };
+        result.restoreState = () => {
+            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) {
@@ -461,18 +550,39 @@ namespace ts {
          * in that order would be used to write the files
          */
         function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
-            const affected = getNextAffectedFile(state, cancellationToken, computeHash);
+            let affected = getNextAffectedFile(state, cancellationToken, computeHash);
+            let isPendingEmitFile = false;
             if (!affected) {
-                // Done
-                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;
+                }
+            }
+
+            // 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(
                 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),
-                affected
+                Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers),
+                affected,
+                isPendingEmitFile
             );
         }
 
@@ -512,7 +622,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);
         }
 
         /**
@@ -560,33 +670,74 @@ namespace ts {
          */
         function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
             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) {
                 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);
+                // 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;
                 }
             }
 
             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 | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray<Diagnostic>): BuilderProgram {
+        return {
+            getState: notImplemented,
+            backupState: noop,
+            restoreState: noop,
+            getProgram,
+            getProgramOrUndefined: () => state.program,
+            releaseProgram: () => state.program = undefined,
+            getCompilerOptions: () => state.compilerOptions,
+            getSourceFile: fileName => getProgram().getSourceFile(fileName),
+            getSourceFiles: () => getProgram().getSourceFiles(),
+            getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken),
+            getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken),
+            getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics,
+            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: () => getProgram().getCurrentDirectory()
+        };
+
+        function getProgram() {
+            return Debug.assertDefined(state.program);
+        }
+    }
 }
 
 namespace ts {
@@ -614,10 +765,24 @@ namespace ts {
     export interface BuilderProgram {
         /*@internal*/
         getState(): BuilderProgramState;
+        /*@internal*/
+        backupState(): void;
+        /*@internal*/
+        restoreState(): void;
         /**
          * 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
          */
@@ -646,10 +811,15 @@ namespace ts {
          * Get the syntax diagnostics, for all source files if source file is not supplied
          */
         getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
+        /**
+         * Get the declaration diagnostics, for all source files if source file is not supplied
+         */
+        getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
         /**
          * Get all the dependencies of the file
          */
         getAllDependencies(sourceFile: SourceFile): ReadonlyArray<string>;
+
         /**
          * 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
@@ -726,22 +896,7 @@ namespace ts {
     export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderProgram;
     export function createAbstractBuilder(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderProgram;
     export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): 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);
+        return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics);
     }
 }
diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts
index 7c7bebde9f9be..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<SourceFile> | undefined;
+        allFilesExcludingDefaultLibraryFile?: ReadonlyArray<SourceFile>;
         /**
          * Cache of all the file names
          */
-        allFileNames: ReadonlyArray<string> | undefined;
+        allFileNames?: ReadonlyArray<string>;
+    }
+
+    export function cloneMapOrUndefined<T>(map: ReadonlyMap<T> | 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>): BuilderState {
+        const fileInfos = createMap<FileInfo>();
+        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),
         };
     }
 
@@ -505,14 +532,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/core.ts b/src/compiler/core.ts
index cb9e385b3667a..d4f5fe9664afa 100644
--- a/src/compiler/core.ts
+++ b/src/compiler/core.ts
@@ -1387,6 +1387,18 @@ namespace ts {
         return result;
     }
 
+    export function copyProperties<T1 extends T2, T2>(first: T1, second: T2) {
+        for (const id in second) {
+            if (hasOwnProperty.call(second, id)) {
+                (first as any)[id] = second[id];
+            }
+        }
+    }
+
+    export function maybeBind<T, A extends any[], R>(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined {
+        return fn ? fn.bind(obj) : undefined;
+    }
+
     export interface MultiMap<T> extends Map<T[]> {
         /**
          * Adds the value to an array of values associated with the key, and returns the array.
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 5bcaf4485998d..0496d07405fbc 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3949,6 +3949,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/program.ts b/src/compiler/program.ts
index 271bdab976e6c..14042a89e9458 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 CompilerHostLikeForCache {
+        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: CompilerHostLikeForCache,
         toPath: (fileName: string) => Path,
-        useCacheForSourceFile: boolean
+        getSourceFile?: CompilerHost["getSourceFile"]
     ) {
         const originalReadFile = host.readFile;
         const originalFileExists = host.fileExists;
         const originalDirectoryExists = host.directoryExists;
         const originalCreateDirectory = host.createDirectory;
         const originalWriteFile = host.writeFile;
-        const originalGetSourceFile = host.getSourceFile;
         const readFileCache = createMap<string | false>();
         const fileExistsCache = createMap<boolean>();
         const directoryExistsCache = createMap<boolean>();
@@ -242,19 +249,17 @@ namespace ts {
             return setReadFileCache(key, fileName);
         };
 
-        if (useCacheForSourceFile) {
-            host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
-                const key = toPath(fileName);
-                const value = sourceFileCache.get(key);
-                if (value) return value;
+        const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
+            const key = toPath(fileName);
+            const value = sourceFileCache.get(key);
+            if (value) return value;
 
-                const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
-                if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
-                    sourceFileCache.set(key, sourceFile);
-                }
-                return sourceFile;
-            };
-        }
+            const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
+            if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
+                sourceFileCache.set(key, sourceFile);
+            }
+            return sourceFile;
+        } : undefined;
 
         // fileExists for any kind of extension
         host.fileExists = fileName => {
@@ -265,23 +270,25 @@ namespace ts {
             fileExistsCache.set(key, !!newValue);
             return newValue;
         };
-        host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
-            const key = toPath(fileName);
-            fileExistsCache.delete(key);
+        if (originalWriteFile) {
+            host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
+                const key = toPath(fileName);
+                fileExistsCache.delete(key);
 
-            const value = readFileCache.get(key);
-            if (value && value !== data) {
-                readFileCache.delete(key);
-                sourceFileCache.delete(key);
-            }
-            else if (useCacheForSourceFile) {
-                const sourceFile = sourceFileCache.get(key);
-                if (sourceFile && sourceFile.text !== data) {
+                const value = readFileCache.get(key);
+                if (value && value !== data) {
+                    readFileCache.delete(key);
                     sourceFileCache.delete(key);
                 }
-            }
-            originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
-        };
+                else if (getSourceFileWithCache) {
+                    const sourceFile = sourceFileCache.get(key);
+                    if (sourceFile && sourceFile.text !== data) {
+                        sourceFileCache.delete(key);
+                    }
+                }
+                originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
+            };
+        }
 
         // directoryExists
         if (originalDirectoryExists && originalCreateDirectory) {
@@ -306,7 +313,7 @@ namespace ts {
             originalDirectoryExists,
             originalCreateDirectory,
             originalWriteFile,
-            originalGetSourceFile,
+            getSourceFileWithCache,
             readFileWithCache
         };
     }
@@ -735,7 +742,7 @@ namespace ts {
         performance.mark("beforeProgram");
 
         const host = createProgramOptions.host || createCompilerHost(options);
-        const configParsingHost = parseConfigHostFromCompilerHost(host);
+        const configParsingHost = parseConfigHostFromCompilerHostLike(host);
 
         let skipDefaultLib = options.noLib;
         const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options));
@@ -3104,18 +3111,28 @@ namespace ts {
         }
     }
 
+    interface CompilerHostLike {
+        useCaseSensitiveFileNames(): boolean;
+        getCurrentDirectory(): string;
+        fileExists(fileName: string): boolean;
+        readFile(fileName: string): string | undefined;
+        readDirectory?(rootDir: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, depth?: number): string[];
+        trace?(s: string): void;
+        onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter;
+    }
+
     /* @internal */
-    export function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigFileHost {
+    export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost {
         return {
-            fileExists: f => host.fileExists(f),
+            fileExists: f => directoryStructureHost.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),
+            readFile: f => directoryStructureHost.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/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 f1e194e307d4f..01c4801b106ed 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;
         }
 
         /**
@@ -321,7 +321,7 @@ namespace ts {
         return fileExtensionIs(fileName, Extension.Dts);
     }
 
-    export interface SolutionBuilderHostBase extends CompilerHost {
+    export interface SolutionBuilderHostBase<T extends BuilderProgram> extends ProgramHost<T> {
         getModifiedTime(fileName: string): Date | undefined;
         setModifiedTime(fileName: string, date: Date): void;
         deleteFile(fileName: string): void;
@@ -331,15 +331,17 @@ 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;
+        afterProgramEmitAndDiagnostics?(program: T): void;
+
+        // For testing
+        now?(): Date;
     }
 
-    export interface SolutionBuilderHost extends SolutionBuilderHostBase {
+    export interface SolutionBuilderHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T> {
         reportErrorSummary?: ReportEmitErrorSummary;
     }
 
-    export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost {
+    export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost {
     }
 
     export interface SolutionBuilder {
@@ -372,8 +374,8 @@ namespace ts {
         };
     }
 
-    function createSolutionBuilderHostBase(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
-        const host = createCompilerHostWorker({}, /*setParentNodes*/ undefined, system) as SolutionBuilderHostBase;
+    function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
+        const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
         host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
         host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
         host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
@@ -382,20 +384,16 @@ namespace ts {
         return host;
     }
 
-    export function createSolutionBuilderHost(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
-        const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost;
+    export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
+        const host = createSolutionBuilderHostBase(system, createProgram || createAbstractBuilder as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
         host.reportErrorSummary = reportErrorSummary;
         return host;
     }
 
-    export function createSolutionBuilderWithWatchHost(system?: System, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
-        const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost;
+    export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = SemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
+        const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
         const watchHost = createWatchHost(system, reportWatchStatus);
-        host.onWatchStatusChange = watchHost.onWatchStatusChange;
-        host.watchFile = watchHost.watchFile;
-        host.watchDirectory = watchHost.watchDirectory;
-        host.setTimeout = watchHost.setTimeout;
-        host.clearTimeout = watchHost.clearTimeout;
+        copyProperties(host, watchHost);
         return host;
     }
 
@@ -413,13 +411,13 @@ namespace ts {
      * TODO: use SolutionBuilderWithWatchHost => watchedSolution
      *  use SolutionBuilderHost => Solution
      */
-    export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
-    export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
-    export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
-        const hostWithWatch = host as SolutionBuilderWithWatchHost;
+    export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
+    export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
+    export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
+        const hostWithWatch = host as SolutionBuilderWithWatchHost<T>;
         const currentDirectory = host.getCurrentDirectory();
         const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
-        const parseConfigFileHost = parseConfigHostFromCompilerHost(host);
+        const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host);
 
         // State of the solution
         let options = defaultOptions;
@@ -434,8 +432,14 @@ 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);
+        const originalGetSourceFile = compilerHost.getSourceFile;
+        const computeHash = host.createHash || generateDjb2Hash;
+        updateGetSourceFile();
 
         // Watch state
+        const builderPrograms = createFileMap<T>(toPath);
         const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
         const projectPendingBuild = createFileMap<ConfigFileProgramReloadLevel>(toPath);
         const projectErrorsReported = createFileMap<true>(toPath);
@@ -443,6 +447,7 @@ namespace ts {
         let nextProjectToBuild = 0;
         let timerToBuildInvalidatedProject: any;
         let reportFileChangeDetected = false;
+        const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(host, options);
 
         // Watches for the solution
         const allWatchedWildcardDirectories = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
@@ -492,6 +497,27 @@ namespace ts {
             clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
             clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
             clearMap(allWatchedConfigFiles, closeFileWatcher);
+            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 {
@@ -542,9 +568,16 @@ namespace ts {
 
         function watchConfigFile(resolved: ResolvedConfigFileName) {
             if (options.watch && !allWatchedConfigFiles.hasKey(resolved)) {
-                allWatchedConfigFiles.setValue(resolved, hostWithWatch.watchFile(resolved, () => {
-                    invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
-                }));
+                allWatchedConfigFiles.setValue(resolved, watchFile(
+                    hostWithWatch,
+                    resolved,
+                    () => {
+                        invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
+                    },
+                    PollingInterval.High,
+                    WatchType.ConfigFile,
+                    resolved
+                ));
             }
         }
 
@@ -554,20 +587,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: ${resolved} 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 +618,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,
                 }
             );
@@ -898,7 +944,7 @@ namespace ts {
         }
 
         function reportErrorSummary() {
-            if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) {
+            if (options.watch || (host as SolutionBuilderHost<T>).reportErrorSummary) {
                 // Report errors from the other projects
                 getGlobalDependencyGraph().buildQueue.forEach(project => {
                     if (!projectErrorsReported.hasKey(project)) {
@@ -911,7 +957,7 @@ namespace ts {
                     reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors);
                 }
                 else {
-                    (host as SolutionBuilderHost).reportErrorSummary!(totalErrors);
+                    (host as SolutionBuilderHost<T>).reportErrorSummary!(totalErrors);
                 }
             }
         }
@@ -944,16 +990,40 @@ namespace ts {
                 return;
             }
 
+            if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
+                // Fake that files have been built by updating output file stamps
+                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 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)) {
+                        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);
                 }
             }
@@ -1030,22 +1100,23 @@ 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,
+                builderPrograms.getValue(proj),
+                configFile.errors,
+                configFile.projectReferences
+            );
+            projectCompilerOptions = baseCompilerOptions;
 
             // 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");
@@ -1057,6 +1128,8 @@ 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.backupState();
             let newestDeclarationFileContentChangedTime = minimumDate;
             let anyDtsChanged = false;
             let declDiagnostics: Diagnostic[] | undefined;
@@ -1065,11 +1138,13 @@ 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.restoreState();
                 return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file");
             }
 
             // Actual Emit
             const emitterDiagnostics = createDiagnosticCollection();
+            const emittedOutputs = createFileMap<true>(toPath as ToPath);
             outputFiles.forEach(({ name, text, writeByteOrderMark }) => {
                 let priorChangeTime: Date | undefined;
                 if (!anyDtsChanged && isDeclarationFile(name)) {
@@ -1083,7 +1158,8 @@ namespace ts {
                     }
                 }
 
-                writeFile(host, emitterDiagnostics, name, text, writeByteOrderMark);
+                emittedOutputs.setValue(name, true);
+                writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
                 if (priorChangeTime !== undefined) {
                     newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime);
                     unchangedOutputs.setValue(name, priorChangeTime);
@@ -1095,49 +1171,70 @@ 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);
-            if (host.afterProgramEmitAndDiagnostics) {
-                host.afterProgramEmitAndDiagnostics(program);
-            }
+            afterProgramCreate(proj, program);
             return resultFlags;
 
             function buildErrors(diagnostics: ReadonlyArray<Diagnostic>, 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!);
             }
+            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<true>) {
             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;
+                    }
 
-                host.setModifiedTime(file, now);
+                    if (isDeclarationFile(file)) {
+                        priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
+                    }
+
+                    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[] {
@@ -1187,12 +1284,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);
@@ -1249,8 +1349,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;
         }
 
@@ -1278,7 +1378,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));
         }
 
         /**
@@ -1311,6 +1411,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 at least one output`);
+    }
+
     export function formatUpToDateStatus<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
         switch (status.type) {
             case UpToDateStatusType.OutOfDateWithSelf:
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index e46a3d7040318..ceec8d60cf2aa 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<SourceFile>,
     ) => void;
 
@@ -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 7a5c12cae4e6c..827a3279b3bd5 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<SourceFile>;
-        getSyntacticDiagnostics(): ReadonlyArray<Diagnostic>;
-        getOptionsDiagnostics(): ReadonlyArray<Diagnostic>;
-        getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
-        getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
-        getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
-        emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
-    }
-
     export type ReportEmitErrorSummary = (errorCount: number) => void;
 
     export function getErrorCountForSummary(diagnostics: ReadonlyArray<Diagnostic>) {
@@ -121,6 +106,21 @@ 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<SourceFile>;
+        getSyntacticDiagnostics(): ReadonlyArray<Diagnostic>;
+        getOptionsDiagnostics(): ReadonlyArray<Diagnostic>;
+        getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
+        getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
+        getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
+        emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
+    }
+
     /**
      * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options
      */
@@ -187,30 +187,110 @@ 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
         };
     }
 
+    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<X, Y = undefined> extends ts.WatchFactory<X, Y> {
+        writeLog: (s: string) => void;
+    }
+
+    export function createWatchFactory<Y = undefined>(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<WatchType, Y>(watchLogLevel, writeLog) as WatchFactory<WatchType, Y>;
+        result.writeLog = writeLog;
+        return result;
+    }
+
+    export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
+        const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
+        const hostGetNewLine = memoize(() => host.getNewLine());
+        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: maybeBind(host, host.getDefaultLibLocation),
+            getDefaultLibFileName: options => host.getDefaultLibFileName(options),
+            writeFile,
+            getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
+            useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
+            getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
+            getNewLine: () => getNewLineCharacter(getCompilerOptions(), hostGetNewLine),
+            fileExists: f => host.fileExists(f),
+            readFile: f => host.readFile(f),
+            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) {
+            if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) {
+                const parentDirectory = getDirectoryPath(directoryPath);
+                ensureDirectoriesExist(parentDirectory);
+                if (host.createDirectory) host.createDirectory(directoryPath);
+            }
+        }
+
+        function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
+            try {
+                performance.mark("beforeIOWrite");
+                ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
+
+                host.writeFile!(fileName, text, writeByteOrderMark);
+
+                performance.mark("afterIOWrite");
+                performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
+            }
+            catch (e) {
+                if (onError) {
+                    onError(e.message);
+                }
+            }
+        }
+    }
+
     /**
      * Creates the watch compiler host that can be extended with config file or root file names and options host
      */
-    function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
-        if (!createProgram) {
-            createProgram = createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>;
-        }
-
+    export function createProgramHost<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>): ProgramHost<T> {
+        const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
         let host: DirectoryStructureHost = system;
         host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
-        const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames;
-        const writeFileName = (s: string) => system.write(s + system.newLine);
-        const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus);
         return {
-            useCaseSensitiveFileNames,
+            useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
             getNewLine: () => system.newLine,
-            getCurrentDirectory: () => system.getCurrentDirectory(),
+            getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
             getDefaultLibLocation,
             getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
             fileExists: path => system.fileExists(path),
@@ -218,27 +298,25 @@ 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)),
-            watchFile,
-            watchDirectory,
-            setTimeout,
-            clearTimeout,
-            trace: s => system.write(s),
-            onWatchStatusChange,
+            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)),
-            createProgram,
-            afterProgramCreate: emitFilesAndReportErrorUsingBuilder
+            createHash: maybeBind(system, system.createHash),
+            createProgram
         };
+    }
 
-        function getDefaultLibLocation() {
-            return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
-        }
-
-        function emitFilesAndReportErrorUsingBuilder(builderProgram: BuilderProgram) {
+    /**
+     * Creates the watch compiler host that can be extended with config file or root file names and options host
+     */
+    function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
+        const writeFileName = (s: string) => system.write(s + system.newLine);
+        const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>) as WatchCompilerHost<T>;
+        copyProperties(result, createWatchHost(system, reportWatchStatus));
+        result.afterProgramCreate = builderProgram => {
             const compilerOptions = builderProgram.getCompilerOptions();
             const newLine = getNewLineCharacter(compilerOptions, () => system.newLine);
 
@@ -246,13 +324,14 @@ namespace ts {
                 builderProgram,
                 reportDiagnostic,
                 writeFileName,
-                errorCount => onWatchStatusChange!(
+                errorCount => result.onWatchStatusChange!(
                     createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(errorCount), errorCount),
                     newLine,
                     compilerOptions
                 )
             );
-        }
+        };
+        return result;
     }
 
     /**
@@ -291,6 +370,7 @@ namespace ts {
     export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
     /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
     export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference> | undefined) => T;
+
     /** Host that has watch functionality used in --watch mode */
     export interface WatchHost {
         /** If provided, called with Diagnostic message that informs about change in watch status */
@@ -305,19 +385,11 @@ namespace ts {
         /** If provided, will be used to reset existing delayed compilation */
         clearTimeout?(timeoutId: any): void;
     }
-    export interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
-        // TODO: GH#18217 Optional methods are frequently asserted
-
+    export interface ProgramHost<T extends BuilderProgram> {
         /**
          * Used to create the program when need for program creation or recreation detected
          */
         createProgram: CreateProgram<T>;
-        /** If provided, callback to invoke after every new program creation */
-        afterProgramCreate?(program: T): void;
-
-        // Only for testing
-        /*@internal*/
-        maxNumberOfFilesToIterateForInvalidation?: number;
 
         // Sub set of compiler host methods to read and generate new program
         useCaseSensitiveFileNames(): boolean;
@@ -357,16 +429,25 @@ namespace ts {
         /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
         resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
     }
-
     /** Internal interface used to wire emit through same host */
+
     /*@internal*/
-    export interface WatchCompilerHost<T extends BuilderProgram> {
+    export interface ProgramHost<T extends BuilderProgram> {
         // TODO: GH#18217 Optional methods are frequently asserted
         createDirectory?(path: string): void;
         writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
         onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void;
     }
 
+    export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
+        /** If provided, callback to invoke after every new program creation */
+        afterProgramCreate?(program: T): void;
+
+        // Only for testing
+        /*@internal*/
+        maxNumberOfFilesToIterateForInvalidation?: number;
+    }
+
     /**
      * Host to create watch with root files and options
      */
@@ -479,8 +560,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;
@@ -493,15 +572,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();
@@ -517,55 +588,37 @@ 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<string>(watchLogLevel, writeLog);
-
+        const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<string>(host, compilerOptions);
         const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
 
         writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
         if (configFileName) {
-            watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, "Config file");
-        }
-
-        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,
-            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, "Failed Lookup Locations"),
-            watchTypeRootsDirectory: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, "Type roots"),
-            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),
+            watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
+        }
+
+        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)) :
@@ -630,11 +683,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;
@@ -708,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();
+                const sourceFile = getNewSourceFile(fileName, languageVersion, onError);
                 if (hostSourceFile) {
                     if (shouldCreateNewSourceFile) {
                         hostSourceFile.version++;
@@ -719,7 +770,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 +784,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 {
@@ -743,23 +794,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) {
@@ -907,7 +941,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,33 +1005,8 @@ namespace ts {
                     }
                 },
                 flags,
-                "Wild card directories"
+                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/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<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
+    export type WatchFile<X, Y> = (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<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
-    export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
+    export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
+    export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
 
     export interface WatchFactory<X, Y> {
         watchFile: WatchFile<X, Y>;
@@ -444,7 +444,7 @@ namespace ts {
     }
 
     function getWatchInfo<T, X, Y>(file: string, flags: T, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | 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<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts
index d25211d36d3a6..e8c8161705252 100644
--- a/src/harness/fakes.ts
+++ b/src/harness/fakes.ts
@@ -375,7 +375,12 @@ namespace fakes {
         }
     }
 
-    export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost {
+    export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
+        createProgram = ts.createAbstractBuilder;
+        now() {
+            return new Date(this.sys.vfs.time());
+        }
+
         diagnostics: ts.Diagnostic[] = [];
 
         reportDiagnostic(diagnostic: ts.Diagnostic) {
diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts
index cd1c69a2d6738..52797f08d29cd 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
             );
         }
@@ -1339,7 +1326,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}`);
         }
@@ -1706,7 +1693,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",
+    }
+}
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..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<true>();
+                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.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");
+                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 c1ddb0aa4e7af..1f410667c6000 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<true>; };
+
+    function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray<TestFSWithWatch.FileOrFolderOrSymLink>, params?: TestFSWithWatch.TestServerHostCreationParameters) {
+        const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
+        const originalWriteFile = host.writeFile;
+        host.writtenFiles = createMap<true>();
+        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<string>, defaultOptions?: BuildOptions) {
         const host = createSolutionBuilderWithWatchHost(system);
         return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true });
     }
 
-    function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
+    function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, 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<string>, modifiedTimeStampFiles: ReadonlyArray<string>) {
             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<File>, 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`,
@@ -444,11 +479,118 @@ 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<string>) {
+                    (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", () => {
             describe("invoking when references are already built", () => {
-                function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
+                function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
                     checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
                     checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false);
                     checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
@@ -457,9 +599,9 @@ let x: string = 10;`);
                 function createSolutionOfProject(allFiles: ReadonlyArray<File>,
                     currentDirectory: string,
                     solutionBuilderconfig: string,
-                    getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
+                    getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
                     // 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 +616,7 @@ let x: string = 10;`);
                     currentDirectory: string,
                     solutionBuilderconfig: string,
                     watchConfig: string,
-                    getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
+                    getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
                     // Build the composite project
                     const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
 
@@ -489,7 +631,7 @@ let x: string = 10;`);
                     currentDirectory: string,
                     solutionBuilderconfig: string,
                     openFileName: string,
-                    getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
+                    getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
                     // Build the composite project
                     const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
 
@@ -525,12 +667,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<string>
                     ) {
                         it("with tsc-watch", () => {
@@ -633,7 +775,7 @@ export function gfoo() {
                     }
 
                     function verifyWatchState(
-                        host: WatchedSystem,
+                        host: TsBuildWatchSystem,
                         watch: Watch,
                         expectedProgramFiles: ReadonlyArray<string>,
                         expectedWatchedFiles: ReadonlyArray<string>,
@@ -720,20 +862,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<string>) {
+                        function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
                             verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
                         }
 
                         function verifyServerState(
-                            host: WatchedSystem,
+                            host: TsBuildWatchSystem,
                             service: projectSystem.TestProjectService,
                             expectedProgramFiles: ReadonlyArray<string>,
                             expectedWatchedFiles: ReadonlyArray<string>,
@@ -753,13 +895,13 @@ export function gfoo() {
                         }
 
                         function verifyScenario(
-                            edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
+                            edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
                             expectedEditErrors: ReadonlyArray<string>,
                             expectedProgramFiles: ReadonlyArray<string>,
                             expectedWatchedFiles: ReadonlyArray<string>,
                             expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
                             dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
-                            revert?: (host: WatchedSystem) => void,
+                            revert?: (host: TsBuildWatchSystem) => void,
                             orphanInfosAfterEdit?: ReadonlyArray<string>,
                             orphanInfosAfterRevert?: ReadonlyArray<string>) {
                             it("with tsc-watch", () => {
@@ -978,8 +1120,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);
diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts
index 9d363dc471538..88c3cdedfd939 100644
--- a/src/tsc/tsc.ts
+++ b/src/tsc/tsc.ts
@@ -204,12 +204,11 @@ namespace ts {
             reportWatchModeWithoutSysSupport();
         }
 
-        // 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;
-        buildHost.afterProgramEmitAndDiagnostics = reportStatistics;
+            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());
 
         const builder = createSolutionBuilder(buildHost, projects, buildOptions);
         if (buildOptions.clean) {
@@ -234,7 +233,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 +254,19 @@ namespace ts {
         return sys.exit(exitStatus);
     }
 
-    function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
-        const compileUsingBuilder = watchCompilerHost.createProgram;
-        watchCompilerHost.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
+    function updateCreateProgram<T extends BuilderProgram>(host: { createProgram: CreateProgram<T>; }) {
+        const compileUsingBuilder = host.createProgram;
+        host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
             Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram));
             if (options !== undefined) {
                 enableStatistics(options);
             }
             return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
         };
+    }
+
+    function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
+        updateCreateProgram(watchCompilerHost);
         const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217
         watchCompilerHost.afterProgramCreate = builderProgram => {
             emitFilesUsingBuilder(builderProgram);
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index 91f492ddd1d52..4801d83de319a 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<SourceFile>) => void;
+    type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
     class OperationCanceledException {
     }
     interface CancellationToken {
@@ -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;
@@ -4278,6 +4277,10 @@ declare namespace ts {
          * Get the syntax diagnostics, for all source files if source file is not supplied
          */
         getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
+        /**
+         * Get the declaration diagnostics, for all source files if source file is not supplied
+         */
+        getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
         /**
          * Get all the dependencies of the file
          */
@@ -4364,13 +4367,11 @@ declare namespace ts {
         /** If provided, will be used to reset existing delayed compilation */
         clearTimeout?(timeoutId: any): void;
     }
-    interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
+    interface ProgramHost<T extends BuilderProgram> {
         /**
          * Used to create the program when need for program creation or recreation detected
          */
         createProgram: CreateProgram<T>;
-        /** If provided, callback to invoke after every new program creation */
-        afterProgramCreate?(program: T): void;
         useCaseSensitiveFileNames(): boolean;
         getNewLine(): string;
         getCurrentDirectory(): string;
@@ -4404,6 +4405,10 @@ declare namespace ts {
         /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
         resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
     }
+    interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
+        /** If provided, callback to invoke after every new program creation */
+        afterProgramCreate?(program: T): void;
+    }
     /**
      * Host to create watch with root files and options
      */
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index 0e693f698f253..2e347d5a53a83 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<SourceFile>) => void;
+    type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
     class OperationCanceledException {
     }
     interface CancellationToken {
@@ -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;
@@ -4278,6 +4277,10 @@ declare namespace ts {
          * Get the syntax diagnostics, for all source files if source file is not supplied
          */
         getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
+        /**
+         * Get the declaration diagnostics, for all source files if source file is not supplied
+         */
+        getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
         /**
          * Get all the dependencies of the file
          */
@@ -4364,13 +4367,11 @@ declare namespace ts {
         /** If provided, will be used to reset existing delayed compilation */
         clearTimeout?(timeoutId: any): void;
     }
-    interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
+    interface ProgramHost<T extends BuilderProgram> {
         /**
          * Used to create the program when need for program creation or recreation detected
          */
         createProgram: CreateProgram<T>;
-        /** If provided, callback to invoke after every new program creation */
-        afterProgramCreate?(program: T): void;
         useCaseSensitiveFileNames(): boolean;
         getNewLine(): string;
         getCurrentDirectory(): string;
@@ -4404,6 +4405,10 @@ declare namespace ts {
         /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
         resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
     }
+    interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
+        /** If provided, callback to invoke after every new program creation */
+        afterProgramCreate?(program: T): void;
+    }
     /**
      * Host to create watch with root files and options
      */