11import * as ts from 'typescript'
22import type { Declaration } from './types'
33
4- // Performance optimization: Cache compiled regexes
5- const DECLARATION_PATTERNS = {
6- import : / ^ i m p o r t \s + / m,
7- export : / ^ e x p o r t \s + / m,
8- function : / ^ ( e x p o r t \s + ) ? ( a s y n c \s + ) ? f u n c t i o n \s + / m,
9- variable : / ^ ( e x p o r t \s + ) ? ( c o n s t | l e t | v a r ) \s + / m,
10- interface : / ^ ( e x p o r t \s + ) ? i n t e r f a c e \s + / m,
11- type : / ^ ( e x p o r t \s + ) ? t y p e \s + / m,
12- class : / ^ ( e x p o r t \s + ) ? ( a b s t r a c t \s + ) ? c l a s s \s + / m,
13- enum : / ^ ( e x p o r t \s + ) ? ( c o n s t \s + ) ? e n u m \s + / m,
14- module : / ^ ( e x p o r t \s + ) ? ( d e c l a r e \s + ) ? ( m o d u l e | n a m e s p a c e ) \s + / m
15- } as const
16-
174/**
185 * Extract only public API declarations from TypeScript source code
196 * This focuses on what should be in .d.ts files, not implementation details
@@ -643,7 +630,9 @@ function extractEnumDeclaration(node: ts.EnumDeclaration, sourceCode: string): D
643630function extractModuleDeclaration ( node : ts . ModuleDeclaration , sourceCode : string ) : Declaration {
644631 const name = node . name . getText ( )
645632 const isExported = hasExportModifier ( node )
646- const text = getNodeText ( node , sourceCode )
633+
634+ // Build clean module declaration for DTS
635+ const text = buildModuleDeclaration ( node , isExported )
647636
648637 // Check if this is an ambient module (quoted name)
649638 const isAmbient = ts . isStringLiteral ( node . name )
@@ -659,6 +648,238 @@ function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string
659648 }
660649}
661650
651+ /**
652+ * Build clean module declaration for DTS
653+ */
654+ function buildModuleDeclaration ( node : ts . ModuleDeclaration , isExported : boolean ) : string {
655+ let result = ''
656+
657+ // Add export if needed
658+ if ( isExported ) {
659+ result += 'export '
660+ }
661+
662+ // Add declare keyword
663+ result += 'declare '
664+
665+ // Check if this is a namespace or module
666+ const isNamespace = node . flags & ts . NodeFlags . Namespace
667+ if ( isNamespace ) {
668+ result += 'namespace '
669+ } else {
670+ result += 'module '
671+ }
672+
673+ // Add module name
674+ result += node . name . getText ( )
675+
676+ // Build module body with only signatures
677+ result += ' ' + buildModuleBody ( node )
678+
679+ return result
680+ }
681+
682+ /**
683+ * Build clean module body for DTS (signatures only, no implementations)
684+ */
685+ function buildModuleBody ( node : ts . ModuleDeclaration ) : string {
686+ if ( ! node . body ) return '{}'
687+
688+ const members : string [ ] = [ ]
689+
690+ function processModuleElement ( element : ts . Node ) {
691+ if ( ts . isFunctionDeclaration ( element ) ) {
692+ // Function signature without implementation (no declare keyword in ambient context)
693+ const isExported = hasExportModifier ( element )
694+ const name = element . name ?. getText ( ) || ''
695+
696+ let signature = ' '
697+ if ( isExported ) signature += 'export '
698+ signature += 'function '
699+ signature += name
700+
701+ // Add generics
702+ if ( element . typeParameters ) {
703+ const generics = element . typeParameters . map ( tp => tp . getText ( ) ) . join ( ', ' )
704+ signature += `<${ generics } >`
705+ }
706+
707+ // Add parameters
708+ const params = element . parameters . map ( param => {
709+ const paramName = getParameterName ( param )
710+ const paramType = param . type ?. getText ( ) || 'any'
711+ const optional = param . questionToken || param . initializer ? '?' : ''
712+ return `${ paramName } ${ optional } : ${ paramType } `
713+ } ) . join ( ', ' )
714+ signature += `(${ params } )`
715+
716+ // Add return type
717+ const returnType = element . type ?. getText ( ) || 'void'
718+ signature += `: ${ returnType } ;`
719+
720+ members . push ( signature )
721+ } else if ( ts . isVariableStatement ( element ) ) {
722+ // Variable declarations
723+ const isExported = hasExportModifier ( element )
724+ for ( const declaration of element . declarationList . declarations ) {
725+ if ( declaration . name && ts . isIdentifier ( declaration . name ) ) {
726+ const name = declaration . name . getText ( )
727+ const typeAnnotation = declaration . type ?. getText ( )
728+ const initializer = declaration . initializer ?. getText ( )
729+ const kind = element . declarationList . flags & ts . NodeFlags . Const ? 'const' :
730+ element . declarationList . flags & ts . NodeFlags . Let ? 'let' : 'var'
731+
732+ let varDecl = ' '
733+ if ( isExported ) varDecl += 'export '
734+ varDecl += kind + ' '
735+ varDecl += name
736+
737+ // Use type annotation if available, otherwise infer from initializer
738+ if ( typeAnnotation ) {
739+ varDecl += `: ${ typeAnnotation } `
740+ } else if ( initializer ) {
741+ // Simple type inference for common cases
742+ if ( initializer . startsWith ( "'" ) || initializer . startsWith ( '"' ) || initializer . startsWith ( '`' ) ) {
743+ varDecl += ': string'
744+ } else if ( / ^ \d + $ / . test ( initializer ) ) {
745+ varDecl += ': number'
746+ } else if ( initializer === 'true' || initializer === 'false' ) {
747+ varDecl += ': boolean'
748+ } else {
749+ varDecl += ': any'
750+ }
751+ } else {
752+ varDecl += ': any'
753+ }
754+
755+ varDecl += ';'
756+ members . push ( varDecl )
757+ }
758+ }
759+ } else if ( ts . isInterfaceDeclaration ( element ) ) {
760+ // Interface declaration (no declare keyword in ambient context)
761+ const isExported = hasExportModifier ( element )
762+ const name = element . name . getText ( )
763+
764+ let interfaceDecl = ' '
765+ if ( isExported ) interfaceDecl += 'export '
766+ interfaceDecl += 'interface '
767+ interfaceDecl += name
768+
769+ // Add generics
770+ if ( element . typeParameters ) {
771+ const generics = element . typeParameters . map ( tp => tp . getText ( ) ) . join ( ', ' )
772+ interfaceDecl += `<${ generics } >`
773+ }
774+
775+ // Add extends
776+ if ( element . heritageClauses ) {
777+ const extendsClause = element . heritageClauses . find ( clause =>
778+ clause . token === ts . SyntaxKind . ExtendsKeyword
779+ )
780+ if ( extendsClause ) {
781+ const types = extendsClause . types . map ( type => type . getText ( ) ) . join ( ', ' )
782+ interfaceDecl += ` extends ${ types } `
783+ }
784+ }
785+
786+ // Add body
787+ const body = getInterfaceBody ( element )
788+ interfaceDecl += ' ' + body
789+
790+ members . push ( interfaceDecl )
791+ } else if ( ts . isTypeAliasDeclaration ( element ) ) {
792+ // Type alias declaration (no declare keyword in ambient context)
793+ const isExported = hasExportModifier ( element )
794+ const name = element . name . getText ( )
795+
796+ let typeDecl = ' '
797+ if ( isExported ) typeDecl += 'export '
798+ typeDecl += 'type '
799+ typeDecl += name
800+
801+ // Add generics
802+ if ( element . typeParameters ) {
803+ const generics = element . typeParameters . map ( tp => tp . getText ( ) ) . join ( ', ' )
804+ typeDecl += `<${ generics } >`
805+ }
806+
807+ typeDecl += ' = '
808+ typeDecl += element . type . getText ( )
809+
810+ members . push ( typeDecl )
811+ } else if ( ts . isEnumDeclaration ( element ) ) {
812+ // Enum declaration
813+ const isExported = hasExportModifier ( element )
814+ const name = element . name . getText ( )
815+ const isConst = element . modifiers ?. some ( mod => mod . kind === ts . SyntaxKind . ConstKeyword )
816+
817+ let enumDecl = ' '
818+ if ( isExported ) enumDecl += 'export '
819+ if ( isConst ) enumDecl += 'const '
820+ enumDecl += 'enum '
821+ enumDecl += name
822+
823+ // Build enum body
824+ const enumMembers : string [ ] = [ ]
825+ for ( const member of element . members ) {
826+ if ( ts . isEnumMember ( member ) ) {
827+ const memberName = member . name . getText ( )
828+ if ( member . initializer ) {
829+ const value = member . initializer . getText ( )
830+ enumMembers . push ( ` ${ memberName } = ${ value } ` )
831+ } else {
832+ enumMembers . push ( ` ${ memberName } ` )
833+ }
834+ }
835+ }
836+
837+ enumDecl += ` {\n${ enumMembers . join ( ',\n' ) } \n }`
838+ members . push ( enumDecl )
839+ } else if ( ts . isModuleDeclaration ( element ) ) {
840+ // Nested namespace/module (no declare keyword in ambient context)
841+ const isExported = hasExportModifier ( element )
842+ const name = element . name . getText ( )
843+
844+ let nestedDecl = ' '
845+ if ( isExported ) nestedDecl += 'export '
846+
847+ // Check if this is a namespace or module
848+ const isNamespace = element . flags & ts . NodeFlags . Namespace
849+ if ( isNamespace ) {
850+ nestedDecl += 'namespace '
851+ } else {
852+ nestedDecl += 'module '
853+ }
854+
855+ nestedDecl += name
856+ nestedDecl += ' ' + buildModuleBody ( element )
857+
858+ members . push ( nestedDecl )
859+ } else if ( ts . isExportAssignment ( element ) ) {
860+ // Export default statement
861+ let exportDecl = ' export default '
862+ if ( element . expression ) {
863+ exportDecl += element . expression . getText ( )
864+ }
865+ exportDecl += ';'
866+ members . push ( exportDecl )
867+ }
868+ }
869+
870+ if ( ts . isModuleBlock ( node . body ) ) {
871+ // Module block with statements
872+ for ( const statement of node . body . statements ) {
873+ processModuleElement ( statement )
874+ }
875+ } else if ( ts . isModuleDeclaration ( node . body ) ) {
876+ // Nested module
877+ processModuleElement ( node . body )
878+ }
879+
880+ return `{\n${ members . join ( '\n' ) } \n}`
881+ }
882+
662883/**
663884 * Get the text of a node from source code
664885 */
0 commit comments