Skip to content

Commit 8f3f1b9

Browse files
chore: wip
1 parent 2924dde commit 8f3f1b9

5 files changed

Lines changed: 124 additions & 86 deletions

File tree

bun.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/dtsx/.test-cli/docs-test/api-docs/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# API Documentation
22

3-
> Generated on 2026-02-10T03:49:37.841Z
3+
> Generated on 2026-02-10T12:25:12.486Z
44
55
## Table of Contents
66

packages/dtsx/src/extractor/extract.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ export function extractDeclarations(
2424
const contentHash = hashContent(sourceCode)
2525
const cacheKey = `${filePath}:${keepComments ? 1 : 0}:${isolatedDeclarations ? 1 : 0}`
2626
const cached = declarationCache.get(cacheKey)
27-
const now = Date.now()
2827

2928
if (cached && cached.contentHash === contentHash) {
30-
cached.lastAccess = now
29+
cached.lastAccess = Date.now()
3130
return cached.declarations
3231
}
3332

3433
const declarations = scanDeclarations(sourceCode, filePath, keepComments, isolatedDeclarations)
3534

36-
declarationCache.set(cacheKey, { declarations, contentHash, lastAccess: now })
35+
declarationCache.set(cacheKey, { declarations, contentHash, lastAccess: Date.now() })
3736

3837
if (declarationCache.size > MAX_DECLARATION_CACHE_SIZE) {
3938
let oldestKey: string | null = null

packages/dtsx/src/extractor/scanner.ts

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,43 @@ export function scanDeclarations(source: string, filename: string, keepComments:
888888
if (!inner)
889889
return '()'
890890

891+
// Fast path: if params are already in DTS-safe form, return as-is
892+
// Requirements: no newlines in raw string, all params typed (have ':'), no destructuring/defaults/decorators/rest/modifiers
893+
if (rawParams.indexOf('\n') === -1 && inner.indexOf(':') !== -1
894+
&& inner.indexOf('{') === -1 && inner.indexOf('[') === -1 && inner.indexOf('=') === -1
895+
&& inner.indexOf('@') === -1 && inner.indexOf('...') === -1) {
896+
// Verify every param has a type annotation: count colons at depth 0 vs commas at depth 0
897+
let colons = 0
898+
let commas = 0
899+
let depth = 0
900+
for (let fi = 0; fi < inner.length; fi++) {
901+
const fc = inner.charCodeAt(fi)
902+
if (fc === CH_LPAREN || fc === CH_LANGLE || fc === CH_LBRACE) depth++
903+
else if (fc === CH_RPAREN || fc === CH_RANGLE || fc === CH_RBRACE) depth--
904+
else if (depth === 0) {
905+
if (fc === CH_COLON) colons++
906+
else if (fc === CH_COMMA) commas++
907+
}
908+
}
909+
// Every param needs at least one colon (commas + 1 params)
910+
if (colons >= commas + 1) {
911+
let hasModifier = false
912+
for (let m = 0; m < PARAM_MODIFIERS.length; m++) {
913+
const mod = PARAM_MODIFIERS[m]
914+
const modIdx = inner.indexOf(mod)
915+
if (modIdx !== -1) {
916+
const afterIdx = modIdx + mod.length
917+
if ((modIdx === 0 || !isIdentChar(inner.charCodeAt(modIdx - 1)))
918+
&& (afterIdx >= inner.length || !isIdentChar(inner.charCodeAt(afterIdx)))) {
919+
hasModifier = true
920+
break
921+
}
922+
}
923+
}
924+
if (!hasModifier) return rawParams
925+
}
926+
}
927+
891928
// Split parameters by comma at depth 0
892929
const params: string[] = []
893930
let paramStart = 0
@@ -1313,7 +1350,7 @@ export function scanDeclarations(source: string, filename: string, keepComments:
13131350

13141351
// Build DTS text
13151352
const dtsParams = buildDtsParams(rawParams)
1316-
const text = (isExported ? 'export ' : '') + 'declare function ' + (name || 'default') + generics + dtsParams + ': ' + returnType + ';'
1353+
const text = `${isExported ? 'export ' : ''}declare function ${name || 'default'}${generics}${dtsParams}: ${returnType};`
13171354
const comments = extractLeadingComments(declStart)
13181355

13191356
// Record index if this function had a body (implementation signature for overloads)
@@ -1510,7 +1547,7 @@ export function scanDeclarations(source: string, filename: string, keepComments:
15101547
const body = cleanBraceBlock(rawBody)
15111548

15121549
// Build DTS text
1513-
const text = (isExported ? 'export ' : '') + 'declare interface ' + name + generics + (extendsClause ? ' extends ' + extendsClause : '') + ' ' + body
1550+
const text = `${isExported ? 'export ' : ''}declare interface ${name}${generics}${extendsClause ? ` extends ${extendsClause}` : ''} ${body}`
15141551
const comments = extractLeadingComments(declStart)
15151552

15161553
return {
@@ -1567,7 +1604,7 @@ export function scanDeclarations(source: string, filename: string, keepComments:
15671604
pos++
15681605

15691606
// Build DTS text
1570-
const text = (isExported ? 'export ' : '') + 'type ' + name + generics + ' = ' + typeBody
1607+
const text = `${isExported ? 'export ' : ''}type ${name}${generics} = ${typeBody}`
15711608
const comments = extractLeadingComments(declStart)
15721609

15731610
return {
@@ -1681,7 +1718,7 @@ export function scanDeclarations(source: string, filename: string, keepComments:
16811718
const classBody = buildClassBodyDts()
16821719

16831720
// Build DTS text
1684-
const text = (isExported ? 'export ' : '') + 'declare ' + (isAbstract ? 'abstract ' : '') + 'class ' + name + generics + (extendsClause ? ' extends ' + extendsClause : '') + (implementsList && implementsList.length > 0 ? ' implements ' + implementsList.join(', ') : '') + ' ' + classBody
1721+
const text = `${isExported ? 'export ' : ''}declare ${isAbstract ? 'abstract ' : ''}class ${name}${generics}${extendsClause ? ` extends ${extendsClause}` : ''}${implementsList && implementsList.length > 0 ? ` implements ${implementsList.join(', ')}` : ''} ${classBody}`
16851722
const comments = extractLeadingComments(declStart)
16861723

16871724
return {
@@ -2484,7 +2521,7 @@ export function scanDeclarations(source: string, filename: string, keepComments:
24842521
const body = pos < len && source.charCodeAt(pos) === CH_LBRACE ? buildNamespaceBodyDts() : '{}'
24852522

24862523
// Build DTS text
2487-
const text = (isExported ? 'export ' : '') + 'declare ' + keyword + ' ' + name + ' ' + body
2524+
const text = `${isExported ? 'export ' : ''}declare ${keyword} ${name} ${body}`
24882525
const comments = extractLeadingComments(declStart)
24892526
const isAmbient = name.startsWith('\'') || name.startsWith('"')
24902527

@@ -3066,24 +3103,29 @@ function resolveReferencedTypes(declarations: Declaration[], nonExportedTypes: M
30663103
const declNames = new Set<string>()
30673104
for (const d of declarations) declNames.add(d.name)
30683105

3069-
// Build initial combined text once (excluding imports)
3070-
const combinedParts: string[] = []
3106+
// Keep declaration texts as an array — search each part individually (avoids giant join allocation)
3107+
const textParts: string[] = []
30713108
for (let i = 0; i < declarations.length; i++) {
30723109
if (declarations[i].kind !== 'import') {
3073-
combinedParts.push(declarations[i].text)
3110+
textParts.push(declarations[i].text)
30743111
}
30753112
}
3076-
let combinedText = combinedParts.length > 1
3077-
? combinedParts.join('\n')
3078-
: (combinedParts[0] || '')
30793113

30803114
for (;;) {
30813115
// Collect referenced non-exported types not yet resolved
30823116
const toInsert: Declaration[] = []
30833117
for (const [name, decl] of nonExportedTypes) {
30843118
if (resolved.has(name))
30853119
continue
3086-
if (combinedText && isWordInText(name, combinedText)) {
3120+
// Search each text part individually with early break
3121+
let found = false
3122+
for (let p = 0; p < textParts.length; p++) {
3123+
if (isWordInText(name, textParts[p])) {
3124+
found = true
3125+
break
3126+
}
3127+
}
3128+
if (found) {
30873129
if (!declNames.has(name)) {
30883130
toInsert.push(decl)
30893131
declNames.add(name)
@@ -3095,31 +3137,25 @@ function resolveReferencedTypes(declarations: Declaration[], nonExportedTypes: M
30953137
if (toInsert.length === 0)
30963138
break
30973139

3098-
// Insert at correct source positions to preserve declaration order
3099-
for (const decl of toInsert) {
3100-
const declStart = decl.start ?? Number.POSITIVE_INFINITY
3101-
let insertIdx = declarations.length
3102-
for (let i = 0; i < declarations.length; i++) {
3103-
const candidateStart = declarations[i].start ?? Number.POSITIVE_INFINITY
3104-
if (candidateStart > declStart) {
3105-
insertIdx = i
3106-
break
3107-
}
3140+
// Merge at correct source positions in a single O(n+k) pass (avoids O(k*n) splice)
3141+
toInsert.sort((a, b) => (a.start ?? Infinity) - (b.start ?? Infinity))
3142+
const merged: Declaration[] = []
3143+
let ti = 0
3144+
for (let i = 0; i < declarations.length; i++) {
3145+
const candidateStart = declarations[i].start ?? Infinity
3146+
while (ti < toInsert.length && (toInsert[ti].start ?? Infinity) <= candidateStart) {
3147+
merged.push(toInsert[ti++])
31083148
}
3109-
declarations.splice(insertIdx, 0, decl)
3149+
merged.push(declarations[i])
31103150
}
3151+
while (ti < toInsert.length) merged.push(toInsert[ti++])
3152+
declarations.length = 0
3153+
for (let i = 0; i < merged.length; i++) declarations.push(merged[i])
31113154

3112-
// Append newly inserted declaration text for next iteration checks
3113-
if (toInsert.length > 0) {
3114-
const appendedParts: string[] = []
3115-
for (const decl of toInsert) {
3116-
if (decl.kind !== 'import') {
3117-
appendedParts.push(decl.text)
3118-
}
3119-
}
3120-
if (appendedParts.length > 0) {
3121-
const appendedText = appendedParts.length > 1 ? appendedParts.join('\n') : appendedParts[0]
3122-
combinedText = combinedText ? `${combinedText}\n${appendedText}` : appendedText
3155+
// Append new texts to parts array for next iteration
3156+
for (const decl of toInsert) {
3157+
if (decl.kind !== 'import') {
3158+
textParts.push(decl.text)
31233159
}
31243160
}
31253161
}

packages/dtsx/src/processor/index.ts

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ export function processDeclarations(
8080
keepComments: boolean = true,
8181
importOrder: string[] = ['bun'],
8282
): string {
83-
const output: string[] = []
83+
// Build result string directly instead of output array (avoids array + join allocation)
84+
let result = ''
8485

8586
// Extract and add triple-slash directives at the top of the file
8687
// Fast check: skip whitespace with charCodeAt to avoid trimStart() allocation
@@ -90,8 +91,10 @@ export function processDeclarations(
9091
if (_si < src.length - 2 && src.charCodeAt(_si) === 47 && src.charCodeAt(_si + 1) === 47 && src.charCodeAt(_si + 2) === 47) {
9192
const tripleSlashDirectives = extractTripleSlashDirectives(src)
9293
if (tripleSlashDirectives.length > 0) {
93-
output.push(...tripleSlashDirectives)
94-
output.push('') // Blank line after directives
94+
for (let i = 0; i < tripleSlashDirectives.length; i++) {
95+
if (result) result += '\n'
96+
result += tripleSlashDirectives[i]
97+
}
9598
}
9699
}
97100

@@ -183,29 +186,27 @@ export function processDeclarations(
183186
}
184187
}
185188

186-
// Build reference check sets for interfaces (optimized: single pass over text sources)
189+
// Build reference check sets for interfaces (search each declaration directly, no join)
187190
const interfaceReferences = new Set<string>()
188191
if (interfaces.length > 0) {
189-
// Build combined text from functions/classes/types for interface reference check
190-
const refCheckParts: string[] = []
191-
for (const func of functions) {
192-
if (func.isExported)
193-
refCheckParts.push(func.text)
194-
}
195-
for (const cls of classes) {
196-
refCheckParts.push(cls.text)
197-
}
198-
for (const type of types) {
199-
refCheckParts.push(type.text)
200-
}
201-
const refCheckText = refCheckParts.length > 0 ? refCheckParts.join('\n') : ''
202-
203-
if (refCheckText) {
204-
for (const iface of interfaces) {
205-
if (refCheckText.includes(iface.name)) {
206-
interfaceReferences.add(iface.name)
192+
for (const iface of interfaces) {
193+
let found = false
194+
if (!found) {
195+
for (const func of functions) {
196+
if (func.isExported && func.text.includes(iface.name)) { found = true; break }
197+
}
198+
}
199+
if (!found) {
200+
for (const cls of classes) {
201+
if (cls.text.includes(iface.name)) { found = true; break }
207202
}
208203
}
204+
if (!found) {
205+
for (const type of types) {
206+
if (type.text.includes(iface.name)) { found = true; break }
207+
}
208+
}
209+
if (found) interfaceReferences.add(iface.name)
209210
}
210211
}
211212

@@ -261,14 +262,14 @@ export function processDeclarations(
261262
combinedTextParts.push(exp.text)
262263
}
263264

264-
// Import detection: scan combined declaration text once per import item
265-
const combinedText = combinedTextParts.length > 0
266-
? (combinedTextParts.length > 1 ? combinedTextParts.join('\n') : combinedTextParts[0])
267-
: ''
268-
if (combinedText) {
265+
// Import detection: scan each declaration text individually (avoids giant join allocation)
266+
if (combinedTextParts.length > 0) {
269267
for (const item of allImportedItemsMap.keys()) {
270-
if (isWordInText(item, combinedText)) {
271-
usedImportItems.add(item)
268+
for (let p = 0; p < combinedTextParts.length; p++) {
269+
if (isWordInText(item, combinedTextParts[p])) {
270+
usedImportItems.add(item)
271+
break
272+
}
272273
}
273274
}
274275
}
@@ -344,14 +345,17 @@ export function processDeclarations(
344345
})
345346
}
346347

347-
output.push(...processedImports)
348-
349-
// Always add blank line after imports if there are any imports
350-
if (processedImports.length > 0)
351-
output.push('')
348+
// Append imports to result
349+
for (let i = 0; i < processedImports.length; i++) {
350+
if (result) result += '\n'
351+
result += processedImports[i]
352+
}
352353

353354
// Process type exports first
354-
for (let i = 0; i < typeExportStatements.length; i++) output.push(typeExportStatements[i])
355+
for (let i = 0; i < typeExportStatements.length; i++) {
356+
if (result) result += '\n'
357+
result += typeExportStatements[i]
358+
}
355359

356360
// Process other declarations — iterate each group directly (no spread allocation)
357361
const declGroups = [functions, variables, interfaces, types, classes, enums, modules]
@@ -391,23 +395,24 @@ export function processDeclarations(
391395
default:
392396
assertNever(kind, `Unhandled declaration kind in processor: ${kind}`)
393397
}
394-
if (processed) output.push(processed)
398+
if (processed) {
399+
if (result) result += '\n'
400+
result += processed
401+
}
395402
}
396403
}
397404

398405
// Process value exports
399-
for (let i = 0; i < valueExportStatements.length; i++) output.push(valueExportStatements[i])
406+
for (let i = 0; i < valueExportStatements.length; i++) {
407+
if (result) result += '\n'
408+
result += valueExportStatements[i]
409+
}
400410

401411
// Process default export last
402-
for (let i = 0; i < defaultExport.length; i++) output.push(defaultExport[i])
403-
404-
// Build final output — skip empty strings inline instead of filter()
405-
let result = ''
406-
for (let i = 0; i < output.length; i++) {
407-
if (output[i] !== '') {
408-
if (result) result += '\n'
409-
result += output[i]
410-
}
412+
for (let i = 0; i < defaultExport.length; i++) {
413+
if (result) result += '\n'
414+
result += defaultExport[i]
411415
}
416+
412417
return result
413418
}

0 commit comments

Comments
 (0)