diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c93c9639ab2..00e8065a9f9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2076,8 +2076,8 @@ namespace ts { } export interface FileSystemEntries { - files: ReadonlyArray; - directories: ReadonlyArray; + readonly files: ReadonlyArray; + readonly directories: ReadonlyArray; } export interface FileMatcherPatterns { @@ -2644,8 +2644,13 @@ namespace ts { export interface CachedPartialSystem extends PartialSystem, CachedHost { } + interface MutableFileSystemEntries { + readonly files: string[]; + readonly directories: string[]; + } + export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { - const cachedReadDirectoryResult = createMap(); + const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { @@ -2665,11 +2670,11 @@ namespace ts { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } - function getCachedFileSystemEntries(rootDirPath: Path): FileSystemEntries | undefined { + function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { return cachedReadDirectoryResult.get(rootDirPath); } - function getCachedFileSystemEntriesForBaseDir(path: Path): FileSystemEntries | undefined { + function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { return getCachedFileSystemEntries(getDirectoryPath(path)); } @@ -2678,8 +2683,8 @@ namespace ts { } function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { - const resultFromHost: FileSystemEntries = { - files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], + const resultFromHost: MutableFileSystemEntries = { + files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], directories: host.getDirectories(rootDir) || [] }; @@ -2692,7 +2697,7 @@ namespace ts { * Otherwise gets result from host and caches it. * The host request is done under try catch block to avoid caching incorrect result */ - function tryReadDirectory(rootDir: string, rootDirPath: Path): FileSystemEntries | undefined { + function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { const cachedResult = getCachedFileSystemEntries(rootDirPath); if (cachedResult) { return cachedResult; @@ -2716,16 +2721,15 @@ namespace ts { return some(entries, file => fileNameEqual(file, name)); } - function updateFileSystemEntry(entries: ReadonlyArray, baseName: string, isValid: boolean) { + function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { if (hasEntry(entries, baseName)) { if (!isValid) { - return filter(entries, entry => !fileNameEqual(entry, baseName)); + return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); } } else if (isValid) { - return entries.concat(baseName); + return entries.push(baseName); } - return entries; } function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { @@ -2754,7 +2758,7 @@ namespace ts { const result = getCachedFileSystemEntriesForBaseDir(path); const baseFileName = getBaseNameOfFileName(dirPath); if (result) { - result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); } host.createDirectory(dirPath); } @@ -2801,7 +2805,7 @@ namespace ts { const baseName = getBaseNameOfFileName(fileOrFolder); if (parentResult) { updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); - parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); + updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); } } } @@ -2818,8 +2822,8 @@ namespace ts { } } - function updateFilesOfFileSystemEntry(parentResult: FileSystemEntries, baseName: string, fileExists: boolean) { - parentResult.files = updateFileSystemEntry(parentResult.files, baseName, fileExists); + function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { + updateFileSystemEntry(parentResult.files, baseName, fileExists); } function clearCache() { diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 4fefd87a834..e331133fe74 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void; + invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; @@ -300,12 +300,12 @@ namespace ts { } function invalidateResolutionCacheOfChangedFailedLookupLocation( - failedLookupLocation: string, + failedLookupLocationPath: Path, cache: Map>) { cache.forEach((value, containingFile) => { if (value) { value.forEach(resolution => { - if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocation)) { + if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) { // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); @@ -320,9 +320,9 @@ namespace ts { invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } - function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string) { - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedTypeReferenceDirectives); + function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) { + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames); + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives); } } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index bd2473a699d..084c707cea8 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -564,7 +564,7 @@ namespace ts { function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); - resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); scheduleProgramUpdate(); } @@ -633,7 +633,7 @@ namespace ts { // Reload is pending, do the reload if (!needsReload) { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); - if (!configFileSpecs.filesSpecs) { + if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 079eabebc1e..b7b382e5a1d 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -61,7 +61,7 @@ namespace ts { typingsInstaller: undefined }; const projectService = new server.ProjectService(svcOpts); - const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined); + const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, serverHost); const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index e8cba10f088..f5ed08b9b87 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -130,7 +130,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host); - checkProgramActualFiles(watch(), [f1.path]); + checkProgramActualFiles(watch(), [combinePaths(getDirectoryPath(upperCaseConfigFilePath), getBaseFileName(f1.path))]); }); it("create configured project without file list", () => { @@ -722,12 +722,13 @@ namespace ts.tscWatch { const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); createWatchModeWithConfigFile(configFile.path, host); - const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`; + const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n"; checkOutputDoesNotContain(host, [error]); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; + host.clearOutput(); host.reloadFS([moduleFile, file1, configFile, libFile]); host.runQueuedTimeoutCallbacks(); checkOutputContains(host, [error]); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 86fe129a1c7..37cbd20521d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3998,33 +3998,206 @@ namespace ts.projectSystem { }); describe("CachingFileSystemInformation", () => { - function getFunctionWithCalledMapForSingleArgumentCb(cb: (f: string) => T) { - const calledMap = createMultiMap(); + type CalledMaps = { + fileExists: MultiMap; + directoryExists: MultiMap; + getDirectories: MultiMap; + readFile: MultiMap; + readDirectory: MultiMap<[ReadonlyArray, ReadonlyArray, ReadonlyArray, number]>; + }; + + function createCallsTrackingHost(host: TestServerHost) { + const keys: Array = ["fileExists", "directoryExists", "getDirectories", "readFile", "readDirectory"]; + const calledMaps = getCallsTrackingMap(); return { - cb: (f: string) => { - calledMap.add(f, /*value*/ true); - return cb(f); - }, - calledMap + verifyNoCall, + verifyCalledOnEachEntryOnce, + verifyCalledOnEachEntry, + verifyNoHostCalls, + verifyNoHostCallsExceptFileExistsOnce, + clear }; + + function getCallsTrackingMap() { + const calledMaps: { [s: string]: Map } = {}; + for (let i = 0; i < keys.length - 1; i++) { + setCallsTrackingWithSingleArgFn(keys[i]); + } + setCallsTrackingWithFiveArgFn(keys[keys.length - 1]); + return calledMaps as CalledMaps; + + function setCallsTrackingWithSingleArgFn(prop: keyof CalledMaps) { + const calledMap = createMultiMap(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); + }; + calledMaps[prop] = calledMap; + } + + function setCallsTrackingWithFiveArgFn(prop: keyof CalledMaps) { + const calledMap = createMultiMap<[U, V, W, X]>(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1, arg2, arg3, arg4]); + return cb(f, arg1, arg2, arg3, arg4); + }; + calledMaps[prop] = calledMap; + } + } + + function verifyNoCall(callback: keyof CalledMaps) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`); + } + + function verifyCalledOnEachEntry(callback: keyof CalledMaps, expectedKeys: Map) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, expectedKeys.size, `${callback}: incorrect size of map: Actual keys: ${arrayFrom(calledMap.keys())} Expected: ${arrayFrom(expectedKeys.keys())}`); + expectedKeys.forEach((called, name) => { + assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`); + assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`); + }); + } + + function verifyCalledOnEachEntryOnce(callback: keyof CalledMaps, expectedKeys: string[]) { + return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => 1))); + } + + function verifyNoHostCalls() { + for (const key of keys) { + verifyNoCall(key); + } + } + + function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) { + verifyCalledOnEachEntryOnce("fileExists", expectedKeys); + verifyNoCall("directoryExists"); + verifyNoCall("getDirectories"); + verifyNoCall("readFile"); + verifyNoCall("readDirectory"); + } + + function clear() { + for (const key of keys) { + calledMaps[key].clear(); + } + } } - function getFunctionWithCalledMapForFiveArgumentCb(cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => T) { - const calledMap = createMultiMap<[U, V, W, X]>(); - return { - cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { - calledMap.add(f, [arg1, arg2, arg3, arg4]); - return cb(f, arg1, arg2, arg3, arg4); - }, - calledMap + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); + const file1: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" }; - } + const file2: FileOrFolder = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: FileOrFolder = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const tsconfigFile: FileOrFolder = { + 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": [ + "node", + "jest" + ], + "noUnusedLocals": true, + "outDir": "./compiled", + "typeRoots": [ + "types", + "node_modules/@types" + ], + "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 = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); + const { configFileName } = projectService.openClientFile(file1.path); + assert.equal(configFileName, tsconfigFile.path, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); - function checkMultiMapKeysForSingleEntry(caption: string, multiMap: MultiMap, expectedKeys: string[]) { - assert.equal(multiMap.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(multiMap.keys())} Expected: ${expectedKeys}`); - for (const name of expectedKeys) { - assert.isTrue(multiMap.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(multiMap.keys())}`); - assert.equal(multiMap.get(name).length, 1, `${caption} is expected to have just one entry for key ${name}, actual entry: ${multiMap.get(name)}`); + const project = projectService.configuredProjects.get(canonicalConfigPath); + verifyProjectAndWatchedDirectories(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Create file cookie.ts + projectFiles.push(file3); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + + const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); + callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]); + + // Called for type root resolution + const directoryExistsCalled = createMap(); + for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) { + directoryExistsCalled.set(`${dir}/node_modules`, 2); + } + directoryExistsCalled.set(`/node_modules`, 2); + directoryExistsCalled.set(`${frontendDir}/types`, 2); + directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); + directoryExistsCalled.set(canonicalFile3Path, 1); + callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); + + callsTrackingHost.verifyNoCall("getDirectories"); + callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]); + callsTrackingHost.verifyNoCall("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 verifyProjectAndWatchedDirectories() { + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); } } @@ -4069,7 +4242,6 @@ namespace ts.projectSystem { ] }) }; - const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; const host = createServerHost(projectFiles); const session = createSession(host); @@ -4082,17 +4254,7 @@ namespace ts.projectSystem { const project = projectService.configuredProjects.get(tsconfigFile.path); checkProjectActualFiles(project, map(projectFiles, f => f.path)); - const fileExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.fileExists.bind(host)); - host.fileExists = fileExistsCalledOn.cb; - const directoryExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.directoryExists.bind(host)); - host.directoryExists = directoryExistsCalledOn.cb; - const getDirectoriesCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.getDirectories.bind(host)); - host.getDirectories = getDirectoriesCalledOn.cb; - const readFileCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.readFile.bind(host)); - host.readFile = readFileCalledOn.cb; - const readDirectoryCalledOn = getFunctionWithCalledMapForFiveArgumentCb, ReadonlyArray, ReadonlyArray, number>(host.readDirectory.bind(host)); - host.readDirectory = readDirectoryCalledOn.cb; - + const callsTrackingHost = createCallsTrackingHost(host); // Get definitions shouldnt make host requests const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { @@ -4103,23 +4265,23 @@ namespace ts.projectSystem { }); const { response } = session.executeCommand(getDefinitionRequest); assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); - assert.equal(fileExistsCalledOn.calledMap.size, 0, `fileExists shouldnt be called`); - assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); - assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); - assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); - assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + callsTrackingHost.verifyNoHostCalls(); // 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); - checkMultiMapKeysForSingleEntry("fileExists", fileExistsCalledOn.calledMap, ["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); - assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); - assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); - assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); - assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); checkNumberOfConfiguredProjects(projectService, 1); assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); }); + + it("WatchDirectories for config file With non case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); + + it("WatchDirectories for config file With case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index b77618df360..44beb83922f 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -197,9 +197,12 @@ namespace ts.TestFSWithWatch { this.reloadFS(fileOrFolderList); } + toNormalizedAbsolutePath(s: string) { + return getNormalizedAbsolutePath(s, this.currentDirectory); + } + toFullPath(s: string) { - const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); - return this.toPath(fullPath); + return this.toPath(this.toNormalizedAbsolutePath(s)); } reloadFS(fileOrFolderList: FileOrFolder[]) { @@ -413,7 +416,7 @@ namespace ts.TestFSWithWatch { } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { + return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const directories: string[] = []; const files: string[] = []; const dirEntry = this.fs.get(this.toPath(dir)); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index fb4419df79d..6cbc5263249 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1429,7 +1429,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1583,9 +1583,12 @@ namespace ts.server { * @param uncheckedFileName is absolute pathname * @param fileContent is a known version of the file content that is more up to date than the one on disk */ - - getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { - return this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName), openedByClient, fileContent, scriptKind); + /*@internal*/ + getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) { + return this.getOrCreateScriptInfoForNormalizedPath( + toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined, + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn + ); } getScriptInfo(uncheckedFileName: string) { @@ -1610,11 +1613,11 @@ namespace ts.server { } } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) { const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { - if (openedByClient || this.host.fileExists(fileName)) { + if (openedByClient || (hostToQueryFileExistsOn || this.host).fileExists(fileName)) { info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path); this.filenameToScriptInfo.set(info.path, info); diff --git a/src/server/project.ts b/src/server/project.ts index d8344bd6a2c..b35298b2938 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -240,7 +240,7 @@ namespace ts.server { if (this.projectKind === ProjectKind.Configured) { (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); }); @@ -701,7 +701,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host); scriptInfo.attachToProject(this); }, removed => { @@ -744,7 +744,7 @@ namespace ts.server { } getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); if (existingValue !== undefined && existingValue !== scriptInfo) { @@ -758,7 +758,10 @@ namespace ts.server { } getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath( + fileName, /*openedByClient*/ false, /*fileContent*/ undefined, + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host + ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); }