11// src/extraction/typescript.ts
22import ts from 'typescript' ;
33import path from 'node:path' ;
4- import type { ExtractionRequest , ExtractResult , ExtractorOptions , SymbolInfo } from '.. /core/types ' ;
4+ import type { ExtractionRequest , ExtractResult , ExtractorOptions , SymbolInfo } from './core' ;
55
66// Load TypeScript configuration
77function loadTsConfig ( tsConfigPath ?: string ) : {
@@ -14,7 +14,7 @@ function loadTsConfig(tsConfigPath?: string): {
1414 let compilerOptions : ts . CompilerOptions = {
1515 target : ts . ScriptTarget . ES2020 ,
1616 module : ts . ModuleKind . CommonJS ,
17- moduleResolution : ts . ModuleResolutionKind . NodeJs ,
17+ moduleResolution : ts . ModuleResolutionKind . Bundler ,
1818 esModuleInterop : true ,
1919 allowSyntheticDefaultImports : true ,
2020 skipLibCheck : true ,
@@ -71,18 +71,16 @@ function resolveModule(
7171 moduleName : string ,
7272 compilerOptions : ts . CompilerOptions ,
7373) : { sourceFile : ts . SourceFile ; program : ts . Program } | null {
74- // Use TypeScript's built-in module resolution
74+ // First try normal module resolution
7575 const moduleResolver = ts . resolveModuleName (
7676 moduleName ,
77- path . resolve ( './index.ts' ) , // Use absolute path for containing file
77+ path . resolve ( './index.ts' ) ,
7878 compilerOptions ,
7979 ts . sys ,
8080 ) ;
8181
8282 if ( moduleResolver . resolvedModule ) {
8383 const { resolvedFileName } = moduleResolver . resolvedModule ;
84-
85- // Create a dedicated program for this file like
8684 const dedicatedProgram = ts . createProgram ( [ resolvedFileName ] , compilerOptions ) ;
8785 const sourceFile = dedicatedProgram . getSourceFile ( resolvedFileName ) ;
8886
@@ -94,6 +92,53 @@ function resolveModule(
9492 return null ;
9593}
9694
95+ // Handle global symbols from lib.d.ts (like Math, console, etc.)
96+ function resolveGlobalSymbol (
97+ symbolName : string ,
98+ compilerOptions : ts . CompilerOptions ,
99+ ) : { sourceFile : ts . SourceFile ; program : ts . Program ; isGlobal : true } | null {
100+ try {
101+ // Create a minimal TypeScript file that references the global symbol
102+ const virtualFileName = 'virtual-globals.ts' ;
103+ const virtualContent = `// Global symbol reference\nconst _ref = ${ symbolName } ;` ;
104+
105+ // Create program with default host and proper lib files
106+ const program = ts . createProgram (
107+ [ virtualFileName ] ,
108+ {
109+ ...compilerOptions ,
110+ lib : compilerOptions . lib || [ 'lib.es2020.d.ts' ] ,
111+ skipLibCheck : false ,
112+ moduleResolution : ts . ModuleResolutionKind . NodeJs ,
113+ } ,
114+ {
115+ ...ts . createCompilerHost ( compilerOptions ) ,
116+ getSourceFile : ( fileName : string , languageVersion : ts . ScriptTarget ) => {
117+ if ( fileName === virtualFileName ) {
118+ return ts . createSourceFile ( virtualFileName , virtualContent , languageVersion , true ) ;
119+ }
120+ // Use the default compiler host for everything else (including lib.d.ts files)
121+ return ts . createCompilerHost ( compilerOptions ) . getSourceFile ( fileName , languageVersion ) ;
122+ } ,
123+ fileExists : ( fileName : string ) => {
124+ if ( fileName === virtualFileName ) return true ;
125+ return ts . createCompilerHost ( compilerOptions ) . fileExists ( fileName ) ;
126+ } ,
127+ } ,
128+ ) ;
129+
130+ const sourceFile = program . getSourceFile ( virtualFileName ) ;
131+ if ( sourceFile ) {
132+ return { sourceFile, program, isGlobal : true } ;
133+ }
134+ } catch ( error ) {
135+ // If global symbol resolution fails, return null
136+ console . debug ( `Failed to resolve global symbol ${ symbolName } :` , error ) ;
137+ }
138+
139+ return null ;
140+ }
141+
97142// Find symbol in JavaScript file
98143function findSymbolInJavaScriptFile (
99144 sourceFile : ts . SourceFile ,
@@ -334,45 +379,73 @@ export async function extractDocs(
334379) : Promise < SymbolInfo | null > {
335380 try {
336381 const { module, symbol, member, isStatic } = parseModulePath ( modulePath ) ;
337-
338- // Load config like
339382 const config = loadTsConfig ( options . tsConfigPath ) ;
340383
341- // Resolve module with dedicated program like
342- const resolvedModule = resolveModule ( module , config . options ) ;
384+ // Try to resolve as a regular module first
385+ let resolvedModule = resolveModule ( module , config . options ) ;
386+ let isGlobalModule = false ;
387+
388+ // If regular module resolution fails, try as a global symbol
389+ if ( ! resolvedModule ) {
390+ const globalResolution = resolveGlobalSymbol ( module , config . options ) ;
391+ if ( globalResolution ) {
392+ resolvedModule = globalResolution ;
393+ isGlobalModule = true ;
394+ }
395+ }
396+
343397 if ( ! resolvedModule ) {
344398 throw new Error ( `Module '${ module } ' not found` ) ;
345399 }
346400
347401 const { sourceFile, program } = resolvedModule ;
348402 const checker = program . getTypeChecker ( ) ;
349-
350- // Get the module symbol
351- const moduleSymbol = checker . getSymbolAtLocation ( sourceFile ) ;
352403 let targetSymbol : ts . Symbol | undefined ;
353404
354- if ( moduleSymbol ) {
355- // TypeScript module with proper exports
356- const exports = checker . getExportsOfModule ( moduleSymbol ) ;
357- targetSymbol = exports . find ( exp => exp . getName ( ) === symbol ) ;
405+ if ( isGlobalModule ) {
406+ // For global modules, get the symbol from the global scope
407+ const globalSymbols = checker . getSymbolsInScope ( sourceFile , ts . SymbolFlags . Value ) ;
408+ targetSymbol = globalSymbols . find ( s => s . getName ( ) === module ) ;
358409
359410 if ( ! targetSymbol ) {
360- throw new Error (
361- `Symbol '${ symbol } ' not found in module '${ module } '. Available exports: ${ exports
362- . map ( e => e . getName ( ) )
363- . join ( ', ' ) } `,
364- ) ;
411+ // Try getting it from the AST node directly
412+ const [ identifierNode ] = sourceFile . statements ;
413+ if ( ts . isVariableStatement ( identifierNode ) ) {
414+ const [ declaration ] = identifierNode . declarationList . declarations ;
415+ if ( ts . isVariableDeclaration ( declaration ) && declaration . initializer ) {
416+ targetSymbol = checker . getSymbolAtLocation ( declaration . initializer ) ;
417+ }
418+ }
365419 }
366420 } else {
367- // JavaScript file - look for symbols in statements
368- targetSymbol = findSymbolInJavaScriptFile ( sourceFile , checker , symbol ) ;
421+ // Regular module handling
422+ const moduleSymbol = checker . getSymbolAtLocation ( sourceFile ) ;
369423
370- if ( ! targetSymbol ) {
371- throw new Error ( `Symbol '${ symbol } ' not found in JavaScript module '${ module } '` ) ;
424+ if ( moduleSymbol ) {
425+ const exports = checker . getExportsOfModule ( moduleSymbol ) ;
426+ targetSymbol = exports . find ( exp => exp . getName ( ) === symbol ) ;
427+
428+ if ( ! targetSymbol ) {
429+ throw new Error (
430+ `Symbol '${ symbol } ' not found in module '${ module } '. Available exports: ${ exports
431+ . map ( e => e . getName ( ) )
432+ . join ( ', ' ) } `,
433+ ) ;
434+ }
435+ } else {
436+ // JavaScript file - look for symbols in statements
437+ targetSymbol = findSymbolInJavaScriptFile ( sourceFile , checker , symbol ) ;
438+
439+ if ( ! targetSymbol ) {
440+ throw new Error ( `Symbol '${ symbol } ' not found in JavaScript module '${ module } '` ) ;
441+ }
372442 }
373443 }
374444
375- // Get symbol info like
445+ if ( ! targetSymbol ) {
446+ throw new Error ( `Symbol '${ isGlobalModule ? module : symbol } ' not found` ) ;
447+ }
448+
376449 const result = getSymbolInfo ( checker , targetSymbol , member , isStatic ) ;
377450 return result ;
378451 } catch ( error ) {
0 commit comments