@@ -5,7 +5,7 @@ import * as ts from 'typescript'
5
5
* Extract only public API declarations from TypeScript source code
6
6
* This focuses on what should be in .d.ts files, not implementation details
7
7
*/
8
- export function extractDeclarations ( sourceCode : string , filePath : string ) : Declaration [ ] {
8
+ export function extractDeclarations ( sourceCode : string , filePath : string , keepComments : boolean = true ) : Declaration [ ] {
9
9
const declarations : Declaration [ ] = [ ]
10
10
11
11
// Create TypeScript source file
@@ -38,40 +38,40 @@ export function extractDeclarations(sourceCode: string, filePath: string): Decla
38
38
break
39
39
40
40
case ts . SyntaxKind . FunctionDeclaration :
41
- const funcDecl = extractFunctionDeclaration ( node as ts . FunctionDeclaration , sourceCode )
41
+ const funcDecl = extractFunctionDeclaration ( node as ts . FunctionDeclaration , sourceCode , sourceFile , keepComments )
42
42
// Only include exported functions or functions that are referenced by exported items
43
43
if ( funcDecl && ( funcDecl . isExported || shouldIncludeNonExportedFunction ( funcDecl . name , sourceCode ) ) ) {
44
44
declarations . push ( funcDecl )
45
45
}
46
46
break
47
47
48
48
case ts . SyntaxKind . VariableStatement :
49
- const varDecls = extractVariableStatement ( node as ts . VariableStatement , sourceCode )
49
+ const varDecls = extractVariableStatement ( node as ts . VariableStatement , sourceCode , sourceFile , keepComments )
50
50
declarations . push ( ...varDecls )
51
51
break
52
52
53
53
case ts . SyntaxKind . InterfaceDeclaration :
54
- const interfaceDecl = extractInterfaceDeclaration ( node as ts . InterfaceDeclaration , sourceCode )
54
+ const interfaceDecl = extractInterfaceDeclaration ( node as ts . InterfaceDeclaration , sourceCode , sourceFile , keepComments )
55
55
// Include interfaces that are exported or referenced by exported items
56
56
if ( interfaceDecl . isExported || shouldIncludeNonExportedInterface ( interfaceDecl . name , sourceCode ) ) {
57
57
declarations . push ( interfaceDecl )
58
58
}
59
59
break
60
60
61
61
case ts . SyntaxKind . TypeAliasDeclaration :
62
- declarations . push ( extractTypeAliasDeclaration ( node as ts . TypeAliasDeclaration , sourceCode ) )
62
+ declarations . push ( extractTypeAliasDeclaration ( node as ts . TypeAliasDeclaration , sourceCode , sourceFile , keepComments ) )
63
63
break
64
64
65
65
case ts . SyntaxKind . ClassDeclaration :
66
- declarations . push ( extractClassDeclaration ( node as ts . ClassDeclaration , sourceCode ) )
66
+ declarations . push ( extractClassDeclaration ( node as ts . ClassDeclaration , sourceCode , sourceFile , keepComments ) )
67
67
break
68
68
69
69
case ts . SyntaxKind . EnumDeclaration :
70
- declarations . push ( extractEnumDeclaration ( node as ts . EnumDeclaration , sourceCode ) )
70
+ declarations . push ( extractEnumDeclaration ( node as ts . EnumDeclaration , sourceCode , sourceFile , keepComments ) )
71
71
break
72
72
73
73
case ts . SyntaxKind . ModuleDeclaration :
74
- declarations . push ( extractModuleDeclaration ( node as ts . ModuleDeclaration , sourceCode ) )
74
+ declarations . push ( extractModuleDeclaration ( node as ts . ModuleDeclaration , sourceCode , sourceFile , keepComments ) )
75
75
break
76
76
}
77
77
@@ -147,7 +147,7 @@ function extractExportAssignment(node: ts.ExportAssignment, sourceCode: string):
147
147
/**
148
148
* Extract function declaration with proper signature
149
149
*/
150
- function extractFunctionDeclaration ( node : ts . FunctionDeclaration , sourceCode : string ) : Declaration | null {
150
+ function extractFunctionDeclaration ( node : ts . FunctionDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration | null {
151
151
if ( ! node . name )
152
152
return null // Skip anonymous functions
153
153
@@ -173,6 +173,9 @@ function extractFunctionDeclaration(node: ts.FunctionDeclaration, sourceCode: st
173
173
// Extract generics
174
174
const generics = node . typeParameters ?. map ( tp => tp . getText ( ) ) . join ( ', ' )
175
175
176
+ // Extract comments if enabled
177
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
178
+
176
179
return {
177
180
kind : 'function' ,
178
181
name,
@@ -183,6 +186,7 @@ function extractFunctionDeclaration(node: ts.FunctionDeclaration, sourceCode: st
183
186
parameters,
184
187
returnType,
185
188
generics : generics ? `<${ generics } >` : undefined ,
189
+ leadingComments,
186
190
start : node . getStart ( ) ,
187
191
end : node . getEnd ( ) ,
188
192
}
@@ -235,7 +239,7 @@ function buildFunctionSignature(node: ts.FunctionDeclaration): string {
235
239
/**
236
240
* Extract variable statement (only exported ones for DTS)
237
241
*/
238
- function extractVariableStatement ( node : ts . VariableStatement , sourceCode : string ) : Declaration [ ] {
242
+ function extractVariableStatement ( node : ts . VariableStatement , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration [ ] {
239
243
const declarations : Declaration [ ] = [ ]
240
244
const isExported = hasExportModifier ( node )
241
245
@@ -257,6 +261,9 @@ function extractVariableStatement(node: ts.VariableStatement, sourceCode: string
257
261
// Build clean variable declaration for DTS
258
262
const dtsText = buildVariableDeclaration ( name , typeAnnotation , kind , true )
259
263
264
+ // Extract comments if enabled
265
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
266
+
260
267
declarations . push ( {
261
268
kind : 'variable' ,
262
269
name,
@@ -265,6 +272,7 @@ function extractVariableStatement(node: ts.VariableStatement, sourceCode: string
265
272
typeAnnotation,
266
273
value : initializer ,
267
274
modifiers : [ kind ] ,
275
+ leadingComments,
268
276
start : node . getStart ( ) ,
269
277
end : node . getEnd ( ) ,
270
278
} )
@@ -295,7 +303,7 @@ function buildVariableDeclaration(name: string, type: string | undefined, kind:
295
303
/**
296
304
* Extract interface declaration
297
305
*/
298
- function extractInterfaceDeclaration ( node : ts . InterfaceDeclaration , sourceCode : string ) : Declaration {
306
+ function extractInterfaceDeclaration ( node : ts . InterfaceDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration {
299
307
const name = node . name . getText ( )
300
308
const isExported = hasExportModifier ( node )
301
309
@@ -310,13 +318,17 @@ function extractInterfaceDeclaration(node: ts.InterfaceDeclaration, sourceCode:
310
318
// Extract generics
311
319
const generics = node . typeParameters ?. map ( tp => tp . getText ( ) ) . join ( ', ' )
312
320
321
+ // Extract comments if enabled
322
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
323
+
313
324
return {
314
325
kind : 'interface' ,
315
326
name,
316
327
text,
317
328
isExported,
318
329
extends : extendsClause ,
319
330
generics : generics ? `<${ generics } >` : undefined ,
331
+ leadingComments,
320
332
start : node . getStart ( ) ,
321
333
end : node . getEnd ( ) ,
322
334
}
@@ -427,7 +439,7 @@ function getInterfaceBody(node: ts.InterfaceDeclaration): string {
427
439
/**
428
440
* Extract type alias declaration
429
441
*/
430
- function extractTypeAliasDeclaration ( node : ts . TypeAliasDeclaration , sourceCode : string ) : Declaration {
442
+ function extractTypeAliasDeclaration ( node : ts . TypeAliasDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration {
431
443
const name = node . name . getText ( )
432
444
const isExported = hasExportModifier ( node )
433
445
@@ -437,12 +449,16 @@ function extractTypeAliasDeclaration(node: ts.TypeAliasDeclaration, sourceCode:
437
449
// Extract generics
438
450
const generics = node . typeParameters ?. map ( tp => tp . getText ( ) ) . join ( ', ' )
439
451
452
+ // Extract comments if enabled
453
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
454
+
440
455
return {
441
456
kind : 'type' ,
442
457
name,
443
458
text,
444
459
isExported,
445
460
generics : generics ? `<${ generics } >` : undefined ,
461
+ leadingComments,
446
462
start : node . getStart ( ) ,
447
463
end : node . getEnd ( ) ,
448
464
}
@@ -474,7 +490,7 @@ function buildTypeDeclaration(node: ts.TypeAliasDeclaration, isExported: boolean
474
490
/**
475
491
* Extract class declaration
476
492
*/
477
- function extractClassDeclaration ( node : ts . ClassDeclaration , sourceCode : string ) : Declaration {
493
+ function extractClassDeclaration ( node : ts . ClassDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration {
478
494
const name = node . name ?. getText ( ) || 'AnonymousClass'
479
495
const isExported = hasExportModifier ( node )
480
496
@@ -497,6 +513,9 @@ function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string):
497
513
// Check for abstract modifier
498
514
const isAbstract = node . modifiers ?. some ( mod => mod . kind === ts . SyntaxKind . AbstractKeyword )
499
515
516
+ // Extract comments if enabled
517
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
518
+
500
519
return {
501
520
kind : 'class' ,
502
521
name,
@@ -506,6 +525,7 @@ function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string):
506
525
implements : implementsClause ,
507
526
generics : generics ? `<${ generics } >` : undefined ,
508
527
modifiers : isAbstract ? [ 'abstract' ] : undefined ,
528
+ leadingComments,
509
529
start : node . getStart ( ) ,
510
530
end : node . getEnd ( ) ,
511
531
}
@@ -677,20 +697,24 @@ function buildClassBody(node: ts.ClassDeclaration): string {
677
697
/**
678
698
* Extract enum declaration
679
699
*/
680
- function extractEnumDeclaration ( node : ts . EnumDeclaration , sourceCode : string ) : Declaration {
700
+ function extractEnumDeclaration ( node : ts . EnumDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration {
681
701
const name = node . name . getText ( )
682
702
const isExported = hasExportModifier ( node )
683
703
const text = getNodeText ( node , sourceCode )
684
704
685
705
// Check for const modifier
686
706
const isConst = node . modifiers ?. some ( mod => mod . kind === ts . SyntaxKind . ConstKeyword )
687
707
708
+ // Extract comments if enabled
709
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
710
+
688
711
return {
689
712
kind : 'enum' ,
690
713
name,
691
714
text,
692
715
isExported,
693
716
modifiers : isConst ? [ 'const' ] : undefined ,
717
+ leadingComments,
694
718
start : node . getStart ( ) ,
695
719
end : node . getEnd ( ) ,
696
720
}
@@ -699,7 +723,7 @@ function extractEnumDeclaration(node: ts.EnumDeclaration, sourceCode: string): D
699
723
/**
700
724
* Extract module/namespace declaration
701
725
*/
702
- function extractModuleDeclaration ( node : ts . ModuleDeclaration , sourceCode : string ) : Declaration {
726
+ function extractModuleDeclaration ( node : ts . ModuleDeclaration , sourceCode : string , sourceFile : ts . SourceFile , keepComments : boolean ) : Declaration {
703
727
const name = node . name . getText ( )
704
728
const isExported = hasExportModifier ( node )
705
729
@@ -709,12 +733,16 @@ function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string
709
733
// Check if this is an ambient module (quoted name)
710
734
const isAmbient = ts . isStringLiteral ( node . name )
711
735
736
+ // Extract comments if enabled
737
+ const leadingComments = keepComments ? extractJSDocComments ( node , sourceFile ) : undefined
738
+
712
739
return {
713
740
kind : 'module' ,
714
741
name,
715
742
text,
716
743
isExported,
717
744
source : isAmbient ? name . slice ( 1 , - 1 ) : undefined , // Remove quotes for ambient modules
745
+ leadingComments,
718
746
start : node . getStart ( ) ,
719
747
end : node . getEnd ( ) ,
720
748
}
@@ -983,6 +1011,57 @@ function getNodeText(node: ts.Node, sourceCode: string): string {
983
1011
return sourceCode . slice ( node . getStart ( ) , node . getEnd ( ) )
984
1012
}
985
1013
1014
+ /**
1015
+ * Extract JSDoc comments from a node
1016
+ */
1017
+ function extractJSDocComments ( node : ts . Node , sourceFile : ts . SourceFile ) : string [ ] {
1018
+ const comments : string [ ] = [ ]
1019
+
1020
+ // Get leading trivia (comments before the node)
1021
+ const fullStart = node . getFullStart ( )
1022
+ const start = node . getStart ( sourceFile )
1023
+
1024
+ if ( fullStart !== start ) {
1025
+ const triviaText = sourceFile . text . substring ( fullStart , start )
1026
+
1027
+ // Extract JSDoc comments (/** ... */) and single-line comments (// ...)
1028
+ const jsDocMatches = triviaText . match ( / \/ \* \* [ \s \S ] * ?\* \/ / g)
1029
+ if ( jsDocMatches ) {
1030
+ comments . push ( ...jsDocMatches )
1031
+ }
1032
+
1033
+ // Also capture regular block comments (/* ... */) that might be documentation
1034
+ const blockCommentMatches = triviaText . match ( / \/ \* (? ! \* ) [ \s \S ] * ?\* \/ / g)
1035
+ if ( blockCommentMatches ) {
1036
+ comments . push ( ...blockCommentMatches )
1037
+ }
1038
+
1039
+ // Capture single-line comments that appear right before the declaration
1040
+ const lines = triviaText . split ( '\n' )
1041
+ const commentLines : string [ ] = [ ]
1042
+
1043
+ // Look for consecutive comment lines at the end of the trivia
1044
+ for ( let i = lines . length - 1 ; i >= 0 ; i -- ) {
1045
+ const line = lines [ i ] . trim ( )
1046
+ if ( line . startsWith ( '//' ) ) {
1047
+ commentLines . unshift ( line )
1048
+ } else if ( line === '' ) {
1049
+ // Empty line is okay, continue
1050
+ continue
1051
+ } else {
1052
+ // Non-comment, non-empty line - stop
1053
+ break
1054
+ }
1055
+ }
1056
+
1057
+ if ( commentLines . length > 0 ) {
1058
+ comments . push ( commentLines . join ( '\n' ) )
1059
+ }
1060
+ }
1061
+
1062
+ return comments
1063
+ }
1064
+
986
1065
/**
987
1066
* Get parameter name without default values for DTS
988
1067
*/
@@ -1129,7 +1208,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
1129
1208
const interfaceNode = node as ts . InterfaceDeclaration
1130
1209
const interfaceName = interfaceNode . name . getText ( )
1131
1210
if ( referencedTypes . has ( interfaceName ) ) {
1132
- const decl = extractInterfaceDeclaration ( interfaceNode , sourceCode )
1211
+ const decl = extractInterfaceDeclaration ( interfaceNode , sourceCode , sourceFile , false ) // Don't extract comments for referenced types
1133
1212
additionalDeclarations . push ( decl )
1134
1213
referencedTypes . delete ( interfaceName ) // Remove to avoid duplicates
1135
1214
}
@@ -1139,7 +1218,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
1139
1218
const typeNode = node as ts . TypeAliasDeclaration
1140
1219
const typeName = typeNode . name . getText ( )
1141
1220
if ( referencedTypes . has ( typeName ) ) {
1142
- const decl = extractTypeAliasDeclaration ( typeNode , sourceCode )
1221
+ const decl = extractTypeAliasDeclaration ( typeNode , sourceCode , sourceFile , false ) // Don't extract comments for referenced types
1143
1222
additionalDeclarations . push ( decl )
1144
1223
referencedTypes . delete ( typeName )
1145
1224
}
@@ -1150,7 +1229,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
1150
1229
if ( classNode . name ) {
1151
1230
const className = classNode . name . getText ( )
1152
1231
if ( referencedTypes . has ( className ) ) {
1153
- const decl = extractClassDeclaration ( classNode , sourceCode )
1232
+ const decl = extractClassDeclaration ( classNode , sourceCode , sourceFile , false ) // Don't extract comments for referenced types
1154
1233
additionalDeclarations . push ( decl )
1155
1234
referencedTypes . delete ( className )
1156
1235
}
@@ -1161,7 +1240,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
1161
1240
const enumNode = node as ts . EnumDeclaration
1162
1241
const enumName = enumNode . name . getText ( )
1163
1242
if ( referencedTypes . has ( enumName ) ) {
1164
- const decl = extractEnumDeclaration ( enumNode , sourceCode )
1243
+ const decl = extractEnumDeclaration ( enumNode , sourceCode , sourceFile , false ) // Don't extract comments for referenced types
1165
1244
additionalDeclarations . push ( decl )
1166
1245
referencedTypes . delete ( enumName )
1167
1246
}
0 commit comments