Skip to content

Commit afa595a

Browse files
committed
chore: wip
1 parent db7ed2a commit afa595a

File tree

5 files changed

+542
-29
lines changed

5 files changed

+542
-29
lines changed

src/extractor.ts

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as ts from 'typescript'
55
* Extract only public API declarations from TypeScript source code
66
* This focuses on what should be in .d.ts files, not implementation details
77
*/
8-
export function extractDeclarations(sourceCode: string, filePath: string): Declaration[] {
8+
export function extractDeclarations(sourceCode: string, filePath: string, keepComments: boolean = true): Declaration[] {
99
const declarations: Declaration[] = []
1010

1111
// Create TypeScript source file
@@ -38,40 +38,40 @@ export function extractDeclarations(sourceCode: string, filePath: string): Decla
3838
break
3939

4040
case ts.SyntaxKind.FunctionDeclaration:
41-
const funcDecl = extractFunctionDeclaration(node as ts.FunctionDeclaration, sourceCode)
41+
const funcDecl = extractFunctionDeclaration(node as ts.FunctionDeclaration, sourceCode, sourceFile, keepComments)
4242
// Only include exported functions or functions that are referenced by exported items
4343
if (funcDecl && (funcDecl.isExported || shouldIncludeNonExportedFunction(funcDecl.name, sourceCode))) {
4444
declarations.push(funcDecl)
4545
}
4646
break
4747

4848
case ts.SyntaxKind.VariableStatement:
49-
const varDecls = extractVariableStatement(node as ts.VariableStatement, sourceCode)
49+
const varDecls = extractVariableStatement(node as ts.VariableStatement, sourceCode, sourceFile, keepComments)
5050
declarations.push(...varDecls)
5151
break
5252

5353
case ts.SyntaxKind.InterfaceDeclaration:
54-
const interfaceDecl = extractInterfaceDeclaration(node as ts.InterfaceDeclaration, sourceCode)
54+
const interfaceDecl = extractInterfaceDeclaration(node as ts.InterfaceDeclaration, sourceCode, sourceFile, keepComments)
5555
// Include interfaces that are exported or referenced by exported items
5656
if (interfaceDecl.isExported || shouldIncludeNonExportedInterface(interfaceDecl.name, sourceCode)) {
5757
declarations.push(interfaceDecl)
5858
}
5959
break
6060

6161
case ts.SyntaxKind.TypeAliasDeclaration:
62-
declarations.push(extractTypeAliasDeclaration(node as ts.TypeAliasDeclaration, sourceCode))
62+
declarations.push(extractTypeAliasDeclaration(node as ts.TypeAliasDeclaration, sourceCode, sourceFile, keepComments))
6363
break
6464

6565
case ts.SyntaxKind.ClassDeclaration:
66-
declarations.push(extractClassDeclaration(node as ts.ClassDeclaration, sourceCode))
66+
declarations.push(extractClassDeclaration(node as ts.ClassDeclaration, sourceCode, sourceFile, keepComments))
6767
break
6868

6969
case ts.SyntaxKind.EnumDeclaration:
70-
declarations.push(extractEnumDeclaration(node as ts.EnumDeclaration, sourceCode))
70+
declarations.push(extractEnumDeclaration(node as ts.EnumDeclaration, sourceCode, sourceFile, keepComments))
7171
break
7272

7373
case ts.SyntaxKind.ModuleDeclaration:
74-
declarations.push(extractModuleDeclaration(node as ts.ModuleDeclaration, sourceCode))
74+
declarations.push(extractModuleDeclaration(node as ts.ModuleDeclaration, sourceCode, sourceFile, keepComments))
7575
break
7676
}
7777

@@ -147,7 +147,7 @@ function extractExportAssignment(node: ts.ExportAssignment, sourceCode: string):
147147
/**
148148
* Extract function declaration with proper signature
149149
*/
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 {
151151
if (!node.name)
152152
return null // Skip anonymous functions
153153

@@ -173,6 +173,9 @@ function extractFunctionDeclaration(node: ts.FunctionDeclaration, sourceCode: st
173173
// Extract generics
174174
const generics = node.typeParameters?.map(tp => tp.getText()).join(', ')
175175

176+
// Extract comments if enabled
177+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
178+
176179
return {
177180
kind: 'function',
178181
name,
@@ -183,6 +186,7 @@ function extractFunctionDeclaration(node: ts.FunctionDeclaration, sourceCode: st
183186
parameters,
184187
returnType,
185188
generics: generics ? `<${generics}>` : undefined,
189+
leadingComments,
186190
start: node.getStart(),
187191
end: node.getEnd(),
188192
}
@@ -235,7 +239,7 @@ function buildFunctionSignature(node: ts.FunctionDeclaration): string {
235239
/**
236240
* Extract variable statement (only exported ones for DTS)
237241
*/
238-
function extractVariableStatement(node: ts.VariableStatement, sourceCode: string): Declaration[] {
242+
function extractVariableStatement(node: ts.VariableStatement, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration[] {
239243
const declarations: Declaration[] = []
240244
const isExported = hasExportModifier(node)
241245

@@ -257,6 +261,9 @@ function extractVariableStatement(node: ts.VariableStatement, sourceCode: string
257261
// Build clean variable declaration for DTS
258262
const dtsText = buildVariableDeclaration(name, typeAnnotation, kind, true)
259263

264+
// Extract comments if enabled
265+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
266+
260267
declarations.push({
261268
kind: 'variable',
262269
name,
@@ -265,6 +272,7 @@ function extractVariableStatement(node: ts.VariableStatement, sourceCode: string
265272
typeAnnotation,
266273
value: initializer,
267274
modifiers: [kind],
275+
leadingComments,
268276
start: node.getStart(),
269277
end: node.getEnd(),
270278
})
@@ -295,7 +303,7 @@ function buildVariableDeclaration(name: string, type: string | undefined, kind:
295303
/**
296304
* Extract interface declaration
297305
*/
298-
function extractInterfaceDeclaration(node: ts.InterfaceDeclaration, sourceCode: string): Declaration {
306+
function extractInterfaceDeclaration(node: ts.InterfaceDeclaration, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration {
299307
const name = node.name.getText()
300308
const isExported = hasExportModifier(node)
301309

@@ -310,13 +318,17 @@ function extractInterfaceDeclaration(node: ts.InterfaceDeclaration, sourceCode:
310318
// Extract generics
311319
const generics = node.typeParameters?.map(tp => tp.getText()).join(', ')
312320

321+
// Extract comments if enabled
322+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
323+
313324
return {
314325
kind: 'interface',
315326
name,
316327
text,
317328
isExported,
318329
extends: extendsClause,
319330
generics: generics ? `<${generics}>` : undefined,
331+
leadingComments,
320332
start: node.getStart(),
321333
end: node.getEnd(),
322334
}
@@ -427,7 +439,7 @@ function getInterfaceBody(node: ts.InterfaceDeclaration): string {
427439
/**
428440
* Extract type alias declaration
429441
*/
430-
function extractTypeAliasDeclaration(node: ts.TypeAliasDeclaration, sourceCode: string): Declaration {
442+
function extractTypeAliasDeclaration(node: ts.TypeAliasDeclaration, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration {
431443
const name = node.name.getText()
432444
const isExported = hasExportModifier(node)
433445

@@ -437,12 +449,16 @@ function extractTypeAliasDeclaration(node: ts.TypeAliasDeclaration, sourceCode:
437449
// Extract generics
438450
const generics = node.typeParameters?.map(tp => tp.getText()).join(', ')
439451

452+
// Extract comments if enabled
453+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
454+
440455
return {
441456
kind: 'type',
442457
name,
443458
text,
444459
isExported,
445460
generics: generics ? `<${generics}>` : undefined,
461+
leadingComments,
446462
start: node.getStart(),
447463
end: node.getEnd(),
448464
}
@@ -474,7 +490,7 @@ function buildTypeDeclaration(node: ts.TypeAliasDeclaration, isExported: boolean
474490
/**
475491
* Extract class declaration
476492
*/
477-
function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string): Declaration {
493+
function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration {
478494
const name = node.name?.getText() || 'AnonymousClass'
479495
const isExported = hasExportModifier(node)
480496

@@ -497,6 +513,9 @@ function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string):
497513
// Check for abstract modifier
498514
const isAbstract = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.AbstractKeyword)
499515

516+
// Extract comments if enabled
517+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
518+
500519
return {
501520
kind: 'class',
502521
name,
@@ -506,6 +525,7 @@ function extractClassDeclaration(node: ts.ClassDeclaration, sourceCode: string):
506525
implements: implementsClause,
507526
generics: generics ? `<${generics}>` : undefined,
508527
modifiers: isAbstract ? ['abstract'] : undefined,
528+
leadingComments,
509529
start: node.getStart(),
510530
end: node.getEnd(),
511531
}
@@ -677,20 +697,24 @@ function buildClassBody(node: ts.ClassDeclaration): string {
677697
/**
678698
* Extract enum declaration
679699
*/
680-
function extractEnumDeclaration(node: ts.EnumDeclaration, sourceCode: string): Declaration {
700+
function extractEnumDeclaration(node: ts.EnumDeclaration, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration {
681701
const name = node.name.getText()
682702
const isExported = hasExportModifier(node)
683703
const text = getNodeText(node, sourceCode)
684704

685705
// Check for const modifier
686706
const isConst = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ConstKeyword)
687707

708+
// Extract comments if enabled
709+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
710+
688711
return {
689712
kind: 'enum',
690713
name,
691714
text,
692715
isExported,
693716
modifiers: isConst ? ['const'] : undefined,
717+
leadingComments,
694718
start: node.getStart(),
695719
end: node.getEnd(),
696720
}
@@ -699,7 +723,7 @@ function extractEnumDeclaration(node: ts.EnumDeclaration, sourceCode: string): D
699723
/**
700724
* Extract module/namespace declaration
701725
*/
702-
function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string): Declaration {
726+
function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string, sourceFile: ts.SourceFile, keepComments: boolean): Declaration {
703727
const name = node.name.getText()
704728
const isExported = hasExportModifier(node)
705729

@@ -709,12 +733,16 @@ function extractModuleDeclaration(node: ts.ModuleDeclaration, sourceCode: string
709733
// Check if this is an ambient module (quoted name)
710734
const isAmbient = ts.isStringLiteral(node.name)
711735

736+
// Extract comments if enabled
737+
const leadingComments = keepComments ? extractJSDocComments(node, sourceFile) : undefined
738+
712739
return {
713740
kind: 'module',
714741
name,
715742
text,
716743
isExported,
717744
source: isAmbient ? name.slice(1, -1) : undefined, // Remove quotes for ambient modules
745+
leadingComments,
718746
start: node.getStart(),
719747
end: node.getEnd(),
720748
}
@@ -983,6 +1011,57 @@ function getNodeText(node: ts.Node, sourceCode: string): string {
9831011
return sourceCode.slice(node.getStart(), node.getEnd())
9841012
}
9851013

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+
9861065
/**
9871066
* Get parameter name without default values for DTS
9881067
*/
@@ -1129,7 +1208,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
11291208
const interfaceNode = node as ts.InterfaceDeclaration
11301209
const interfaceName = interfaceNode.name.getText()
11311210
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
11331212
additionalDeclarations.push(decl)
11341213
referencedTypes.delete(interfaceName) // Remove to avoid duplicates
11351214
}
@@ -1139,7 +1218,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
11391218
const typeNode = node as ts.TypeAliasDeclaration
11401219
const typeName = typeNode.name.getText()
11411220
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
11431222
additionalDeclarations.push(decl)
11441223
referencedTypes.delete(typeName)
11451224
}
@@ -1150,7 +1229,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
11501229
if (classNode.name) {
11511230
const className = classNode.name.getText()
11521231
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
11541233
additionalDeclarations.push(decl)
11551234
referencedTypes.delete(className)
11561235
}
@@ -1161,7 +1240,7 @@ function extractReferencedTypeDeclarations(sourceFile: ts.SourceFile, referenced
11611240
const enumNode = node as ts.EnumDeclaration
11621241
const enumName = enumNode.name.getText()
11631242
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
11651244
additionalDeclarations.push(decl)
11661245
referencedTypes.delete(enumName)
11671246
}

src/generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export async function processFile(
122122
const sourceCode = await readFile(filePath, 'utf-8')
123123

124124
// Extract declarations
125-
const declarations = extractDeclarations(sourceCode, filePath)
125+
const declarations = extractDeclarations(sourceCode, filePath, config.keepComments)
126126

127127
// Create processing context
128128
const context: ProcessingContext = {

0 commit comments

Comments
 (0)