mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-10 06:41:59 -06:00
Refactoring to watches and caching of system such that we minimize function expressions
Also unified watcher info logging
This commit is contained in:
parent
9e570c375b
commit
4c79033894
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user