Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Nov 2, 2024
1 parent 01dc5cb commit 64851ec
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 54 deletions.
12 changes: 6 additions & 6 deletions fixtures/input/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ export function extractFunctionSignature(declaration: string): FunctionSignature
}
}

// TODO: Type Inference in Functions
// export function createApi<T extends Record<string, (...args: any[]) => any>>(
// endpoints: T
// ): { [K in keyof T]: ReturnType<T[K]> extends Promise<infer R> ? R : ReturnType<T[K]> } {
// return {} as any
// }
// Type Inference in Functions
export function createApi<T extends Record<string, (...args: any[]) => any>>(
endpoints: T
): { [K in keyof T]: ReturnType<T[K]> extends Promise<infer R> ? R : ReturnType<T[K]> } {
return {} as any
}
9 changes: 6 additions & 3 deletions fixtures/output/function.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { BunPlugin } from 'bun';
import type { DtsGenerationOption } from '@stacksjs/dtsx';
export declare function fetchUsers(): Promise<ResponseData>;
export declare function getProduct(id: number): Promise<ApiResponse<Product>>;
export declare function authenticate(user: string, password: string): Promise<AuthResponse>;
export declare function fetchUsers(): Promise<ResponseData;
export declare function getProduct(id: number): Promise<ApiResponse<Product;
export declare function authenticate(user: string, password: string): Promise<AuthResponse;
export declare function dts(options?: DtsGenerationOption): BunPlugin;
declare const cwd: unknown;
export declare function loadConfig<T extends Record<string, unknown>>({ name, cwd, defaultConfig }: Options<T>): Promise<T;
declare const c: unknown;
declare const configPath: unknown;
declare const importedConfig: unknown;
Expand All @@ -14,6 +15,7 @@ export declare function processData(data: number): number;
export declare function processData(data: boolean): boolean;
export declare function processData<T extends object>(data: T): T;
export declare function processData(data: unknown): unknown;
export declare function complexAsyncGenerator(): any;
declare const results: unknown;
export declare function isUser(value: unknown): value is User;
export declare function extractFunctionSignature(declaration: string): FunctionSignature;
Expand All @@ -24,3 +26,4 @@ declare const name: unknown;
declare const genericsResult: unknown;
declare const paramsResult: unknown;
declare const match: unknown;
export declare function createApi<T extends Record<string,(...args: any[]) => any>();
2 changes: 1 addition & 1 deletion fixtures/output/variable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export declare const complexArrays: {
};
export declare const complexObject: {
handlers: {
onSuccess: <T> (data: T) => Promise<void>;
onSuccess: <T> (data: T) => Promise<unknown>;
onError: (error: Error & { code?: number }) => never
};
utils: {
Expand Down
102 changes: 58 additions & 44 deletions src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,8 @@ function extractBalancedSymbols(text: string, openSymbol: string, closeSymbol: s
* Extract complete function signature using regex
*/
function extractFunctionSignature(declaration: string): FunctionSignature {
// Remove comments and clean up the declaration
const cleanDeclaration = removeLeadingComments(declaration).trim()
const functionPattern = /^\s*(export\s+)?(async\s+)?function\s*(?:(\*)\s*)?([^(<\s]+)/
const functionPattern = /^\s*(export\s+)?(async\s+)?function\s*(\*)?\s*([a-zA-Z_$][\w$]*)/
const functionMatch = cleanDeclaration.match(functionPattern)

if (!functionMatch) {
Expand Down Expand Up @@ -237,10 +236,10 @@ function extractFunctionSignature(declaration: string): FunctionSignature {
}
}

// Extract return type - keep it exactly as specified
// Extract return type
let returnType = 'void'
if (rest.startsWith(':')) {
const match = rest.match(/^:\s*([^{]+)/)
const match = rest.match(/^:\s*([^=>{]+)/)
if (match) {
returnType = match[1].trim()
}
Expand Down Expand Up @@ -828,43 +827,65 @@ function inferReturnType(value: string, declaration: string): string {
const isAsync = declaration.startsWith('async ') || value.includes('async ') || (value.includes('=>') && value.includes('await'))
debugLog(undefined, 'return-type', `Is async method: ${isAsync}`)

let effectiveReturnType = 'void'
// Check for generator functions
const isGenerator = declaration.includes('function*') || value.includes('function*')

let effectiveReturnType = 'unknown'

// Check for known return patterns
if (value.includes('throw')) {
effectiveReturnType = 'never'
}
else if (value.includes('toISOString()') || value.includes('toString()')) {
else if (value.includes('toISOString()')) {
effectiveReturnType = 'string'
}
else if (value.includes('Intl.NumberFormat') && value.includes('format')) {
effectiveReturnType = 'string'
}
else if (value.match(/^\{\s*\/\/[^}]*\}$/) || value.match(/^\{\s*\}$/) || value.match(/^\{\s*\/\*[\s\S]*?\*\/\s*\}$/)) {
effectiveReturnType = 'void'
else if (value.includes('Promise.all')) {
effectiveReturnType = 'Promise<unknown[]>'
}
else if (value.includes('fetch(')) {
effectiveReturnType = 'Promise<unknown>'
}
else {
// Check for return statements
const returnMatch = value.match(/return\s+([^;\s]+)/)
if (returnMatch) {
const returnValue = returnMatch[1]
if (/^['"`]/.test(returnValue))
if (returnValue.includes('as ')) {
const typeAssertionMatch = returnValue.match(/as\s+([^;\s]+)/)
if (typeAssertionMatch) {
effectiveReturnType = typeAssertionMatch[1]
}
}
else if (/^['"`]/.test(returnValue)) {
effectiveReturnType = 'string'
else if (!Number.isNaN(Number(returnValue)))
}
else if (!Number.isNaN(Number(returnValue))) {
effectiveReturnType = 'number'
else if (returnValue === 'true' || returnValue === 'false')
}
else if (returnValue === 'true' || returnValue === 'false') {
effectiveReturnType = 'boolean'
else if (returnValue === 'null')
}
else if (returnValue === 'null') {
effectiveReturnType = 'null'
else if (returnValue === 'undefined')
}
else if (returnValue === 'undefined') {
effectiveReturnType = 'undefined'
else effectiveReturnType = 'unknown'
}
else {
effectiveReturnType = 'unknown'
}
}
}

// Wrap in Promise for async functions
if (isAsync && !effectiveReturnType.includes('Promise')) {
debugLog(undefined, 'return-type', `Wrapping ${effectiveReturnType} in Promise for async method`)
// Handle generators
if (isGenerator) {
effectiveReturnType = `Generator<unknown, ${effectiveReturnType}, unknown>`
}
// Handle async functions
else if (isAsync && !effectiveReturnType.includes('Promise')) {
effectiveReturnType = `Promise<${effectiveReturnType}>`
}

Expand Down Expand Up @@ -921,7 +942,7 @@ export function isDefaultExport(line: string): boolean {
return line.trim().startsWith('export default')
}

function isDeclarationStart(line: string): boolean {
export function isDeclarationStart(line: string): boolean {
return (
line.startsWith('export ')
|| line.startsWith('interface ')
Expand All @@ -933,6 +954,7 @@ function isDeclarationStart(line: string): boolean {
|| line.startsWith('declare module')
|| /^export\s+(?:interface|type|const|function|async\s+function)/.test(line)
|| line.startsWith('export async function')
|| line.startsWith('export function') // Added this line
)
}

Expand Down Expand Up @@ -1045,7 +1067,7 @@ export function processBlock(lines: string[], comments: string[], state: Process
return
}

if (cleanDeclaration.startsWith('function') || cleanDeclaration.startsWith('export function')) {
if (/^(export\s+)?(async\s+)?function\s*(\*)?/.test(cleanDeclaration)) {
const isExported = cleanDeclaration.startsWith('export')
state.dtsLines.push(processFunction(declarationText, state.usedTypes, isExported))
return
Expand Down Expand Up @@ -1217,16 +1239,12 @@ function processSourceFile(content: string, state: ProcessingState): void {
const trimmedLine = line.trim()

// Track comments
if (trimmedLine.startsWith('/*')) {
currentComments.push(line)
continue
}
if (trimmedLine.startsWith('//')) {
if (trimmedLine.startsWith('/*') || trimmedLine.startsWith('//')) {
currentComments.push(line)
continue
}

// Track brackets and parentheses for nesting depth
// Start of a new declaration
if (isDeclarationStart(trimmedLine)) {
if (inDeclaration && currentBlock.length > 0) {
processBlock(currentBlock, currentComments, state)
Expand All @@ -1238,7 +1256,7 @@ function processSourceFile(content: string, state: ProcessingState): void {
inDeclaration = true
currentBlock = [line]

// Initialize depths for the first line
// Update depths
parenDepth += (line.match(/\(/g) || []).length
parenDepth -= (line.match(/\)/g) || []).length
bracketDepth += (line.match(/\{/g) || []).length
Expand All @@ -1247,7 +1265,7 @@ function processSourceFile(content: string, state: ProcessingState): void {
continue
}

// If we're in a declaration, track the nesting
// If in a declaration, collect lines
if (inDeclaration) {
currentBlock.push(line)

Expand All @@ -1260,16 +1278,14 @@ function processSourceFile(content: string, state: ProcessingState): void {
// Check if the declaration is complete
const isComplete = (
parenDepth === 0
&& bracketDepth === 0 && (
&& bracketDepth === 0
&& (
trimmedLine.endsWith(';')
|| trimmedLine.endsWith('}')
|| trimmedLine.endsWith(',')
|| trimmedLine.match(/\bas\s+const[,;]?$/)
|| (!trimmedLine.endsWith('{') && !trimmedLine.endsWith(',')) // Function overloads
)
)

debugLog(state, 'source-processing', `Line "${trimmedLine}": parenDepth=${parenDepth}, bracketDepth=${bracketDepth}, complete=${isComplete}`)

if (isComplete) {
processBlock(currentBlock, currentComments, state)
currentBlock = []
Expand Down Expand Up @@ -1401,17 +1417,13 @@ export function processFunction(
usedTypes?: Set<string>,
isExported = true,
): string {
// Remove comments from the declaration for parsing
const cleanDeclaration = removeLeadingComments(declaration).trim()

const {
name,
params,
returnType,
generics,
} = extractFunctionSignature(cleanDeclaration)
// Determine if the function has a body
const hasBody = /\{[\s\S]*\}$/.test(cleanDeclaration)

const { name, params, returnType, generics } = extractFunctionSignature(cleanDeclaration)

// Track used types if provided
if (usedTypes) {
trackUsedTypes(`${generics} ${params} ${returnType}`, usedTypes)
}
Expand All @@ -1424,15 +1436,17 @@ export function processFunction(
name,
generics,
`(${params})`,
':',
returnType,
';',
]

if (returnType && returnType !== 'void') {
parts.push(':', returnType)
}

parts.push(';')

return parts
.filter(Boolean)
.join(' ')
// Include ':' in the character classes to handle spacing around colons
.replace(/\s+([<>(),;:])/g, '$1')
.replace(/([<>(),;:])\s+/g, '$1 ')
.replace(/\s{2,}/g, ' ')
Expand Down

0 comments on commit 64851ec

Please sign in to comment.