Skip to content

Commit f881469

Browse files
chore: wip
1 parent cbcb9f8 commit f881469

3 files changed

Lines changed: 315 additions & 101 deletions

File tree

packages/dtsx/src/extractor.ts

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,62 @@ import type { Declaration } from './types'
44
import { createSourceFile, forEachChild, isArrayBindingPattern, isBindingElement, isCallSignatureDeclaration, isConstructorDeclaration, isConstructSignatureDeclaration, isEnumDeclaration, isEnumMember, isExportAssignment, isFunctionDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isMethodSignature, isModuleBlock, isModuleDeclaration, isObjectBindingPattern, isPropertyDeclaration, isPropertySignature, isStringLiteral, isTypeAliasDeclaration, isVariableStatement, NodeFlags, ScriptKind, ScriptTarget, SyntaxKind } from 'typescript'
55

66
/**
7-
* Extract only public API declarations from TypeScript source code
8-
* This focuses on what should be in .d.ts files, not implementation details
7+
* Cache for parsed SourceFile objects to avoid re-parsing
8+
* Key: filePath, Value: { sourceFile, contentHash, lastAccess }
99
*/
10-
export function extractDeclarations(sourceCode: string, filePath: string, keepComments: boolean = true): Declaration[] {
11-
const declarations: Declaration[] = []
10+
const sourceFileCache = new Map<string, { sourceFile: SourceFile, contentHash: number, lastAccess: number }>()
11+
12+
/**
13+
* Maximum number of cached SourceFiles to prevent memory bloat
14+
*/
15+
const MAX_CACHE_SIZE = 100
16+
17+
/**
18+
* Simple hash function for content comparison
19+
*/
20+
function hashContent(content: string): number {
21+
let hash = 0
22+
for (let i = 0; i < content.length; i++) {
23+
const char = content.charCodeAt(i)
24+
hash = ((hash << 5) - hash) + char
25+
hash = hash & hash // Convert to 32-bit integer
26+
}
27+
return hash
28+
}
29+
30+
/**
31+
* Evict oldest entries if cache exceeds max size
32+
*/
33+
function evictOldestEntries(): void {
34+
if (sourceFileCache.size <= MAX_CACHE_SIZE) {
35+
return
36+
}
37+
38+
// Sort by last access time and remove oldest entries
39+
const entries = Array.from(sourceFileCache.entries())
40+
.sort((a, b) => a[1].lastAccess - b[1].lastAccess)
41+
42+
const toRemove = entries.slice(0, entries.length - MAX_CACHE_SIZE)
43+
for (const [key] of toRemove) {
44+
sourceFileCache.delete(key)
45+
}
46+
}
47+
48+
/**
49+
* Get or create a cached SourceFile
50+
*/
51+
function getSourceFile(filePath: string, sourceCode: string): SourceFile {
52+
const contentHash = hashContent(sourceCode)
53+
const cached = sourceFileCache.get(filePath)
54+
const now = Date.now()
55+
56+
if (cached && cached.contentHash === contentHash) {
57+
// Update last access time
58+
cached.lastAccess = now
59+
return cached.sourceFile
60+
}
1261

13-
// Create TypeScript source file
62+
// Create new SourceFile and cache it
1463
const sourceFile = createSourceFile(
1564
filePath,
1665
sourceCode,
@@ -19,6 +68,38 @@ export function extractDeclarations(sourceCode: string, filePath: string, keepCo
1968
ScriptKind.TS,
2069
)
2170

71+
sourceFileCache.set(filePath, { sourceFile, contentHash, lastAccess: now })
72+
73+
// Evict old entries if needed
74+
evictOldestEntries()
75+
76+
return sourceFile
77+
}
78+
79+
/**
80+
* Clear the SourceFile cache (useful for testing or memory management)
81+
*/
82+
export function clearSourceFileCache(): void {
83+
sourceFileCache.clear()
84+
}
85+
86+
/**
87+
* Get the current cache size (useful for debugging)
88+
*/
89+
export function getSourceFileCacheSize(): number {
90+
return sourceFileCache.size
91+
}
92+
93+
/**
94+
* Extract only public API declarations from TypeScript source code
95+
* This focuses on what should be in .d.ts files, not implementation details
96+
*/
97+
export function extractDeclarations(sourceCode: string, filePath: string, keepComments: boolean = true): Declaration[] {
98+
const declarations: Declaration[] = []
99+
100+
// Get or create cached TypeScript source file
101+
const sourceFile = getSourceFile(filePath, sourceCode)
102+
22103
// Visit only top-level declarations
23104
function visitTopLevel(node: Node) {
24105
// Only process top-level declarations, skip function bodies and implementation details

packages/dtsx/src/parser.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
1-
/* eslint-disable regexp/no-super-linear-backtracking */
2-
31
/**
42
* Remove leading comments from a declaration
53
*/
64
export function removeLeadingComments(text: string): string {
7-
return text.replace(/^(\s*\/\*[\s\S]*?\*\/\s*|\s*\/\/.*\n)*/g, '').trim()
5+
let result = text
6+
let changed = true
7+
8+
// Iteratively remove leading comments to avoid regex backtracking
9+
while (changed) {
10+
changed = false
11+
const trimmed = result.trimStart()
12+
13+
// Remove block comments /* ... */
14+
if (trimmed.startsWith('/*')) {
15+
const endIndex = trimmed.indexOf('*/', 2)
16+
if (endIndex !== -1) {
17+
result = trimmed.slice(endIndex + 2)
18+
changed = true
19+
continue
20+
}
21+
}
22+
23+
// Remove single-line comments // ...
24+
if (trimmed.startsWith('//')) {
25+
const newlineIndex = trimmed.indexOf('\n')
26+
if (newlineIndex !== -1) {
27+
result = trimmed.slice(newlineIndex + 1)
28+
changed = true
29+
}
30+
else {
31+
result = ''
32+
changed = true
33+
}
34+
}
35+
}
36+
37+
return result.trim()
838
}
939

1040
/**
@@ -184,9 +214,9 @@ export interface FunctionSignature {
184214
export function parseFunctionDeclaration(text: string): FunctionSignature | null {
185215
const clean = removeLeadingComments(text).trim()
186216

187-
// Match function pattern
217+
// Match function pattern (use \s+ after 'function' to avoid backtracking with optional \s*)
188218
const functionMatch = clean.match(
189-
/^(export\s+)?(async\s+)?function\s*(\*?)\s*([a-zA-Z_$][\w$]*)/,
219+
/^(export\s+)?(async\s+)?function\s*(\*?)([a-zA-Z_$][\w$]*)/,
190220
)
191221

192222
if (!functionMatch)

0 commit comments

Comments
 (0)