@@ -271,6 +271,15 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
271271 getCurrentDirectory : ( ) => basePath ,
272272 } ) ;
273273
274+ for ( let typeRoot of typeRoots || [ ] ) {
275+ traverseTypeRoot ( typeRoot , "" ) ;
276+ }
277+
278+ for ( let sourceFile of program . getSourceFiles ( ) ) {
279+ addModuleBindingsFromModuleDeclarations ( sourceFile ) ;
280+ addModuleBindingsFromFilePath ( sourceFile ) ;
281+ }
282+
274283 /** Concatenates two imports paths. These always use `/` as path separator. */
275284 function joinModulePath ( prefix : string , suffix : string ) {
276285 if ( prefix . length === 0 ) return suffix ;
@@ -300,36 +309,74 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
300309 if ( sourceFile == null ) {
301310 continue ;
302311 }
303- let symbol = typeChecker . getSymbolAtLocation ( sourceFile ) ;
304- if ( symbol == null ) continue ; // Happens if the source file is not a module.
305-
306- let canonicalSymbol = getEffectiveExportTarget ( symbol ) ; // Follow `export = X` declarations.
307- let symbolId = state . typeTable . getSymbolId ( canonicalSymbol ) ;
308-
309- let importPath = ( child === "index.d.ts" )
310- ? importPrefix
311- : joinModulePath ( importPrefix , pathlib . basename ( child , ".d.ts" ) ) ;
312-
313- // Associate the module name with this symbol.
314- state . typeTable . addModuleMapping ( symbolId , importPath ) ;
315-
316- // Associate global variable names with this module.
317- // For each `export as X` declaration, the global X refers to this module.
318- // Note: the `globalExports` map is stored on the original symbol, not the target of `export=`.
319- if ( symbol . globalExports != null ) {
320- symbol . globalExports . forEach ( ( global : ts . Symbol ) => {
321- state . typeTable . addGlobalMapping ( symbolId , global . name ) ;
322- } ) ;
323- }
312+ addModuleBindingFromRelativePath ( sourceFile , importPrefix , child ) ;
324313 }
325314 }
326- for ( let typeRoot of typeRoots || [ ] ) {
327- traverseTypeRoot ( typeRoot , "" ) ;
315+
316+ /**
317+ * Emits module bindings for a module with relative path `folder/baseName`.
318+ */
319+ function addModuleBindingFromRelativePath ( sourceFile : ts . SourceFile , folder : string , baseName : string ) {
320+ let symbol = typeChecker . getSymbolAtLocation ( sourceFile ) ;
321+ if ( symbol == null ) return ; // Happens if the source file is not a module.
322+
323+ let stem = getStem ( baseName ) ;
324+ let importPath = ( stem === "index" )
325+ ? folder
326+ : joinModulePath ( folder , stem ) ;
327+
328+ let canonicalSymbol = getEffectiveExportTarget ( symbol ) ; // Follow `export = X` declarations.
329+ let symbolId = state . typeTable . getSymbolId ( canonicalSymbol ) ;
330+
331+ // Associate the module name with this symbol.
332+ state . typeTable . addModuleMapping ( symbolId , importPath ) ;
333+
334+ // Associate global variable names with this module.
335+ // For each `export as X` declaration, the global X refers to this module.
336+ // Note: the `globalExports` map is stored on the original symbol, not the target of `export=`.
337+ if ( symbol . globalExports != null ) {
338+ symbol . globalExports . forEach ( ( global : ts . Symbol ) => {
339+ state . typeTable . addGlobalMapping ( symbolId , global . name ) ;
340+ } ) ;
341+ }
328342 }
329343
330- // Emit module name bindings for external module declarations, i.e: `declare module 'X' {..}`
331- // These can generally occur anywhere; they may or may not be on the type root path.
332- for ( let sourceFile of program . getSourceFiles ( ) ) {
344+ /**
345+ * Returns the basename of `file` without its extension, while treating `.d.ts` as a
346+ * single extension.
347+ */
348+ function getStem ( file : string ) {
349+ if ( file . endsWith ( ".d.ts" ) ) {
350+ return pathlib . basename ( file , ".d.ts" ) ;
351+ }
352+ let base = pathlib . basename ( file ) ;
353+ let dot = base . lastIndexOf ( '.' ) ;
354+ return dot === - 1 || dot === 0 ? base : base . substring ( 0 , dot ) ;
355+ }
356+
357+ /**
358+ * Emits module bindings for a module based on its file path.
359+ *
360+ * This looks for enclosing `node_modules` folders to determine the module name.
361+ * This is needed for modules that ship their own type definitions as opposed to having
362+ * type definitions loaded from a type root (conventionally named `@types/xxx`).
363+ */
364+ function addModuleBindingsFromFilePath ( sourceFile : ts . SourceFile ) {
365+ let fullPath = sourceFile . fileName ;
366+ let index = fullPath . lastIndexOf ( '/node_modules/' ) ;
367+ if ( index === - 1 ) return ;
368+ let relativePath = fullPath . substring ( index + '/node_modules/' . length ) ;
369+ // Ignore node_modules/@types folders here as they are typically handled as type roots.
370+ if ( relativePath . startsWith ( "@types/" ) ) return ;
371+ let { dir, base } = pathlib . parse ( relativePath ) ;
372+ addModuleBindingFromRelativePath ( sourceFile , dir , base ) ;
373+ }
374+
375+ /**
376+ * Emit module name bindings for external module declarations, i.e: `declare module 'X' {..}`
377+ * These can generally occur anywhere; they may or may not be on the type root path.
378+ */
379+ function addModuleBindingsFromModuleDeclarations ( sourceFile : ts . SourceFile ) {
333380 for ( let stmt of sourceFile . statements ) {
334381 if ( ts . isModuleDeclaration ( stmt ) && ts . isStringLiteral ( stmt . name ) ) {
335382 let symbol = ( stmt as any ) . symbol ;
0 commit comments