diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 1850220a41..339858a4ac 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -171,7 +171,7 @@ namespace ts.moduleSpecifiers { return match ? match.length : 0; } - function comparePathsByNumberOfDirectrorySeparators(a: string, b: string) { + function comparePathsByNumberOfDirectorySeparators(a: string, b: string) { return compareValues( numberOfDirectorySeparators(a), numberOfDirectorySeparators(b) @@ -216,7 +216,6 @@ namespace ts.moduleSpecifiers { for ( let directory = getDirectoryPath(toPath(importingFileName, cwd, getCanonicalFileName)); allFileNames.size !== 0; - directory = getDirectoryPath(directory) ) { const directoryStart = ensureTrailingDirectorySeparator(directory); let pathsInDirectory: string[] | undefined; @@ -228,10 +227,18 @@ namespace ts.moduleSpecifiers { }); if (pathsInDirectory) { if (pathsInDirectory.length > 1) { - pathsInDirectory.sort(comparePathsByNumberOfDirectrorySeparators); + pathsInDirectory.sort(comparePathsByNumberOfDirectorySeparators); } sortedPaths.push(...pathsInDirectory); } + const newDirectory = getDirectoryPath(directory); + if (newDirectory === directory) break; + directory = newDirectory; + } + if (allFileNames.size) { + const remainingPaths = arrayFrom(allFileNames.values()); + if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByNumberOfDirectorySeparators); + sortedPaths.push(...remainingPaths); } return sortedPaths; } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 5b29c8532e..25b8f3efe5 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -645,7 +645,7 @@ interface Array { length: number; [n: number]: T; }` } else { // root folder - Debug.assert(this.fs.size === 0); + Debug.assert(this.fs.size === 0 || !!this.windowsStyleRoot); this.fs.set(path, folder); } } diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 1e50af8935..857f1b6ebc 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -118,5 +118,133 @@ namespace ts.projectSystem { } ]); }); + + it("works when files are included from two different drives of windows", () => { + const projectRoot = "e:/myproject"; + const appPackage: File = { + path: `${projectRoot}/package.json`, + content: JSON.stringify({ + name: "test", + version: "0.1.0", + dependencies: { + "react": "^16.12.0", + "react-router-dom": "^5.1.2", + } + }) + }; + const appFile: File = { + path: `${projectRoot}/src/app.js`, + content: `import React from 'react'; +import { + BrowserRouter as Router, +} from "react-router-dom"; +` + }; + const localNodeModules = `${projectRoot}/node_modules`; + const localAtTypes = `${localNodeModules}/@types`; + const localReactPackage: File = { + path: `${localAtTypes}/react/package.json`, + content: JSON.stringify({ + name: "@types/react", + version: "16.9.14", + }) + }; + const localReact: File = { + path: `${localAtTypes}/react/index.d.ts`, + content: `import * as PropTypes from 'prop-types'; +` + }; + const localReactRouterDomPackage: File = { + path: `${localNodeModules}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "react-router-dom", + version: "5.1.2", + }) + }; + const localReactRouterDom: File = { + path: `${localNodeModules}/react-router-dom/index.js`, + content: `export function foo() {}` + }; + const localPropTypesPackage: File = { + path: `${localAtTypes}/prop-types/package.json`, + content: JSON.stringify({ + name: "@types/prop-types", + version: "15.7.3", + }) + }; + const localPropTypes: File = { + path: `${localAtTypes}/prop-types/index.d.ts`, + content: `export type ReactComponentLike = + | string + | ((props: any, context?: any) => any) + | (new (props: any, context?: any) => any); +` + }; + + const globalCacheLocation = `c:/typescript`; + const globalAtTypes = `${globalCacheLocation}/node_modules/@types`; + const globalReactRouterDomPackage: File = { + path: `${globalAtTypes}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "@types/react-router-dom", + version: "5.1.2", + }) + }; + const globalReactRouterDom: File = { + path: `${globalAtTypes}/react-router-dom/index.d.ts`, + content: `import * as React from 'react'; +export interface BrowserRouterProps { + basename?: string; + getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void); + forceRefresh?: boolean; + keyLength?: number; +}` + }; + const globalReactPackage: File = { + path: `${globalAtTypes}/react/package.json`, + content: localReactPackage.content + }; + const globalReact: File = { + path: `${globalAtTypes}/react/index.d.ts`, + content: localReact.content + }; + + const filesInProject = [ + appFile, + localReact, + localPropTypes, + globalReactRouterDom, + globalReact, + ]; + const files = [ + ...filesInProject, + appPackage, libFile, + localReactPackage, + localReactRouterDomPackage, localReactRouterDom, + localPropTypesPackage, + globalReactRouterDomPackage, + globalReactPackage, + ]; + + const host = createServerHost(files, { windowsStyleRoot: "c:/" }); + const session = createSession(host, { + typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host), + }); + const service = session.getProjectService(); + openFilesForSession([appFile], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1); + checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath)); + session.executeCommandSeq({ + command: protocol.CommandTypes.CompletionInfo, + arguments: { + file: appFile.path, + line: 5, + offset: 1, + includeExternalModuleExports: true, + includeInsertTextCompletions: true + } + }); + }); }); }