diff --git a/.vscode/dictionary.txt b/.vscode/dictionary.txt index 27ac7d2..b024570 100644 --- a/.vscode/dictionary.txt +++ b/.vscode/dictionary.txt @@ -15,6 +15,7 @@ destructurable dtsx entrypoints heroicons +iconify localtunnels lockb mkcert @@ -27,12 +28,16 @@ Postcardware postcompile prefetch preinstall +shikijs socio softprops Solana stacksjs tlsx +twoslash typecheck +unconfig +unocss unplugin unref upath diff --git a/eslint.config.js b/eslint.config.ts similarity index 61% rename from eslint.config.js rename to eslint.config.ts index d24b632..d0d3d85 100644 --- a/eslint.config.js +++ b/eslint.config.ts @@ -1,6 +1,7 @@ +import type { ESLintConfig } from '@stacksjs/eslint-config' import stacks from '@stacksjs/eslint-config' -export default stacks({ +const config: ESLintConfig = stacks({ stylistic: { indent: 2, quotes: 'single', @@ -13,3 +14,5 @@ export default stacks({ 'fixtures/**', ], }) + +export default config diff --git a/fixtures/output/0005.d.ts b/fixtures/output/0005.d.ts index d4a09e2..43689bf 100644 --- a/fixtures/output/0005.d.ts +++ b/fixtures/output/0005.d.ts @@ -18,8 +18,8 @@ declare function createProcessingState(): ProcessingState; declare function createImportTrackingState(): ImportTrackingState; declare function indentMultilineType(type: string, baseIndent: string, isLast: boolean): string; declare function inferValueType(value: string): string; -declare function inferArrayType(value: string, state?: ProcessingState, preserveLineBreaks): string; -declare function inferComplexObjectType(value: string, state?: ProcessingState, indentLevel): string; +declare function inferArrayType(value: string, state?: ProcessingState, preserveLineBreaks?: boolean): string; +declare function inferComplexObjectType(value: string, state?: ProcessingState, indentLevel?: number): string; declare function inferConstArrayType(value: string, state?: ProcessingState): string; declare function inferConstType(value: string, state: ProcessingState): string; declare function inferTypeFromDefaultValue(defaultValue: string): string; @@ -48,16 +48,16 @@ declare function processModuleBlock(cleanDeclaration: string, declarationText: s export declare function processSpecificDeclaration(declarationWithoutComments: string, fullDeclaration: string, state: ProcessingState): void; declare function processSourceFile(content: string, state: ProcessingState): void; declare function processImports(line: string, state: ImportTrackingState): void; -declare function processType(declaration: string, isExported): string; +declare function processType(declaration: string, isExported?: boolean): string; declare function processTypeExport(line: string, state: ProcessingState): void; declare function processVariable(declaration: string, isExported: boolean, state: ProcessingState): string; -declare function processFunction(declaration: string, usedTypes?: Set, isExported): string; +declare function processFunction(declaration: string, usedTypes?: Set, isExported?: boolean): string; declare function getCleanDeclaration(declaration: string): string; declare function processGeneratorFunction(declaration: string): string; -declare function processInterface(declaration: string, isExported): string; +declare function processInterface(declaration: string, isExported?: boolean): string; declare function processModule(declaration: string): string; declare function processObjectMethod(declaration: string): ProcessedMethod; -declare function processObjectProperties(content: string, state?: ProcessingState, indentLevel): Array<{ key: string, value: string }>; +declare function processObjectProperties(content: string, state?: ProcessingState, indentLevel?: number): Array<{ key: string, value: string }>; declare function processPropertyValue(value: string, indentLevel: number, state?: ProcessingState): string; declare function trackTypeUsage(content: string, state: ImportTrackingState): void; declare function trackValueUsage(content: string, state: ImportTrackingState): void; diff --git a/fixtures/output/0007.d.ts b/fixtures/output/0007.d.ts index 9036c9c..ecdcf6c 100644 --- a/fixtures/output/0007.d.ts +++ b/fixtures/output/0007.d.ts @@ -6,9 +6,14 @@ export declare interface Cert { privateKey: string } export declare function generateRandomSerial(verbose?: boolean): string; -export declare function calculateValidityDates(options: { validityDays?: number, validityYears?: number, notBeforeDays?: number, verbose?: boolean }): void; +export declare function calculateValidityDates(options: { + validityDays?: number + validityYears?: number + notBeforeDays?: number + verbose?: boolean +}): void; declare function generateCertificateExtensions(options: CertificateOptions): void; -export declare function createRootCA(options: CAOptions): Promise; +export declare function createRootCA(options: CAOptions = {}): Promise; export declare function generateCertificate(options: CertificateOptions): Promise; export declare function addCertToSystemTrustStoreAndSaveCert(cert: Cert, caCert: string, options?: TlsOption): Promise; export declare function storeCertificate(cert: Cert, options?: TlsOption): string; diff --git a/fixtures/output/0009.d.ts b/fixtures/output/0009.d.ts index 644d6a8..1a441c6 100644 --- a/fixtures/output/0009.d.ts +++ b/fixtures/output/0009.d.ts @@ -1,4 +1,15 @@ -export declare function loadConfig({ name, cwd, defaultConfig, endpoint, headers): Promise; +import type { Config } from './types'; + +export declare function loadConfig({ + name, + cwd, + defaultConfig, + endpoint, + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, +}: Config): Promise; export * from './types' export * from './utils' \ No newline at end of file diff --git a/fixtures/output/imports.d.ts b/fixtures/output/imports.d.ts index 5a6517c..0240bb5 100644 --- a/fixtures/output/imports.d.ts +++ b/fixtures/output/imports.d.ts @@ -5,11 +5,11 @@ import { something as dts } from './generate'; export declare function actionsPath(path?: string): string; export declare function corePath(path?: string): string; -export declare function frameworkPath(path?: string, options?: { relative?: , boolean, cwd?: , string }): string; +export declare function frameworkPath(path?: string, options?: { relative?: boolean, cwd?: string }): string; export declare function storagePath(path?: string): string; -export declare function projectPath(filePath, options?: { relative: , boolean }): string; -export declare function userActionsPath(path?: string, options?: { relative: , true }): string; -export declare function builtUserActionsPath(path?: string, options?: { relative: , boolean }): string; +export declare function projectPath(filePath = '', options?: { relative: boolean }): string; +export declare function userActionsPath(path?: string, options?: { relative: true }): string; +export declare function builtUserActionsPath(path?: string, options?: { relative: boolean }): string; export declare function homeDir(path?: string): string; export declare type LibraryType = 'vue-components' | 'web-components' | 'functions' export declare function libraryEntryPath(type: LibraryType): string; diff --git a/src/extract.ts b/src/extract.ts index 6844166..a134167 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -196,7 +196,7 @@ function extractFunctionSignature(declaration: string, verbose?: boolean | strin rest = restAfterGenerics.trim() debugLog('signature-after-generics', `Remaining content: ${rest}`, verbose) - // Extract parameters + // Extract parameters with full object type support const { params, rest: restAfterParams } = extractParams(rest, verbose) rest = restAfterParams.trim() debugLog('signature-after-params', `Remaining content: ${rest}`, verbose) @@ -205,10 +205,20 @@ function extractFunctionSignature(declaration: string, verbose?: boolean | strin const { returnType } = extractReturnType(rest) debugLog('signature-return', `Extracted return type: ${returnType}`, verbose) + // Handle object parameter types + let processedParams = params + if (params.includes('{')) { + const objectMatch = params.match(/\{([^}]+)\}:\s*([^)]+)/) + if (objectMatch) { + const [, paramList, typeRef] = objectMatch + processedParams = `{ ${paramList} }: ${typeRef}` + } + } + const signature = { name, generics, - params, + params: processedParams, returnType, } @@ -2046,12 +2056,89 @@ function processVariable(declaration: string, isExported: boolean, state: Proces * Process function declarations with overloads */ function processFunction(declaration: string, usedTypes?: Set, isExported = true, verbose?: boolean | string[]): string { - const normalizedDeclaration = declaration.trim().replace(/\s+/g, ' ') + const normalizedDeclaration = declaration.trim() const signature = extractFunctionSignature(normalizedDeclaration, verbose) - // Clean up params + // Clean up all parameters - both object destructuring and regular parameters if (signature.params) { - signature.params = cleanParameterTypes(signature.params) + // Handle regular parameters + if (!signature.params.includes('{')) { + const cleanedParams = signature.params + .split(',') + .map((param) => { + const paramTrimmed = param.trim() + if (!paramTrimmed) + return null + + // Handle parameters with default values and type annotations + if (paramTrimmed.includes('=')) { + const [paramPart] = paramTrimmed.split(/\s*=\s*/) + const paramWithoutDefault = paramPart.trim() + + // If there's an explicit type annotation, use it + if (paramWithoutDefault.includes(':')) { + const [paramName, paramType] = paramWithoutDefault.split(':').map(p => p.trim()) + return `${paramName}?: ${paramType}` + } + + // Infer type from default value if no explicit type + const defaultValue = paramTrimmed.split(/\s*=\s*/)[1].trim() + const inferredType = defaultValue === '{}' + ? paramTrimmed.includes(':') ? paramTrimmed.split(':')[1].trim().split('=')[0].trim() : 'Record' + : defaultValue === '' + ? 'string' + : typeof eval(defaultValue) + + return `${paramWithoutDefault}?: ${inferredType}` + } + + // Handle parameters without default values + if (paramTrimmed.includes(':')) { + const [paramName, paramType] = paramTrimmed.split(':').map(p => p.trim()) + const isOptional = paramName.endsWith('?') + const cleanName = paramName.replace(/\?$/, '') + return `${cleanName}${isOptional ? '?' : ''}: ${paramType}` + } + + return paramTrimmed + }) + .filter(Boolean) + .join(', ') + + signature.params = cleanedParams + } + // Handle object destructuring (existing code) + else { + const paramMatch = declaration.match(/\{([^}]+)\}:\s*([^)]+)/) + if (paramMatch) { + const [, paramList, typeRef] = paramMatch + + const cleanedParams = paramList + .split(',') + .map((param) => { + const paramTrimmed = param.trim() + if (!paramTrimmed) + return null + + const hasDefaultValue = paramTrimmed.includes('=') + const [paramName] = paramTrimmed.split(/\s*=\s*/) + const nameOnly = paramName.trim() + + const isOptional = hasDefaultValue || nameOnly.endsWith('?') + const cleanName = nameOnly.replace(/\?$/, '') + + if (cleanName.includes(':')) { + const [name, type] = cleanName.split(':').map(p => p.trim()) + return `${name}${isOptional ? '?' : ''}: ${type}` + } + + return `${cleanName}${isOptional ? '?' : ''}` + }) + .filter(Boolean) + + signature.params = `{ ${cleanedParams.join(', ')} }: ${typeRef.trim()}` + } + } } // Preserve type predicates and complete return types