@@ -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 */
187193export 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 ( / ( ^ | \/ ) j s c o n f i g \. j s o n $ / . 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 */
491501export 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 && / \/ [ t j ] s c o n f i g \. j s o n / . 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