From 68d360585a6b09d93b39774fa54dc3622caee88f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 13:34:56 -0700 Subject: [PATCH] PR feedback --- src/compiler/builder.ts | 49 +++-- src/compiler/commandLineParser.ts | 2 +- src/compiler/core.ts | 22 +-- src/compiler/program.ts | 84 +-------- src/compiler/resolutionCache.ts | 167 +++++++++++------- src/compiler/sys.ts | 29 ++- src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 72 +------- src/compiler/watch.ts | 79 +++------ src/compiler/watchUtilities.ts | 136 ++++++++++++++ .../unittests/reuseProgramStructure.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 6 +- src/harness/virtualFileSystemWithWatch.ts | 78 ++++---- src/server/editorServices.ts | 32 ++-- src/server/project.ts | 102 ++++++----- src/server/scriptInfo.ts | 23 ++- src/services/jsTyping.ts | 2 +- src/services/services.ts | 8 +- src/services/types.ts | 3 +- 20 files changed, 472 insertions(+), 431 deletions(-) create mode 100644 src/compiler/watchUtilities.ts diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index e7bcb28d820..9436be6e8a1 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -43,10 +43,23 @@ namespace ts { } interface EmitHandler { - addScriptInfo(program: Program, sourceFile: SourceFile): void; - removeScriptInfo(path: Path): void; - updateScriptInfo(program: Program, sourceFile: SourceFile): void; - updaterScriptInfoWithSameVersion(program: Program, sourceFile: SourceFile): boolean; + /** + * Called when sourceFile is added to the program + */ + onAddSourceFile(program: Program, sourceFile: SourceFile): void; + /** + * Called when sourceFile is removed from the program + */ + onRemoveSourceFile(path: Path): void; + /** + * Called when sourceFile is changed + */ + onUpdateSourceFile(program: Program, sourceFile: SourceFile): void; + /** + * Called when source file has not changed but has some of the resolutions invalidated + * If returned true, builder will mark the file as changed (noting that something associated with file has changed) + */ + onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean; /** * Gets the files affected by the script info which has updated shape from the known one */ @@ -135,23 +148,23 @@ namespace ts { function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { registerChangedFile(sourceFile.path, sourceFile.fileName); - emitHandler.addScriptInfo(program, sourceFile); + emitHandler.onAddSourceFile(program, sourceFile); return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined }; } function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) { registerChangedFile(path, existingFileInfo.fileName); - emitHandler.removeScriptInfo(path); + emitHandler.onRemoveSourceFile(path); } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { if (existingInfo.version !== sourceFile.version) { registerChangedFile(sourceFile.path, sourceFile.fileName); existingInfo.version = sourceFile.version; - emitHandler.updateScriptInfo(program, sourceFile); + emitHandler.onUpdateSourceFile(program, sourceFile); } else if (hasInvalidatedResolution(sourceFile.path) && - emitHandler.updaterScriptInfoWithSameVersion(program, sourceFile)) { + emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) { registerChangedFile(sourceFile.path, sourceFile.fileName); } } @@ -388,10 +401,10 @@ namespace ts { function getNonModuleEmitHandler(): EmitHandler { return { - addScriptInfo: noop, - removeScriptInfo: noop, - updateScriptInfo: noop, - updaterScriptInfoWithSameVersion: returnFalse, + onAddSourceFile: noop, + onRemoveSourceFile: noop, + onUpdateSourceFile: noop, + onUpdateSourceFileWithSameVersion: returnFalse, getFilesAffectedByUpdatedShape }; @@ -409,17 +422,17 @@ namespace ts { function getModuleEmitHandler(): EmitHandler { const references = createMap>(); return { - addScriptInfo: setReferences, - removeScriptInfo, - updateScriptInfo: updateReferences, - updaterScriptInfoWithSameVersion: updateReferencesTrackingChangedReferences, + onAddSourceFile: setReferences, + onRemoveSourceFile, + onUpdateSourceFile: updateReferences, + onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences, getFilesAffectedByUpdatedShape }; function setReferences(program: Program, sourceFile: SourceFile) { const newReferences = getReferencedFiles(program, sourceFile); if (newReferences) { - references.set(sourceFile.path, getReferencedFiles(program, sourceFile)); + references.set(sourceFile.path, newReferences); } } @@ -452,7 +465,7 @@ namespace ts { !!oldReferences.size; } - function removeScriptInfo(removedFilePath: Path) { + function onRemoveSourceFile(removedFilePath: Path) { // Remove existing references references.forEach((referencesInFile, filePath) => { if (referencesInFile.has(removedFilePath)) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d49d993bce1..fae45709a3c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1683,7 +1683,7 @@ namespace ts { return undefined; } let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); - if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { extendedConfigPath = `${extendedConfigPath}.json` as Path; if (!host.fileExists(extendedConfigPath)) { errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9579dec51cd..8d5883b7755 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2262,7 +2262,7 @@ namespace ts { return ScriptKind.TS; case Extension.Tsx: return ScriptKind.TSX; - case ".json": + case Extension.Json: return ScriptKind.JSON; default: return ScriptKind.Unknown; @@ -2669,7 +2669,7 @@ namespace ts { export function assertTypeIsNever(_: never): void { } export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; clearCache(): void; } @@ -2695,7 +2695,7 @@ namespace ts { getCurrentDirectory, getDirectories, readDirectory, - addOrDeleteFileOrFolder, + addOrDeleteFileOrDirectory, addOrDeleteFile, clearCache, exit: code => host.exit(code) @@ -2824,23 +2824,23 @@ namespace ts { } } - function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) { - const existingResult = getCachedFileSystemEntries(fileOrFolderPath); + function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); if (existingResult) { // This was a folder already present, remove it if this doesnt exist any more - if (!host.directoryExists(fileOrFolder)) { - cachedReadDirectoryResult.delete(fileOrFolderPath); + if (!host.directoryExists(fileOrDirectory)) { + cachedReadDirectoryResult.delete(fileOrDirectoryPath); } } else { // This was earlier a file (hence not in cached directory contents) // or we never cached the directory containing it - const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath); + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); if (parentResult) { - const baseName = getBaseNameOfFileName(fileOrFolder); + const baseName = getBaseNameOfFileName(fileOrDirectory); if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); - updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); + updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath)); + updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath)); } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 01ec2e62b2e..674d0b6cfac 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -395,6 +395,9 @@ namespace ts { allDiagnostics?: Diagnostic[]; } + /** + * Determines if program structure is upto date or needs to be recreated + */ export function isProgramUptoDate( program: Program | undefined, rootFileNames: string[], @@ -402,10 +405,10 @@ namespace ts { getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames: () => boolean, + hasChangedAutomaticTypeDirectiveNames: boolean, ): boolean { // If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date - if (!program || hasChangedAutomaticTypeDirectiveNames()) { + if (!program || hasChangedAutomaticTypeDirectiveNames) { return false; } @@ -416,7 +419,7 @@ namespace ts { // If any file is not up-to-date, then the whole program is not up-to-date if (program.getSourceFiles().some(sourceFileNotUptoDate)) { - return false; + return false; } // If any of the missing file paths are now created @@ -464,78 +467,6 @@ namespace ts { ); } - /** - * Updates the existing missing file watches with the new set of missing files after new program is created - */ - export function updateMissingFilePathsWatch( - program: Program, - missingFileWatches: Map, - createMissingFileWatch: (missingFilePath: Path) => FileWatcher, - ) { - const missingFilePaths = program.getMissingFilePaths(); - const newMissingFilePathMap = arrayToSet(missingFilePaths); - // Update the missing file paths watcher - mutateMap( - missingFileWatches, - newMissingFilePathMap, - { - // Watch the missing files - createNewValue: createMissingFileWatch, - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - onDeleteValue: closeFileWatcher - } - ); - } - - export interface WildcardDirectoryWatcher { - watcher: FileWatcher; - flags: WatchDirectoryFlags; - } - - /** - * Updates the existing wild card directory watches with the new set of wild card directories from the config file - * after new program is created because the config file was reloaded or program was created first time from the config file - * Note that there is no need to call this function when the program is updated with additional files without reloading config files, - * as wildcard directories wont change unless reloading config file - */ - export function updateWatchingWildcardDirectories( - existingWatchedForWildcards: Map, - wildcardDirectories: Map, - watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher - ) { - mutateMap( - existingWatchedForWildcards, - wildcardDirectories, - { - // Create new watch and recursive info - createNewValue: createWildcardDirectoryWatcher, - // Close existing watch thats not needed any more - onDeleteValue: closeFileWatcherOf, - // Close existing watch that doesnt match in the flags - onExistingValue: updateWildcardDirectoryWatcher - } - ); - - function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { - // Create new watch and recursive info - return { - watcher: watchDirectory(directory, flags), - flags - }; - } - - function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { - // Watcher needs to be updated if the recursive flags dont match - if (existingWatcher.flags === flags) { - return; - } - - existingWatcher.watcher.close(); - existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); - } - } - /** * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' * that represent a compilation unit. @@ -599,7 +530,6 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModuleFull[]; const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; - const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile, reusedNames).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. @@ -1105,7 +1035,7 @@ namespace ts { return oldProgram.structureIsReused; } - if (hasChangedAutomaticTypeDirectiveNames()) { + if (host.hasChangedAutomaticTypeDirectiveNames) { return oldProgram.structureIsReused = StructureIsReused.SafeModules; } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 814d5053341..aecc6989891 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,5 +1,6 @@ /// /// +/// /*@internal*/ namespace ts { @@ -18,8 +19,6 @@ namespace ts { startCachingPerDirectoryResolution(): void; finishCachingPerDirectoryResolution(): void; - setRootDirectory(dir: string): void; - updateTypeRootsWatch(): void; closeTypeRootsWatch(): void; @@ -35,6 +34,12 @@ namespace ts { resolvedFileName: string | undefined; } + interface ResolvedModuleWithFailedLookupLocations extends ts.ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { + } + + interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { + } + export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; getCompilationSettings(): CompilerOptions; @@ -67,7 +72,7 @@ namespace ts { (resolution: T): R; } - export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { + export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; let allFilesHaveInvalidatedResolution = false; @@ -83,12 +88,18 @@ namespace ts { const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + /** + * These are the extensions that failed lookup files will have by default, + * any other extension of failed lookup will be store that path in custom failed lookup path + * This helps in not having to comb through all resolutions when files are added/removed + * Note that .d.ts file also has .d.ts extension hence will be part of default extensions + */ const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; const customFailedLookupPaths = createMap(); const directoryWatchesOfFailedLookups = createMap(); - let rootDir: string; - let rootPath: Path; + const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); + const rootPath = rootDir && resolutionHost.toPath(rootDir); // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames const typeRootsWatches = createMap(); @@ -103,7 +114,6 @@ namespace ts { removeResolutionsOfFile, invalidateResolutionOfFile, createHasInvalidatedResolution, - setRootDirectory, updateTypeRootsWatch, closeTypeRootsWatch, clear @@ -117,12 +127,6 @@ namespace ts { return resolution.resolvedTypeReferenceDirective; } - function setRootDirectory(dir: string) { - Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size); - rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory())); - rootPath = resolutionHost.toPath(rootDir); - } - function isInDirectoryPath(dir: Path, file: Path) { if (dir === undefined || file.length <= dir.length) { return false; @@ -238,9 +242,17 @@ namespace ts { perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); - const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations); - watchFailedLookupLocationOfResolution(resolution, diffIndex || 0); - stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0); + if (resolution.failedLookupLocations) { + if (existingResolution && existingResolution.failedLookupLocations) { + watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution); + } + else { + watchFailedLookupLocationOfResolution(resolution, 0); + } + } + else if (existingResolution) { + stopWatchFailedLookupLocationOfResolution(existingResolution); + } if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { filesWithChangedSetOfUnresolvedImports.push(path); // reset log changes to avoid recording the same file multiple times @@ -344,68 +356,91 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations, startIndex?: number) { - if (resolution && resolution.failedLookupLocations) { - for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) { - const failedLookupLocation = resolution.failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - dirWatcher.refCount++; - } - else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); - } + function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) { + const failedLookupLocations = resolution.failedLookupLocations; + const existingFailedLookupLocations = existingResolution.failedLookupLocations; + for (let index = 0; index < failedLookupLocations.length; index++) { + if (index === existingFailedLookupLocations.length) { + // Additional failed lookup locations, watch from this index + watchFailedLookupLocationOfResolution(resolution, index); + return; + } + else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) { + // Different failed lookup locations, + // Watch new resolution failed lookup locations from this index and + // stop watching existing resolutions from this index + watchFailedLookupLocationOfResolution(resolution, index); + stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index); + return; + } + } + + // All new failed lookup locations are already watched (and are same), + // Stop watching failed lookup locations of existing resolution after failed lookup locations length + stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length); + } + + function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { + for (let i = startIndex; i < failedLookupLocations.length; i++) { + const failedLookupLocation = failedLookupLocations[i]; + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); } } } function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + if (resolution.failedLookupLocations) { + stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + } } - function stopWatchFailedLookupLocationOfResolutionFrom(resolution: ResolutionWithFailedLookupLocations, startIndex: number) { - if (resolution && resolution.failedLookupLocations) { - for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) { - const failedLookupLocation = resolution.failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } + function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { + for (let i = startIndex; i < failedLookupLocations.length; i++) { + const failedLookupLocation = failedLookupLocations[i]; + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); + } + else { + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); } - const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; } + const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; } } function createDirectoryWatcher(directory: string, dirPath: Path) { - return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => { - const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (!allFilesHaveInvalidatedResolution && - dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { - if (invalidateResolutionOfFailedLookupLocation(fileOrFolderPath, dirPath === fileOrFolderPath)) { + dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath) { + if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { resolutionHost.onInvalidatedResolution(); } } @@ -482,21 +517,21 @@ namespace ts { ); } - function invalidateResolutionOfFailedLookupLocation(fileOrFolderPath: Path, isCreatingWatchedDirectory: boolean) { + function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { let isChangedFailedLookupLocation: (location: string) => boolean; if (isCreatingWatchedDirectory) { // Watching directory is created // Invalidate any resolution has failed lookup in this directory - isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrFolderPath, resolutionHost.toPath(location)); + isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location)); } else { - // Some file or folder in the watching directory is created + // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path - if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) { + if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } - // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created - isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath; + // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created + isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath; } const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; @@ -513,11 +548,11 @@ namespace ts { function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher { // Create new watch and recursive info - return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => { - const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 822de1eef52..fa3ec22f586 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -78,8 +78,7 @@ namespace ts { close(): void; } - export interface DirectoryWatcher extends FileWatcher { - directoryName: string; + interface DirectoryWatcher extends FileWatcher { referenceCount: number; } @@ -187,7 +186,7 @@ namespace ts { fileWatcherCallbacks.remove(filePath, callback); } - function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) { + function fileEventHandler(eventName: string, relativeFileName: string | undefined, baseDirPath: string) { // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" const fileName = !isString(relativeFileName) ? undefined @@ -255,19 +254,25 @@ namespace ts { } function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher { - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) let options: any; + /** Watcher for the directory depending on whether it is missing or present */ let watcher = !directoryExists(directoryName) ? watchMissingDirectory() : watchPresentDirectory(); return { close: () => { + // Close the watcher (either existing directory watcher or missing directory watcher) watcher.close(); } }; + /** + * Watch the directory that is currently present + * and when the watched directory is deleted, switch to missing directory watcher + */ function watchPresentDirectory(): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) if (options === undefined) { if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { options = { persistent: true, recursive: !!recursive }; @@ -283,14 +288,20 @@ namespace ts { callback ); dirWatcher.on("error", () => { - // Deleting file - watcher = watchMissingDirectory(); - // Call the callback for current directory - callback("rename", ""); + if (!directoryExists(directoryName)) { + // Deleting directory + watcher = watchMissingDirectory(); + // Call the callback for current directory + callback("rename", ""); + } }); return dirWatcher; } + /** + * Watch the directory that is missing + * and switch to existing directory when the directory is created + */ function watchMissingDirectory(): FileWatcher { return fsWatchFile(directoryName, (_fileName, eventKind) => { if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) { diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index dee33cbf163..07f69ddfe28 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -36,6 +36,7 @@ "sourcemap.ts", "declarationEmitter.ts", "emitter.ts", + "watchUtilities.ts", "program.ts", "builder.ts", "resolutionCache.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2d955da1252..e62981866b3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4099,8 +4099,6 @@ namespace ts { readonly resolvedModule: ResolvedModuleFull | undefined; /* @internal */ readonly failedLookupLocations: ReadonlyArray; - /*@internal*/ - isInvalidated?: boolean; } export interface ResolvedTypeReferenceDirective { @@ -4114,8 +4112,6 @@ namespace ts { export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; readonly failedLookupLocations: ReadonlyArray; - /*@internal*/ - isInvalidated?: boolean; } export interface HasInvalidatedResolution { @@ -4150,7 +4146,7 @@ namespace ts { getEnvironmentVariable?(name: string): string; onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; hasInvalidatedResolution?: HasInvalidatedResolution; - hasChangedAutomaticTypeDirectiveNames?(): boolean; + hasChangedAutomaticTypeDirectiveNames?: boolean; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 87bc601bd7e..fcd390cded6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3521,7 +3521,7 @@ namespace ts { /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ - export function clearMap(map: Map, onDeleteValue: (existingValue: T, key: string) => void) { + export function clearMap(map: Map, onDeleteValue: (valueInMap: T, key: string) => void) { // Remove all map.forEach(onDeleteValue); map.clear(); @@ -3568,76 +3568,6 @@ namespace ts { }); } - /** - * Find the index where arrayA and arrayB differ - */ - export function findDiffIndex(arrayA: ReadonlyArray, arrayB: ReadonlyArray) { - for (let i = 0; i < arrayA.length; i++) { - if (i === arrayB.length || arrayA[i] !== arrayB[i]) { - return i; - } - } - return arrayA.length; - } - - 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, cb, 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 = (fileName: string, cbOptional1?: T, optional?: U) => void; - type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; - function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, 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}`); - const start = timestamp(); - cb(fileName, cbOptional1, optional); - const elapsed = timestamp() - start; - log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`); - }, optional); - return { - close: () => { - log(`${watcherCaption}Close: ${info}`); - watcher.close(); - } - }; - } - - export function closeFileWatcher(watcher: FileWatcher) { - watcher.close(); - } - - export function closeFileWatcherOf(objWithWatcher: T) { - objWithWatcher.watcher.close(); - } - /** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */ export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T): T { while (true) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 2d1d6e8bf58..8b098f41098 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -245,7 +245,7 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations - let changedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed + let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop; @@ -269,26 +269,25 @@ namespace ts { const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost - getSourceFile: getVersionedSourceFile, + getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile, - trace, - directoryExists, + readFile: fileName => system.readFile(fileName), + trace: s => system.write(s + newLine), + directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName), getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories, + getDirectories: path => directoryStructureHost.getDirectories(path), realpath, - resolveTypeReferenceDirectives, - resolveModuleNames, + resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), + resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false), onReleaseOldSourceFile, - hasChangedAutomaticTypeDirectiveNames, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, @@ -296,12 +295,14 @@ namespace ts { watchTypeRootsDirectory: watchDirectory, getCachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, - onChangedAutomaticTypeDirectiveNames, + onChangedAutomaticTypeDirectiveNames: () => { + hasChangedAutomaticTypeDirectiveNames = true; + scheduleProgramUpdate(); + }, writeLog }; // Cache for the module resolution - const resolutionCache = createResolutionCache(compilerHost); - resolutionCache.setRootDirectory(configFileName ? + const resolutionCache = createResolutionCache(compilerHost, configFileName ? getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : getCurrentDirectory() ); @@ -337,6 +338,7 @@ namespace ts { // Compile the program resolutionCache.startCachingPerDirectoryResolution(); compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; + compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); resolutionCache.finishCachingPerDirectoryResolution(); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); @@ -379,38 +381,10 @@ namespace ts { return directoryStructureHost.fileExists(fileName); } - function directoryExists(directoryName: string) { - return directoryStructureHost.directoryExists(directoryName); - } - - function readFile(fileName: string) { - return system.readFile(fileName); - } - - function trace(s: string) { - return system.write(s + newLine); - } - - function getDirectories(path: string) { - return directoryStructureHost.getDirectories(path); - } - - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]) { - return resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); - } - - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { - return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); - } - function getDefaultLibLocation(): string { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } - function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - return getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile); - } - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { const hostSourceFile = sourceFilesCache.get(path); // No source file on the host @@ -594,15 +568,6 @@ namespace ts { return watchDirectoryWorker(system, directory, cb, flags, writeLog); } - function onChangedAutomaticTypeDirectiveNames() { - changedAutomaticTypeDirectiveNames = true; - scheduleProgramUpdate(); - } - - function hasChangedAutomaticTypeDirectiveNames() { - return changedAutomaticTypeDirectiveNames; - } - function watchMissingFilePath(missingFilePath: Path) { return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } @@ -633,19 +598,19 @@ namespace ts { function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { return watchDirectory( directory, - fileOrFolder => { + fileOrDirectory => { Debug.assert(!!configFileName); - const fileOrFolderPath = toPath(fileOrFolder); + const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - removeSourceFile(fileOrFolderPath); + (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + removeSourceFile(fileOrDirectoryPath); - // If the the added or created file or folder is not supported file name, ignore the file + // If the the added or created file or directory is not supported file name, ignore the file // But when watched directory is added/removed, we need to reload the file list - if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); + if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); return; } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts new file mode 100644 index 00000000000..de415293954 --- /dev/null +++ b/src/compiler/watchUtilities.ts @@ -0,0 +1,136 @@ +/// + +namespace ts { + /** + * Updates the existing missing file watches with the new set of missing files after new program is created + */ + export function updateMissingFilePathsWatch( + program: Program, + missingFileWatches: Map, + createMissingFileWatch: (missingFilePath: Path) => FileWatcher, + ) { + const missingFilePaths = program.getMissingFilePaths(); + const newMissingFilePathMap = arrayToSet(missingFilePaths); + // Update the missing file paths watcher + mutateMap( + missingFileWatches, + newMissingFilePathMap, + { + // Watch the missing files + createNewValue: createMissingFileWatch, + // Files that are no longer missing (e.g. because they are no longer required) + // should no longer be watched. + onDeleteValue: closeFileWatcher + } + ); + } + + export interface WildcardDirectoryWatcher { + watcher: FileWatcher; + flags: WatchDirectoryFlags; + } + + /** + * Updates the existing wild card directory watches with the new set of wild card directories from the config file + * after new program is created because the config file was reloaded or program was created first time from the config file + * Note that there is no need to call this function when the program is updated with additional files without reloading config files, + * as wildcard directories wont change unless reloading config file + */ + export function updateWatchingWildcardDirectories( + existingWatchedForWildcards: Map, + wildcardDirectories: Map, + watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher + ) { + mutateMap( + existingWatchedForWildcards, + wildcardDirectories, + { + // Create new watch and recursive info + createNewValue: createWildcardDirectoryWatcher, + // Close existing watch thats not needed any more + onDeleteValue: closeFileWatcherOf, + // Close existing watch that doesnt match in the flags + onExistingValue: updateWildcardDirectoryWatcher + } + ); + + function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { + // Create new watch and recursive info + return { + watcher: watchDirectory(directory, flags), + flags + }; + } + + function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { + // Watcher needs to be updated if the recursive flags dont match + if (existingWatcher.flags === flags) { + return; + } + + existingWatcher.watcher.close(); + existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); + } + } +} + +/* @internal */ +namespace ts { + 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, cb, 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 = (fileName: string, cbOptional1?: T, optional?: U) => void; + type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, 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}`); + const start = timestamp(); + cb(fileName, cbOptional1, optional); + const elapsed = timestamp() - start; + log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`); + }, optional); + return { + close: () => { + log(`${watcherCaption}Close: ${info}`); + watcher.close(); + } + }; + } + + export function closeFileWatcher(watcher: FileWatcher) { + watcher.close(); + } + + export function closeFileWatcherOf(objWithWatcher: T) { + objWithWatcher.watcher.close(); + } +} diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 53a26c2ae95..5c2b9edd9d0 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -885,7 +885,7 @@ namespace ts { program, newRootFileNames, newOptions, path => program.getSourceFileByPath(path).version, /*fileExists*/ returnFalse, /*hasInvalidatedResolution*/ returnFalse, - /*hasChangedAutomaticTypeDirectiveNames*/ returnFalse + /*hasChangedAutomaticTypeDirectiveNames*/ false ); assert.isTrue(actual); } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index cac52d34a7e..7e1205f76cf 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -967,7 +967,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); }); - it("should resuse same project if file is opened from the configured project that has no open files", () => { + it("should reuse same project if file is opened from the configured project that has no open files", () => { const file1 = { path: "/a/b/main.ts", content: "let x =1;" @@ -4639,7 +4639,7 @@ namespace ts.projectSystem { verifyProjectAndWatchedDirectories(); callsTrackingHost.verifyNoHostCalls(); - function getFilePathIfOpen(f: FileOrFolder) { + function getFilePathIfNotOpen(f: FileOrFolder) { const path = toCanonical(f.path); const info = projectService.getScriptInfoForPath(toCanonical(f.path)); return info && info.isScriptOpen() ? undefined : path; @@ -4647,7 +4647,7 @@ namespace ts.projectSystem { function verifyProjectAndWatchedDirectories() { checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); checkWatchedDirectories(host, [], /*recursive*/ false); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index e1e0e6f4c7e..c16f57235e4 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -246,17 +246,17 @@ namespace ts.TestFSWithWatch { const mapNewLeaves = createMap(); const isNewFs = this.fs.size === 0; // always inject safelist file in the list of files - for (const fileOrFolder of fileOrFolderList.concat(this.withSafeList ? safeList : [])) { - const path = this.toFullPath(fileOrFolder.path); + for (const fileOrDirectory of fileOrFolderList.concat(this.withSafeList ? safeList : [])) { + const path = this.toFullPath(fileOrDirectory.path); mapNewLeaves.set(path, true); // If its a change const currentEntry = this.fs.get(path); if (currentEntry) { if (isFile(currentEntry)) { - if (isString(fileOrFolder.content)) { + if (isString(fileOrDirectory.content)) { // Update file - if (currentEntry.content !== fileOrFolder.content) { - currentEntry.content = fileOrFolder.content; + if (currentEntry.content !== fileOrDirectory.content) { + currentEntry.content = fileOrDirectory.content; this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); } } @@ -266,7 +266,7 @@ namespace ts.TestFSWithWatch { } else { // Folder - if (isString(fileOrFolder.content)) { + if (isString(fileOrDirectory.content)) { // TODO: Changing from folder => file } else { @@ -275,32 +275,32 @@ namespace ts.TestFSWithWatch { } } else { - this.ensureFileOrFolder(fileOrFolder); + this.ensureFileOrFolder(fileOrDirectory); } } if (!isNewFs) { - this.fs.forEach((fileOrFolder, path) => { + this.fs.forEach((fileOrDirectory, path) => { // If this entry is not from the new file or folder if (!mapNewLeaves.get(path)) { // Leaf entries that arent in new list => remove these - if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) { - this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path)); + if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { + this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path)); } } }); } } - ensureFileOrFolder(fileOrFolder: FileOrFolder) { - if (isString(fileOrFolder.content)) { - const file = this.toFile(fileOrFolder); + ensureFileOrFolder(fileOrDirectory: FileOrFolder) { + if (isString(fileOrDirectory.content)) { + const file = this.toFile(fileOrDirectory); Debug.assert(!this.fs.get(file.path)); const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); this.addFileOrFolderInFolder(baseFolder, file); } else { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); this.ensureFolder(fullPath); } } @@ -326,44 +326,44 @@ namespace ts.TestFSWithWatch { return folder; } - private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) { - folder.entries.push(fileOrFolder); - this.fs.set(fileOrFolder.path, fileOrFolder); + private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder) { + folder.entries.push(fileOrDirectory); + this.fs.set(fileOrDirectory.path, fileOrDirectory); - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created); + if (isFile(fileOrDirectory)) { + this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created); } - this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath); + this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath); } - private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { - const basePath = getDirectoryPath(fileOrFolder.path); + private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { + const basePath = getDirectoryPath(fileOrDirectory.path); const baseFolder = this.fs.get(basePath) as Folder; - if (basePath !== fileOrFolder.path) { + if (basePath !== fileOrDirectory.path) { Debug.assert(!!baseFolder); - filterMutate(baseFolder.entries, entry => entry !== fileOrFolder); + filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory); } - this.fs.delete(fileOrFolder.path); + this.fs.delete(fileOrDirectory.path); - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted); + if (isFile(fileOrDirectory)) { + this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); } else { - Debug.assert(fileOrFolder.entries.length === 0); - const relativePath = this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath); + Debug.assert(fileOrDirectory.entries.length === 0); + const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath); // Invoke directory and recursive directory watcher for the folder // Here we arent invoking recursive directory watchers for the base folders // since that is something we would want to do for both file as well as folder we are deleting - invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath)); } - if (basePath !== fileOrFolder.path) { + if (basePath !== fileOrDirectory.path) { if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) { this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); } else { - this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath); + this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath); } } } @@ -402,13 +402,13 @@ namespace ts.TestFSWithWatch { } } - private toFile(fileOrFolder: FileOrFolder): File { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + private toFile(fileOrDirectory: FileOrFolder): File { + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); return { path: this.toPath(fullPath), - content: fileOrFolder.content, + content: fileOrDirectory.content, fullPath, - fileSize: fileOrFolder.fileSize + fileSize: fileOrDirectory.fileSize }; } @@ -477,7 +477,7 @@ namespace ts.TestFSWithWatch { }); } - watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { + watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher { const path = this.toFullPath(directoryName); const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; const callback: TestDirectoryWatcher = { @@ -486,8 +486,6 @@ namespace ts.TestFSWithWatch { }; map.add(path, callback); return { - referenceCount: 0, - directoryName, close: () => map.remove(path, callback) }; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index c93098c32c2..25a285929be 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -349,7 +349,7 @@ namespace ts.server { private readonly projectToSizeMap: Map = createMap(); /** * This is a map of config file paths existance that doesnt need query to disk - * - The entry can be present because there is inferred project that needs to watch addition of config file to folder + * - The entry can be present because there is inferred project that needs to watch addition of config file to directory * In this case the exists could be true/false based on config file is present or not * - Or it is present if we have configured project open with config file at that location * In this case the exists property is always true @@ -753,15 +753,15 @@ namespace ts.server { return this.watchDirectory( this.host, directory, - fileOrFolder => { - const fileOrFolderPath = this.toPath(fileOrFolder); - project.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + fileOrDirectory => { + const fileOrDirectoryPath = this.toPath(fileOrDirectory); + project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); const configFilename = project.getConfigFilePath(); - // If the the added or created file or folder is not supported file name, ignore the file + // If the the added or created file or directory is not supported file name, ignore the file // But when watched directory is added/removed, we need to reload the file list - if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { - this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); + if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`); return; } @@ -833,7 +833,7 @@ namespace ts.server { switch (project.projectKind) { case ProjectKind.External: unorderedRemoveItem(this.externalProjects, project); - this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); + this.projectToSizeMap.delete(project.getProjectName()); break; case ProjectKind.Configured: this.configuredProjects.delete((project).canonicalConfigFilePath); @@ -852,7 +852,7 @@ namespace ts.server { const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) || this.getOrCreateSingleInferredProjectIfEnabled() || - this.createInferredProject(); + this.createInferredProject(getDirectoryPath(info.path)); project.addRoot(info); project.updateGraph(); @@ -1374,7 +1374,7 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition); this.externalProjects.push(project); - this.sendProjectTelemetry(project.externalProjectName, project); + this.sendProjectTelemetry(projectFileName, project); return project; } @@ -1461,7 +1461,7 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, filesToAdd, fileNamePropertyReader, projectOptions.typeAcquisition); this.configuredProjects.set(project.canonicalConfigFilePath, project); this.setConfigFileExistenceByNewConfiguredProject(project); - this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); + this.sendProjectTelemetry(configFileName, project, projectOptions); return project; } @@ -1584,7 +1584,7 @@ namespace ts.server { return project; } } - return this.createInferredProject(/*isSingleInferredProject*/ false, projectRootPath); + return this.createInferredProject(projectRootPath, /*isSingleInferredProject*/ false, projectRootPath); } // we don't have an explicit root path, so we should try to find an inferred project @@ -1621,12 +1621,12 @@ namespace ts.server { return this.inferredProjects[0]; } - return this.createInferredProject(/*isSingleInferredProject*/ true); + return this.createInferredProject(/*rootDirectoryForResolution*/ undefined, /*isSingleInferredProject*/ true); } - private createInferredProject(isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { + private createInferredProject(rootDirectoryForResolution: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects; - const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath); + const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, rootDirectoryForResolution); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } @@ -1669,10 +1669,12 @@ namespace ts.server { } } + /*@internal*/ getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } + /*@internal*/ getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } diff --git a/src/server/project.ts b/src/server/project.ts index 746e6bf3fbe..76d4499f688 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -162,7 +162,8 @@ namespace ts.server { */ private projectStateVersion = 0; - private changedAutomaticTypeDirectiveNames = false; + /*@internal*/ + hasChangedAutomaticTypeDirectiveNames = false; private typingFiles: SortedReadonlyArray; @@ -201,7 +202,8 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - /*@internal*/public directoryStructureHost: DirectoryStructureHost) { + /*@internal*/public directoryStructureHost: DirectoryStructureHost, + rootDirectoryForResolution: string | undefined) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -224,7 +226,7 @@ namespace ts.server { } this.languageService = createLanguageService(this, this.documentRegistry); - this.resolutionCache = createResolutionCache(this); + this.resolutionCache = createResolutionCache(this, rootDirectoryForResolution); if (!languageServiceEnabled) { this.disableLanguageService(); } @@ -244,29 +246,26 @@ namespace ts.server { } getScriptFileNames() { - const result: string[] = []; - if (this.rootFiles) { - this.rootFilesMap.forEach((value, _path) => { - const f: ScriptInfo = isScriptInfo(value) && value; - if (this.languageServiceEnabled || (f && f.isScriptOpen())) { - // if language service is disabled - process only files that are open - result.push(f ? f.fileName : value as NormalizedPath); - } - }); - if (this.typingFiles) { - for (const f of this.typingFiles) { - result.push(f); - } - } + if (!this.rootFiles) { + return ts.emptyArray; } - return result; + + let result: string[] | undefined; + this.rootFilesMap.forEach(value => { + if (this.languageServiceEnabled || (isScriptInfo(value) && value.isScriptOpen())) { + // if language service is disabled - process only files that are open + (result || (result = [])).push(isScriptInfo(value) ? value.fileName : value); + } + }); + + return addRange(result, this.typingFiles) || ts.emptyArray; } - private getScriptInfoLSHost(fileName: string) { + private getOrCreateScriptInfoAndAttachToProject(fileName: string) { const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); - if (existingValue !== undefined && existingValue !== scriptInfo) { + if (existingValue !== scriptInfo && existingValue !== undefined) { // This was missing path earlier but now the file exists. Update the root this.rootFiles.push(scriptInfo); this.rootFilesMap.set(scriptInfo.path, scriptInfo); @@ -277,17 +276,17 @@ namespace ts.server { } getScriptKind(fileName: string) { - const info = this.getScriptInfoLSHost(fileName); + const info = this.getOrCreateScriptInfoAndAttachToProject(fileName); return info && info.scriptKind; } getScriptVersion(filename: string) { - const info = this.getScriptInfoLSHost(filename); + const info = this.getOrCreateScriptInfoAndAttachToProject(filename); return info && info.getLatestVersion(); } getScriptSnapshot(filename: string): IScriptSnapshot { - const scriptInfo = this.getScriptInfoLSHost(filename); + const scriptInfo = this.getOrCreateScriptInfoAndAttachToProject(filename); if (scriptInfo) { return scriptInfo.getSnapshot(); } @@ -377,15 +376,10 @@ namespace ts.server { /*@internal*/ onChangedAutomaticTypeDirectiveNames() { - this.changedAutomaticTypeDirectiveNames = true; + this.hasChangedAutomaticTypeDirectiveNames = true; this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - /*@internal*/ - hasChangedAutomaticTypeDirectiveNames() { - return this.changedAutomaticTypeDirectiveNames; - } - /*@internal*/ getGlobalCache() { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; @@ -802,7 +796,7 @@ namespace ts.server { // - oldProgram is not set - this is a first time updateGraph is called // - newProgram is different from the old program and structure of the old program was not reused. const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)); - this.changedAutomaticTypeDirectiveNames = false; + this.hasChangedAutomaticTypeDirectiveNames = false; if (hasChanges) { if (oldProgram) { for (const f of oldProgram.getSourceFiles()) { @@ -1035,7 +1029,12 @@ namespace ts.server { super.setCompilerOptions(newOptions); } - constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, public readonly projectRootPath?: string | undefined) { + constructor( + projectService: ProjectService, + documentRegistry: DocumentRegistry, + compilerOptions: CompilerOptions, + public readonly projectRootPath: string | undefined, + rootDirectoryForResolution: string | undefined) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -1044,7 +1043,8 @@ namespace ts.server { /*languageServiceEnabled*/ true, compilerOptions, /*compileOnSaveEnabled*/ false, - projectService.host); + projectService.host, + rootDirectoryForResolution); } addRoot(info: ScriptInfo) { @@ -1053,13 +1053,6 @@ namespace ts.server { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } super.addRoot(info); - // For first root set the resolution cache root dir - if (this.getRootFiles.length === 1) { - const rootDirPath = this.getProjectRootPath(); - if (rootDirPath) { - this.resolutionCache.setRootDirectory(rootDirPath); - } - } } removeRoot(info: ScriptInfo) { @@ -1081,11 +1074,9 @@ namespace ts.server { } getProjectRootPath() { - // Single inferred project does not have a project root. - if (this.projectService.useSingleInferredProject) { - return undefined; - } - return this.projectRootPath || getDirectoryPath(this.getRootFiles()[0]); + return this.projectRootPath || + // Single inferred project does not have a project root. + !this.projectService.useSingleInferredProject && getDirectoryPath(this.getRootFiles()[0]); } close() { @@ -1135,10 +1126,18 @@ namespace ts.server { languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, cachedDirectoryStructureHost: CachedDirectoryStructureHost) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedDirectoryStructureHost); + super(configFileName, + ProjectKind.Configured, + projectService, + documentRegistry, + hasExplicitListOfFiles, + languageServiceEnabled, + compilerOptions, + compileOnSaveEnabled, + cachedDirectoryStructureHost, + getDirectoryPath(configFileName)); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); - this.resolutionCache.setRootDirectory(getDirectoryPath(configFileName)); } /** @@ -1370,8 +1369,15 @@ namespace ts.server { languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, private readonly projectFilePath?: string) { - super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host); - this.resolutionCache.setRootDirectory(this.getProjectRootPath()); + super(externalProjectName, + ProjectKind.External, + projectService, + documentRegistry, + /*hasExplicitListOfFiles*/ true, + languageServiceEnabled, compilerOptions, + compileOnSaveEnabled, + projectService.host, + getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); } getExcludedFiles() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 3971b29fe7e..245b27dd58a 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -4,17 +4,36 @@ namespace ts.server { /* @internal */ export class TextStorage { - // Generated only on demand and removes the text if any edits + /** + * Generated only on demand (based on edits, or information requested) + * The property text is set to undefined when edits happen on the cache + */ private svc: ScriptVersionCache | undefined; private svcVersion = 0; - // Store text when there is no svc or svc has no change, on edit, remove the text + /** + * Stores the text when there are no changes to the script version cache + * The script version cache is generated on demand and text is still retained. + * Only on edits to the script version cache, the text will be set to undefined + */ private text: string; + /** + * Line map for the text when there is no script version cache present + */ private lineMap: number[]; private textVersion = 0; + /** + * True if the text is for the file thats open in the editor + */ public isOpen: boolean; + /** + * True if the text present is the text from the file on the disk + */ private ownFileText: boolean; + /** + * True when reloading contents of file from the disk is pending + */ private pendingReloadFromDisk: boolean; constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) { diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index c775a995fd6..f1859a90b98 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -207,7 +207,7 @@ namespace ts.JsTyping { } // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar` - const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); + const fileNames = host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`); const packageNames: string[] = []; for (const fileName of fileNames) { diff --git a/src/services/services.ts b/src/services/services.ts index d91ebbd95ec..5057ced0c90 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1101,13 +1101,11 @@ namespace ts { } function synchronizeHostData(): void { - const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; - // perform fast check if host supports it if (host.getProjectVersion) { const hostProjectVersion = host.getProjectVersion(); if (hostProjectVersion) { - if (lastProjectVersion === hostProjectVersion && !hasChangedAutomaticTypeDirectiveNames()) { + if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames) { return; } @@ -1129,7 +1127,7 @@ namespace ts { const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, host.hasChangedAutomaticTypeDirectiveNames)) { return; } @@ -1170,7 +1168,7 @@ namespace ts { }, onReleaseOldSourceFile, hasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames + hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames }; if (host.trace) { compilerHost.trace = message => host.trace(message); diff --git a/src/services/types.ts b/src/services/types.ts index bd1358b09c1..4c77cd14d9c 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -186,7 +186,7 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; hasInvalidatedResolution?: HasInvalidatedResolution; - hasChangedAutomaticTypeDirectiveNames?(): boolean; + hasChangedAutomaticTypeDirectiveNames?: boolean; directoryExists?(directoryName: string): boolean; /* @@ -278,6 +278,7 @@ namespace ts { getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed; getProgram(): Program;