@@ -173,13 +173,38 @@ export class JavaScriptTypeMapping {
173173 declaringType = this . getType ( exprType ) as Type . FullyQualified ;
174174 } else if ( ts . isIdentifier ( node . expression ) ) {
175175 methodName = node . expression . getText ( ) ;
176- // For standalone functions, use the symbol's parent or module
177- const parent = ( symbol as any ) . parent ;
178- if ( parent ) {
179- const parentType = this . checker . getDeclaredTypeOfSymbol ( parent ) ;
180- declaringType = this . getType ( parentType ) as Type . FullyQualified ;
176+ // For standalone functions, we need to determine the appropriate declaring type
177+ const exprType = this . checker . getTypeAtLocation ( node . expression ) ;
178+ const funcType = this . getType ( exprType ) ;
179+
180+ if ( funcType && funcType . kind === Type . Kind . Class ) {
181+ const fqn = ( funcType as Type . Class ) . fullyQualifiedName ;
182+ const lastDot = fqn . lastIndexOf ( '.' ) ;
183+
184+ if ( lastDot > 0 ) {
185+ // For functions from modules, use the module part as declaring type
186+ // Examples:
187+ // - "node.assert" -> declaring type: "node"
188+ // - "@types/lodash.map" -> declaring type: "@types/lodash"
189+ // - "@types/express.express" -> declaring type: "@types/express"
190+ declaringType = {
191+ kind : Type . Kind . Class ,
192+ fullyQualifiedName : fqn . substring ( 0 , lastDot )
193+ } as Type . FullyQualified ;
194+ } else {
195+ // No dots in the name - the type IS the module itself
196+ // This handles single-name modules like "axios", "lodash" etc.
197+ declaringType = funcType as Type . FullyQualified ;
198+ }
181199 } else {
182- declaringType = Type . unknownType as Type . FullyQualified ;
200+ // Try to use the symbol's parent or module
201+ const parent = ( symbol as any ) . parent ;
202+ if ( parent ) {
203+ const parentType = this . checker . getDeclaredTypeOfSymbol ( parent ) ;
204+ declaringType = this . getType ( parentType ) as Type . FullyQualified ;
205+ } else {
206+ declaringType = Type . unknownType as Type . FullyQualified ;
207+ }
183208 }
184209 } else {
185210 methodName = symbol . getName ( ) ;
@@ -330,6 +355,30 @@ export class JavaScriptTypeMapping {
330355 if ( sourceFile . isDeclarationFile || fileName . includes ( "node_modules" ) ) {
331356 const packageName = this . extractPackageName ( fileName ) ;
332357 if ( packageName ) {
358+ // Special handling for @types /node - these are Node.js built-in modules
359+ // and should be mapped to "node.*" instead of "@types/node.*"
360+ if ( packageName === "@types/node" ) {
361+ // Extract the module name from the file path
362+ // e.g., /node_modules/@types/node/assert.d.ts -> assert
363+ // e.g., /node_modules/@types/node/fs/promises.d.ts -> fs/promises
364+ const nodeMatch = fileName . match ( / n o d e _ m o d u l e s \/ @ t y p e s \/ n o d e \/ ( [ ^ . ] + ) \. d \. t s / ) ;
365+ if ( nodeMatch ) {
366+ const modulePath = nodeMatch [ 1 ] ;
367+ // For default exports from Node modules, we want the module to be the "class"
368+ // But we still need to include the type name for proper identification
369+ if ( typeName === "default" || typeName === modulePath ) {
370+ // This is likely the default export, just use the module name
371+ return `node.${ modulePath } ` ;
372+ }
373+ // For named exports, include both module and type name
374+ if ( modulePath . includes ( '/' ) ) {
375+ return `node.${ modulePath . replace ( / \/ / g, '.' ) } .${ typeName } ` ;
376+ }
377+ return `node.${ modulePath } .${ typeName } ` ;
378+ }
379+ // Fallback for @types /node types that don't match the pattern
380+ return `node.${ typeName } ` ;
381+ }
333382 return `${ packageName } .${ typeName } ` ;
334383 }
335384 }
@@ -392,8 +441,22 @@ export class JavaScriptTypeMapping {
392441 const typesMatch = fileName . match ( / n o d e _ m o d u l e s \/ @ t y p e s \/ ( [ ^ / ] + ) / ) ;
393442 if ( typesMatch ) {
394443 const packageName = typesMatch [ 1 ] ;
395- // Replace the module specifier part with @types /package
396- fullyQualifiedName = fullyQualifiedName . replace ( / ^ [ ^ . ] + \. / , `@types/${ packageName } .` ) ;
444+ // Special handling for @types /node - use "node" prefix instead
445+ if ( packageName === "node" ) {
446+ // Extract the module name from the file path if possible
447+ const nodeMatch = fileName . match ( / n o d e _ m o d u l e s \/ @ t y p e s \/ n o d e \/ ( [ ^ . ] + ) \. d \. t s / ) ;
448+ if ( nodeMatch ) {
449+ const modulePath = nodeMatch [ 1 ] ;
450+ // Replace the module specifier with node.module
451+ fullyQualifiedName = fullyQualifiedName . replace ( / ^ [ ^ . ] + \. / , `node.${ modulePath } .` ) ;
452+ } else {
453+ // Fallback: just use "node" prefix
454+ fullyQualifiedName = fullyQualifiedName . replace ( / ^ [ ^ . ] + \. / , `node.` ) ;
455+ }
456+ } else {
457+ // Replace the module specifier part with @types /package
458+ fullyQualifiedName = fullyQualifiedName . replace ( / ^ [ ^ . ] + \. / , `@types/${ packageName } .` ) ;
459+ }
397460 }
398461 }
399462 }
0 commit comments