Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit 6fcc4e1

Browse files
tomv564felixfbecker
authored andcommitted
fix: global type loading on Windows paths (#362)
* Use unixPath for matching windows paths to configuration paths * Normalise typeRoots and filePath used against config checks to forward slashes
1 parent c5902d7 commit 6fcc4e1

File tree

2 files changed

+185
-140
lines changed

2 files changed

+185
-140
lines changed

src/project-manager.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
uri2path
2525
} from './util'
2626

27+
const LAST_FORWARD_OR_BACKWARD_SLASH = /[\\\/][^\\\/]*$/
28+
2729
/**
2830
* Implementaton of LanguageServiceHost that works with in-memory file system.
2931
* It takes file content from local cache and provides it to TS compiler on demand
@@ -183,6 +185,10 @@ export class InMemoryLanguageServiceHost implements ts.LanguageServiceHost {
183185
* made available to the compiler before calling any other methods on
184186
* the ProjectConfiguration or its public members. By default, no
185187
* files are parsed.
188+
*
189+
* Windows file paths are converted to UNIX-style forward slashes
190+
* when compared with Typescript configuration (isGlobalTSFile,
191+
* expectedFilePaths and typeRoots)
186192
*/
187193
export class ProjectConfiguration {
188194

@@ -225,12 +231,13 @@ export class ProjectConfiguration {
225231

226232
/**
227233
* List of files that project consist of (based on tsconfig includes/excludes and wildcards).
228-
* Each item is a relative file path
234+
* Each item is a relative UNIX-like file path
229235
*/
230236
private expectedFilePaths = new Set<string>()
231237

232238
/**
233239
* List of resolved extra root directories to allow global type declaration files to be loaded from.
240+
* Each item is an absolute UNIX-like file path
234241
*/
235242
private typeRoots: string[]
236243

@@ -343,7 +350,7 @@ export class ProjectConfiguration {
343350
const options = configParseResult.options
344351
const pathResolver = /^[a-z]:\//i.test(base) ? path.win32 : path.posix
345352
this.typeRoots = options.typeRoots ?
346-
options.typeRoots.map((r: string) => pathResolver.resolve(this.rootFilePath, r)) :
353+
options.typeRoots.map((r: string) => toUnixPath(pathResolver.resolve(this.rootFilePath, r))) :
347354
[]
348355

349356
if (/(^|\/)jsconfig\.json$/.test(this.configFilePath)) {
@@ -401,11 +408,11 @@ export class ProjectConfiguration {
401408

402409
/**
403410
* Determines if a fileName is a declaration file within expected files or type roots
404-
* @param fileName
411+
* @param fileName A Unix-like absolute file path.
405412
*/
406413
public isExpectedDeclarationFile(fileName: string): boolean {
407414
return isDeclarationFile(fileName) &&
408-
(this.expectedFilePaths.has(toUnixPath(fileName)) ||
415+
(this.expectedFilePaths.has(fileName) ||
409416
this.typeRoots.some(root => fileName.startsWith(root)))
410417
}
411418

@@ -427,8 +434,9 @@ export class ProjectConfiguration {
427434
// Add all global declaration files from the workspace and all declarations from the project
428435
for (const uri of this.fs.uris()) {
429436
const fileName = uri2path(uri)
430-
if (isGlobalTSFile(fileName) ||
431-
this.isExpectedDeclarationFile(fileName)) {
437+
const unixPath = toUnixPath(fileName)
438+
if (isGlobalTSFile(unixPath) ||
439+
this.isExpectedDeclarationFile(unixPath)) {
432440
const sourceFile = program.getSourceFile(fileName)
433441
if (!sourceFile) {
434442
this.getHost().addFile(fileName)
@@ -487,6 +495,8 @@ export type ConfigType = 'js' | 'ts'
487495
* makes one or more LanguageService objects. By default all LanguageService objects contain no files,
488496
* they are added on demand - current file for hover or definition, project's files for references and
489497
* all files from all projects for workspace symbols.
498+
*
499+
* ProjectManager preserves Windows paths until passed to ProjectConfiguration or TS APIs.
490500
*/
491501
export class ProjectManager implements Disposable {
492502

@@ -588,7 +598,7 @@ export class ProjectManager implements Disposable {
588598

589599
// Create catch-all fallback configs in case there are no tsconfig.json files
590600
// They are removed once at least one tsconfig.json is found
591-
const trimmedRootPath = this.rootPath.replace(/\/+$/, '')
601+
const trimmedRootPath = this.rootPath.replace(/[\\\/]+$/, '')
592602
const fallbackConfigs: {js?: ProjectConfiguration, ts?: ProjectConfiguration} = {}
593603
for (const configType of ['js', 'ts'] as ConfigType[]) {
594604
const configs = this.configs[configType]
@@ -621,13 +631,8 @@ export class ProjectManager implements Disposable {
621631
.filter(([uri, content]) => !!content && /\/[tj]sconfig\.json/.test(uri) && !uri.includes('/node_modules/'))
622632
.subscribe(([uri, content]) => {
623633
const filePath = uri2path(uri)
624-
let dir = toUnixPath(filePath)
625-
const pos = dir.lastIndexOf('/')
626-
if (pos <= 0) {
627-
dir = ''
628-
} else {
629-
dir = dir.substring(0, pos)
630-
}
634+
const pos = filePath.search(LAST_FORWARD_OR_BACKWARD_SLASH)
635+
const dir = pos <= 0 ? '' : filePath.substring(0, pos)
631636
const configType = this.getConfigurationType(filePath)
632637
const configs = this.configs[configType]
633638
configs.set(dir, new ProjectConfiguration(
@@ -813,7 +818,7 @@ export class ProjectManager implements Disposable {
813818

814819
/**
815820
* Determines if a tsconfig/jsconfig needs additional declaration files loaded.
816-
* @param filePath
821+
* @param filePath A UNIX-like absolute file path
817822
*/
818823
public isConfigDependency(filePath: string): boolean {
819824
for (const config of this.configurations()) {
@@ -832,7 +837,7 @@ export class ProjectManager implements Disposable {
832837
return traceObservable('Ensure config dependencies', childOf, span => {
833838
if (!this.ensuredConfigDependencies) {
834839
this.ensuredConfigDependencies = observableFromIterable(this.inMemoryFs.uris())
835-
.filter(uri => this.isConfigDependency(uri2path(uri)))
840+
.filter(uri => this.isConfigDependency(toUnixPath(uri2path(uri))))
836841
.mergeMap(uri => this.updater.ensure(uri))
837842
.do(noop, err => {
838843
this.ensuredConfigDependencies = undefined
@@ -929,19 +934,19 @@ export class ProjectManager implements Disposable {
929934
* @return closest configuration for a given file path or undefined if there is no such configuration
930935
*/
931936
public getConfigurationIfExists(filePath: string, configType = this.getConfigurationType(filePath)): ProjectConfiguration | undefined {
932-
let dir = toUnixPath(filePath)
937+
let dir = filePath
933938
let config: ProjectConfiguration | undefined
934939
const configs = this.configs[configType]
935940
if (!configs) {
936941
return undefined
937942
}
938-
const rootPath = this.rootPath.replace(/\/+$/, '')
943+
const rootPath = this.rootPath.replace(/[\\\/]+$/, '')
939944
while (dir && dir !== rootPath) {
940945
config = configs.get(dir)
941946
if (config) {
942947
return config
943948
}
944-
const pos = dir.lastIndexOf('/')
949+
const pos = dir.search(LAST_FORWARD_OR_BACKWARD_SLASH)
945950
if (pos <= 0) {
946951
dir = ''
947952
} else {
@@ -1029,13 +1034,14 @@ export class ProjectManager implements Disposable {
10291034
* @return configuration type to use for a given file
10301035
*/
10311036
private getConfigurationType(filePath: string): ConfigType {
1032-
const name = path.posix.basename(filePath)
1037+
const unixPath = toUnixPath(filePath)
1038+
const name = path.posix.basename(unixPath)
10331039
if (name === 'tsconfig.json') {
10341040
return 'ts'
10351041
} else if (name === 'jsconfig.json') {
10361042
return 'js'
10371043
}
1038-
const extension = path.posix.extname(filePath)
1044+
const extension = path.posix.extname(unixPath)
10391045
if (extension === '.js' || extension === '.jsx') {
10401046
return 'js'
10411047
}

0 commit comments

Comments
 (0)