1
1
import * as ts from 'typescript'
2
2
import type { Declaration } from './types'
3
3
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
-
17
4
/**
18
5
* Extract only public API declarations from TypeScript source code
19
6
* 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
643
630
function extractModuleDeclaration ( node : ts . ModuleDeclaration , sourceCode : string ) : Declaration {
644
631
const name = node . name . getText ( )
645
632
const isExported = hasExportModifier ( node )
646
- const text = getNodeText ( node , sourceCode )
633
+
634
+ // Build clean module declaration for DTS
635
+ const text = buildModuleDeclaration ( node , isExported )
647
636
648
637
// Check if this is an ambient module (quoted name)
649
638
const isAmbient = ts . isStringLiteral ( node . name )
@@ -659,6 +648,238 @@ function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string
659
648
}
660
649
}
661
650
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
+
662
883
/**
663
884
* Get the text of a node from source code
664
885
*/
0 commit comments