diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 0f04836566e6a..701276b9da920 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -518,7 +518,7 @@ namespace ts { useCaseSensitiveFileNames: boolean; getCurrentDirectory: System["getCurrentDirectory"]; getAccessibleSortedChildDirectories(path: string): readonly string[]; - directoryExists(dir: string): boolean; + fileSystemEntryExists: FileSystemEntryExists; realpath(s: string): string; setTimeout: NonNullable; clearTimeout: NonNullable; @@ -535,7 +535,7 @@ namespace ts { useCaseSensitiveFileNames, getCurrentDirectory, getAccessibleSortedChildDirectories, - directoryExists, + fileSystemEntryExists, realpath, setTimeout, clearTimeout @@ -655,7 +655,7 @@ namespace ts { function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { // Iterate through existing children and update the watches if needed const parentWatcher = cache.get(dirPath); - if (parentWatcher && directoryExists(dirName)) { + if (parentWatcher && fileSystemEntryExists(dirName, FileSystemEntryKind.Directory)) { // Schedule the update and postpone invoke for callbacks scheduleUpdateChildWatches(dirName, dirPath, fileName, options); return; @@ -733,7 +733,7 @@ namespace ts { if (!parentWatcher) return false; let newChildWatches: ChildDirectoryWatcher[] | undefined; const hasChanges = enumerateInsertsAndDeletes( - directoryExists(parentDir) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { + fileSystemEntryExists(parentDir, FileSystemEntryKind.Directory) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { const childFullName = getNormalizedAbsolutePath(child, parentDir); // Filter our the symbolic link directories since those arent included in recursive watch // which is same behaviour when recursive: true is passed to fs.watch @@ -780,7 +780,12 @@ namespace ts { export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined, modifiedTime?: Date) => void; /*@internal*/ export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; - + /*@internal*/ + export interface FsWatchWorkerWatcher extends FileWatcher { + on(eventName: string, listener: () => void): void; + } + /*@internal*/ + export type FsWatchWorker = (fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback) => FsWatchWorkerWatcher; /*@internal*/ export const enum FileSystemEntryKind { File, @@ -843,6 +848,9 @@ namespace ts { }; } + /*@internal*/ + export type FileSystemEntryExists = (fileorDirectrory: string, entryKind: FileSystemEntryKind) => boolean; + /*@internal*/ export interface CreateSystemWatchFunctions { // Polling watch file @@ -852,11 +860,11 @@ namespace ts { setTimeout: NonNullable; clearTimeout: NonNullable; // For fs events : - fsWatch: FsWatch; + fsWatchWorker: FsWatchWorker; + fileSystemEntryExists: FileSystemEntryExists; useCaseSensitiveFileNames: boolean; getCurrentDirectory: System["getCurrentDirectory"]; fsSupportsRecursiveFsWatch: boolean; - directoryExists: System["directoryExists"]; getAccessibleSortedChildDirectories(path: string): readonly string[]; realpath(s: string): string; // For backward compatibility environment variables @@ -864,6 +872,8 @@ namespace ts { useNonPollingWatchers?: boolean; tscWatchDirectory: string | undefined; defaultWatchFileKind: System["defaultWatchFileKind"]; + inodeWatching: boolean; + sysLog: (s: string) => void; } /*@internal*/ @@ -872,22 +882,25 @@ namespace ts { getModifiedTime, setTimeout, clearTimeout, - fsWatch, + fsWatchWorker, + fileSystemEntryExists, useCaseSensitiveFileNames, getCurrentDirectory, fsSupportsRecursiveFsWatch, - directoryExists, getAccessibleSortedChildDirectories, realpath, tscWatchFile, useNonPollingWatchers, tscWatchDirectory, defaultWatchFileKind, + inodeWatching, + sysLog, }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { let dynamicPollingWatchFile: HostWatchFile | undefined; let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; let nonPollingWatchFile: HostWatchFile | undefined; let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; + let hitSystemWatcherLimit = false; return { watchFile, watchDirectory @@ -989,7 +1002,7 @@ namespace ts { hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ useCaseSensitiveFileNames, getCurrentDirectory, - directoryExists, + fileSystemEntryExists, getAccessibleSortedChildDirectories, watchDirectory: nonRecursiveWatchDirectory, realpath, @@ -1059,6 +1072,147 @@ namespace ts { }; } } + + function fsWatch( + fileOrDirectory: string, + entryKind: FileSystemEntryKind, + callback: FsWatchCallback, + recursive: boolean, + fallbackPollingInterval: PollingInterval, + fallbackOptions: WatchOptions | undefined + ): FileWatcher { + let lastDirectoryPartWithDirectorySeparator: string | undefined; + let lastDirectoryPart: string | undefined; + if (inodeWatching) { + lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substring(fileOrDirectory.lastIndexOf(directorySeparator)); + lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); + } + /** Watcher for the file system entry depending on whether it is missing or present */ + let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? + watchMissingFileSystemEntry() : + watchPresentFileSystemEntry(); + return { + close: () => { + // Close the watcher (either existing file system entry watcher or missing file system entry watcher) + watcher.close(); + watcher = undefined!; + } + }; + + function updateWatcher(createWatcher: () => FileWatcher) { + // If watcher is not closed, update it + if (watcher) { + sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); + watcher.close(); + watcher = createWatcher(); + } + } + + /** + * Watch the file or directory that is currently present + * and when the watched file or directory is deleted, switch to missing file system entry watcher + */ + function watchPresentFileSystemEntry(): FileWatcher { + if (hitSystemWatcherLimit) { + sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to watchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); + } + try { + const presentWatcher = fsWatchWorker( + fileOrDirectory, + recursive, + inodeWatching ? + callbackChangingToMissingFileSystemEntry : + callback + ); + // Watch the missing file or directory or error + presentWatcher.on("error", () => { + callback("rename", ""); + updateWatcher(watchMissingFileSystemEntry); + }); + return presentWatcher; + } + catch (e) { + // Catch the exception and use polling instead + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + // so instead of throwing error, use fs.watchFile + hitSystemWatcherLimit ||= e.code === "ENOSPC"; + sysLog(`sysLog:: ${fileOrDirectory}:: Changing to watchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); + } + } + + function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { + // In some scenarios, file save operation fires event with fileName.ext~ instead of fileName.ext + // To ensure we see the file going missing and coming back up (file delete and then recreated) + // and watches being updated correctly we are calling back with fileName.ext as well as fileName.ext~ + // The worst is we have fired event that was not needed but we wont miss any changes + // especially in cases where file goes missing and watches wrong inode + let originalRelativeName: string | undefined; + if (relativeName && endsWith(relativeName, "~")) { + originalRelativeName = relativeName; + relativeName = relativeName.slice(0, relativeName.length - 1); + } + // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations + // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path + if (event === "rename" && + (!relativeName || + relativeName === lastDirectoryPart || + endsWith(relativeName, lastDirectoryPartWithDirectorySeparator!))) { + const modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + if (originalRelativeName) callback(event, originalRelativeName, modifiedTime); + callback(event, relativeName, modifiedTime); + if (inodeWatching) { + // If this was rename event, inode has changed means we need to update watcher + updateWatcher(modifiedTime === missingFileModifiedTime ? watchMissingFileSystemEntry : watchPresentFileSystemEntry); + } + else if (modifiedTime === missingFileModifiedTime) { + updateWatcher(watchMissingFileSystemEntry); + } + } + else { + if (originalRelativeName) callback(event, originalRelativeName); + callback(event, relativeName); + } + } + + /** + * Watch the file or directory using fs.watchFile since fs.watch threw exception + * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + */ + function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { + return watchFile( + fileOrDirectory, + createFileWatcherCallback(callback), + fallbackPollingInterval, + fallbackOptions + ); + } + + /** + * Watch the file or directory that is missing + * and switch to existing file or directory when the missing filesystem entry is created + */ + function watchMissingFileSystemEntry(): FileWatcher { + return watchFile( + fileOrDirectory, + (_fileName, eventKind, modifiedTime) => { + if (eventKind === FileWatcherEventKind.Created) { + modifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + if (modifiedTime !== missingFileModifiedTime) { + callback("rename", "", modifiedTime); + // Call the callback for current file or directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + updateWatcher(watchPresentFileSystemEntry); + } + } + }, + fallbackPollingInterval, + fallbackOptions + ); + } + } } /** @@ -1273,8 +1427,6 @@ namespace ts { let activeSession: import("inspector").Session | "stopping" | undefined; let profilePath = "./profile.cpuprofile"; - let hitSystemWatcherLimit = false; - const Buffer: { new (input: string, encoding?: string): any; from?(input: string, encoding?: string): any; @@ -1295,19 +1447,21 @@ namespace ts { getModifiedTime, setTimeout, clearTimeout, - fsWatch, + fsWatchWorker, useCaseSensitiveFileNames, getCurrentDirectory, + fileSystemEntryExists, // 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) fsSupportsRecursiveFsWatch, - directoryExists, getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, realpath, tscWatchFile: process.env.TSC_WATCHFILE, useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, defaultWatchFileKind: () => sys!.defaultWatchFileKind?.(), + inodeWatching: isLinuxOrMacOs, + sysLog, }); const nodeSystem: System = { args: process.argv.slice(2), @@ -1568,139 +1722,19 @@ namespace ts { } } - function fsWatch( + function fsWatchWorker( fileOrDirectory: string, - entryKind: FileSystemEntryKind, - callback: FsWatchCallback, recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined - ): FileWatcher { - let options: any; - let lastDirectoryPartWithDirectorySeparator: string | undefined; - let lastDirectoryPart: string | undefined; - if (isLinuxOrMacOs) { - lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); - lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); - } - /** Watcher for the file system entry depending on whether it is missing or present */ - let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? - watchMissingFileSystemEntry() : - watchPresentFileSystemEntry(); - return { - close: () => { - // Close the watcher (either existing file system entry watcher or missing file system entry watcher) - watcher.close(); - watcher = undefined!; - } - }; - - /** - * Invoke the callback with rename and update the watcher if not closed - * @param createWatcher - */ - function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher, modifiedTime?: Date) { - sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); - // Call the callback for current directory - callback("rename", "", modifiedTime); - - // If watcher is not closed, update it - if (watcher) { - watcher.close(); - watcher = createWatcher(); - } - } - - /** - * Watch the file or directory that is currently present - * and when the watched file or directory is deleted, switch to missing file system entry watcher - */ - function watchPresentFileSystemEntry(): 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 (fsSupportsRecursiveFsWatch) { - options = { persistent: true, recursive: !!recursive }; - } - else { - options = { persistent: true }; - } - } - - if (hitSystemWatcherLimit) { - sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to fsWatchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); - } - try { - const presentWatcher = _fs.watch( - fileOrDirectory, - options, - isLinuxOrMacOs ? - callbackChangingToMissingFileSystemEntry : - callback - ); - // Watch the missing file or directory or error - presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); - return presentWatcher; - } - catch (e) { - // Catch the exception and use polling instead - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - // so instead of throwing error, use fs.watchFile - hitSystemWatcherLimit ||= e.code === "ENOSPC"; - sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); - } - } - - function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { - // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations - // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path - const modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; - return event === "rename" && - (!relativeName || - relativeName === lastDirectoryPart || - (relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) !== -1 && relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length)) && - modifiedTime === missingFileModifiedTime ? - invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry, modifiedTime) : - callback(event, relativeName, modifiedTime); - } - - /** - * Watch the file or directory using fs.watchFile since fs.watch threw exception - * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - */ - function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { - return watchFile( - fileOrDirectory, - createFileWatcherCallback(callback), - fallbackPollingInterval, - fallbackOptions - ); - } - - /** - * Watch the file or directory that is missing - * and switch to existing file or directory when the missing filesystem entry is created - */ - function watchMissingFileSystemEntry(): FileWatcher { - return watchFile( - fileOrDirectory, - (_fileName, eventKind, modifiedTime) => { - if (eventKind === FileWatcherEventKind.Created) { - modifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; - if (modifiedTime !== missingFileModifiedTime) { - // Call the callback for current file or directory - // For now it could be callback for the inner directory creation, - // but just return current directory, better than current no-op - invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry, modifiedTime); - } - } - }, - fallbackPollingInterval, - fallbackOptions - ); - } + callback: FsWatchCallback, + ) { + // 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) + return _fs.watch( + fileOrDirectory, + fsSupportsRecursiveFsWatch ? + { persistent: true, recursive: !!recursive } : { persistent: true }, + callback + ); } function readFileWorker(fileName: string, _encoding?: string): string | undefined { diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index c6d68efc99df3..00f32c84b1925 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -14,18 +14,6 @@ interface String { charAt: any; } interface Array { length: number; [n: number]: T; }` }; - export const safeList = { - path: "/safeList.json" as Path, - content: JSON.stringify({ - commander: "commander", - express: "express", - jquery: "jquery", - lodash: "lodash", - moment: "moment", - chroma: "chroma-js" - }) - }; - function getExecutingFilePathFromLibFile(): string { return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); } @@ -39,14 +27,15 @@ interface Array { length: number; [n: number]: T; }` environmentVariables?: ESMap; runWithoutRecursiveWatches?: boolean; runWithFallbackPolling?: boolean; + inodeWatching?: boolean; } - export function createWatchedSystem(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { - return new TestServerHost(/*withSafelist*/ false, fileOrFolderList, params); + export function createWatchedSystem(fileOrFolderList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { + return new TestServerHost(fileOrFolderList, params); } - export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { - const host = new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params); + export function createServerHost(fileOrFolderList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { + const host = new TestServerHost(fileOrFolderList, params); // Just like sys, patch the host to use writeFile patchWriteFileEnsuringDirectory(host); return host; @@ -70,6 +59,9 @@ interface Array { length: number; [n: number]: T; }` } export type FileOrFolderOrSymLink = File | Folder | SymLink; + export interface FileOrFolderOrSymLinkMap { + [path: string]: string | Omit; + } export function isFile(fileOrFolderOrSymLink: FileOrFolderOrSymLink): fileOrFolderOrSymLink is File { return isString((fileOrFolderOrSymLink as File).content); } @@ -195,70 +187,6 @@ interface Array { length: number; [n: number]: T; }` checkMap(caption, arrayToMap(actual, identity), expected, /*eachKeyCount*/ undefined); } - export function checkWatchedFiles(host: TestServerHost, expectedFiles: readonly string[], additionalInfo?: string) { - checkMap(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles, /*eachKeyCount*/ undefined); - } - - export interface WatchFileDetails { - fileName: string; - pollingInterval: PollingInterval; - } - export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyESMap, expectedDetails?: ESMap): void; - export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: readonly string[], eachFileWatchCount: number, expectedDetails?: ESMap): void; - export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyESMap | readonly string[], eachFileWatchCountOrExpectedDetails?: number | ESMap, expectedDetails?: ESMap) { - if (!isNumber(eachFileWatchCountOrExpectedDetails)) expectedDetails = eachFileWatchCountOrExpectedDetails; - if (isArray(expectedFiles)) { - checkMap( - "watchedFiles", - host.watchedFiles, - expectedFiles, - eachFileWatchCountOrExpectedDetails as number, - [expectedDetails, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval })] - ); - } - else { - checkMap( - "watchedFiles", - host.watchedFiles, - expectedFiles, - [expectedDetails, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval })] - ); - } - } - - export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive: boolean) { - checkMap(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.fsWatchesRecursive : host.fsWatches, expectedDirectories, /*eachKeyCount*/ undefined); - } - - export interface WatchDirectoryDetails { - directoryName: string; - fallbackPollingInterval: PollingInterval; - fallbackOptions: WatchOptions | undefined; - } - export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyESMap, recursive: boolean, expectedDetails?: ESMap): void; - export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: readonly string[], eachDirectoryWatchCount: number, recursive: boolean, expectedDetails?: ESMap): void; - export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyESMap | readonly string[], recursiveOrEachDirectoryWatchCount: boolean | number, recursiveOrExpectedDetails?: boolean | ESMap, expectedDetails?: ESMap) { - if (typeof recursiveOrExpectedDetails !== "boolean") expectedDetails = recursiveOrExpectedDetails; - if (isArray(expectedDirectories)) { - checkMap( - `fsWatches${recursiveOrExpectedDetails ? " recursive" : ""}`, - recursiveOrExpectedDetails as boolean ? host.fsWatchesRecursive : host.fsWatches, - expectedDirectories, - recursiveOrEachDirectoryWatchCount as number, - [expectedDetails, ({ directoryName, fallbackPollingInterval, fallbackOptions }) => ({ directoryName, fallbackPollingInterval, fallbackOptions })] - ); - } - else { - recursiveOrExpectedDetails = recursiveOrEachDirectoryWatchCount as boolean; - checkMap( - `fsWatches${recursiveOrExpectedDetails ? " recursive" : ""}`, - recursiveOrExpectedDetails ? host.fsWatchesRecursive : host.fsWatches, - expectedDirectories, - [expectedDetails, ({ directoryName, fallbackPollingInterval, fallbackOptions }) => ({ directoryName, fallbackPollingInterval, fallbackOptions })] - ); - } - } - export function checkOutputContains(host: TestServerHost, expected: readonly string[]) { const mapExpected = new Set(expected); const mapSeen = new Set(); @@ -355,17 +283,22 @@ interface Array { length: number; [n: number]: T; }` export interface TestFsWatcher { cb: FsWatchCallback; directoryName: string; - fallbackPollingInterval: PollingInterval; - fallbackOptions: WatchOptions | undefined; + inode: number | undefined; } - export interface ReloadWatchInvokeOptions { + export interface WatchInvokeOptions { /** Invokes the directory watcher for the parent instead of the file changed */ invokeDirectoryWatcherInsteadOfFileChanged: boolean; /** When new file is created, do not invoke watches for it */ ignoreWatchInvokedWithTriggerAsFileCreate: boolean; /** Invoke the file delete, followed by create instead of file changed */ invokeFileDeleteCreateAsPartInsteadOfChange: boolean; + /** Dont invoke delete watches */ + ignoreDelete: boolean; + /** Skip inode check on file or folder create*/ + skipInodeCheckOnCreate: boolean; + /** When invoking rename event on fs watch, send event with file name suffixed with tilde */ + useTildeAsSuffixInRenameEventFileName: boolean; } export enum Tsc_WatchFile { @@ -384,7 +317,6 @@ interface Array { length: number; [n: number]: T; }` useCaseSensitiveFileNames: boolean; executingFilePath: string; currentDirectory: string; - fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[]; newLine?: string; useWindowsStylePaths?: boolean; environmentVariables?: ESMap; @@ -417,14 +349,16 @@ interface Array { length: number; [n: number]: T; }` public defaultWatchFileKind?: () => WatchFileKind | undefined; public storeFilesChangingSignatureDuringEmit = true; watchFile: HostWatchFile; + private inodeWatching: boolean | undefined; + private readonly inodes?: ESMap; watchDirectory: HostWatchDirectory; constructor( - public withSafeList: boolean, - fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[], + fileOrFolderorSymLinkList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], { useCaseSensitiveFileNames, executingFilePath, currentDirectory, newLine, windowsStyleRoot, environmentVariables, - runWithoutRecursiveWatches, runWithFallbackPolling + runWithoutRecursiveWatches, runWithFallbackPolling, + inodeWatching, }: TestServerHostCreationParameters = {}) { this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames; this.newLine = newLine || "\n"; @@ -438,6 +372,11 @@ interface Array { length: number; [n: number]: T; }` this.runWithFallbackPolling = !!runWithFallbackPolling; const tscWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE"); const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY"); + if (inodeWatching) { + this.inodeWatching = true; + this.inodes = new Map(); + } + const { watchFile, watchDirectory } = createSystemWatchFunctions({ // We dont have polling watch file // it is essentially fsWatch but lets get that separate from fsWatch and @@ -451,22 +390,29 @@ interface Array { length: number; [n: number]: T; }` getModifiedTime: this.getModifiedTime.bind(this), setTimeout: this.setTimeout.bind(this), clearTimeout: this.clearTimeout.bind(this), - fsWatch: this.fsWatch.bind(this), + fsWatchWorker: this.fsWatchWorker.bind(this), + fileSystemEntryExists: this.fileSystemEntryExists.bind(this), useCaseSensitiveFileNames: this.useCaseSensitiveFileNames, getCurrentDirectory: this.getCurrentDirectory.bind(this), fsSupportsRecursiveFsWatch: tscWatchDirectory ? false : !runWithoutRecursiveWatches, - directoryExists: this.directoryExists.bind(this), getAccessibleSortedChildDirectories: path => this.getDirectories(path), realpath: this.realpath.bind(this), tscWatchFile, tscWatchDirectory, defaultWatchFileKind: () => this.defaultWatchFileKind?.(), + inodeWatching: !!this.inodeWatching, + sysLog: s => this.write(s + this.newLine), }); this.watchFile = watchFile; this.watchDirectory = watchDirectory; this.reloadFS(fileOrFolderorSymLinkList); } + private nextInode = 0; + private setInode(path: Path) { + if (this.inodes) this.inodes.set(path, this.nextInode++); + } + // Output is pretty writeOutputIsTTY() { return true; @@ -504,53 +450,31 @@ interface Array { length: number; [n: number]: T; }` this.time = time; } - private reloadFS(fileOrFolderOrSymLinkList: readonly FileOrFolderOrSymLink[], options?: Partial) { + private reloadFS(fileOrFolderOrSymLinkList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[]) { Debug.assert(this.fs.size === 0); - fileOrFolderOrSymLinkList = fileOrFolderOrSymLinkList.concat(this.withSafeList ? safeList : []); - const filesOrFoldersToLoad: readonly FileOrFolderOrSymLink[] = !this.windowsStyleRoot ? fileOrFolderOrSymLinkList : - fileOrFolderOrSymLinkList.map(f => { - const result = clone(f); - result.path = this.getHostSpecificPath(f.path); - return result; - }); - for (const fileOrDirectory of filesOrFoldersToLoad) { - const path = this.toFullPath(fileOrDirectory.path); - // If its a change - const currentEntry = this.fs.get(path); - if (currentEntry) { - if (isFsFile(currentEntry)) { - if (isFile(fileOrDirectory)) { - // Update file - if (currentEntry.content !== fileOrDirectory.content) { - this.modifyFile(fileOrDirectory.path, fileOrDirectory.content, options); - } - } - else { - // TODO: Changing from file => folder/Symlink - } - } - else if (isFsSymLink(currentEntry)) { - // TODO: update symlinks - } - else { - // Folder - if (isFile(fileOrDirectory)) { - // TODO: Changing from folder => file + if (isArray(fileOrFolderOrSymLinkList)) { + fileOrFolderOrSymLinkList.forEach(f => this.ensureFileOrFolder(!this.windowsStyleRoot ? + f : + { ...f, path: this.getHostSpecificPath(f.path) } + )); + } + else { + for (const key in fileOrFolderOrSymLinkList) { + if (hasProperty(fileOrFolderOrSymLinkList, key)) { + const path = this.getHostSpecificPath(key); + const value = fileOrFolderOrSymLinkList[key]; + if (isString(value)) { + this.ensureFileOrFolder({ path, content: value }); } else { - // Folder update: Nothing to do. - currentEntry.modifiedTime = this.now(); - this.invokeFsWatches(currentEntry.fullPath, "change", currentEntry.modifiedTime); + this.ensureFileOrFolder({ path, ...value }); } } } - else { - this.ensureFileOrFolder(fileOrDirectory, options && options.ignoreWatchInvokedWithTriggerAsFileCreate); - } } } - modifyFile(filePath: string, content: string, options?: Partial) { + modifyFile(filePath: string, content: string, options?: Partial) { const path = this.toFullPath(filePath); const currentEntry = this.fs.get(path); if (!currentEntry || !isFsFile(currentEntry)) { @@ -558,8 +482,8 @@ interface Array { length: number; [n: number]: T; }` } if (options && options.invokeFileDeleteCreateAsPartInsteadOfChange) { - this.removeFileOrFolder(currentEntry, returnFalse); - this.ensureFileOrFolder({ path: filePath, content }); + this.removeFileOrFolder(currentEntry, /*isRenaming*/ false, options); + this.ensureFileOrFolder({ path: filePath, content }, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ undefined, /*ignoreParentWatch*/ undefined, options); } else { currentEntry.content = content; @@ -568,11 +492,11 @@ interface Array { length: number; [n: number]: T; }` if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) { const directoryFullPath = getDirectoryPath(currentEntry.fullPath); this.invokeFileWatcher(directoryFullPath, FileWatcherEventKind.Changed, currentEntry.modifiedTime, /*useFileNameInCallback*/ true); - this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath); - this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath); + this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName); + this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName); } else { - this.invokeFileAndFsWatches(currentEntry.fullPath, FileWatcherEventKind.Changed, currentEntry.modifiedTime); + this.invokeFileAndFsWatches(currentEntry.fullPath, FileWatcherEventKind.Changed, currentEntry.modifiedTime, options?.useTildeAsSuffixInRenameEventFileName); } } } @@ -584,7 +508,7 @@ interface Array { length: number; [n: number]: T; }` Debug.assert(!!file); // Only remove the file - this.removeFileOrFolder(file, returnFalse, /*isRenaming*/ true); + this.removeFileOrFolder(file, /*isRenaming*/ true); // Add updated folder with new folder name const newFullPath = getNormalizedAbsolutePath(newFileName, this.currentDirectory); @@ -604,7 +528,7 @@ interface Array { length: number; [n: number]: T; }` Debug.assert(!!folder); // Only remove the folder - this.removeFileOrFolder(folder, returnFalse, /*isRenaming*/ true); + this.removeFileOrFolder(folder, /*isRenaming*/ true); // Add updated folder with new folder name const newFullPath = getNormalizedAbsolutePath(newFolderName, this.currentDirectory); @@ -631,6 +555,7 @@ interface Array { length: number; [n: number]: T; }` newFolder.entries.push(entry); } this.fs.set(entry.path, entry); + this.setInode(entry.path); this.invokeFileAndFsWatches(entry.fullPath, FileWatcherEventKind.Created); if (isFsFolder(entry)) { this.renameFolderEntries(entry, entry); @@ -638,29 +563,29 @@ interface Array { length: number; [n: number]: T; }` } } - ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean, ignoreParentWatch?: boolean) { + ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean, ignoreParentWatch?: boolean, options?: Partial) { if (isFile(fileOrDirectoryOrSymLink)) { const file = this.toFsFile(fileOrDirectoryOrSymLink); // file may already exist when updating existing type declaration file if (!this.fs.get(file.path)) { - const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath), ignoreParentWatch); - this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate); + const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath), ignoreParentWatch, options); + this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate, options); } } else if (isSymLink(fileOrDirectoryOrSymLink)) { const symLink = this.toFsSymLink(fileOrDirectoryOrSymLink); Debug.assert(!this.fs.get(symLink.path)); - const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath), ignoreParentWatch); - this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate); + const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath), ignoreParentWatch, options); + this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate, options); } else { const fullPath = getNormalizedAbsolutePath(fileOrDirectoryOrSymLink.path, this.currentDirectory); - this.ensureFolder(getDirectoryPath(fullPath), ignoreParentWatch); - this.ensureFolder(fullPath, ignoreWatchInvokedWithTriggerAsFileCreate); + this.ensureFolder(getDirectoryPath(fullPath), ignoreParentWatch, options); + this.ensureFolder(fullPath, ignoreWatchInvokedWithTriggerAsFileCreate, options); } } - private ensureFolder(fullPath: string, ignoreWatch: boolean | undefined): FsFolder { + private ensureFolder(fullPath: string, ignoreWatch: boolean | undefined, options: Partial | undefined): FsFolder { const path = this.toPath(fullPath); let folder = this.fs.get(path) as FsFolder; if (!folder) { @@ -668,34 +593,39 @@ interface Array { length: number; [n: number]: T; }` const baseFullPath = getDirectoryPath(fullPath); if (fullPath !== baseFullPath) { // Add folder in the base folder - const baseFolder = this.ensureFolder(baseFullPath, ignoreWatch); - this.addFileOrFolderInFolder(baseFolder, folder, ignoreWatch); + const baseFolder = this.ensureFolder(baseFullPath, ignoreWatch, options); + this.addFileOrFolderInFolder(baseFolder, folder, ignoreWatch, options); } else { // root folder Debug.assert(this.fs.size === 0 || !!this.windowsStyleRoot); this.fs.set(path, folder); + this.setInode(path); } } Debug.assert(isFsFolder(folder)); return folder; } - private addFileOrFolderInFolder(folder: FsFolder, fileOrDirectory: FsFile | FsFolder | FsSymLink, ignoreWatch?: boolean) { + private addFileOrFolderInFolder(folder: FsFolder, fileOrDirectory: FsFile | FsFolder | FsSymLink, ignoreWatch?: boolean, options?: Partial) { if (!this.fs.has(fileOrDirectory.path)) { insertSorted(folder.entries, fileOrDirectory, (a, b) => compareStringsCaseSensitive(getBaseFileName(a.path), getBaseFileName(b.path))); } folder.modifiedTime = this.now(); this.fs.set(fileOrDirectory.path, fileOrDirectory); + this.setInode(fileOrDirectory.path); if (ignoreWatch) { return; } - this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created, fileOrDirectory.modifiedTime); - this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed, folder.modifiedTime); + const inodeWatching = this.inodeWatching; + if (options?.skipInodeCheckOnCreate) this.inodeWatching = false; + this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created, fileOrDirectory.modifiedTime, options?.useTildeAsSuffixInRenameEventFileName); + this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed, fileOrDirectory.modifiedTime, options?.useTildeAsSuffixInRenameEventFileName); + this.inodeWatching = inodeWatching; } - private removeFileOrFolder(fileOrDirectory: FsFile | FsFolder | FsSymLink, isRemovableLeafFolder: (folder: FsFolder) => boolean, isRenaming = false) { + private removeFileOrFolder(fileOrDirectory: FsFile | FsFolder | FsSymLink, isRenaming?: boolean, options?: Partial) { const basePath = getDirectoryPath(fileOrDirectory.path); const baseFolder = this.fs.get(basePath) as FsFolder; if (basePath !== fileOrDirectory.path) { @@ -708,20 +638,16 @@ interface Array { length: number; [n: number]: T; }` if (isFsFolder(fileOrDirectory)) { Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming); } - this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); - this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed, baseFolder.modifiedTime); - if (basePath !== fileOrDirectory.path && - baseFolder.entries.length === 0 && - isRemovableLeafFolder(baseFolder)) { - this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); - } + if (!options?.ignoreDelete) this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted, /*modifiedTime*/ undefined, options?.useTildeAsSuffixInRenameEventFileName); + this.inodes?.delete(fileOrDirectory.path); + if (!options?.ignoreDelete) this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed, baseFolder.modifiedTime, options?.useTildeAsSuffixInRenameEventFileName); } deleteFile(filePath: string) { const path = this.toFullPath(filePath); const currentEntry = this.fs.get(path) as FsFile; Debug.assert(isFsFile(currentEntry)); - this.removeFileOrFolder(currentEntry, returnFalse); + this.removeFileOrFolder(currentEntry); } deleteFolder(folderPath: string, recursive?: boolean) { @@ -735,11 +661,11 @@ interface Array { length: number; [n: number]: T; }` this.deleteFolder(fsEntry.fullPath, recursive); } else { - this.removeFileOrFolder(fsEntry, returnFalse); + this.removeFileOrFolder(fsEntry); } }); } - this.removeFileOrFolder(currentEntry, returnFalse); + this.removeFileOrFolder(currentEntry); } private watchFileWorker(fileName: string, cb: FileWatcherCallback, pollingInterval: PollingInterval) { @@ -750,69 +676,73 @@ interface Array { length: number; [n: number]: T; }` ); } - private fsWatch( + private fsWatchWorker( fileOrDirectory: string, - _entryKind: FileSystemEntryKind, - cb: FsWatchCallback, recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined): FileWatcher { - return this.runWithFallbackPolling ? - this.watchFile( - fileOrDirectory, - createFileWatcherCallback(cb), - fallbackPollingInterval, - fallbackOptions - ) : - createWatcher( - recursive ? this.fsWatchesRecursive : this.fsWatches, - this.toFullPath(fileOrDirectory), - { - directoryName: fileOrDirectory, - cb, - fallbackPollingInterval, - fallbackOptions - } - ); + cb: FsWatchCallback, + ) { + if (this.runWithFallbackPolling) throw new Error("Need to use fallback polling instead of file system native watching"); + const path = this.toFullPath(fileOrDirectory); + // Error if the path does not exist + if (this.inodeWatching && !this.inodes?.has(path)) throw new Error(); + const result = createWatcher( + recursive ? this.fsWatchesRecursive : this.fsWatches, + path, + { + directoryName: fileOrDirectory, + cb, + inode: this.inodes?.get(path) + } + ) as FsWatchWorkerWatcher; + result.on = noop; + return result; } invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, modifiedTime?: Date, useFileNameInCallback?: boolean) { invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind, modifiedTime)); } - private fsWatchCallback(map: MultiMap, fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string) { - invokeWatcherCallbacks(map.get(this.toPath(fullPath)), ({ cb }) => cb(eventName, entryFullPath ? this.getRelativePathToDirectory(fullPath, entryFullPath) : "", modifiedTime)); + private fsWatchCallback(map: MultiMap, fullPath: string, eventName: "rename" | "change", modifiedTime: Date | undefined, entryFullPath: string | undefined, useTildeSuffix: boolean | undefined) { + const path = this.toPath(fullPath); + const currentInode = this.inodes?.get(path); + invokeWatcherCallbacks(map.get(path), ({ cb, inode }) => { + // TODO:: + if (this.inodeWatching && inode !== undefined && inode !== currentInode) return; + let relativeFileName = (entryFullPath ? this.getRelativePathToDirectory(fullPath, entryFullPath) : ""); + if (useTildeSuffix) relativeFileName = (relativeFileName ? relativeFileName : getBaseFileName(fullPath)) + "~"; + cb(eventName, relativeFileName, modifiedTime); + }); } - invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string) { - this.fsWatchCallback(this.fsWatches, fullPath, eventName, modifiedTime, entryFullPath); + invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string, useTildeSuffix?: boolean) { + this.fsWatchCallback(this.fsWatches, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix); } - invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string) { - this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, modifiedTime, entryFullPath); + invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string, useTildeSuffix?: boolean) { + this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); } - private invokeRecursiveFsWatches(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string) { - this.invokeFsWatchesRecursiveCallbacks(fullPath, eventName, modifiedTime, entryFullPath); + private invokeRecursiveFsWatches(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string, useTildeSuffix?: boolean) { + this.invokeFsWatchesRecursiveCallbacks(fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix); const basePath = getDirectoryPath(fullPath); if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { - this.invokeRecursiveFsWatches(basePath, eventName, modifiedTime, entryFullPath || fullPath); + this.invokeRecursiveFsWatches(basePath, eventName, modifiedTime, entryFullPath || fullPath, useTildeSuffix); } } - private invokeFsWatches(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date) { - this.invokeFsWatchesCallbacks(fullPath, eventName, modifiedTime); - this.invokeFsWatchesCallbacks(getDirectoryPath(fullPath), eventName, modifiedTime, fullPath); - this.invokeRecursiveFsWatches(fullPath, eventName, modifiedTime); + private invokeFsWatches(fullPath: string, eventName: "rename" | "change", modifiedTime: Date | undefined, useTildeSuffix: boolean | undefined) { + this.invokeFsWatchesCallbacks(fullPath, eventName, modifiedTime, fullPath, useTildeSuffix); + this.invokeFsWatchesCallbacks(getDirectoryPath(fullPath), eventName, modifiedTime, fullPath, useTildeSuffix); + this.invokeRecursiveFsWatches(fullPath, eventName, modifiedTime, /*entryFullPath*/ undefined, useTildeSuffix); } - private invokeFileAndFsWatches(fileOrFolderFullPath: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) { + private invokeFileAndFsWatches(fileOrFolderFullPath: string, eventKind: FileWatcherEventKind, modifiedTime?: Date, useTildeSuffix?: boolean) { this.invokeFileWatcher(fileOrFolderFullPath, eventKind, modifiedTime); - this.invokeFsWatches(fileOrFolderFullPath, eventKind === FileWatcherEventKind.Changed ? "change" : "rename", modifiedTime); + this.invokeFsWatches(fileOrFolderFullPath, eventKind === FileWatcherEventKind.Changed ? "change" : "rename", modifiedTime, useTildeSuffix); } private toFsEntry(path: string): FSEntryBase { @@ -881,6 +811,10 @@ interface Array { length: number; [n: number]: T; }` return this.getRealFsEntry(isFsFolder, path, fsEntry); } + fileSystemEntryExists(s: string, entryKind: FileSystemEntryKind) { + return entryKind === FileSystemEntryKind.File ? this.fileExists(s) : this.directoryExists(s); + } + fileExists(s: string) { const path = this.toFullPath(s); return !!this.getRealFile(path); @@ -1046,11 +980,11 @@ interface Array { length: number; [n: number]: T; }` } } - prependFile(path: string, content: string, options?: Partial): void { + prependFile(path: string, content: string, options?: Partial): void { this.modifyFile(path, content + this.readFile(path), options); } - appendFile(path: string, content: string, options?: Partial): void { + appendFile(path: string, content: string, options?: Partial): void { this.modifyFile(path, this.readFile(path) + content, options); } @@ -1095,14 +1029,14 @@ interface Array { length: number; [n: number]: T; }` } writtenFiles?: ESMap; - diff(baseline: string[], base: ESMap = new Map()) { - this.fs.forEach(newFsEntry => { - diffFsEntry(baseline, base.get(newFsEntry.path), newFsEntry, this.writtenFiles); + diff(baseline: string[], base: ESMap = new Map()) { + this.fs.forEach((newFsEntry, path) => { + diffFsEntry(baseline, base.get(path), newFsEntry, this.inodes?.get(path), this.writtenFiles); }); - base.forEach(oldFsEntry => { - const newFsEntry = this.fs.get(oldFsEntry.path); + base.forEach((oldFsEntry, path) => { + const newFsEntry = this.fs.get(path); if (!newFsEntry) { - diffFsEntry(baseline, oldFsEntry, newFsEntry, this.writtenFiles); + diffFsEntry(baseline, oldFsEntry, newFsEntry, this.inodes?.get(path), this.writtenFiles); } }); baseline.push(""); @@ -1150,86 +1084,77 @@ interface Array { length: number; [n: number]: T; }` } } - function diffFsFile(baseline: string[], fsEntry: FsFile) { - baseline.push(`//// [${fsEntry.fullPath}]\r\n${fsEntry.content}`, ""); + function diffFsFile(baseline: string[], fsEntry: FsFile, newInode: number | undefined) { + baseline.push(`//// [${fsEntry.fullPath}]${inodeString(newInode)}\r\n${fsEntry.content}`, ""); + } + function diffFsSymLink(baseline: string[], fsEntry: FsSymLink, newInode: number | undefined) { + baseline.push(`//// [${fsEntry.fullPath}] symlink(${fsEntry.symLink})${inodeString(newInode)}`); } - function diffFsSymLink(baseline: string[], fsEntry: FsSymLink) { - baseline.push(`//// [${fsEntry.fullPath}] symlink(${fsEntry.symLink})`); + function inodeString(inode: number | undefined) { + return inode !== undefined ? ` Inode:: ${inode}` : ""; } - function diffFsEntry(baseline: string[], oldFsEntry: FSEntry | undefined, newFsEntry: FSEntry | undefined, writtenFiles: ESMap | undefined): void { + function diffFsEntry(baseline: string[], oldFsEntry: FSEntry | undefined, newFsEntry: FSEntry | undefined, newInode: number | undefined, writtenFiles: ESMap | undefined): void { const file = newFsEntry && newFsEntry.fullPath; if (isFsFile(oldFsEntry)) { if (isFsFile(newFsEntry)) { if (oldFsEntry.content !== newFsEntry.content) { - diffFsFile(baseline, newFsEntry); + diffFsFile(baseline, newFsEntry, newInode); } else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) { if (oldFsEntry.fullPath !== newFsEntry.fullPath) { - baseline.push(`//// [${file}] file was renamed from file ${oldFsEntry.fullPath}`); + baseline.push(`//// [${file}] file was renamed from file ${oldFsEntry.fullPath}${inodeString(newInode)}`); } else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) { - baseline.push(`//// [${file}] file changed its modified time`); + baseline.push(`//// [${file}] file changed its modified time${inodeString(newInode)}`); } else { - baseline.push(`//// [${file}] file written with same contents`); + baseline.push(`//// [${file}] file written with same contents${inodeString(newInode)}`); } } } else { baseline.push(`//// [${oldFsEntry.fullPath}] deleted`); if (isFsSymLink(newFsEntry)) { - diffFsSymLink(baseline, newFsEntry); + diffFsSymLink(baseline, newFsEntry, newInode); } } } else if (isFsSymLink(oldFsEntry)) { if (isFsSymLink(newFsEntry)) { if (oldFsEntry.symLink !== newFsEntry.symLink) { - diffFsSymLink(baseline, newFsEntry); + diffFsSymLink(baseline, newFsEntry, newInode); } else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) { if (oldFsEntry.fullPath !== newFsEntry.fullPath) { - baseline.push(`//// [${file}] symlink was renamed from symlink ${oldFsEntry.fullPath}`); + baseline.push(`//// [${file}] symlink was renamed from symlink ${oldFsEntry.fullPath}${inodeString(newInode)}`); } else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) { - baseline.push(`//// [${file}] symlink changed its modified time`); + baseline.push(`//// [${file}] symlink changed its modified time${inodeString(newInode)}`); } else { - baseline.push(`//// [${file}] symlink written with same link`); + baseline.push(`//// [${file}] symlink written with same link${inodeString(newInode)}`); } } } else { baseline.push(`//// [${oldFsEntry.fullPath}] deleted symlink`); if (isFsFile(newFsEntry)) { - diffFsFile(baseline, newFsEntry); + diffFsFile(baseline, newFsEntry, newInode); } } } else if (isFsFile(newFsEntry)) { - diffFsFile(baseline, newFsEntry); + diffFsFile(baseline, newFsEntry, newInode); } else if (isFsSymLink(newFsEntry)) { - diffFsSymLink(baseline, newFsEntry); + diffFsSymLink(baseline, newFsEntry, newInode); } } - function serializeTestFsWatcher({ directoryName, fallbackPollingInterval, fallbackOptions }: TestFsWatcher) { + function serializeTestFsWatcher({ directoryName, inode }: TestFsWatcher) { return { directoryName, - fallbackPollingInterval, - fallbackOptions: serializeWatchOptions(fallbackOptions) - }; - } - - function serializeWatchOptions(fallbackOptions: WatchOptions | undefined) { - if (!fallbackOptions) return undefined; - const { watchFile, watchDirectory, fallbackPolling, ...rest } = fallbackOptions; - return { - watchFile: watchFile !== undefined ? WatchFileKind[watchFile] : undefined, - watchDirectory: watchDirectory !== undefined ? WatchDirectoryKind[watchDirectory] : undefined, - fallbackPolling: fallbackPolling !== undefined ? PollingWatchKind[fallbackPolling] : undefined, - ...rest + inode, }; } diff --git a/src/testRunner/unittests/tsbuildWatch/noEmit.ts b/src/testRunner/unittests/tsbuildWatch/noEmit.ts index ddcf1dd0b2893..456bfe3614478 100644 --- a/src/testRunner/unittests/tsbuildWatch/noEmit.ts +++ b/src/testRunner/unittests/tsbuildWatch/noEmit.ts @@ -6,11 +6,10 @@ namespace ts.tscWatch { commandLineArgs: ["-b", "-w", "-verbose"], sys: () => createWatchedSystem( [ - libFile, + { path: libFile.path, content: libContent }, { path: `${projectRoot}/a.js`, content: "" }, { path: `${projectRoot}/b.ts`, content: "" }, { path: `${projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) }, - { path: libFile.path, content: libContent } ], { currentDirectory: projectRoot } ), diff --git a/src/testRunner/unittests/tscWatch/emit.ts b/src/testRunner/unittests/tscWatch/emit.ts index 217d4c059a5ba..8c9ec7150b199 100644 --- a/src/testRunner/unittests/tscWatch/emit.ts +++ b/src/testRunner/unittests/tscWatch/emit.ts @@ -6,21 +6,12 @@ namespace ts.tscWatch { scenario, subScenario: `emit with outFile or out setting/${subScenario}`, commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], - sys: () => { - const config: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { out, outFile } }) - }; - const f1: File = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2: File = { - path: "/a/b.ts", - content: "let y = 1" - }; - return createWatchedSystem([f1, f2, config, libFile]); - }, + sys: () => createWatchedSystem({ + "/a/a.ts": "let x = 1", + "/a/b.ts": "let y = 1", + "/a/tsconfig.json": JSON.stringify({ compilerOptions: { out, outFile } }), + [libFile.path]: libFile.content, + }), changes: [ { caption: "Make change in the file", diff --git a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts index 5199b780b06e3..60a989f2fa397 100644 --- a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts @@ -20,7 +20,7 @@ namespace ts.tscWatch { scenario: "forceConsistentCasingInFileNames", subScenario, commandLineArgs: ["--w", "--p", tsconfig.path], - sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile, tsconfig]), + sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile]), changes }); } diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index 184c718a294f3..bad2dc7cf4931 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -7,10 +7,6 @@ namespace ts.tscWatch { export import libFile = TestFSWithWatch.libFile; export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; export import checkArray = TestFSWithWatch.checkArray; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; export import checkOutputContains = TestFSWithWatch.checkOutputContains; export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; @@ -436,7 +432,7 @@ namespace ts.tscWatch { return sys; } - export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { + export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: TestFSWithWatch.FileOrFolderOrSymLinkMap | readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots); } } diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index d02afa3137714..95cacc1e7d088 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -581,5 +581,141 @@ namespace ts.tscWatch { verifyWorker("-extendedDiagnostics"); }); }); + + verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats when rename occurs when file is still on the disk`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => createWatchedSystem( + { + [libFile.path]: libFile.content, + [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${projectRoot}/foo.ts`]: `export declare function foo(): string;`, + [`${projectRoot}/tsconfig.json`]: JSON.stringify({ + watchOptions: { watchFile: "useFsEvents" }, + files: ["foo.ts", "main.ts"] + }), + }, + { currentDirectory: projectRoot, } + ), + changes: [ + { + caption: "Introduce error such that when callback happens file is already appeared", + // vm's wq generates this kind of event + // Skip delete event so inode changes but when the create's rename occurs file is on disk + change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo2(): string;`, { + invokeFileDeleteCreateAsPartInsteadOfChange: true, + ignoreDelete: true, + }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + ] + }); + + describe("with fsWatch on inodes", () => { + verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats on inode`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => createWatchedSystem( + { + [libFile.path]: libFile.content, + [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${projectRoot}/foo.d.ts`]: `export function foo(): string;`, + [`${projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), + }, + { + currentDirectory: projectRoot, + inodeWatching: true + } + ), + changes: [ + { + caption: "Replace file with rename event that introduces error", + change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats on inode when rename event ends with tilde`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => createWatchedSystem( + { + [libFile.path]: libFile.content, + [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${projectRoot}/foo.d.ts`]: `export function foo(): string;`, + [`${projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), + }, + { + currentDirectory: projectRoot, + inodeWatching: true + } + ), + changes: [ + { + caption: "Replace file with rename event that introduces error", + change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => createWatchedSystem( + { + [libFile.path]: libFile.content, + [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${projectRoot}/foo.ts`]: `export declare function foo(): string;`, + [`${projectRoot}/tsconfig.json`]: JSON.stringify({ + watchOptions: { watchFile: "useFsEvents" }, + files: ["foo.ts", "main.ts"] + }), + }, + { + currentDirectory: projectRoot, + inodeWatching: true, + } + ), + changes: [ + { + caption: "Introduce error such that when callback happens file is already appeared", + // vm's wq generates this kind of event + // Skip delete event so inode changes but when the create's rename occurs file is on disk + change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo2(): string;`, { + invokeFileDeleteCreateAsPartInsteadOfChange: true, + ignoreDelete: true, + skipInodeCheckOnCreate: true + }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + ] + }); + }); }); } diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index bdad9ec6066fb..e6048f6c7a6c6 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -1,8 +1,4 @@ namespace ts.projectSystem { - function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { - return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); - } - describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { enum CalledMapsWithSingleArg { fileExists = "fileExists", @@ -15,7 +11,7 @@ namespace ts.projectSystem { } type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number]; - function createCallsTrackingHost(host: TestServerHost) { + function createLoggerTrackingHostCalls(host: TestServerHost) { const calledMaps: Record> & Record> = { fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), @@ -24,15 +20,7 @@ namespace ts.projectSystem { readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) }; - return { - verifyNoCall, - verifyCalledOnEachEntryNTimes, - verifyCalledOnEachEntry, - verifyNoHostCalls, - verifyNoHostCallsExceptFileExistsOnce, - verifyCalledOn, - clear - }; + return logCacheAndClear; function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { const calledMap = createMultiMap(); @@ -54,49 +42,25 @@ namespace ts.projectSystem { return calledMap; } - function verifyCalledOn(callback: CalledMaps, name: string) { - const calledMap = calledMaps[callback]; - const result = calledMap.get(name); - assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); - } - - function verifyNoCall(callback: CalledMaps) { - const calledMap = calledMaps[callback]; - assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`); - } - - function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: ESMap) { - TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys); - } - - function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: readonly string[], nTimes: number) { - TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys, nTimes); - } - - function verifyNoHostCalls() { - iterateOnCalledMaps(key => verifyNoCall(key)); - } - - function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: readonly string[]) { - verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1); - verifyNoCall(CalledMapsWithSingleArg.directoryExists); - verifyNoCall(CalledMapsWithSingleArg.getDirectories); - verifyNoCall(CalledMapsWithSingleArg.readFile); - verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + function logCacheEntry(logger: Logger, callback: CalledMaps) { + const result = arrayFrom<[string, (true | CalledWithFiveArgs)[]], { key: string, count: number }>(calledMaps[callback].entries(), ([key, arr]) => ({ key, count: arr.length })); + logger.info(`${callback}:: ${JSON.stringify(result)}`); + calledMaps[callback].clear(); } - function clear() { - iterateOnCalledMaps(key => calledMaps[key].clear()); + function logCacheAndClear(logger: Logger) { + logCacheEntry(logger, CalledMapsWithSingleArg.fileExists); + logCacheEntry(logger, CalledMapsWithSingleArg.directoryExists); + logCacheEntry(logger, CalledMapsWithSingleArg.getDirectories); + logCacheEntry(logger, CalledMapsWithSingleArg.readFile); + logCacheEntry(logger, CalledMapsWithFiveArgs.readDirectory); } + } - function iterateOnCalledMaps(cb: (key: CalledMaps) => void) { - for (const key in CalledMapsWithSingleArg) { - cb(key as CalledMapsWithSingleArg); - } - for (const key in CalledMapsWithFiveArgs) { - cb(key as CalledMapsWithFiveArgs); - } - } + function logSemanticDiagnostics(projectService: server.ProjectService, project: server.Project, file: File) { + const diags = project.getLanguageService().getSemanticDiagnostics(file.path); + projectService.logger.info(`getSemanticDiagnostics:: ${file.path}:: ${diags.length}`); + diags.forEach(d => projectService.logger.info(formatDiagnostic(d, project))); } it("works using legacy resolution logic", () => { @@ -112,108 +76,50 @@ namespace ts.projectSystem { }; const host = createServerHost([root, imported]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); const project = projectService.inferredProjects[0]; const rootScriptInfo = project.getRootScriptInfos()[0]; assert.equal(rootScriptInfo.fileName, root.path); // ensure that imported file was found - verifyImportedDiagnostics(); + logSemanticDiagnostics(projectService, project, imported); - const callsTrackingHost = createCallsTrackingHost(host); + const logCacheAndClear = createLoggerTrackingHostCalls(host); // trigger synchronization to make sure that import will be fetched from the cache // ensure file has correct number of errors after edit editContent(`import {x} from "f1"; var x: string = 1;`); - verifyImportedDiagnostics(); - callsTrackingHost.verifyNoHostCalls(); + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); // trigger synchronization to make sure that the host will try to find 'f2' module on disk editContent(`import {x} from "f2"`); try { // trigger synchronization to make sure that the host will try to find 'f2' module on disk - verifyImportedDiagnostics(); - assert.isTrue(false, `should not find file '${imported.path}'`); + logSemanticDiagnostics(projectService, project, imported); } catch (e) { - assert.isTrue(e.message.indexOf(`Could not find source file: '${imported.path}'.`) === 0, `Actual: ${e.message}`); + projectService.logger.info(e.message); } - const f2Lookups = getLocationsForModuleLookup("f2"); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); - const f2DirLookups = getLocationsForDirectoryLookup(); - callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + logCacheAndClear(projectService.logger); editContent(`import {x} from "f1"`); - verifyImportedDiagnostics(); - const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); - f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; - const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)]; - vertifyF1Lookups(); + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); // setting compiler options discards module resolution cache - callsTrackingHost.clear(); projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); - verifyImportedDiagnostics(); - vertifyF1Lookups(); - - function vertifyF1Lookups() { - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - } + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); + baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService); function editContent(newContent: string) { - callsTrackingHost.clear(); rootScriptInfo.editContent(0, rootContent.length, newContent); rootContent = newContent; } - - function verifyImportedDiagnostics() { - const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); - assert.equal(diags.length, 1); - const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); - } - - function getLocationsForModuleLookup(module: string) { - const locations: string[] = []; - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.ts`), - combinePaths(ancestor, `${module}.tsx`), - combinePaths(ancestor, `${module}.d.ts`) - ); - }); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.js`), - combinePaths(ancestor, `${module}.jsx`) - ); - }); - return locations; - } - - function getLocationsForDirectoryLookup() { - const result = new Map(); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - // To resolve modules - result.set(ancestor, 2); - // for type roots - result.set(combinePaths(ancestor, nodeModules), 1); - result.set(combinePaths(ancestor, nodeModulesAtTypes), 1); - }); - return result; - } }); it("loads missing files from disk", () => { @@ -228,29 +134,22 @@ namespace ts.projectSystem { }; const host = createServerHost([root]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - const callsTrackingHost = createCallsTrackingHost(host); + const logCacheAndClear = createLoggerTrackingHostCalls(host); projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); const project = projectService.inferredProjects[0]; const rootScriptInfo = project.getRootScriptInfos()[0]; assert.equal(rootScriptInfo.fileName, root.path); - let diags = project.getLanguageService().getSemanticDiagnostics(root.path); - assert.equal(diags.length, 1); - const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?"); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); - + logSemanticDiagnostics(projectService, project, root); + logCacheAndClear(projectService.logger); - callsTrackingHost.clear(); host.writeFile(imported.path, imported.content); host.runQueuedTimeoutCallbacks(); - diags = project.getLanguageService().getSemanticDiagnostics(root.path); - assert.equal(diags.length, 0); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + logSemanticDiagnostics(projectService, project, root); + logCacheAndClear(projectService.logger); + baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService); }); it("when calling goto definition of module", () => { @@ -296,17 +195,9 @@ namespace ts.projectSystem { }; const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; const host = createServerHost(projectFiles); - const session = createSession(host); - const projectService = session.getProjectService(); - const { configFileName } = projectService.openClientFile(clientFile.path); - - assert.isDefined(configFileName, `should find config`); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = projectService.configuredProjects.get(tsconfigFile.path)!; - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - - const callsTrackingHost = createCallsTrackingHost(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([clientFile], session); + const logCacheAndClear = createLoggerTrackingHostCalls(host); // Get definitions shouldnt make host requests const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { @@ -315,137 +206,90 @@ namespace ts.projectSystem { line: undefined!, // TODO: GH#18217 offset: undefined! // TODO: GH#18217 }); - const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[]; - assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); - callsTrackingHost.verifyNoHostCalls(); + session.executeCommand(getDefinitionRequest); + logCacheAndClear(session.logger); // Open the file should call only file exists on module directory and use cached value for parental directory - const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); - assert.equal(config2, configFileName); - callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); + openFilesForSession([moduleFile], session); + logCacheAndClear(session.logger); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); + baselineTsserverLogs("cachingFileSystemInformation", "when calling goto definition of module", session); }); describe("WatchDirectories for config file with", () => { function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const frontendDir = "/Users/someuser/work/applications/frontend"; - const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; - const canonicalFrontendDir = toCanonical(frontendDir); - const file1: File = { - path: `${frontendDir}/src/app/utils/Analytic.ts`, - content: "export class SomeClass { };" - }; - const file2: File = { - path: `${frontendDir}/src/app/redux/configureStore.ts`, - content: "export class configureStore { }" - }; - const file3: File = { - path: `${frontendDir}/src/app/utils/Cookie.ts`, - content: "export class Cookie { }" - }; - const es2016LibFile: File = { - path: "/a/lib/lib.es2016.full.d.ts", - content: libFile.content - }; - const typeRoots = ["types", "node_modules/@types"]; - const types = ["node", "jest"]; - const tsconfigFile: File = { - path: `${frontendDir}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - strict: true, - strictNullChecks: true, - target: "es2016", - module: "commonjs", - moduleResolution: "node", - sourceMap: true, - noEmitOnError: true, - experimentalDecorators: true, - emitDecoratorMetadata: true, - types, - noUnusedLocals: true, - outDir: "./compiled", - typeRoots, - baseUrl: ".", - paths: { - "*": [ - "types/*" - ] - } - }, - include: [ - "src/**/*" - ], - exclude: [ - "node_modules", - "compiled" - ] - }) - }; - const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; - const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host); - const canonicalConfigPath = toCanonical(tsconfigFile.path); - const { configFileName } = projectService.openClientFile(file1.path); - assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); - checkNumberOfConfiguredProjects(projectService, 1); - const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); - - const project = projectService.configuredProjects.get(canonicalConfigPath)!; - verifyProjectAndWatchedDirectories(); - - const callsTrackingHost = createCallsTrackingHost(host); - - // Create file cookie.ts - projectFiles.push(file3); - host.writeFile(file3.path, file3.content); - host.runQueuedTimeoutCallbacks(); - - const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - - callsTrackingHost.clear(); - - const { configFileName: configFile2 } = projectService.openClientFile(file3.path); - assert.equal(configFile2, configFileName); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - callsTrackingHost.verifyNoHostCalls(); - - function getFilePathIfNotOpen(f: File) { - const path = toCanonical(f.path); - const info = projectService.getScriptInfoForPath(toCanonical(f.path)); - return info && info.isScriptOpen() ? undefined : path; - } - - function verifyProjectAndWatchedDirectories() { - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); - checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } + it(`watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, () => { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const file1: File = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" + }; + const file2: File = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: File = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: File = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; + const tsconfigFile: File = { + path: `${frontendDir}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + strict: true, + strictNullChecks: true, + target: "es2016", + module: "commonjs", + moduleResolution: "node", + sourceMap: true, + noEmitOnError: true, + experimentalDecorators: true, + emitDecoratorMetadata: true, + types, + noUnusedLocals: true, + outDir: "./compiled", + typeRoots, + baseUrl: ".", + paths: { + "*": [ + "types/*" + ] + } + }, + include: [ + "src/**/*" + ], + exclude: [ + "node_modules", + "compiled" + ] + }) + }; + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); + projectService.openClientFile(file1.path); + + const logCacheAndClear = createLoggerTrackingHostCalls(host); + + // Create file cookie.ts + host.writeFile(file3.path, file3.content); + host.runQueuedTimeoutCallbacks(); + logCacheAndClear(projectService.logger); + + projectService.openClientFile(file3.path); + logCacheAndClear(projectService.logger); + baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService); + }); } - - it("case insensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); - - it("case sensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); }); describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { @@ -538,15 +382,10 @@ namespace ts.projectSystem { } ` }); - const appFolder = getDirectoryPath(app.path); - const projectFiles = [app, libFile, tsconfigJson]; - const otherFiles = [packageJson]; - const host = createServerHost(projectFiles.concat(otherFiles)); + const host = createServerHost([app, libFile, tsconfigJson, packageJson]); const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); - const { configFileName } = projectService.openClientFile(app.path); - assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217 - const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); + projectService.openClientFile(app.path); let npmInstallComplete = false; @@ -625,10 +464,6 @@ namespace ts.projectSystem { }); host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true); - const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts"; - projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!); - // we would now not have failed lookup in the parent of appFolder since lodash is available - recursiveWatchedDirectories.length = 2; // npm installation complete, timeout after reload fs npmInstallComplete = true; verifyAfterPartialOrCompleteNpmInstall(2); @@ -660,7 +495,6 @@ namespace ts.projectSystem { it("timeouts occur inbetween installation", () => { verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); }); - it("timeout occurs after installation", () => { verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); }); diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts index 152184e9ae5d3..39ba56ced2127 100644 --- a/src/testRunner/unittests/tsserver/configFileSearch.ts +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -38,22 +38,14 @@ namespace ts.projectSystem { content: "{}" }; const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host); + const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - const typeRootLocations = getTypeRootsFromLocation(configFileLocation); - checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true); // Delete config file - should create inferred project and not configured project host.deleteFile(configFile.path); host.runQueuedTimeoutCallbacks(); checkNumberOfProjects(service, { inferredProjects: 1 }); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true); + baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service); }); it("should use projectRootPath when searching for inferred project again 2", () => { @@ -72,22 +64,17 @@ namespace ts.projectSystem { content: "{}" }; const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); + const service = createProjectService(host, { + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true, + logger: createLoggerWithInMemoryLogs(), + }); service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true); // Delete config file - should create inferred project with project root path set host.deleteFile(configFile.path); host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - assert.equal(service.inferredProjects[0].projectRootPath, projectDir); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true); + baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service); }); describe("when the opened file is not from project root", () => { @@ -100,89 +87,50 @@ namespace ts.projectSystem { path: `${projectRoot}/tsconfig.json`, content: "{}" }; - const dirOfFile = getDirectoryPath(file.path); - function openClientFile(files: File[]) { const host = createServerHost(files); - const projectService = createProjectService(host); - + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); return { host, projectService }; } - function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) { - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 }); - const project = Debug.checkDefined(projectService.configuredProjects.get(tsconfig.path)); - - if (orphanInferredProject) { - const inferredProject = projectService.inferredProjects[0]; - assert.isTrue(inferredProject.isOrphan()); - } - - checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]); - checkWatchedFiles(host, [libFile.path, tsconfig.path]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true); - } - - function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) { - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - assert.isDefined(project); - - const filesToWatch = [libFile.path, ...getConfigFilesToWatch(dirOfFile)]; - - checkProjectActualFiles(project, [file.path, libFile.path]); - checkWatchedFiles(host, filesToWatch); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true); - } - it("tsconfig for the file exists", () => { const { host, projectService } = openClientFile([file, libFile, tsconfig]); - verifyConfiguredProject(host, projectService); host.deleteFile(tsconfig.path); host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); host.writeFile(tsconfig.path, tsconfig.content); host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); + + baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService); }); it("tsconfig for the file does not exist", () => { const { host, projectService } = openClientFile([file, libFile]); - verifyInferredProject(host, projectService); host.writeFile(tsconfig.path, tsconfig.content); host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); host.deleteFile(tsconfig.path); host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); + + baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService); }); }); describe("should not search and watch config files from directories that cannot be watched", () => { - const root = "/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace"; - function verifyConfigFileWatch(projectRootPath: string | undefined) { - const path = `${root}/x.js`; - const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host); - service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [path, libFile.path]); - checkWatchedFilesDetailed(host, [libFile.path, ...getConfigFilesToWatch(root)], 1); + function verifyConfigFileWatch(scenario: string, projectRootPath: string | undefined) { + it(scenario, () => { + const path = `/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace/x.js`; + const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); + const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); + service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); + baselineTsserverLogs("configFileSearch", scenario, service); + }); } - - it("when projectRootPath is not present", () => { - verifyConfigFileWatch(/*projectRootPath*/ undefined); - }); - it("when projectRootPath is present but file is not from project root", () => { - verifyConfigFileWatch("/a/b"); - }); + verifyConfigFileWatch("when projectRootPath is not present", /*projectRootPath*/ undefined); + verifyConfigFileWatch("when projectRootPath is present but file is not from project root", "/a/b"); }); }); } diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index cd5bd4d81f306..d8246838c0756 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -25,21 +25,13 @@ namespace ts.projectSystem { }; const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); assert(configFileName, "should find config file"); assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); - checkProjectRootFiles(project, [file1.path, file2.path]); - // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - const configFileDirectory = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); + baselineTsserverLogs("configuredProjects", "create configured project without file list", projectService); }); it("create configured project with the file list", () => { @@ -65,20 +57,13 @@ namespace ts.projectSystem { }; const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); assert(configFileName, "should find config file"); assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); - checkProjectRootFiles(project, [file1.path, file2.path]); - // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false); + baselineTsserverLogs("configuredProjects", "create configured project with the file list", projectService); }); it("add and then remove a config file in a folder with loose files", () => { @@ -99,41 +84,19 @@ namespace ts.projectSystem { const host = createServerHost([libFile, commonFile1, commonFile2]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(commonFile1.path); projectService.openClientFile(commonFile2.path); - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - - const watchedFiles = getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path); - checkWatchedFiles(host, watchedFiles); - // Add a tsconfig file host.writeFile(configFile.path, configFile.content); host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 }); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - checkProjectActualFiles(projectService.configuredProjects.get(configFile.path)!, [libFile.path, commonFile1.path, configFile.path]); - - checkWatchedFiles(host, watchedFiles); - // remove the tsconfig file host.deleteFile(configFile.path); - - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - checkWatchedFiles(host, watchedFiles); + baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", projectService); }); it("add new files to a configured project without file list", () => { @@ -142,20 +105,13 @@ namespace ts.projectSystem { content: `{}` }; const host = createServerHost([commonFile1, libFile, configFile]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(commonFile1.path); - const configFileDir = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); // add a new ts file host.writeFile(commonFile2.path, commonFile2.content); host.checkTimeoutQueueLengthAndRun(2); - // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", projectService); }); it("should ignore non-existing files specified in the config file", () => { @@ -581,56 +537,37 @@ namespace ts.projectSystem { const files = [file1, file2, file3, file4]; const host = createServerHost(files.concat(configFile)); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(file1.path); projectService.openClientFile(file2.path); projectService.openClientFile(file3.path); projectService.openClientFile(file4.path); - const infos = files.map(file => projectService.getScriptInfoForPath(file.path as Path)!); - checkOpenFiles(projectService, files); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); const configProject1 = projectService.configuredProjects.get(configFile.path)!; assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 - checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]); - const inferredProject1 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject1, [file2.path]); - const inferredProject2 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject2, [file4.path]); host.writeFile(configFile.path, "{}"); host.runQueuedTimeoutCallbacks(); - verifyScriptInfos(); - checkOpenFiles(projectService, files); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file1, file2, file3 + assert.isTrue(configProject1.hasOpenRef()); // file1, file2, file3 assert.isTrue(projectService.inferredProjects[0].isOrphan()); - const inferredProject3 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject3, [file4.path]); - assert.strictEqual(inferredProject3, inferredProject2); projectService.closeClientFile(file1.path); projectService.closeClientFile(file2.path); projectService.closeClientFile(file4.path); - verifyScriptInfos(); - checkOpenFiles(projectService, [file3]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file3 + assert.isTrue(configProject1.hasOpenRef()); // file3 assert.isTrue(projectService.inferredProjects[0].isOrphan()); assert.isTrue(projectService.inferredProjects[1].isOrphan()); projectService.openClientFile(file4.path); - verifyScriptInfos(); - checkOpenFiles(projectService, [file3, file4]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 1); // file3 + assert.isTrue(configProject1.hasOpenRef()); // file3 const inferredProject4 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject4, [file4.path]); projectService.closeClientFile(file3.path); - verifyScriptInfos(); - checkOpenFiles(projectService, [file4]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ false, 1); // No open files + assert.isFalse(configProject1.hasOpenRef()); // No open files const inferredProject5 = projectService.inferredProjects[0]; checkProjectActualFiles(inferredProject4, [file4.path]); assert.strictEqual(inferredProject5, inferredProject4); @@ -641,31 +578,8 @@ namespace ts.projectSystem { }; host.writeFile(file5.path, file5.content); projectService.openClientFile(file5.path); - verifyScriptInfosAreUndefined([file1, file2, file3]); - assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); - assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path)); - checkOpenFiles(projectService, [file4, file5]); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file5.path]); - function verifyScriptInfos() { - infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info)); - } - - function verifyScriptInfosAreUndefined(files: File[]) { - for (const file of files) { - assert.isUndefined(projectService.getScriptInfoForPath(file.path as Path)); - } - } - - function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean, inferredProjects: number) { - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects }); - const configProject2 = projectService.configuredProjects.get(configFile.path)!; - assert.strictEqual(configProject2, configProject1); - checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]); - assert.equal(configProject2.hasOpenRef(), hasOpenRef); - } + baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", projectService); }); it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { @@ -1103,35 +1017,22 @@ foo();` }; const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; } it("should watch the extended configs of multiple projects", () => { - const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); + const { host, projectService, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); projectService.openClientFile(aFile.path); projectService.openClientFile(bFile.path); - checkNumberOfConfiguredProjects(projectService, 2); - const aProject = projectService.configuredProjects.get(aConfig.path)!; - const bProject = projectService.configuredProjects.get(bConfig.path)!; - checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); - checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - assert.isUndefined(aProject.getCompilerOptions().strict); - assert.isUndefined(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); host.writeFile(alphaExtendedConfig.path, JSON.stringify({ compilerOptions: { strict: true } })); - assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); host.checkTimeoutQueueLengthAndRun(3); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isTrue(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); host.writeFile(bravoExtendedConfig.path, JSON.stringify({ extends: "./alpha.tsconfig.json", @@ -1139,30 +1040,16 @@ foo();` strict: false } })); - assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); host.checkTimeoutQueueLengthAndRun(2); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isFalse(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); host.writeFile(bConfig.path, JSON.stringify({ extends: "../extended/alpha.tsconfig.json", })); - assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); host.checkTimeoutQueueLengthAndRun(2); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isTrue(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); host.writeFile(alphaExtendedConfig.path, "{}"); - assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); host.checkTimeoutQueueLengthAndRun(3); - assert.isUndefined(aProject.getCompilerOptions().strict); - assert.isUndefined(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); + baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", projectService); }); it("should stop watching the extended configs of closed projects", () => { @@ -1174,27 +1061,21 @@ foo();` path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, content: "{}" }; - const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService([dummy, dummyConfig]); + const { projectService, aFile, bFile } = getService([dummy, dummyConfig]); projectService.openClientFile(aFile.path); projectService.openClientFile(bFile.path); projectService.openClientFile(dummy.path); - checkNumberOfConfiguredProjects(projectService, 3); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path, dummyConfig.path]); projectService.closeClientFile(bFile.path); projectService.closeClientFile(dummy.path); projectService.openClientFile(dummy.path); - checkNumberOfConfiguredProjects(projectService, 2); - checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, dummyConfig.path]); projectService.closeClientFile(aFile.path); projectService.closeClientFile(dummy.path); projectService.openClientFile(dummy.path); - - checkNumberOfConfiguredProjects(projectService, 1); - checkWatchedFiles(host, [libFile.path, dummyConfig.path]); + baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", projectService); }); }); }); @@ -1297,35 +1178,16 @@ foo();` }; const files = [file1, file2a, configFile, libFile]; const host = createServerHost(files); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - checkProjectActualFiles(project, map(files, file => file.path)); - checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); - checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); - files.push(file2); host.writeFile(file2.path, file2.content); host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions host.runQueuedTimeoutCallbacks(); // Actual update - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); - checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); // On next file open the files file2a should be closed and not watched any more projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", projectService); }); it("Failed lookup locations uses parent most node_modules directory", () => { @@ -1355,17 +1217,9 @@ foo();` nonLibFiles.forEach(f => f.path = root + f.path); const files = nonLibFiles.concat(libFile); const host = createServerHost(files); - const projectService = createProjectService(host); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - checkProjectActualFiles(project, [file1.path, libFile.path, module1.path, module2.path, configFile.path]); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src"); - watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", projectService); }); }); diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 150f7c1c724e3..53a233c8d2ac7 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -198,7 +198,7 @@ namespace ts.projectSystem { describe("when using event handler", () => { verifyProjectLoadingStartAndFinish(host => { - const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); + const { session, events } = createSessionWithEventTracking(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); return { session, getNumberOfEvents: () => events.length, diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 34e8f7d8e0ad8..248fd25e95ca3 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -28,7 +28,7 @@ namespace ts.projectSystem { verifyInitialOpen(file: File): void; } - function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { + function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: TestServerHost, logger?: Logger) => ProjectsUpdatedInBackgroundEventVerifier) { it("when adding new file", () => { const commonFile1: File = { path: "/a/b/file1.ts", @@ -415,96 +415,70 @@ namespace ts.projectSystem { }); describe("resolution when resolution cache size", () => { - function verifyWithMaxCacheLimit(useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { - const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; - const file1: File = { - path: rootFolder + "a/b/project/file1.ts", - content: 'import a from "file2"' - }; - const file2: File = { - path: rootFolder + "a/b/node_modules/file2.d.ts", - content: "export class a { }" - }; - const file3: File = { - path: rootFolder + "a/b/project/file3.ts", - content: "export class c { }" - }; - const configFile: File = { - path: rootFolder + "a/b/project/tsconfig.json", - content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) - }; - - const projectFiles = [file1, file3, libFile, configFile]; - const openFiles = [file1.path]; - const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ? - // Folders of node_modules lookup not in changedRoot - ["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) : - // Folder of tsconfig - ["/a/b/project", "/a/b/project/node_modules"]; - const host = createServerHost(projectFiles); - const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - const projectService = session.getProjectService(); - verifyInitialOpen(file1); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - verifyProject(); - - file3.content += "export class d {}"; - host.writeFile(file3.path, file3.content); - host.checkTimeoutQueueLengthAndRun(2); - - // Since this is first event - verifyProject(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); + function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { + it(subScenario, () => { + const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; + const file1: File = { + path: rootFolder + "a/b/project/file1.ts", + content: 'import a from "file2"' + }; + const file2: File = { + path: rootFolder + "a/b/node_modules/file2.d.ts", + content: "export class a { }" + }; + const file3: File = { + path: rootFolder + "a/b/project/file3.ts", + content: "export class c { }" + }; + const configFile: File = { + path: rootFolder + "a/b/project/tsconfig.json", + content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) + }; - projectFiles.push(file2); - host.writeFile(file2.path, file2.content); - host.runQueuedTimeoutCallbacks(); // For invalidation - host.runQueuedTimeoutCallbacks(); // For actual update - if (useSlashRootAsSomeNotRootFolderInUserDirectory) { - watchedRecursiveDirectories.length = 3; - } - else { - // file2 addition wont be detected - projectFiles.pop(); - assert.isTrue(host.fileExists(file2.path)); - } - verifyProject(); + const openFiles = [file1.path]; + const host = createServerHost([file1, file3, libFile, configFile]); + const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, createLoggerWithInMemoryLogs()); + verifyInitialOpen(file1); - verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }] : []); + file3.content += "export class d {}"; + host.writeFile(file3.path, file3.content); + host.checkTimeoutQueueLengthAndRun(2); - function verifyProject() { - checkProjectActualFiles(project, map(projectFiles, file => file.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); - } - } + // Since this is first event + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); - it("project is not at root level", () => { - verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); - }); + host.writeFile(file2.path, file2.content); + host.runQueuedTimeoutCallbacks(); // For invalidation + host.runQueuedTimeoutCallbacks(); // For actual update - it("project is at root level", () => { - verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); - }); + verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }] : []); + baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session); + }); + } + verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); + verifyWithMaxCacheLimit("project is at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); }); } describe("when event handler is set in the session", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); - - function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { - const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); + verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler); + + function createSessionWithProjectChangedEventHandler(host: TestServerHost, logger: Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier { + const { session, events: projectChangedEvents } = createSessionWithEventTracking( + host, + server.ProjectsUpdatedInBackgroundEvent, + logger && { logger } + ); return { session, verifyProjectsUpdatedInBackgroundEventHandler, @@ -535,16 +509,20 @@ namespace ts.projectSystem { describe("when event handler is not set but session is created with canUseEvents = true", () => { describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); + verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents); }); describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { - verifyProjectsUpdatedInBackgroundEvent(host => createSessionThatUsesEvents(host, /*noGetErrOnBackgroundUpdate*/ true)); + verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true)); }); - function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); + function createSessionThatUsesEvents(host: TestServerHost, logger: Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler( + host, + server.ProjectsUpdatedInBackgroundEvent, + { noGetErrOnBackgroundUpdate, logger: logger || createHasErrorMessageLogger() } + ); return { session, diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index eb7c74653436a..bd99f8c79c4a6 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -10,10 +10,6 @@ namespace ts.projectSystem { export import createServerHost = TestFSWithWatch.createServerHost; export import checkArray = TestFSWithWatch.checkArray; export import libFile = TestFSWithWatch.libFile; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; export import commonFile1 = tscWatch.commonFile1; export import commonFile2 = tscWatch.commonFile2; @@ -103,11 +99,22 @@ namespace ts.projectSystem { .replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`) .replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`) .replace(`"version":"${version}"`, `"version":"FakeVersion"`) + .replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`) + .replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`) + .replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`) + .replace(/getCompletionsAtPosition: isCompletionListBlocker: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: isCompletionListBlocker: *`) + .replace(/getCompletionData: Semantic work: \d+(?:\.\d+)?/g, `getCompletionData: Semantic work: *`) + .replace(/getCompletionsAtPosition: getCompletionEntriesFromSymbols: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: getCompletionEntriesFromSymbols: *`) + .replace(/forEachExternalModuleToImportFrom autoImportProvider: \d+(?:\.\d+)?/g, `forEachExternalModuleToImportFrom autoImportProvider: *`) + .replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`) + .replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`) + .replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`) + .replace(/\"exportMapKey\"\:\"[_$a-zA-Z][_$_$a-zA-Z0-9]*\|\d+\|/g, match => match.replace(/\|\d+\|/, `|*|`)) ) }; } - export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: TestSession | TestProjectService) { + export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: Logger; }) { Debug.assert(sessionOrService.logger.logs.length); // Ensure caller used in memory logger Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); } @@ -138,7 +145,7 @@ namespace ts.projectSystem { installTypingHost: server.ServerHost, readonly typesRegistry = new Map>(), log?: TI.Log) { - super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log); + super(installTypingHost, globalTypingsCacheLocation, "/safeList.json" as Path, customTypesMap.path, throttleLimit, log); } protected postExecActions: PostExecAction[] = []; @@ -379,14 +386,15 @@ namespace ts.projectSystem { return new TestSession({ ...sessionOptions, ...opts }); } - export function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { + export function createSessionWithEventTracking(host: server.ServerHost, eventNames: T["eventName"] | T["eventName"][], opts: Partial = {}) { const events: T[] = []; const session = createSession(host, { eventHandler: e => { - if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { + if (isArray(eventNames) ? eventNames.some(eventName => e.eventName === eventName) : eventNames === e.eventName) { events.push(e as T); } - } + }, + ...opts }); return { session, events }; @@ -475,13 +483,6 @@ namespace ts.projectSystem { return iterResult.value; } - export function checkOrphanScriptInfos(service: server.ProjectService, expectedFiles: readonly string[]) { - checkArray("Orphan ScriptInfos:", arrayFrom(mapDefinedIterator( - service.filenameToScriptInfo.values(), - v => v.containingProjects.length === 0 ? v.fileName : undefined - )), expectedFiles); - } - export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) { checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles); } @@ -522,14 +523,6 @@ namespace ts.projectSystem { ]; } - export function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { - checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); - } - - export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) { - checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); - } - export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location { const start = nthIndexOf(str, substring, options ? options.index : 0); Debug.assert(start !== -1); diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index be7238323fe58..d4390e111add0 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -14,19 +14,9 @@ namespace ts.projectSystem { content: `export let x: number` }; const host = createServerHost([appFile, moduleFile, libFile]); - const projectService = createProjectService(host); - const { configFileName } = projectService.openClientFile(appFile.path); - - assert(!configFileName, `should not find config, got: '${configFileName}`); - checkNumberOfConfiguredProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 1); - - const project = projectService.inferredProjects[0]; - - checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - checkWatchedFiles(host, getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path, moduleFile.path)); - checkWatchedDirectories(host, [tscWatch.projectRoot], /*recursive*/ false); - checkWatchedDirectories(host, [combinePaths(tscWatch.projectRoot, nodeModulesAtTypes)], /*recursive*/ true); + const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); + projectService.openClientFile(appFile.path); + baselineTsserverLogs("inferredProjects", "create inferred project", projectService); }); it("should use only one inferred project if 'useOneInferredProject' is set", () => { diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index e86cd6a694e1b..a89d9d56fdd43 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -59,13 +59,13 @@ namespace ts.projectSystem { }); it("invalidates module specifiers when changes happen in contained node_modules directories", () => { - const { host, moduleSpecifierCache, triggerCompletions } = setup(); + const { host, session, moduleSpecifierCache, triggerCompletions } = setup(createLoggerWithInMemoryLogs()); // Completion at an import statement will calculate and cache module specifiers triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); - checkWatchedDirectories(host, ["/src", "/node_modules"], /*recursive*/ true); host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); host.runQueuedTimeoutCallbacks(); assert.equal(moduleSpecifierCache.count(), 0); + baselineTsserverLogs("moduleSpecifierCache", "invalidates module specifiers when changes happen in contained node_modules directories", session); }); it("does not invalidate the cache when new files are added", () => { @@ -123,9 +123,9 @@ namespace ts.projectSystem { }); }); - function setup() { + function setup(logger?: Logger) { const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts]); - const session = createSession(host); + const session = createSession(host, logger && { logger }); openFilesForSession([aTs, bTs, cTs], session); const projectService = session.getProjectService(); const project = configuredProjectAt(projectService, 0); diff --git a/src/testRunner/unittests/tsserver/partialSemanticServer.ts b/src/testRunner/unittests/tsserver/partialSemanticServer.ts index 2aee331883dcf..621c4db5c5374 100644 --- a/src/testRunner/unittests/tsserver/partialSemanticServer.ts +++ b/src/testRunner/unittests/tsserver/partialSemanticServer.ts @@ -26,17 +26,19 @@ import { something } from "something"; content: "{}" }; const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); + const session = createSession(host, { + serverMode: LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: createLoggerWithInMemoryLogs(), + }); return { host, session, file1, file2, file3, something, configFile }; } it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { - const { host, session, file1, file2 } = setup(); + const { session, file1, file2 } = setup(); const service = session.getProjectService(); openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // no imports are resolved verifyCompletions(); openFilesForSession([file2], session); @@ -44,39 +46,13 @@ import { something } from "something"; checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); verifyCompletions(); + baselineTsserverLogs("partialSemanticServer", "files are added to inferred project", session); + function verifyCompletions() { - assert.isTrue(project.languageServiceEnabled); - checkWatchedFiles(host, emptyArray); - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - const response = session.executeCommandSeq({ + session.executeCommandSeq({ command: protocol.CommandTypes.Completions, arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) - }).response as protocol.CompletionEntry[]; - assert.deepEqual(response, [ - completionEntry("foo", ScriptElementKind.memberFunctionElement), - completionEntry("prop", ScriptElementKind.memberVariableElement), - ]); - } - - function completionEntry(name: string, kind: ScriptElementKind): protocol.CompletionEntry { - return { - name, - kind, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority, - hasAction: undefined, - insertText: undefined, - isPackageJsonImport: undefined, - isImportStatementCompletion: undefined, - isRecommended: undefined, - replacementSpan: undefined, - source: undefined, - data: undefined, - sourceDisplay: undefined, - isSnippet: undefined, - labelDetails: undefined, - }; + }); } }); @@ -84,7 +60,6 @@ import { something } from "something"; const { session, file1 } = setup(); const service = session.getProjectService(); openFilesForSession([file1], session); - let hasException = false; const request: protocol.SemanticDiagnosticsSyncRequest = { type: "request", seq: 1, @@ -95,21 +70,17 @@ import { something } from "something"; session.executeCommand(request); } catch (e) { - assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed in LanguageServiceMode.PartialSemantic`); - hasException = true; + session.logger.info(e.message); } - assert.isTrue(hasException); - hasException = false; const project = service.inferredProjects[0]; try { project.getLanguageService().getSemanticDiagnostics(file1.path); } catch (e) { - assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed in LanguageServiceMode.PartialSemantic`); - hasException = true; + session.logger.info(e.message); } - assert.isTrue(hasException); + baselineTsserverLogs("partialSemanticServer", "throws unsupported commands", session); }); it("allows syntactic diagnostic commands", () => { @@ -159,11 +130,8 @@ import { something } from "something"; content: "export const something = 10;" }; host.ensureFileOrFolder(atTypes); - const service = session.getProjectService(); openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes + baselineTsserverLogs("partialSemanticServer", "should not include auto type reference directives", session); }); it("should not include referenced files from unopened files", () => { @@ -192,30 +160,26 @@ function fooB() { }` content: "{}" }; const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); - const service = session.getProjectService(); + const session = createSession(host, { + serverMode: LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: createLoggerWithInMemoryLogs(), + }); openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // no resolve + baselineTsserverLogs("partialSemanticServer", "should not include referenced files from unopened files", session); }); it("should not crash when external module name resolution is reused", () => { const { session, file1, file2, file3 } = setup(); - const service = session.getProjectService(); openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import closeFilesForSession([file1], session); openFilesForSession([file3], session); - checkProjectActualFiles(project, [libFile.path, file3.path]); // Open file with non relative external module name openFilesForSession([file2], session); - checkProjectActualFiles(project, [libFile.path, file2.path, file3.path]); + baselineTsserverLogs("partialSemanticServer", "should not crash when external module name resolution is reused", session); }); it("should not create autoImportProvider or handle package jsons", () => { @@ -250,18 +214,13 @@ function fooB() { }` }); it("should support go-to-definition on module specifiers", () => { - const { session, file1, file2 } = setup(); + const { session, file1 } = setup(); openFilesForSession([file1], session); - const response = session.executeCommandSeq({ + session.executeCommandSeq({ command: protocol.CommandTypes.DefinitionAndBoundSpan, arguments: protocolFileLocationFromSubstring(file1, `"./b"`) - }).response as protocol.DefinitionInfoAndBoundSpan; - assert.isDefined(response); - assert.deepEqual(response.definitions, [{ - file: file2.path, - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - }]); + }); + baselineTsserverLogs("partialSemanticServer", "should support go-to-definition on module specifiers", session); }); }); } diff --git a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts index e49522f7dc96b..b0f2c2a15b85a 100644 --- a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts +++ b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts @@ -12,7 +12,6 @@ export function fn4() { } export function fn5() { } ` }; - const dependencyTsPath = dependencyTs.path.toLowerCase(); const dependencyConfig: File = { path: `${dependecyLocation}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) @@ -76,210 +75,22 @@ fn5(); ); } - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo?: string) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); - } - - function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); - } - - function declarationSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn, offset: 17 }, - end: { line: fn, offset: 20 }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 26 } - }; - } - function importSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn + 1, offset: 5 }, - end: { line: fn + 1, offset: 8 }, - contextStart: { line: 1, offset: 1 }, - contextEnd: { line: 7, offset: 22 } - }; - } - function usageSpan(fn: number): protocol.TextSpan { - return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; - } - - function goToDefFromMainTs(fn: number): Action { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - } - }; - } - - function goToDefFromMainTsWithNoMap(fn: number): Action { - const textSpan = usageSpan(fn); - const definition = declarationSpan(fn); - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To the dts - definitions: [{ - file: dtsPath, - start: { line: fn, offset: definition.start.offset + declareSpaceLength }, - end: { line: fn, offset: definition.end.offset + declareSpaceLength }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 37 } - }], - textSpan - } - }; - } - - function goToDefFromMainTsWithNoDts(fn: number): Action { - const textSpan = usageSpan(fn); - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - } - }; - } - - function goToDefFromMainTsWithDependencyChange(fn: number): Action { - const textSpan = usageSpan(fn); + function goToDefFromMainTs(fn: number): Partial { return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // Definition on fn + 1 line - definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], - textSpan - } + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, line: fn + 8, offset: 1 } }; } - function renameFromDependencyTs(fn: number): Action { - const defSpan = declarationSpan(fn); - const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; + function renameFromDependencyTs(fn: number): Partial { return { - reqName: "rename", - request: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpan.start } - }, - expectedResponse: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan - }, - locs: [ - { file: dependencyTs.path, locs: [defSpan] } - ] - } + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, line: fn, offset: 17 } }; } - function renameFromDependencyTsWithDependencyChange(fn: number): Action { - const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); - - return { - ...rest, - expectedResponse: { - info: { - ...info as protocol.RenameInfoSuccess, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - }, - locs - } - }; - } - - function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { - const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] - } - ] - } - }; - } - - function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { - const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] - } - ] - } - }; - } - - function removePath(array: readonly string[], ...delPaths: string[]) { - return array.filter(a => { - const aLower = a.toLowerCase(); - return delPaths.every(dPath => dPath !== aLower); - }); - } - - interface Action { - reqName: string; - request: Partial; - expectedResponse: Response; - } - - function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { - const { response } = session.executeCommandSeq(request); - assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); + function renameFromDependencyTsWithDependencyChange(fn: number) { + return renameFromDependencyTs(fn + 1); } function verifyDocumentPositionMapper( @@ -313,12 +124,9 @@ fn5(); } } - function verifyAllFnAction( + function verifyAllFnAction( session: TestSession, - host: TestServerHost, - action: (fn: number) => Action, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], + action: (fn: number) => Partial, existingDependencyMap: server.ScriptInfo | undefined, existingDocumentPositionMapper: server.ScriptInfo["documentPositionMapper"], existingMapEqual: boolean, @@ -330,10 +138,8 @@ fn5(); let documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; for (let fn = 1; fn <= 5; fn++) { const fnAction = action(fn); - verifyAction(session, fnAction); - const debugInfo = `${fnAction.reqName}:: ${fn}`; - checkScriptInfos(session.getProjectService(), expectedInfos, debugInfo); - checkWatchedFiles(host, expectedWatchedFiles, debugInfo); + session.executeCommandSeq(fnAction); + const debugInfo = `${JSON.stringify(fnAction)}:: ${fn}`; const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); @@ -365,10 +171,7 @@ fn5(); function verifyScriptInfoCollectionWith( session: TestSession, - host: TestServerHost, openFiles: readonly File[], - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], ) { const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); @@ -376,8 +179,6 @@ fn5(); closeFilesForSession([randomFile], session); openFilesForSession([randomFile], session); - checkScriptInfos(session.getProjectService(), expectedInfos); - checkWatchedFiles(host, expectedWatchedFiles); // If map is not collected, document position mapper shouldnt change if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); @@ -386,7 +187,6 @@ fn5(); // Closing open file, removes dependencies too closeFilesForSession([...openFiles, randomFile], session); openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); } type OnHostCreate = (host: TestServerHost) => void; @@ -416,14 +216,14 @@ fn5(); compilerOptions: { composite: true, declarationMap: true } })); onHostCreate?.(host); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); return { host, session }; } function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { const host = createHostWithSolutionBuild(files, [mainConfig.path]); onHostCreate?.(host); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); return { host, session }; } @@ -439,7 +239,7 @@ fn5(); references: [{ path: "../dependency" }] })); onHostCreate?.(host); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); return { host, session }; } @@ -449,30 +249,6 @@ fn5(); return { dependencyMap, documentPositionMapper }; } - function checkMainProjectWithoutProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); - } - - function checkMainProjectWithoutProjectReferencesWithoutDts(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); - } - - function checkMainProjectWithProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path]); - } - - function checkMainProjectWithDisabledProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); - } - - function checkMainProjectWithDisabledProjectReferencesWithoutDts(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); - } - - function checkDependencyProjectWith(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); - } - function makeChangeToMainTs(session: TestSession) { session.executeCommandSeq({ command: protocol.CommandTypes.Change, @@ -501,22 +277,14 @@ fn5(); }); } + describe("from project that uses dependency: goToDef", () => { function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1).request); + result.session.executeCommandSeq(goToDefFromMainTs(1)); return { ...result, ...getDocumentPositionMapper(result.session) }; } - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [mainTs], expectedInfos, expectedWatchedFiles); - } - describe("when main tsconfig doesnt have project reference", () => { function setup(onHostCreate?: OnHostCreate) { return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); @@ -526,64 +294,18 @@ fn5(); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithoutProjectReferences(session); - } - - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithoutProjectReferencesWithoutDts(session); - } - - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } - - function expectedWatchedFilesWhenMapped() { - return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path]; - } - - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); - } - - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); - } - - function expectedScriptInfosWhenNoDts() { - // No dts, no map, no dependency - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } - - function expectedWatchedFilesWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/can go to definition correctly", session); }); // Edit @@ -594,25 +316,22 @@ fn5(); // change makeChangeToMainTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -620,15 +339,13 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes", session); }); // Edit dts to add new fn @@ -639,21 +356,18 @@ fn5(); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -665,15 +379,13 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -684,21 +396,18 @@ fn5(); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -710,39 +419,28 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -754,22 +452,14 @@ fn5(); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -778,49 +468,29 @@ fn5(); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoMap().concat(dependencyTs.path), - expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -832,22 +502,14 @@ fn5(); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -856,25 +518,14 @@ fn5(); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts deleted", session); }); }); describe("when main tsconfig has project reference", () => { @@ -886,40 +537,18 @@ fn5(); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithProjectReferences(session); - } - - function expectedScriptInfos() { - return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; - } - - function expectedWatchedFiles() { - return [dependencyTsPath, dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; - } - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/can go to definition correctly", session); }); // Edit @@ -930,25 +559,22 @@ fn5(); // change makeChangeToMainTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -956,15 +582,13 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes", session); }); // Edit dts to add new fn @@ -975,21 +599,18 @@ fn5(); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1001,15 +622,13 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -1020,21 +639,18 @@ fn5(); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1046,39 +662,28 @@ fn5(); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1090,22 +695,14 @@ fn5(); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1114,48 +711,29 @@ fn5(); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1167,22 +745,14 @@ fn5(); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1191,26 +761,15 @@ fn5(); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { // Create DocumentPositionMapper const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1220,21 +779,18 @@ fn5(); host.writeFile(dependencyTs.path, `function fooBar() { } ${dependencyTs.content}`); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes with timeout before request", session); }); it(`when defining project source changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1248,39 +804,29 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes", session); }); it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); openFilesForSession([mainTs, randomFile], session); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/when projects are not built", session); }); }); describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { @@ -1292,64 +838,18 @@ ${dependencyTs.content}`); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithDisabledProjectReferences(session); - } - - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithDisabledProjectReferencesWithoutDts(session); - } - - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } - - function expectedWatchedFilesWhenMapped() { - return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } - - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); - } - - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); - } - - function expectedScriptInfosWhenNoDts() { - // No dts, no map, no dependency - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } - - function expectedWatchedFilesWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/can go to definition correctly", session); }); // Edit @@ -1360,25 +860,22 @@ ${dependencyTs.content}`); // change makeChangeToMainTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -1386,15 +883,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes", session); }); // Edit dts to add new fn @@ -1405,21 +900,18 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1431,15 +923,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes", session); }); // Edit map file to represent added new line @@ -1450,21 +940,18 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1476,39 +963,28 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1520,22 +996,14 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1544,49 +1012,29 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoMap().concat(dependencyTs.path), - expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1598,22 +1046,14 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1622,25 +1062,14 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs]); + baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts deleted", session); }); }); }); @@ -1648,52 +1077,10 @@ ${dependencyTs.content}`); describe("from defining project: rename", () => { function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { const result = setup(onHostCreate); - result.session.executeCommandSeq(renameFromDependencyTs(1).request); + result.session.executeCommandSeq(renameFromDependencyTs(1)); return { ...result, ...getDocumentPositionMapper(result.session) }; } - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [dependencyTs], expectedInfos, expectedWatchedFiles); - } - - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkDependencyProjectWith(session); - } - - function expectedScriptInfos() { - return [libFile.path, dtsLocation, dtsMapLocation, dependencyTs.path, randomFile.path]; - } - - function expectedWatchedFiles() { - return [libFile.path, dtsPath, dtsMapPath, dependencyConfig.path, randomConfig.path]; - } - - function expectedScriptInfosWhenNoMap() { - // No map - return removePath(expectedScriptInfos(), dtsMapPath); - } - - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file = map file - return expectedWatchedFiles(); - } - - function expectedScriptInfosWhenNoDts() { - // no dts or map since dts itself doesnt exist - return removePath(expectedScriptInfos(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesWhenNoDts() { - // watch deleted file - return removePath(expectedWatchedFiles(), dtsMapPath); - } - describe("when main tsconfig doesnt have project reference", () => { function setup(onHostCreate?: OnHostCreate) { return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); @@ -1704,26 +1091,17 @@ ${dependencyTs.content}`); } it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/rename locations", session); }); // Edit @@ -1734,25 +1112,22 @@ ${dependencyTs.content}`); // change makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToDependencyTs(session); @@ -1760,15 +1135,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes", session); }); // Edit dts to add new fn @@ -1779,21 +1152,18 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1805,15 +1175,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -1824,21 +1192,18 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -1850,39 +1215,28 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1894,22 +1248,14 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1918,48 +1264,29 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -1971,22 +1298,14 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -1995,25 +1314,14 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts deleted", session); }); }); describe("when main tsconfig has project reference", () => { @@ -2026,26 +1334,17 @@ ${dependencyTs.content}`); } it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/rename locations", session); }); // Edit @@ -2056,25 +1355,22 @@ ${dependencyTs.content}`); // change makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToDependencyTs(session); @@ -2082,15 +1378,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes", session); }); // Edit dts to add new fn @@ -2101,21 +1395,18 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -2127,15 +1418,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -2146,21 +1435,18 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -2172,39 +1458,28 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -2216,22 +1491,14 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -2240,48 +1507,29 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -2293,22 +1541,14 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -2317,27 +1557,15 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { // Create DocumentPositionMapper const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -2351,25 +1579,22 @@ ${dependencyTs.content}`); `} }); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes with timeout before request", session); }); it(`when defining project source changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session } = setupWithAction(); + const { session } = setupWithAction(); // change // Make change, without rebuild of solution @@ -2383,39 +1608,29 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes", session); }); it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); openFilesForSession([dependencyTs, randomFile], session); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/when projects are not built", session); }); }); describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { @@ -2428,26 +1643,17 @@ ${dependencyTs.content}`); } it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/rename locations", session); }); // Edit @@ -2458,25 +1664,22 @@ ${dependencyTs.content}`); // change makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToDependencyTs(session); @@ -2484,15 +1687,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes", session); }); // Edit dts to add new fn @@ -2503,21 +1704,18 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -2529,15 +1727,13 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes", session); }); // Edit map file to represent added new line @@ -2548,21 +1744,18 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -2574,39 +1767,28 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -2618,22 +1800,14 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -2642,48 +1816,29 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -2695,22 +1850,14 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -2719,25 +1866,14 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts deleted", session); }); }); }); @@ -2745,20 +1881,11 @@ ${dependencyTs.content}`); describe("when opening depedency and usage project: goToDef and rename", () => { function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1).request); - result.session.executeCommandSeq(renameFromDependencyTsWithBothProjectsOpen(1).request); + result.session.executeCommandSeq(goToDefFromMainTs(1)); + result.session.executeCommandSeq(renameFromDependencyTs(1)); return { ...result, ...getDocumentPositionMapper(result.session) }; } - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [mainTs, dependencyTs], expectedInfos, expectedWatchedFiles); - } - describe("when main tsconfig doesnt have project reference", () => { function setup(onHostCreate?: OnHostCreate) { return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); @@ -2768,64 +1895,11 @@ ${dependencyTs.content}`); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithoutProjectReferences(session); - checkDependencyProjectWith(session); - } - - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithoutProjectReferencesWithoutDts(session); - checkDependencyProjectWith(session); - } - - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } - - function expectedWatchedFilesWhenMapped() { - return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } - - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); - } - - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return expectedWatchedFilesWhenMapped(); - } - - function expectedScriptInfosAfterGotoDefWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesAfterGotoDefWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedScriptInfosAfterRenameWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map file - return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); - } - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, @@ -2834,22 +1908,14 @@ ${dependencyTs.content}`); const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/goToDef and rename locations", session); }); // Edit @@ -2861,16 +1927,12 @@ ${dependencyTs.content}`); makeChangeToMainTs(session); makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -2878,19 +1940,17 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -2899,10 +1959,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -2910,15 +1967,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes", session); }); // Edit dts to add new fn @@ -2929,16 +1984,12 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -2946,15 +1997,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -2966,10 +2015,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -2977,15 +2023,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -2996,16 +2040,12 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3014,15 +2054,13 @@ ${dependencyTs.content}`); const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -3034,10 +2072,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3046,27 +2081,21 @@ ${dependencyTs.content}`); const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3074,22 +2103,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -3101,10 +2122,7 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -3113,22 +2131,14 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -3137,10 +2147,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -3149,36 +2156,21 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); - + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosAfterGotoDefWhenNoDts(), - expectedWatchedFilesAfterGotoDefWhenNoDts(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3186,22 +2178,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -3213,10 +2197,7 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -3225,22 +2206,14 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -3249,11 +2222,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3261,25 +2230,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - // The script info for map is collected only after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts deleted", session); }); }); describe("when main tsconfig has project reference", () => { @@ -3291,57 +2249,11 @@ ${dependencyTs.content}`); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithProjectReferences(session); - checkDependencyProjectWith(session); - } - - function expectedScriptInfosAfterGotoDef() { - return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; - } - - function expectedWatchedFilesAfterGotoDef() { - return [dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; - } - - function expectedScriptInfosAfterRenameWhenMapped() { - return expectedScriptInfosAfterGotoDef().concat(dtsLocation, dtsMapLocation); - } - - function expectedWatchedFilesAfterRenameWhenMapped() { - return expectedWatchedFilesAfterGotoDef().concat(dtsPath, dtsMapPath); - } - - function expectedScriptInfosAfterRenameWhenNoMap() { - // Map file is not present - return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsMapPath); - } - - function expectedWatchedFilesAfterRenameWhenNoMap() { - // Watches map file - return expectedWatchedFilesAfterRenameWhenMapped(); - } - - function expectedScriptInfosAfterRenameWhenNoDts() { - // map and dts not present - return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map - return removePath(expectedWatchedFilesAfterRenameWhenMapped(), dtsMapPath); - } - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3349,22 +2261,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/gotoDef and rename locations", session); }); // Edit @@ -3376,36 +2280,30 @@ ${dependencyTs.content}`); makeChangeToMainTs(session); makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -3414,10 +2312,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3425,15 +2320,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes", session); }); // Edit dts to add new fn @@ -3444,32 +2337,26 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -3481,10 +2368,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3492,15 +2376,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes", session); }); // Edit map file to represent added new line @@ -3511,16 +2393,12 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3528,15 +2406,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -3548,38 +2424,29 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3587,22 +2454,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), + renameFromDependencyTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -3614,11 +2473,7 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenNoMap(), - // Map file is reset so its not watched any more, as this action doesnt need map - removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3627,22 +2482,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -3651,11 +2498,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenNoMap(), - // Map file is reset so its not watched any more, as this action doesnt need map - removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -3664,36 +2507,22 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3701,22 +2530,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), + renameFromDependencyTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -3728,11 +2549,7 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - // Since the project for dependency is not updated, the watcher from rename for dts still there - expectedWatchedFilesAfterGotoDef().concat(dtsPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3740,22 +2557,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, /*existingDocumentPositionMapperEqual*/ false ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -3764,12 +2573,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, goToDefFromMainTs, - // Map collection after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - // not watching dts since this operation doesnt need it - removePath(expectedWatchedFilesAfterRenameWhenNoDts(), dtsPath).concat(dtsMapPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3777,27 +2581,15 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - // Map collection after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { // Create DocumentPositionMapper const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -3811,16 +2603,12 @@ ${dependencyTs.content}`); `} }); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -3828,19 +2616,17 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTsWithDependencyChange, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes with timeout before request", session); }); it(`when defining project source changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change // Make change, without rebuild of solution @@ -3848,44 +2634,36 @@ ${dependencyTs.content}`); command: protocol.CommandTypes.Change, arguments: { file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } -`} + `} }); // action verifyAllFnAction( session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + goToDefFromMainTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), + renameFromDependencyTsWithDependencyChange, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes", session); }); it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); openFilesForSession([mainTs, dependencyTs, randomFile], session); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -3893,22 +2671,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), + renameFromDependencyTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/when projects are not built", session); }); }); describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { @@ -3920,64 +2690,11 @@ ${dependencyTs.content}`); return setupWithActionWith(setup, onHostCreate); } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithDisabledProjectReferences(session); - checkDependencyProjectWith(session); - } - - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithDisabledProjectReferencesWithoutDts(session); - checkDependencyProjectWith(session); - } - - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } - - function expectedWatchedFilesWhenMapped() { - return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } - - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); - } - - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return expectedWatchedFilesWhenMapped(); - } - - function expectedScriptInfosAfterGotoDefWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesAfterGotoDefWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedScriptInfosAfterRenameWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } - - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map file - return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); - } - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); + const { session } = setup(); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ false, @@ -3986,22 +2703,14 @@ ${dependencyTs.content}`); const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/gotoDef and rename locations", session); }); // Edit @@ -4013,36 +2722,30 @@ ${dependencyTs.content}`); makeChangeToMainTs(session); makeChangeToDependencyTs(session); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes with timeout before request", session); }); it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); // change makeChangeToMainTs(session); @@ -4051,26 +2754,21 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes", session); }); // Edit dts to add new fn @@ -4081,16 +2779,12 @@ ${dependencyTs.content}`); // change changeDtsFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -4098,15 +2792,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes with timeout before request", session); }); it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -4118,10 +2810,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -4129,15 +2818,13 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes", session); }); // Edit map file to represent added new line @@ -4148,33 +2835,27 @@ ${dependencyTs.content}`); // change changeDtsMapFile(host); host.runQueuedTimeoutCallbacks(); - checkProjects(session); verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false ); const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes with timeout before request", session); }); it(`when dependency file's map changes, when timeout does not occur before request`, () => { // Create DocumentPositionMapper @@ -4186,10 +2867,7 @@ ${dependencyTs.content}`); // action verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -4198,27 +2876,21 @@ ${dependencyTs.content}`); const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, dependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes", session); }); it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); + const { session } = setup(host => host.deleteFile(dtsMapLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -4226,22 +2898,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap not present", session); }); it(`with depedency files map file, when file is created after actions on projects`, () => { let fileContents: string; @@ -4253,10 +2917,7 @@ ${dependencyTs.content}`); host.writeFile(dtsMapLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -4265,22 +2926,14 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap created", session); }); it(`with depedency files map file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -4289,10 +2942,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsMapLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -4301,36 +2951,22 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap deleted", session); }); it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); + const { session } = setup(host => host.deleteFile(dtsLocation)); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosAfterGotoDefWhenNoDts(), - expectedWatchedFilesAfterGotoDefWhenNoDts(), + goToDefFromMainTs, /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, @@ -4338,22 +2974,14 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), /*existingDependencyMap*/ undefined, /*existingDocumentPositionMapper*/ undefined, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts not present", session); }); it(`with depedency .d.ts file, when file is created after actions on projects`, () => { let fileContents: string; @@ -4365,10 +2993,7 @@ ${dependencyTs.content}`); host.writeFile(dtsLocation, fileContents!); verifyAllFnAction( session, - host, goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), dependencyMap, documentPositionMapper, /*existingMapEqual*/ false, @@ -4377,22 +3002,14 @@ ${dependencyTs.content}`); const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); verifyAllFnAction( session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), + renameFromDependencyTs, newDependencyMap, newDocumentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts created", session); }); it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); @@ -4401,11 +3018,7 @@ ${dependencyTs.content}`); host.deleteFile(dtsLocation); verifyAllFnAction( session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + goToDefFromMainTs, dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, @@ -4413,27 +3026,16 @@ ${dependencyTs.content}`); ); verifyAllFnAction( session, - host, renameFromDependencyTs, - // The script info for map is collected only after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), dependencyMap, documentPositionMapper, /*existingMapEqual*/ true, /*existingDocumentPositionMapperEqual*/ true ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts deleted", session); }); }); }); }); -} +} \ No newline at end of file diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index a9df947bf2b73..7bd5af3fa4f34 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -861,31 +861,30 @@ namespace ts.projectSystem { content: `