Refactoring to watches and caching of system such that we minimize function expressions

Also unified watcher info logging
This commit is contained in:
Sheetal Nandi 2017-08-23 13:34:41 -07:00
parent 9e570c375b
commit 4c79033894
11 changed files with 301 additions and 461 deletions

View File

@ -120,7 +120,7 @@ namespace ts {
// Remove existing file info
onDeleteValue: removeExistingFileInfo,
// We will update in place instead of deleting existing value and adding new one
onExistingValue: (_key, existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution)
onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution)
}
);
}
@ -137,7 +137,7 @@ namespace ts {
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
}
function removeExistingFileInfo(path: Path, existingFileInfo: FileInfo) {
function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) {
registerChangedFile(path, existingFileInfo.fileName);
emitHandler.removeScriptInfo(path);
}

View File

@ -2631,10 +2631,6 @@ namespace ts {
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
}
export interface HostForCaching extends PartialSystem {
useCaseSensitiveFileNames: boolean;
}
export interface CachedHost {
addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void;
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
@ -2649,11 +2645,15 @@ namespace ts {
readonly directories: string[];
}
export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem {
export function createCachedPartialSystem(host: PartialSystem): CachedPartialSystem {
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
return {
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
newLine: host.newLine,
readFile: (path, encoding) => host.readFile(path, encoding),
write: s => host.write(s),
writeFile,
fileExists,
directoryExists,
@ -2663,7 +2663,8 @@ namespace ts {
readDirectory,
addOrDeleteFileOrFolder,
addOrDeleteFile,
clearCache
clearCache,
exit: code => host.exit(code)
};
function toPath(fileName: string) {

View File

@ -461,7 +461,6 @@ namespace ts {
program: Program,
missingFileWatches: Map<FileWatcher>,
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void
) {
const missingFilePaths = program.getMissingFilePaths();
const newMissingFilePathMap = arrayToSet(missingFilePaths);
@ -474,7 +473,7 @@ namespace ts {
createNewValue: createMissingFileWatch,
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
onDeleteValue: closeExistingMissingFilePathFileWatcher
onDeleteValue: closeFileWatcher
}
);
}
@ -493,8 +492,7 @@ namespace ts {
export function updateWatchingWildcardDirectories(
existingWatchedForWildcards: Map<WildcardDirectoryWatcher>,
wildcardDirectories: Map<WatchDirectoryFlags>,
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher,
closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flagsChanged: boolean) => void
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher
) {
mutateMap(
existingWatchedForWildcards,
@ -503,8 +501,7 @@ namespace ts {
// Create new watch and recursive info
createNewValue: createWildcardDirectoryWatcher,
// Close existing watch thats not needed any more
onDeleteValue: (directory, wildcardDirectoryWatcher) =>
closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ false),
onDeleteValue: closeFileWatcherOf,
// Close existing watch that doesnt match in the flags
onExistingValue: updateWildcardDirectoryWatcher
}
@ -518,13 +515,13 @@ namespace ts {
};
}
function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags) {
function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) {
// Watcher needs to be updated if the recursive flags dont match
if (wildcardDirectoryWatcher.flags === flags) {
if (existingWatcher.flags === flags) {
return;
}
closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true);
existingWatcher.watcher.close();
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
}
}

View File

@ -27,14 +27,14 @@ namespace ts {
}
interface FailedLookupLocationsWatcher {
fileWatcher: FileWatcher;
watcher: FileWatcher;
refCount: number;
}
export function createResolutionCache(
toPath: (fileName: string) => Path,
getCompilerOptions: () => CompilerOptions,
watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher,
watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher,
log: (s: string) => void,
projectName?: string,
getGlobalCache?: () => string | undefined): ResolutionCache {
@ -69,10 +69,7 @@ namespace ts {
function clear() {
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => {
log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`);
failedLookupLocationWatcher.fileWatcher.close();
});
clearMap(failedLookupLocationsWatches, closeFileWatcherOf);
resolvedModuleNames.clear();
resolvedTypeReferenceDirectives.clear();
}
@ -142,7 +139,7 @@ namespace ts {
}
else {
resolution = loader(name, containingFile, compilerOptions, host);
updateFailedLookupLocationWatches(containingFile, name, existingResolution && existingResolution.failedLookupLocations, resolution.failedLookupLocations);
updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations);
}
newResolutions.set(name, resolution);
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
@ -205,46 +202,44 @@ namespace ts {
m => m.resolvedModule, r => r.resolvedFileName, logChanges);
}
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
if (failedLookupLocationWatcher) {
failedLookupLocationWatcher.refCount++;
log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`);
log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`);
}
else {
log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name);
failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 });
const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 });
}
}
function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) {
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
Debug.assert(!!failedLookupLocationWatcher);
failedLookupLocationWatcher.refCount--;
if (failedLookupLocationWatcher.refCount) {
log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}: refCount: ${failedLookupLocationWatcher.refCount}`);
log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`);
}
else {
log(`Watcher: FailedLookupLocations: Status: Closing the file watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
failedLookupLocationWatcher.fileWatcher.close();
failedLookupLocationWatcher.watcher.close();
failedLookupLocationsWatches.delete(failedLookupLocationPath);
}
}
type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void;
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string>, containingFile: string, name: string, fn: FailedLookupLocationAction) {
type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void;
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string> | undefined, fn: FailedLookupLocationAction) {
forEach(failedLookupLocations, failedLookupLocation => {
fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name);
fn(failedLookupLocation, toPath(failedLookupLocation));
});
}
function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray<string> | undefined, failedLookupLocations: ReadonlyArray<string>) {
function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray<string> | undefined, existingFailedLookupLocations: ReadonlyArray<string> | undefined) {
// Watch all the failed lookup locations
withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation);
withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation);
// Close existing watches for the failed locations
withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher);
withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher);
}
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
@ -255,8 +250,8 @@ namespace ts {
cache.forEach((value, path) => {
if (path === deletedFilePath) {
cache.delete(path);
value.forEach((resolution, name) => {
withFailedLookupLocations(resolution.failedLookupLocations, path, name, closeFailedLookupLocationWatcher);
value.forEach(resolution => {
withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher);
});
}
else if (value) {

View File

@ -34,6 +34,10 @@ namespace ts {
* Partial interface of the System thats needed to support the caching of directory structure
*/
export interface PartialSystem {
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
readFile(path: string, encoding?: string): string | undefined;
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
@ -41,6 +45,7 @@ namespace ts {
getCurrentDirectory(): string;
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[];
exit(exitCode?: number): void;
}
export interface System extends PartialSystem {
@ -48,7 +53,6 @@ namespace ts {
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
readFile(path: string, encoding?: string): string | undefined;
getFileSize?(path: string): number;
/**
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
@ -65,7 +69,6 @@ namespace ts {
*/
createHash?(data: string): string;
getMemoryUsage?(): number;
exit(exitCode?: number): void;
realpath?(path: string): string;
/*@internal*/ getEnvironmentVariable(name: string): string;
/*@internal*/ tryEnableSourceMapsForHost?(): void;

View File

@ -3490,17 +3490,15 @@ namespace ts {
/**
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
*/
export function clearMap<T>(map: Map<T>, onDeleteValue: (key: string, existingValue: T) => void) {
export function clearMap<T>(map: Map<T>, onDeleteValue: (existingValue: T, key: string) => void) {
// Remove all
map.forEach((existingValue, key) => {
onDeleteValue(key, existingValue);
});
map.forEach(onDeleteValue);
map.clear();
}
export interface MutateMapOptions<T, U> {
createNewValue(key: string, valueInNewMap: U): T;
onDeleteValue(key: string, existingValue: T): void;
onDeleteValue(existingValue: T, key: string): void;
/**
* If present this is called with the key when there is value for that key both in new map as well as existing map provided
@ -3508,7 +3506,7 @@ namespace ts {
* If the key is removed, caller will get callback of createNewValue for that key.
* If this callback is not provided, the value of such keys is not updated.
*/
onExistingValue?(key: string, existingValue: T, valueInNewMap: U): void;
onExistingValue?(existingValue: T, valueInNewMap: U, key: string): void;
}
/**
@ -3524,11 +3522,11 @@ namespace ts {
// Not present any more in new map, remove it
if (valueInNewMap === undefined) {
map.delete(key);
onDeleteValue(key, existingValue);
onDeleteValue(existingValue, key);
}
// If present notify about existing values
else if (onExistingValue) {
onExistingValue(key, existingValue, valueInNewMap);
onExistingValue(existingValue, valueInNewMap, key);
}
});
@ -3544,6 +3542,63 @@ namespace ts {
clearMap(map, options.onDeleteValue);
}
}
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
return host.watchFile(file, cb);
}
export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb);
}
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher {
return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path));
}
export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path);
}
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
return host.watchDirectory(directory, fileName => {
cb(getNormalizedAbsolutePath(fileName, directory));
}, recursive);
}
export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags);
}
type WatchCallback<T, U> = (fileName: string, cbOptional1?: T, optional?: U) => void;
type AddWatch<T, U> = (host: System, file: string, cb: WatchCallback<T, U>, optional?: U) => FileWatcher;
function createWatcherWithLogging<T, U>(addWatch: AddWatch<T, U>, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
const info = `PathInfo: ${file}`;
log(`${watcherCaption}Added: ${info}`);
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
cb(fileName, cbOptional1, optional);
}, optional);
return {
close: () => {
log(`${watcherCaption}Close: ${info}`);
watcher.close();
}
};
}
export function closeFileWatcher(watcher: FileWatcher) {
watcher.close();
}
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
objWithWatcher.watcher.close();
}
}
namespace ts {

View File

@ -4,8 +4,7 @@
namespace ts {
export type DiagnosticReporter = (diagnostic: Diagnostic) => void;
export type DiagnosticWorker = (diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System) => void;
export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine;
export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine;
export interface WatchingSystemHost {
// FS system to use
system: System;
@ -19,7 +18,7 @@ namespace ts {
// Callbacks to do custom action before creating program and after creating program
beforeCompile(compilerOptions: CompilerOptions): void;
afterCompile(host: System, program: Program, builder: Builder): void;
afterCompile(host: PartialSystem, program: Program, builder: Builder): void;
}
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? {
@ -62,7 +61,7 @@ namespace ts {
system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine());
}
export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine {
export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine {
let configFileText: string;
try {
configFileText = system.readFile(configFileName);
@ -90,7 +89,7 @@ namespace ts {
return configParseResult;
}
function reportEmittedFiles(files: string[], system: System): void {
function reportEmittedFiles(files: string[], system: PartialSystem): void {
if (!files || files.length === 0) {
return;
}
@ -101,7 +100,7 @@ namespace ts {
}
}
export function handleEmitOutputAndReportErrors(system: System, program: Program,
export function handleEmitOutputAndReportErrors(system: PartialSystem, program: Program,
emittedFiles: string[], emitSkipped: boolean,
diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter
): ExitStatus {
@ -142,7 +141,7 @@ namespace ts {
afterCompile: compileWatchedProgram,
};
function compileWatchedProgram(host: System, program: Program, builder: Builder) {
function compileWatchedProgram(host: PartialSystem, program: Program, builder: Builder) {
// First get and report any syntactic errors.
let diagnostics = program.getSyntacticDiagnostics();
let reportSemanticDiagnostics = false;
@ -249,26 +248,28 @@ namespace ts {
let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop;
const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher;
const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher;
const watchDirectory = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher;
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
let host: System;
const host = configFileName ? createCachedPartialSystem(system) : system;
if (configFileName) {
host = createCachedSystem(system);
configFileWatcher = system.watchFile(configFileName, onConfigFileChanged);
}
else {
host = system;
configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog);
}
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
// Cache for the module resolution
const resolutionCache = createResolutionCache(
fileName => toPath(fileName),
() => compilerOptions,
watchFailedLookupLocation,
s => writeLog(s)
writeLog
);
// There is no extra check needed since we can just rely on the program to decide emit
@ -303,7 +304,7 @@ namespace ts {
builder.onProgramUpdateGraph(program, hasInvalidatedResolution);
// Update watches
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath, closeMissingFilePathWatcher);
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath);
if (missingFilePathsRequestedForRelease) {
// These are the paths that program creater told us as not in use any more but were missing on the disk.
// We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
@ -324,7 +325,7 @@ namespace ts {
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
const newLine = getNewLineCharacter(options, system);
const realpath = host.realpath && ((path: string) => host.realpath(path));
const realpath = system.realpath && ((path: string) => system.realpath(path));
return {
getSourceFile: getVersionedSourceFile,
@ -333,14 +334,14 @@ namespace ts {
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames,
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine,
fileExists,
readFile: fileName => host.readFile(fileName),
trace: (s: string) => host.write(s + newLine),
readFile: fileName => system.readFile(fileName),
trace: (s: string) => system.write(s + newLine),
directoryExists: directoryName => host.directoryExists(directoryName),
getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "",
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => host.getDirectories(path),
realpath,
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
@ -365,7 +366,7 @@ namespace ts {
}
function getDefaultLibLocation(): string {
return getDirectoryPath(normalizePath(host.getExecutingFilePath()));
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
}
function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
@ -390,7 +391,7 @@ namespace ts {
hostSourceFile.sourceFile = sourceFile;
sourceFile.version = hostSourceFile.version.toString();
if (!hostSourceFile.fileWatcher) {
hostSourceFile.fileWatcher = watchSourceFileForChanges(path);
hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog);
}
}
else {
@ -403,7 +404,7 @@ namespace ts {
let fileWatcher: FileWatcher;
if (sourceFile) {
sourceFile.version = "0";
fileWatcher = watchSourceFileForChanges(path);
fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog);
sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher });
}
else {
@ -418,7 +419,7 @@ namespace ts {
let text: string;
try {
performance.mark("beforeIORead");
text = host.readFile(fileName, compilerOptions.charset);
text = system.readFile(fileName, compilerOptions.charset);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
@ -502,7 +503,7 @@ namespace ts {
writeLog(`Reloading config file: ${configFileName}`);
needsReload = false;
const cachedHost = host as CachedSystem;
const cachedHost = host as CachedPartialSystem;
cachedHost.clearCache();
const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic);
rootFileNames = configParseResult.fileNames;
@ -517,13 +518,7 @@ namespace ts {
watchConfigFileWildCardDirectories();
}
function watchSourceFileForChanges(path: Path) {
return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind));
}
function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
updateCachedSystemWithFile(fileName, path, eventKind);
const hostSourceFile = sourceFilesCache.get(path);
if (hostSourceFile) {
@ -553,35 +548,29 @@ namespace ts {
function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
if (configFileName) {
(host as CachedSystem).addOrDeleteFile(fileName, path, eventKind);
(host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind);
}
}
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, failedLookupLocationPath, containingFile, name));
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog);
}
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`);
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) {
updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind);
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
scheduleProgramUpdate();
}
function watchMissingFilePath(missingFilePath: Path) {
return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind));
return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog);
}
function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) {
fileWatcher.close();
}
function onMissingFileChange(fileName: string, missingFilePath: Path, eventKind: FileWatcherEventKind) {
writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
updateCachedSystemWithFile(fileName, missingFilePath, eventKind);
if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath));
missingFilesMap.get(missingFilePath).close();
missingFilesMap.delete(missingFilePath);
// Delete the entry in the source files cache so that new source file is created
@ -596,19 +585,12 @@ namespace ts {
updateWatchingWildcardDirectories(
watchedWildcardDirectories || (watchedWildcardDirectories = createMap()),
createMapFromTemplate(configFileWildCardDirectories),
watchWildCardDirectory,
stopWatchingWildCardDirectory
watchWildCardDirectory
);
}
function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) {
return host.watchDirectory(directory, fileOrFolder =>
onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileOrFolder, directory)),
(flags & WatchDirectoryFlags.Recursive) !== 0);
}
function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatcher, _recursiveChanged: boolean) {
watcher.close();
return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog);
}
function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) {
@ -617,7 +599,7 @@ namespace ts {
const fileOrFolderPath = toPath(fileOrFolder);
// Since the file existance changed, update the sourceFiles cache
(host as CachedSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
(host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
removeSourceFile(fileOrFolderPath);
// If a change was made inside "folder/file", node will trigger the callback twice:
@ -628,8 +610,6 @@ namespace ts {
return;
}
writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileOrFolder}`);
// Reload is pending, do the reload
if (!needsReload) {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host);
@ -643,70 +623,8 @@ namespace ts {
}
}
function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) {
writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`);
scheduleProgramReload();
}
function writeLog(s: string) {
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
if (hasDiagnostics) {
host.write(s);
}
}
function computeHash(data: string) {
return system.createHash ? system.createHash(data) : data;
}
}
interface CachedSystem extends System, CachedHost {
}
function createCachedSystem(host: System): CachedSystem {
const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined;
const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined;
const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined;
const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined;
const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined;
const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined;
const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined;
const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined;
const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined;
const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined;
const cachedPartialSystem = createCachedPartialSystem(host);
return {
args: host.args,
newLine: host.newLine,
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
write: s => host.write(s),
readFile: (path, encoding?) => host.readFile(path, encoding),
getFileSize,
writeFile: (fileName, data, writeByteOrderMark?) => cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark),
watchFile,
watchDirectory,
resolvePath: path => host.resolvePath(path),
fileExists: fileName => cachedPartialSystem.fileExists(fileName),
directoryExists: dir => cachedPartialSystem.directoryExists(dir),
createDirectory: dir => cachedPartialSystem.createDirectory(dir),
getExecutingFilePath: () => host.getExecutingFilePath(),
getCurrentDirectory: () => cachedPartialSystem.getCurrentDirectory(),
getDirectories: dir => cachedPartialSystem.getDirectories(dir),
readDirectory: (path, extensions, excludes, includes, depth) => cachedPartialSystem.readDirectory(path, extensions, excludes, includes, depth),
getModifiedTime,
createHash,
getMemoryUsage,
exit: exitCode => host.exit(exitCode),
realpath,
getEnvironmentVariable: name => host.getEnvironmentVariable(name),
tryEnableSourceMapsForHost,
debugMode: host.debugMode,
setTimeout,
clearTimeout,
addOrDeleteFileOrFolder: (fileOrFolder, fileOrFolderPath) => cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath),
addOrDeleteFile: (file, filePath, eventKind) => cachedPartialSystem.addOrDeleteFile(file, filePath, eventKind),
clearCache: () => cachedPartialSystem.clearCache()
};
}
}

View File

@ -248,22 +248,7 @@ namespace ts.server {
TypeRoot = "Type root of the project",
ClosedScriptInfo = "Closed Script info",
ConfigFileForInferredRoot = "Config file for the inferred project root",
FailedLookupLocation = "Failed lookup locations in module resolution"
}
/* @internal */
export const enum WatcherCloseReason {
ProjectClose = "Project close",
NotNeeded = "After project update isnt required any more",
FileCreated = "File got created",
RecursiveChanged = "Recursive changed for the watch",
ProjectReloadHitMaxSize = "Project reloaded and hit the max file size capacity",
OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more",
OrphanScriptInfo = "Removing Orphan script info as part of cleanup",
FileDeleted = "File was deleted",
FileOpened = "File opened",
ConfigProjectCreated = "Config file project created",
FileClosed = "File is closed"
FailedLookupLocation = "Directory of Failed lookup locations in module resolution"
}
const enum ConfigFileWatcherStatus {
@ -276,9 +261,6 @@ namespace ts.server {
RootOfInferredProjectFalse = "Open file was set as not inferred root",
}
/* @internal */
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
interface ConfigFileExistenceInfo {
/**
* Cached value of existence of config file
@ -318,6 +300,10 @@ namespace ts.server {
allowLocalPluginLoads?: boolean;
}
type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher;
type WatchFilePath = (host: ServerHost, file: string, cb: FilePathWatcherCallback, path: Path, watchType: WatchType, project?: Project) => FileWatcher;
type WatchDirectory = (host: ServerHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, watchType: WatchType, project?: Project) => FileWatcher;
export class ProjectService {
public readonly typingsCache: TypingsCache;
@ -392,6 +378,13 @@ namespace ts.server {
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects = createMap<true>();
/*@internal*/
readonly watchFile: WatchFile;
/*@internal*/
readonly watchFilePath: WatchFilePath;
/*@internal*/
readonly watchDirectory: WatchDirectory;
constructor(opts: ProjectServiceOptions) {
this.host = opts.host;
this.logger = opts.logger;
@ -422,6 +415,21 @@ namespace ts.server {
};
this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory);
if (this.logger.hasLevel(LogLevel.verbose)) {
this.watchFile = (host, file, cb, watchType, project) => ts.addFileWatcherWithLogging(host, file, cb, this.createWatcherLog(watchType, project));
this.watchFilePath = (host, file, cb, path, watchType, project) => ts.addFilePathWatcherWithLogging(host, file, cb, path, this.createWatcherLog(watchType, project));
this.watchDirectory = (host, dir, cb, flags, watchType, project) => ts.addDirectoryWatcherWithLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project));
}
else {
this.watchFile = ts.addFileWatcher;
this.watchFilePath = ts.addFilePathWatcher;
this.watchDirectory = ts.addDirectoryWatcher;
}
}
private createWatcherLog(watchType: WatchType, project: Project | undefined): (s: string) => void {
const detailedInfo = ` Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
return s => this.logger.info(s + detailedInfo);
}
toPath(fileName: string, basePath = this.currentDirectory) {
@ -674,7 +682,7 @@ namespace ts.server {
else if (!info.isScriptOpen()) {
if (info.containingProjects.length === 0) {
// Orphan script info, remove it as we can always reload it on next open file request
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange);
this.stopWatchingScriptInfo(info);
this.filenameToScriptInfo.delete(info.path);
}
else {
@ -687,7 +695,7 @@ namespace ts.server {
}
private handleDeletedFile(info: ScriptInfo) {
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted);
this.stopWatchingScriptInfo(info);
// TODO: handle isOpen = true case
@ -705,8 +713,8 @@ namespace ts.server {
}
/* @internal */
onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: NormalizedPath) {
project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: string) {
project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
project.updateTypes();
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
}
@ -717,8 +725,8 @@ namespace ts.server {
* @param fileName the absolute file name that changed in watched directory
*/
/* @internal */
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: NormalizedPath) {
project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: string) {
project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder));
const configFilename = project.getConfigFilePath();
// If a change was made inside "folder/file", node will trigger the callback twice:
@ -730,7 +738,7 @@ namespace ts.server {
}
const configFileSpecs = project.configFileSpecs;
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions);
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(result.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined);
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
@ -923,7 +931,7 @@ namespace ts.server {
this.filenameToScriptInfo.forEach(info => {
if (!info.isScriptOpen() && info.isOrphan()) {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo);
this.stopWatchingScriptInfo(info);
this.filenameToScriptInfo.delete(info.path);
}
});
@ -967,10 +975,7 @@ namespace ts.server {
// close existing watcher
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) {
const configFileName = project.getConfigFilePath();
this.closeFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
configFileExistenceInfo.configFileWatcherForRootOfInferredProject, WatcherCloseReason.ConfigProjectCreated
);
configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close();
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined;
this.logConfigFileWatchUpdate(configFileName, project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
}
@ -1002,11 +1007,7 @@ namespace ts.server {
// created when any of the script infos are added as root of inferred project
if (this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) {
Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject);
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
);
this.logConfigFileWatchUpdate(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
this.createConfigFileWatcherOfConfigFileExistence(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo);
}
}
else {
@ -1016,7 +1017,7 @@ namespace ts.server {
}
private logConfigFileWatchUpdate(configFileName: NormalizedPath, canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo, status: ConfigFileWatcherStatus) {
if (!this.logger.loggingEnabled()) {
if (!this.logger.hasLevel(LogLevel.verbose)) {
return;
}
const inferredRoots: string[] = [];
@ -1036,21 +1037,32 @@ namespace ts.server {
this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
}
/**
* Create the watcher for the configFileExistenceInfo
*/
private createConfigFileWatcherOfConfigFileExistence(
configFileName: NormalizedPath,
canonicalConfigFilePath: string,
configFileExistenceInfo: ConfigFileExistenceInfo
) {
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFile(
this.host,
configFileName,
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind),
WatchType.ConfigFileForInferredRoot
);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
}
/**
* Close the config file watcher in the cached ConfigFileExistenceInfo
* if there arent any open files that are root of inferred project
*/
private closeConfigFileWatcherOfConfigFileExistenceInfo(
configFileName: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo,
reason: WatcherCloseReason
) {
private closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo: ConfigFileExistenceInfo) {
// Close the config file watcher if there are no more open files that are root of inferred project
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject &&
!this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) {
this.closeFileWatcher(
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
configFileExistenceInfo.configFileWatcherForRootOfInferredProject, reason
);
configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close();
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined;
}
}
@ -1074,9 +1086,7 @@ namespace ts.server {
if (infoIsRootOfInferredProject) {
// But if it is a root, it could be the last script info that is root of inferred project
// and hence we would need to close the config file watcher
this.closeConfigFileWatcherOfConfigFileExistenceInfo(
configFileName, configFileExistenceInfo, WatcherCloseReason.FileClosed
);
this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo);
}
// If there are no open files that are impacted by configFileExistenceInfo after closing this script info
@ -1097,27 +1107,24 @@ namespace ts.server {
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
Debug.assert(info.isScriptOpen());
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
let configFilePresenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (!configFilePresenceInfo) {
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (!configFileExistenceInfo) {
// Create the cache
configFilePresenceInfo = {
configFileExistenceInfo = {
exists: this.host.fileExists(configFileName),
openFilesImpactedByConfigFile: createMap<boolean>()
};
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFilePresenceInfo);
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo);
}
// Set this file as the root of inferred project
configFilePresenceInfo.openFilesImpactedByConfigFile.set(info.path, true);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue);
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, true);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue);
// If there is no configured project for this config file, add the file watcher
if (!configFilePresenceInfo.configFileWatcherForRootOfInferredProject &&
if (!configFileExistenceInfo.configFileWatcherForRootOfInferredProject &&
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
configFilePresenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
(_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
this.createConfigFileWatcherOfConfigFileExistence(configFileName, canonicalConfigFilePath, configFileExistenceInfo);
}
});
}
@ -1126,7 +1133,7 @@ namespace ts.server {
* This is called by inferred project whenever root script info is removed from it
*/
/* @internal */
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) {
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
@ -1137,9 +1144,7 @@ namespace ts.server {
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectFalse);
// Close the config file watcher
this.closeConfigFileWatcherOfConfigFileExistenceInfo(
configFileName, configFileExistenceInfo, reason
);
this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo);
}
});
}
@ -1248,7 +1253,7 @@ namespace ts.server {
return findProjectByName(projectFileName, this.externalProjects);
}
private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) {
private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: PartialSystem) {
configFilename = normalizePath(configFilename);
const configFileContent = this.host.readFile(configFilename);
@ -1385,8 +1390,8 @@ namespace ts.server {
}
private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) {
const cachedServerHost = new CachedServerHost(this.host);
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
const cachedPartialSystem = createCachedPartialSystem(this.host);
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedPartialSystem);
this.logger.info(`Opened configuration file ${configFileName}`);
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
const project = new ConfiguredProject(
@ -1397,12 +1402,16 @@ namespace ts.server {
projectOptions.compilerOptions,
languageServiceEnabled,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
cachedServerHost);
cachedPartialSystem);
project.configFileSpecs = configFileSpecs;
// TODO: We probably should also watch the configFiles that are extended
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project,
configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)
project.configFileWatcher = this.watchFile(
this.host,
configFileName,
(_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind),
WatchType.ConfigFilePath,
project
);
if (languageServiceEnabled) {
project.watchWildcards(projectOptions.wildcardDirectories);
@ -1490,7 +1499,7 @@ namespace ts.server {
/* @internal */
reloadConfiguredProject(project: ConfiguredProject) {
// At this point, there is no reason to not have configFile in the host
const host = project.getCachedServerHost();
const host = project.getCachedPartialSystem();
// Clear the cache since we are reloading the project from disk
host.clearCache();
@ -1505,8 +1514,8 @@ namespace ts.server {
project.setProjectErrors(configFileErrors);
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) {
project.disableLanguageService();
project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize);
project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize);
project.stopWatchingWildCards();
project.stopWatchingTypeRoots();
}
else {
project.enableLanguageService();
@ -1594,7 +1603,7 @@ namespace ts.server {
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
/*@internal*/
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) {
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: PartialSystem) {
return this.getOrCreateScriptInfoForNormalizedPath(
toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined,
/*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn
@ -1610,20 +1619,23 @@ namespace ts.server {
// do not watch files with mixed content - server doesn't know how to interpret it
if (!info.hasMixedContent) {
const { fileName } = info;
info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName,
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)
info.fileWatcher = this.watchFile(
this.host,
fileName,
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind),
WatchType.ClosedScriptInfo
);
}
}
private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) {
private stopWatchingScriptInfo(info: ScriptInfo) {
if (info.fileWatcher) {
this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason);
info.fileWatcher.close();
info.fileWatcher = undefined;
}
}
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) {
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) {
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
let info = this.getScriptInfoForPath(path);
if (!info) {
@ -1645,7 +1657,7 @@ namespace ts.server {
}
if (info) {
if (openedByClient && !info.isScriptOpen()) {
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened);
this.stopWatchingScriptInfo(info);
info.open(fileContent);
if (hasMixedContent) {
info.registerFileUpdate();
@ -1693,39 +1705,6 @@ namespace ts.server {
}
}
/* @internal */
closeFileWatcher(watchType: WatchType, project: Project, file: string, watcher: FileWatcher, reason: WatcherCloseReason) {
this.logger.info(`FileWatcher:: Close: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType} Reason: ${reason}`);
watcher.close();
}
/* @internal */
addFileWatcher(watchType: WatchType, project: Project, file: string, cb: FileWatcherCallback) {
this.logger.info(`FileWatcher:: Added: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
return this.host.watchFile(file, (fileName, eventKind) => {
this.logger.info(`FileWatcher:: File ${FileWatcherEventKind[eventKind]}: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
cb(fileName, eventKind);
});
}
/* @internal */
closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, flags: WatchDirectoryFlags, reason: WatcherCloseReason) {
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Close: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType} Reason: ${reason}`);
watcher.close();
}
/* @internal */
addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, flags: WatchDirectoryFlags) {
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Added: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType}`);
return this.host.watchDirectory(directory, fileName => {
const path = toNormalizedPath(getNormalizedAbsolutePath(fileName, directory));
this.logger.info(`DirectoryWatcher:: EventOn: ${directory} Trigger: ${fileName} Path: ${path} Project: ${project.getProjectName()} WatchType: ${watchType}`);
cb(path);
}, recursive);
}
closeLog() {
this.logger.close();
}

View File

@ -4,110 +4,6 @@
/// <reference path="..\compiler\resolutionCache.ts" />
namespace ts.server {
/*@internal*/
export class CachedServerHost implements ServerHost {
args: string[];
newLine: string;
useCaseSensitiveFileNames: boolean;
private readonly cachedPartialSystem: CachedPartialSystem;
readonly trace: (s: string) => void;
readonly realpath?: (path: string) => string;
constructor(private readonly host: ServerHost) {
this.args = host.args;
this.newLine = host.newLine;
this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames;
if (host.trace) {
this.trace = s => host.trace(s);
}
if (this.host.realpath) {
this.realpath = path => this.host.realpath(path);
}
this.cachedPartialSystem = createCachedPartialSystem(host);
}
write(s: string) {
return this.host.write(s);
}
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
this.cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark);
}
resolvePath(path: string) {
return this.host.resolvePath(path);
}
createDirectory(path: string) {
Debug.fail(`Why is createDirectory called on the cached server for ${path}`);
}
getExecutingFilePath() {
return this.host.getExecutingFilePath();
}
getCurrentDirectory() {
return this.cachedPartialSystem.getCurrentDirectory();
}
exit(exitCode?: number) {
Debug.fail(`Why is exit called on the cached server: ${exitCode}`);
}
getEnvironmentVariable(name: string) {
Debug.fail(`Why is getEnvironmentVariable called on the cached server: ${name}`);
return this.host.getEnvironmentVariable(name);
}
getDirectories(rootDir: string) {
return this.cachedPartialSystem.getDirectories(rootDir);
}
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
return this.cachedPartialSystem.readDirectory(rootDir, extensions, excludes, includes, depth);
}
fileExists(fileName: string): boolean {
return this.cachedPartialSystem.fileExists(fileName);
}
directoryExists(dirPath: string) {
return this.cachedPartialSystem.directoryExists(dirPath);
}
readFile(path: string, encoding?: string): string {
return this.host.readFile(path, encoding);
}
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath, fileOrFolderPath: Path) {
return this.cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
}
addOrDeleteFile(file: string, path: Path, eventKind: FileWatcherEventKind) {
return this.cachedPartialSystem.addOrDeleteFile(file, path, eventKind);
}
clearCache() {
return this.cachedPartialSystem.clearCache();
}
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
return this.host.setTimeout(callback, ms, ...args);
}
clearTimeout(timeoutId: any) {
return this.host.clearTimeout(timeoutId);
}
setImmediate(callback: (...args: any[]) => void, ...args: any[]) {
this.host.setImmediate(callback, ...args);
}
clearImmediate(timeoutId: any) {
this.host.clearImmediate(timeoutId);
}
}
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
/*@internal*/
compilationSettings: CompilerOptions;
@ -124,21 +20,26 @@ namespace ts.server {
* file system entries as we would anyways be watching files in the project (so safe to cache)
*/
/*@internal*/
host: ServerHost;
host: PartialSystem;
constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) {
constructor(host: PartialSystem, private project: Project, private readonly cancellationToken: HostCancellationToken) {
this.host = host;
this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds);
if (host.trace) {
this.trace = s => host.trace(s);
const serverHost = this.getServerHost();
if (serverHost.trace) {
this.trace = s => serverHost.trace(s);
}
if (this.host.realpath) {
this.realpath = path => this.host.realpath(path);
if (serverHost.realpath) {
this.realpath = path => serverHost.realpath(path);
}
}
private getServerHost() {
return this.project.projectService.host;
}
dispose() {
this.project = undefined;
this.host = undefined;
@ -173,7 +74,7 @@ namespace ts.server {
}
getDefaultLibFileName() {
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath()));
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.getServerHost().getExecutingFilePath()));
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings));
}
@ -207,7 +108,7 @@ namespace ts.server {
}
resolvePath(path: string): string {
return this.host.resolvePath(path);
return this.getServerHost().resolvePath(path);
}
fileExists(file: string): boolean {

View File

@ -198,7 +198,7 @@ namespace ts.server {
languageServiceEnabled: boolean,
private compilerOptions: CompilerOptions,
public compileOnSaveEnabled: boolean,
host: ServerHost) {
host: PartialSystem) {
if (!this.compilerOptions) {
this.compilerOptions = getDefaultCompilerOptions();
@ -216,7 +216,7 @@ namespace ts.server {
this.resolutionCache = createResolutionCache(
fileName => this.projectService.toPath(fileName),
() => this.compilerOptions,
(failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name),
(failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath),
s => this.projectService.logger.info(s),
this.getProjectName(),
() => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined
@ -233,17 +233,24 @@ namespace ts.server {
this.markAsDirty();
}
private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
return this.projectService.watchFile(
this.projectService.host,
failedLookupLocation,
(fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath),
WatchType.FailedLookupLocation,
this
);
}
private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) {
// There is some kind of change in the failed lookup location, update the program
return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (fileName, eventKind) => {
this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`);
if (this.projectKind === ProjectKind.Configured) {
(this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind);
}
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
});
if (this.projectKind === ProjectKind.Configured) {
(this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind);
}
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
private setInternalCompilerOptionsForEmittingJsFiles() {
@ -387,9 +394,7 @@ namespace ts.server {
// Clean up file watchers waiting for missing files
if (this.missingFilesMap) {
clearMap(this.missingFilesMap, (missingFilePath, fileWatcher) => {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose);
});
clearMap(this.missingFilesMap, closeFileWatcher);
this.missingFilesMap = undefined;
}
@ -698,10 +703,7 @@ namespace ts.server {
this.program,
this.missingFilesMap || (this.missingFilesMap = createMap()),
// Watch the missing files
missingFilePath => this.addMissingFileWatcher(missingFilePath),
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
(missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded)
missingFilePath => this.addMissingFileWatcher(missingFilePath)
);
}
@ -726,30 +728,29 @@ namespace ts.server {
}
private addMissingFileWatcher(missingFilePath: Path) {
const fileWatcher = this.projectService.addFileWatcher(
WatchType.MissingFilePath, this, missingFilePath,
const fileWatcher = this.projectService.watchFile(
this.projectService.host,
missingFilePath,
(fileName, eventKind) => {
if (this.projectKind === ProjectKind.Configured) {
(this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, missingFilePath, eventKind);
(this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind);
}
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
fileWatcher.close();
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
}
}
},
WatchType.MissingFilePath,
this
);
return fileWatcher;
}
private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) {
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason);
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap && this.missingFilesMap.has(path);
}
@ -942,7 +943,7 @@ namespace ts.server {
}
removeRoot(info: ScriptInfo) {
this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded);
this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info);
super.removeRoot(info);
if (this._isJsInferredProject && info.isJavaScript()) {
if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) {
@ -968,7 +969,7 @@ namespace ts.server {
}
close() {
forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.ProjectClose));
forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info));
super.close();
}
@ -1014,8 +1015,8 @@ namespace ts.server {
compilerOptions: CompilerOptions,
languageServiceEnabled: boolean,
public compileOnSaveEnabled: boolean,
cachedServerHost: CachedServerHost) {
super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedServerHost);
cachedPartialSystem: PartialSystem) {
super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedPartialSystem);
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
this.enablePlugins();
}
@ -1034,8 +1035,8 @@ namespace ts.server {
}
/*@internal*/
getCachedServerHost() {
return this.lsHost.host as CachedServerHost;
getCachedPartialSystem() {
return this.lsHost.host as CachedPartialSystem;
}
getConfigFilePath() {
@ -1167,29 +1168,21 @@ namespace ts.server {
this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()),
wildcardDirectories,
// Create new directory watcher
(directory, flags) => this.projectService.addDirectoryWatcher(
WatchType.WildcardDirectories, this, directory,
(directory, flags) => this.projectService.watchDirectory(
this.projectService.host,
directory,
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
flags
),
// Close directory watcher
(directory, wildcardDirectoryWatcher, flagsChanged) => this.closeWildcardDirectoryWatcher(
directory, wildcardDirectoryWatcher, flagsChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded
flags,
WatchType.WildcardDirectories,
this
)
);
}
private closeWildcardDirectoryWatcher(directory: string, { watcher, flags }: WildcardDirectoryWatcher, closeReason: WatcherCloseReason) {
this.projectService.closeDirectoryWatcher(WatchType.WildcardDirectories, this, directory, watcher, flags, closeReason);
}
/*@internal*/
stopWatchingWildCards(reason: WatcherCloseReason) {
stopWatchingWildCards() {
if (this.directoriesWatchedForWildcards) {
clearMap(
this.directoriesWatchedForWildcards,
(directory, wildcardDirectoryWatcher) => this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, reason)
);
clearMap(this.directoriesWatchedForWildcards, closeFileWatcherOf);
this.directoriesWatchedForWildcards = undefined;
}
}
@ -1202,26 +1195,24 @@ namespace ts.server {
newTypeRoots,
{
// Create new watch
createNewValue: root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root,
path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None
createNewValue: root => this.projectService.watchDirectory(
this.projectService.host,
root,
path => this.projectService.onTypeRootFileChanged(this, path),
WatchDirectoryFlags.None,
WatchType.TypeRoot,
this
),
// Close existing watch thats not needed any more
onDeleteValue: (directory, watcher) => this.projectService.closeDirectoryWatcher(
WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded
)
onDeleteValue: closeFileWatcher
}
);
}
/*@internal*/
stopWatchingTypeRoots(reason: WatcherCloseReason) {
stopWatchingTypeRoots() {
if (this.typeRootsWatchers) {
clearMap(
this.typeRootsWatchers,
(directory, watcher) =>
this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this,
directory, watcher, WatchDirectoryFlags.None, reason)
);
clearMap(this.typeRootsWatchers, closeFileWatcher);
this.typeRootsWatchers = undefined;
}
}
@ -1230,12 +1221,12 @@ namespace ts.server {
super.close();
if (this.configFileWatcher) {
this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose);
this.configFileWatcher.close();
this.configFileWatcher = undefined;
}
this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose);
this.stopWatchingWildCards(WatcherCloseReason.ProjectClose);
this.stopWatchingTypeRoots();
this.stopWatchingWildCards();
}
addOpenRef() {

View File

@ -237,7 +237,7 @@ namespace ts.server {
detachAllProjects() {
for (const p of this.containingProjects) {
if (p.projectKind === ProjectKind.Configured) {
(p.lsHost.host as CachedServerHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted);
(p.lsHost.host as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted);
}
const isInfoRoot = p.isRoot(this);
// detach is unnecessary since we'll clean the list of containing projects anyways