Skip to content

Commit 619dfa2

Browse files
committed
chore: wip
chore: wip chore: wip
1 parent 381b121 commit 619dfa2

File tree

2 files changed

+131
-39
lines changed

2 files changed

+131
-39
lines changed

fixtures/output/example-0001.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ declare interface Options<T> {
9292
cwd?: string;
9393
defaultConfig: T;
9494
}
95-
export declare async function loadConfig<T extends Record<string, unknown>>(): void;
95+
export declare async function loadConfig<T extends Record<string, unknown>(): void;
9696
declare const dtsConfig: DtsGenerationConfig;
9797
export { generate, dtsConfig }
9898
export declare type { DtsGenerationOption }

src/extract.ts

+130-38
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,54 @@ export function parseObjectLiteral(objStr: string): PropertyInfo[] {
606606
return extractObjectProperties([content])
607607
}
608608

609+
/**
610+
* Parses a function declaration into its components
611+
*/
612+
export function parseFunctionDeclaration(declaration: string): FunctionParseState {
613+
// Initial state
614+
const state: FunctionParseState = {
615+
genericParams: '',
616+
functionName: '',
617+
parameters: '',
618+
returnType: 'void',
619+
isAsync: false,
620+
}
621+
622+
// Clean the declaration and check for async
623+
state.isAsync = declaration.includes('async')
624+
let cleanDeclaration = declaration
625+
.replace(/^export\s+/, '')
626+
.replace(/^async\s+/, '')
627+
.replace(/^function\s+/, '')
628+
.trim()
629+
630+
// Function name and generic parameters extraction
631+
const functionMatch = cleanDeclaration.match(/^([^(<\s]+)(\s*<[^>]+>)?/)
632+
if (functionMatch) {
633+
state.functionName = functionMatch[1]
634+
if (functionMatch[2]) {
635+
state.genericParams = functionMatch[2].trim()
636+
}
637+
cleanDeclaration = cleanDeclaration.slice(functionMatch[0].length).trim()
638+
}
639+
640+
// Parameter extraction
641+
const paramsMatch = cleanDeclaration.match(/\(([\s\S]*?)\)/)
642+
if (paramsMatch) {
643+
state.parameters = paramsMatch[1].trim()
644+
cleanDeclaration = cleanDeclaration.slice(paramsMatch[0].length).trim()
645+
}
646+
647+
// Return type extraction
648+
// eslint-disable-next-line regexp/no-super-linear-backtracking
649+
const returnMatch = cleanDeclaration.match(/^:\s*(.+?)(?:$|\{)/)
650+
if (returnMatch) {
651+
state.returnType = returnMatch[1].trim().replace(/;$/, '')
652+
}
653+
654+
return state
655+
}
656+
609657
/**
610658
* Process simple value types (string, number, boolean)
611659
*/
@@ -701,54 +749,86 @@ export function processTypeDeclaration(declaration: string, isExported = true):
701749
}
702750

703751
/**
704-
* Process function declarations
752+
* Process function declarations with full type information preservation
705753
*/
706754
export function processFunctionDeclaration(
707755
declaration: string,
708756
usedTypes: Set<string>,
709757
isExported = true,
710758
): string {
711-
const functionSignature = declaration.split('{')[0].trim()
712-
const asyncKeyword = functionSignature.includes('async') ? 'async ' : ''
713-
const functionName = functionSignature
714-
.replace('export ', '')
715-
.replace('async ', '')
716-
.split('(')[0]
717-
.trim()
718-
const params = functionSignature.split('(')[1].split(')')[0].trim()
719-
const returnType = getReturnType(functionSignature)
720-
721-
if (returnType && returnType !== 'void') {
722-
// Add base type and any generic parameters to usedTypes
723-
const baseType = returnType.split('<')[0].trim()
724-
usedTypes.add(baseType)
725-
726-
// Extract types from generic parameters if present
727-
const genericMatch = returnType.match(/<([^>]+)>/)?.[1]
728-
if (genericMatch) {
729-
genericMatch.split(',').forEach((type) => {
730-
const cleanType = type.trim().split('<')[0].trim()
731-
if (cleanType)
732-
usedTypes.add(cleanType)
733-
})
734-
}
759+
// Get the declaration up to the function body
760+
const signatureMatch = declaration.match(/^.*?(?=\{|$)/)
761+
if (!signatureMatch)
762+
return declaration
763+
764+
const signature = signatureMatch[0].trim()
765+
const parseResult = parseFunctionDeclaration(signature)
766+
767+
// Add types to usedTypes set
768+
const addTypeToUsed = (type: string) => {
769+
if (!type)
770+
return
771+
772+
// Split on any special characters that might separate types
773+
const types = type.split(/[<>,\s()]+/)
774+
775+
types.forEach((t) => {
776+
const cleanType = t.trim()
777+
if (cleanType && !cleanType.match(/^(void|any|number|string|boolean|null|undefined|never|unknown|Promise)$/)) {
778+
usedTypes.add(cleanType)
779+
}
780+
})
735781
}
736782

737-
return `${isExported ? 'export ' : ''}declare ${asyncKeyword}function ${functionName}(${params}): ${returnType};`
738-
.replace('function function', 'function')
783+
// Process return type and add to used types
784+
addTypeToUsed(parseResult.returnType)
785+
786+
// Process generic parameters if present
787+
if (parseResult.genericParams) {
788+
const genericContent = parseResult.genericParams.slice(1, -1) // Remove < >
789+
genericContent.split(',').forEach((param) => {
790+
const parts = param.trim().split(' extends ')
791+
if (parts[1]) {
792+
addTypeToUsed(parts[1])
793+
}
794+
})
795+
}
796+
797+
// Construct the final declaration
798+
return [
799+
isExported ? 'export ' : '',
800+
'declare ',
801+
parseResult.isAsync ? 'async ' : '',
802+
'function ',
803+
parseResult.functionName,
804+
parseResult.genericParams,
805+
'(',
806+
parseResult.parameters,
807+
'): ',
808+
parseResult.returnType,
809+
';',
810+
].join('')
739811
}
740812

741813
/**
742814
* Get function return type
743815
*/
744816
export function getReturnType(functionSignature: string): string {
745-
const returnTypeMatch = functionSignature.match(REGEX.returnType)
817+
const returnTypeMatch = functionSignature.match(/\):\s*([^{;]+)/)
746818
if (!returnTypeMatch)
747819
return 'void'
748820

749-
return returnTypeMatch[1]
750-
.replace(/[;,]$/, '')
751-
.trim()
821+
const returnType = returnTypeMatch[1].trim()
822+
823+
// Handle Promise types
824+
if (returnType.includes('Promise')) {
825+
const promiseMatch = returnType.match(/Promise\s*<(.+)>/)
826+
if (promiseMatch) {
827+
return `Promise<${promiseMatch[1].trim()}>`
828+
}
829+
}
830+
831+
return returnType
752832
}
753833

754834
// Helper functions for line processing
@@ -796,6 +876,17 @@ export function processDeclarationLine(line: string, state: ProcessingState): vo
796876
}
797877
}
798878

879+
/**
880+
* Represents the current state of function parsing
881+
*/
882+
export interface FunctionParseState {
883+
genericParams: string
884+
functionName: string
885+
parameters: string
886+
returnType: string
887+
isAsync: boolean
888+
}
889+
799890
export function formatOutput(state: ProcessingState): string {
800891
const uniqueImports = processImports(state.imports, state.usedTypes)
801892
const dynamicImports = Array.from(state.usedTypes)
@@ -816,19 +907,20 @@ export function formatOutput(state: ProcessingState): string {
816907
line.startsWith('*') ? ` ${line}` : line,
817908
)
818909

910+
// Ensure double newline after imports
911+
const importSection = allImports.length > 0 ? [...allImports, '', ''] : []
912+
819913
let result = [
820-
...allImports,
821-
'',
822-
'', // Extra newline after imports
914+
...importSection,
823915
...declarations,
824916
].filter(Boolean).join('\n')
825917

826-
// Clean up default export - extract just the identifier
918+
// Clean up default export if present
827919
if (state.defaultExport) {
828920
const exportIdentifier = state.defaultExport
829-
.replace(/^export\s+default\s+/, '') // Remove leading export default
830-
.replace(/export\s+default\s+/, '') // Remove any additional export default
831-
.replace(/;+$/, '') // Remove trailing semicolons
921+
.replace(/^export\s+default\s+/, '')
922+
.replace(/export\s+default\s+/, '')
923+
.replace(/;+$/, '')
832924
.trim()
833925

834926
result += `\nexport default ${exportIdentifier};\n`

0 commit comments

Comments
 (0)