From 25bad4615f05da1653cf669373d8793b8e07b91d Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 29 Oct 2024 23:29:38 +0100 Subject: [PATCH] chore: wip --- fixtures/input/example-0001.ts | 546 ------------------------------ fixtures/input/variable.ts | 185 ++++++++++ fixtures/output/example-0001.d.ts | 201 ----------- fixtures/output/variable.d.ts | 48 +++ fixtures/tbd/class.ts | 11 + fixtures/tbd/enum.ts | 7 + fixtures/tbd/exports.ts | 12 + fixtures/tbd/function.ts | 154 +++++++++ fixtures/tbd/interface.ts | 89 +++++ fixtures/tbd/module.ts | 12 + fixtures/tbd/type.ts | 67 ++++ 11 files changed, 585 insertions(+), 747 deletions(-) delete mode 100644 fixtures/input/example-0001.ts create mode 100644 fixtures/input/variable.ts delete mode 100644 fixtures/output/example-0001.d.ts create mode 100644 fixtures/output/variable.d.ts create mode 100644 fixtures/tbd/class.ts create mode 100644 fixtures/tbd/enum.ts create mode 100644 fixtures/tbd/exports.ts create mode 100644 fixtures/tbd/function.ts create mode 100644 fixtures/tbd/interface.ts create mode 100644 fixtures/tbd/module.ts create mode 100644 fixtures/tbd/type.ts diff --git a/fixtures/input/example-0001.ts b/fixtures/input/example-0001.ts deleted file mode 100644 index 16863e4..0000000 --- a/fixtures/input/example-0001.ts +++ /dev/null @@ -1,546 +0,0 @@ -import type { BunPlugin } from 'bun' -import process from 'node:process' -import { generate, deepMerge } from '@stacksjs/dtsx' -import type { DtsGenerationConfig, DtsGenerationOption } from '@stacksjs/dtsx' -import { existsSync } from 'node:fs' -import { resolve } from 'node:path' - -// Testing some randomness -/** - * Example of const declaration - */ -export const conf: { [key: string]: string } = { - apiUrl: 'https://api.stacksjs.org', - timeout: '5000', // as string -} - -export const someObject = { - someString: 'Stacks', - someNumber: 1000, - someBoolean: true, - someFalse: false, - someFunction: () => { console.log('hello world') }, - anotherOne: () => { - // some comment - /* some other comment */ - return some.object ?? 'default' - }, - someArray: [1, 2, 3], - someNestedArray: [ - [1, 2, 3], - [4, 5, 6, 7, 8, 9, 10], - ], - someNestedArray2: [ - [1, 2, 3], - [4, 5, 6, 7, 8, 9, 10], - 'dummy value', - ], - someNestedArray3: [ - [1, 2, 3], - [4, 5, 6, 7, 8, 9, 10], - 'dummy value', - [11, 12, 13], - ], - someOtherNestedArray: [ - [ - 'some text', - 2, - console.log, - () => console.log('hello world'), - helloWorld, - ], - [4, 5, 6, 7, 8, 9, 10], - ], - someComplexArray: [ - [ - { key: 'value' }, - ], - [ - { key2: 'value2' }, - 'test', - 1000, - ], - [ - 'some string', - console.log, - someFunction(), - ] - ], - someObject: { key: 'value' }, - someNestedObject: { - key: { - nestedKey: 'value', - }, - otherKey: { - nestedKey: process.cwd(), - nestedKey2: () => { console.log('hello world') }, - } - }, - someNestedObjectArray: [ - { key: 'value' }, - { key2: 'value2' }, - ], - someOtherObject: some.deep.object, - someInlineCall2: console.log, - someInlineCall3: console.log(), -} - -/** - * Example of interface declaration - * with another comment in an extra line - */ -export interface User { - id: number - name: string - email: string -} - -/** - * Example of type declaration - * - * with multiple lines of comments, including an empty line - */ -export interface ResponseData { - success: boolean - data: User[] -} - -/** - * Example of function declaration - * - * - * with multiple empty lines, including an empty lines - */ -export function fetchUsers(): Promise { - return fetch(conf.apiUrl) - .then(response => response.json()) as Promise -} - -export interface ApiResponse { - status: number - message: string - data: T -} - -/** - * Example of another const declaration - * -* with multiple empty lines, including being poorly formatted - */ -const settings: { [key: string]: any } = { - theme: 'dark', - language: 'en', -} - -export interface Product { - id: number - name: string - price: number -} - -/** - * Example of function declaration - */ -export function getProduct(id: number): Promise> { - return fetch(`${settings.apiUrl}/products/${id}`) - .then(response => response.json()) as Promise> -} - -export interface AuthResponse { - token: string - expiresIn: number -} - -export type AuthStatus = 'authenticated' | 'unauthenticated' - -export function authenticate(user: string, password: string): Promise { - return fetch('/auth/login', { - method: 'POST', - body: JSON.stringify({ user, password }), - }).then(response => response.json()) as Promise -} - -export const defaultHeaders = { - 'Content-Type': 'application/json', -} - -export function dts(options?: DtsGenerationOption): BunPlugin { - return { - name: 'bun-plugin-dtsx', - - async setup(build) { - const cwd = options?.cwd ?? process.cwd() - const root = options?.root ?? build.config.root - const entrypoints = options?.entrypoints ?? build.config.entrypoints - const outdir = options?.outdir ?? build.config.outdir - const keepComments = options?.keepComments ?? true - const clean = options?.clean ?? false - const tsconfigPath = options?.tsconfigPath ?? './tsconfig.json' - - await generate({ - ...options, - cwd, - root, - entrypoints, - outdir, - keepComments, - clean, - tsconfigPath, - }) - }, - } -} - -interface Options { - name: string - cwd?: string - defaultConfig: T -} - -export async function loadConfig>({ name, cwd, defaultConfig }: Options): Promise { - const c = cwd ?? process.cwd() - const configPath = resolve(c, `${name}.config`) - - if (existsSync(configPath)) { - try { - const importedConfig = await import(configPath) - const loadedConfig = importedConfig.default || importedConfig - return deepMerge(defaultConfig, loadedConfig) - } - catch (error) { - console.error(`Error loading config from ${configPath}:`, error) - } - } - - return defaultConfig -} - -// Get loaded config -// eslint-disable-next-line antfu/no-top-level-await -const dtsConfig: DtsGenerationConfig = await loadConfig({ - name: 'dts', - cwd: process.cwd(), - defaultConfig: { - cwd: process.cwd(), - root: './src', - entrypoints: ['**/*.ts'], - outdir: './dist', - keepComments: true, - clean: true, - tsconfigPath: './tsconfig.json', - }, -}) - -export { generate, dtsConfig } - -export type { DtsGenerationOption } - -export { config } from './config' -export * from './extract' -export * from './generate' -export * from './types' -export * from './utils' - -// Complex Generic Types -export interface ComplexGeneric, K extends keyof T> { - data: T - key: K - value: T[K] - transform: (input: T[K]) => string - nested: Array> -} - -// Intersection and Union Types -export type ComplexUnionIntersection = - | (User & { role: 'admin' }) - | (Product & { category: string }) - & { - metadata: Record - } - -// Mapped and Conditional Types -export type ReadonlyDeep = { - readonly [P in keyof T]: T[P] extends object ? ReadonlyDeep : T[P] -} - -export type ConditionalResponse = T extends Array - ? ApiResponse - : T extends object - ? ApiResponse - : ApiResponse - -// Complex Function Overloads -export function processData(data: string): string -export function processData(data: number): number -export function processData(data: boolean): boolean -export function processData(data: T): T -export function processData(data: unknown): unknown { - return data -} - -export type EventType = 'click' | 'focus' | 'blur' -export type ElementType = 'button' | 'input' | 'form' -export type EventHandler = `on${Capitalize}${Capitalize}` - -// Recursive Types -export type RecursiveObject = { - id: string - children?: RecursiveObject[] - parent?: RecursiveObject - metadata: Record -} - -// Complex Arrays and Tuples -export const complexArrays = { - matrix: [ - [1, 2, [3, 4, [5, 6]]], - ['a', 'b', ['c', 'd']], - [true, [false, [true]]], - ], - tuples: [ - [1, 'string', true] as const, - ['literal', 42, false] as const, - ], - // TODO: get this part to generate correctly - // mixedArrays: [ - // new Date(), - // Promise.resolve('async'), - // async () => 'result', - // function* generator() { yield 42 }, - // ] -} - -// TODO: Nested Object Types with Methods -// export const complexObject = { -// handlers: { -// async onSuccess(data: T): Promise { -// console.log(data) -// }, -// onError(error: Error & { code?: number }): never { -// throw error -// } -// }, -// utils: { -// formatters: { -// date: (input: Date) => input.toISOString(), -// currency: (amount: number, currency = 'USD') => -// new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount) -// } -// } -// } - -// Default Type Parameters -export interface DefaultGeneric< - T = string, - K extends keyof any = string, - V extends Record = Record -> { - key: K - value: T - record: V -} - -// TODO: Method Decorators and Metadata -// export const methodDecorator = ( -// target: any, -// propertyKey: string, -// descriptor: PropertyDescriptor -// ) => { -// return { -// ...descriptor, -// enumerable: true, -// } -// } - -// Complex Async Patterns -> due to isolatedDeclarations, we can assume the return type here -export async function* complexAsyncGenerator(): any { - const results = await Promise.all([ - fetchUsers(), - getProduct(1), - authenticate('user', 'pass'), - ]) - - for (const result of results) { - yield result - } -} - -// Type Assertions and Guards -export function isUser(value: unknown): value is User { - return ( - typeof value === 'object' - && value !== null - && 'id' in value - && 'email' in value - ) -} - -// Branded Types -export type UserId = string & { readonly __brand: unique symbol } -export type ProductId = number & { - readonly __brand: unique symbol -} - -// TODO: Complex Error Handling -// export class CustomError extends Error { -// constructor( -// message: string, -// public readonly code: number, -// public readonly metadata: Record -// ) { -// super(message) -// this.name = 'CustomError' -// } -// } - -// Module Augmentation -declare module '@stacksjs/some-module' { - interface DtsGenerationConfig { - customPlugins?: Array<{ - name: string - transform: (code: string) => string - }> - } -} - -// Utility Type Implementations -export type DeepPartial = T extends object ? { - [P in keyof T]?: DeepPartial -} : T - -export type DeepRequired = T extends object ? { - [P in keyof T]-?: DeepRequired -} : T - -// TODO: Complex Constants with Type Inference -// export const CONFIG_MAP = { -// development: { -// features: { -// auth: { -// providers: ['google', 'github'] as const, -// settings: { timeout: 5000, retries: 3 } -// } -// } -// }, -// production: { -// features: { -// auth: { -// providers: ['google', 'github', 'microsoft'] as const, -// settings: { timeout: 3000, retries: 5 } -// } -// } -// } -// } as const - -// Polymorphic Types -export type PolymorphicComponent

= { - ( - props: { as?: C } & Omit, keyof P> & P - ): React.ReactElement | null -} - -// TODO: Type Inference in Functions -// export function createApi any>>( -// endpoints: T -// ): { [K in keyof T]: ReturnType extends Promise ? R : ReturnType } { -// return {} as any -// } - -// Complex Index Types -export type DynamicRecord = { - [P in K]: P extends number - ? Array - : P extends string - ? Record - : never -} - -// Comments variations -/** - * Regular expression patterns used throughout the module - */ -interface RegexPatterns { - /** Import type declarations */ - readonly typeImport: RegExp - /** Regular import declarations */ - readonly regularImport: RegExp - /** Async function declarations */ - readonly asyncFunction: RegExp - /** Generic type parameters */ - readonly functionOverload: RegExp - /** Module declaration pattern */ - readonly moduleDeclaration: RegExp - /** - * Module augmentation pattern - */ - readonly moduleAugmentation: RegExp -} - -/** - * Extract complete function signature using regex - */ -export 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 functionMatch = cleanDeclaration.match(functionPattern) - - if (!functionMatch) { - console.error('Function name could not be extracted from declaration:', declaration) - return { - name: '', - params: '', - returnType: 'void', - generics: '', - } - } - - const name = functionMatch[4] - let rest = cleanDeclaration.slice(cleanDeclaration.indexOf(name) + name.length).trim() - - // Extract generics - let generics = '' - if (rest.startsWith('<')) { - const genericsResult = extractBalancedSymbols(rest, '<', '>') - if (genericsResult) { - generics = genericsResult.content - rest = genericsResult.rest.trim() - } - } - - // Extract parameters - let params = '' - if (rest.startsWith('(')) { - const paramsResult = extractBalancedSymbols(rest, '(', ')') - if (paramsResult) { - params = paramsResult.content.slice(1, -1).trim() - rest = paramsResult.rest.trim() - } - } - - // Extract return type - keep it exactly as specified - let returnType = 'void' - if (rest.startsWith(':')) { - const match = rest.match(/^:\s*([^{]+)/) - if (match) { - returnType = match[1].trim() - } - } - - return { - name, - params, - returnType: normalizeType(returnType), - generics, - } -} - -// export interface ImportTrackingState { -// typeImports: Map> -// valueImports: Map> -// usedTypes: Set -// usedValues: Set -// } - -export default dts diff --git a/fixtures/input/variable.ts b/fixtures/input/variable.ts new file mode 100644 index 0000000..8e2d6fe --- /dev/null +++ b/fixtures/input/variable.ts @@ -0,0 +1,185 @@ +import process from 'node:process' +import type { DtsGenerationConfig } from '@stacksjs/dtsx' + +/** + * Example of const declaration + */ +export const conf: { [key: string]: string } = { + apiUrl: 'https://api.stacksjs.org', + timeout: '5000', // as string +} + +export let test = 'test' + +export var helloWorld = 'Hello World' + +export const someObject = { + someString: 'Stacks', + someNumber: 1000, + someBoolean: true, + someFalse: false, + someFunction: () => { console.log('hello world') }, + anotherOne: () => { + // some comment + /* some other comment */ + return some.object ?? 'default' + }, + someArray: [1, 2, 3], + someNestedArray: [ + [1, 2, 3], + [4, 5, 6, 7, 8, 9, 10], + ], + someNestedArray2: [ + [1, 2, 3], + [4, 5, 6, 7, 8, 9, 10], + 'dummy value', + ], + someNestedArray3: [ + [1, 2, 3], + [4, 5, 6, 7, 8, 9, 10], + 'dummy value', + [11, 12, 13], + ], + someOtherNestedArray: [ + [ + 'some text', + 2, + console.log, + () => console.log('hello world'), + helloWorld, + ], + [4, 5, 6, 7, 8, 9, 10], + ], + someComplexArray: [ + [ + { key: 'value' }, + ], + [ + { key2: 'value2' }, + 'test', + 1000, + ], + [ + 'some string', + console.log, + someFunction(), + ] + ], + someObject: { key: 'value' }, + someNestedObject: { + key: { + nestedKey: 'value', + }, + otherKey: { + nestedKey: process.cwd(), + nestedKey2: () => { console.log('hello world') }, + } + }, + someNestedObjectArray: [ + { key: 'value' }, + { key2: 'value2' }, + ], + someOtherObject: some.deep.object, + someInlineCall2: console.log, + someInlineCall3: console.log(), +} + +/** + * Example of another const declaration + * +* with multiple empty lines, including being poorly formatted + */ +const settings: { [key: string]: any } = { + theme: 'dark', + language: 'en', +} + +export const defaultHeaders = { + 'Content-Type': 'application/json', +} + +// eslint-disable-next-line antfu/no-top-level-await +const dtsConfig: DtsGenerationConfig = await loadConfig({ + name: 'dts', + cwd: process.cwd(), + defaultConfig: { + cwd: process.cwd(), + root: './src', + entrypoints: ['**/*.ts'], + outdir: './dist', + keepComments: true, + clean: true, + tsconfigPath: './tsconfig.json', + }, +}) + +// Complex Arrays and Tuples +export const complexArrays = { + matrix: [ + [1, 2, [3, 4, [5, 6]]], + ['a', 'b', ['c', 'd']], + [true, [false, [true]]], + ], + tuples: [ + [1, 'string', true] as const, + ['literal', 42, false] as const, + ], + // TODO: get this part to generate correctly + // mixedArrays: [ + // new Date(), + // Promise.resolve('async'), + // async () => 'result', + // function* generator() { yield 42 }, + // ] +} + +// TODO: Nested Object Types with Methods +// export const complexObject = { +// handlers: { +// async onSuccess(data: T): Promise { +// console.log(data) +// }, +// onError(error: Error & { code?: number }): never { +// throw error +// } +// }, +// utils: { +// formatters: { +// date: (input: Date) => input.toISOString(), +// currency: (amount: number, currency = 'USD') => +// new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount) +// } +// } +// } + +// TODO: Method Decorators and Metadata +// export const methodDecorator = ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) => { +// return { +// ...descriptor, +// enumerable: true, +// } +// } + +// TODO: Complex Constants with Type Inference +// export const CONFIG_MAP = { +// development: { +// features: { +// auth: { +// providers: ['google', 'github'] as const, +// settings: { timeout: 5000, retries: 3 } +// } +// } +// }, +// production: { +// features: { +// auth: { +// providers: ['google', 'github', 'microsoft'] as const, +// settings: { timeout: 3000, retries: 5 } +// } +// } +// } +// } as const diff --git a/fixtures/output/example-0001.d.ts b/fixtures/output/example-0001.d.ts deleted file mode 100644 index a467461..0000000 --- a/fixtures/output/example-0001.d.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { BunPlugin } from 'bun'; -import type { DtsGenerationConfig, DtsGenerationOption } from '@stacksjs/dtsx'; -import { generate } from '@stacksjs/dtsx'; - -/** - * Example of const declaration - */ -export declare const conf: { [key: string]: string }; -export declare const someObject: { - someString: 'Stacks'; - someNumber: 1000; - someBoolean: true; - someFalse: false; - someFunction: (...args: any[]) => unknown; - anotherOne: (...args: any[]) => unknown; - someArray: Array<1 | 2 | 3>; - someNestedArray: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10>>; - someNestedArray2: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10> | 'dummy value'>; - someNestedArray3: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10> | 'dummy value' | Array<11 | 12 | 13>>; - someOtherNestedArray: Array unknown)> | Array<4 | 5 | 6 | 7 | 8 | 9 | 10>>; - someComplexArray: Array | Array<{ key2: 'value2' } | 'test' | 1000> | Array<'some string' | unknown>>; - someObject: { - key: 'value'; - }; - someNestedObject: { - key: { - nestedKey: 'value'; - }; - otherKey: { - nestedKey: () => void; - nestedKey2: (...args: any[]) => unknown; - }; - }; -}; -/** - * Example of interface declaration - * with another comment in an extra line - */ -export declare interface User { - id: number - name: string - email: string -} -/** - * Example of type declaration - * - * with multiple lines of comments, including an empty line - */ -export declare interface ResponseData { - success: boolean - data: User[] -} -/** - * Example of function declaration - * - * - * with multiple empty lines, including an empty lines - */ -export declare function fetchUsers(): Promise; -export declare interface ApiResponse { - status: number - message: string - data: T -} -/** - * Example of another const declaration - * -* with multiple empty lines, including being poorly formatted - */ -declare const settings: { [key: string]: any }; -export declare interface Product { - id: number - name: string - price: number -} -/** - * Example of function declaration - */ -export declare function getProduct(id: number): Promise>; -export declare interface AuthResponse { - token: string - expiresIn: number -} -export declare type AuthStatus = 'authenticated' | 'unauthenticated' -export declare function authenticate(user: string, password: string): Promise; -export declare const defaultHeaders: { - 'Content-Type': 'application/json'; -}; -export declare function dts(options?: DtsGenerationOption): BunPlugin; -declare interface Options { - name: string - cwd?: string - defaultConfig: T -} -export declare function loadConfig>({ name, cwd, defaultConfig }: Options): Promise; -declare const dtsConfig: DtsGenerationConfig; -export declare interface ComplexGeneric, K extends keyof T> { - data: T - key: K - value: T[K] - transform: (input: T[K]) => string - nested: Array> -} -export declare type ComplexUnionIntersection = - | (User & { role: 'admin' }) - | (Product & { category: string }) - & { - metadata: Record - } -export declare type ReadonlyDeep = { - readonly [P in keyof T]: T[P] extends object ? ReadonlyDeep : T[P] -} -export declare type ConditionalResponse = T extends Array - ? ApiResponse - : T extends object - ? ApiResponse - : ApiResponse -export declare function processData(data: string): string; -export declare function processData(data: number): number; -export declare function processData(data: boolean): boolean; -export declare function processData(data: T): T; -export declare function processData(data: unknown): unknown; -export declare type EventType = 'click' | 'focus' | 'blur' -export declare type ElementType = 'button' | 'input' | 'form' -export declare type EventHandler = `on${Capitalize}${Capitalize}` -export declare type RecursiveObject = { - id: string - children?: RecursiveObject[] - parent?: RecursiveObject - metadata: Record -} -export declare const complexArrays: { - matrix: Array>> | Array<'a' | 'b' | Array<'c' | 'd'>> | Array>>>; - tuples: Array | Array<'literal' | 42 | false>>; -}; -export declare interface DefaultGeneric< - T = string, - K extends keyof any = string, - V extends Record = Record -> { - key: K - value: T - record: V -} -export declare function complexAsyncGenerator(): any; -export declare function isUser(value: unknown): value is User; -export declare type UserId = string & { readonly __brand: unique symbol } -export declare type ProductId = number & { - readonly __brand: unique symbol -} -declare module '@stacksjs/some-module' { - interface DtsGenerationConfig { - customPlugins?: Array<{ - name: string - transform: (code: string) => string - }> - } -} -export declare type DeepPartial = T extends object ? { - [P in keyof T]?: DeepPartial -} : T -export declare type DeepRequired = T extends object ? { - [P in keyof T]-?: DeepRequired -} : T -export declare type PolymorphicComponent

= { - ( - props: { as?: C } & Omit, keyof P> & P - ): React.ReactElement | null -} -export declare type DynamicRecord = { - [P in K]: P extends number - ? Array - : P extends string - ? Record - : never -} -/** - * Regular expression patterns used throughout the module - */ -declare interface RegexPatterns { - readonly typeImport: RegExp - readonly regularImport: RegExp - readonly asyncFunction: RegExp - readonly functionOverload: RegExp - readonly moduleDeclaration: RegExp - readonly moduleAugmentation: RegExp -} -/** - * Extract complete function signature using regex - */ -export declare function extractFunctionSignature(declaration: string): FunctionSignature; - -export { generate, dtsConfig } -export type { DtsGenerationOption } -export { config } from './config' -export * from './extract' -export * from './generate' -export * from './types' -export * from './utils' - -export default dts; diff --git a/fixtures/output/variable.d.ts b/fixtures/output/variable.d.ts new file mode 100644 index 0000000..4b581dc --- /dev/null +++ b/fixtures/output/variable.d.ts @@ -0,0 +1,48 @@ +import type { DtsGenerationConfig } from '@stacksjs/dtsx'; + +/** + * Example of const declaration + */ +export declare const conf: { [key: string]: string }; +export declare let test = 'test' +export declare var helloWorld = 'Hello World' +export declare const someObject: { + someString: 'Stacks'; + someNumber: 1000; + someBoolean: true; + someFalse: false; + someFunction: (...args: any[]) => unknown; + anotherOne: (...args: any[]) => unknown; + someArray: Array<1 | 2 | 3>; + someNestedArray: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10>>; + someNestedArray2: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10> | 'dummy value'>; + someNestedArray3: Array | Array<4 | 5 | 6 | 7 | 8 | 9 | 10> | 'dummy value' | Array<11 | 12 | 13>>; + someOtherNestedArray: Array unknown)> | Array<4 | 5 | 6 | 7 | 8 | 9 | 10>>; + someComplexArray: Array | Array<{ key2: 'value2' } | 'test' | 1000> | Array<'some string' | unknown>>; + someObject: { + key: 'value'; + }; + someNestedObject: { + key: { + nestedKey: 'value'; + }; + otherKey: { + nestedKey: () => void; + nestedKey2: (...args: any[]) => unknown; + }; + }; +}; +/** + * Example of another const declaration + * +* with multiple empty lines, including being poorly formatted + */ +declare const settings: { [key: string]: any }; +export declare const defaultHeaders: { + 'Content-Type': 'application/json'; +}; +declare const dtsConfig: DtsGenerationConfig; +export declare const complexArrays: { + matrix: Array>> | Array<'a' | 'b' | Array<'c' | 'd'>> | Array>>>; + tuples: Array | Array<'literal' | 42 | false>>; +}; diff --git a/fixtures/tbd/class.ts b/fixtures/tbd/class.ts new file mode 100644 index 0000000..e54e93f --- /dev/null +++ b/fixtures/tbd/class.ts @@ -0,0 +1,11 @@ +// TODO: Complex Error Handling +// export class CustomError extends Error { +// constructor( +// message: string, +// public readonly code: number, +// public readonly metadata: Record +// ) { +// super(message) +// this.name = 'CustomError' +// } +// } diff --git a/fixtures/tbd/enum.ts b/fixtures/tbd/enum.ts new file mode 100644 index 0000000..e4e3c8e --- /dev/null +++ b/fixtures/tbd/enum.ts @@ -0,0 +1,7 @@ +export enum ExampleEnum { + First = 'first', + Second = 'second', + Third = 'third', + Fourth = 4, + Fifth = 5, +} diff --git a/fixtures/tbd/exports.ts b/fixtures/tbd/exports.ts new file mode 100644 index 0000000..7341c0e --- /dev/null +++ b/fixtures/tbd/exports.ts @@ -0,0 +1,12 @@ +import { generate, something as dts } from './generate' +import { dtsConfig } from './config' + +export { generate, dtsConfig } + +export default dts + +export { config } from './config' +export * from './extract' +export * from './generate' +export * from './types' +export * from './utils' diff --git a/fixtures/tbd/function.ts b/fixtures/tbd/function.ts new file mode 100644 index 0000000..798bd9d --- /dev/null +++ b/fixtures/tbd/function.ts @@ -0,0 +1,154 @@ +import type { BunPlugin } from 'bun' +import process from 'node:process' +import { generate, deepMerge } from '@stacksjs/dtsx' +import type { DtsGenerationOption } from '@stacksjs/dtsx' +import { existsSync } from 'node:fs' +import { resolve } from 'node:path' + +/** + * Example of function declaration + * + * + * with multiple empty lines, including an empty lines + */ +export function fetchUsers(): Promise { + return fetch(conf.apiUrl) + .then(response => response.json()) as Promise +} + +/** + * Example of function declaration + */ +export function getProduct(id: number): Promise> { + return fetch(`${settings.apiUrl}/products/${id}`) + .then(response => response.json()) as Promise> +} + +export function authenticate(user: string, password: string): Promise { + return fetch('/auth/login', { + method: 'POST', + body: JSON.stringify({ user, password }), + }).then(response => response.json()) as Promise +} + +export function dts(options?: DtsGenerationOption): BunPlugin { + return { + name: 'bun-plugin-dtsx', + + async setup(build) { + const cwd = options?.cwd ?? process.cwd() + + await generate({ + ...options, + cwd, + }) + }, + } +} + +export async function loadConfig>({ name, cwd, defaultConfig }: Options): Promise { + const c = cwd ?? process.cwd() + const configPath = resolve(c, `${name}.config`) + + if (existsSync(configPath)) { + try { + const importedConfig = await import(configPath) + const loadedConfig = importedConfig.default || importedConfig + return deepMerge(defaultConfig, loadedConfig) + } + catch (error) { + console.error(`Error loading config from ${configPath}:`, error) + } + } + + return defaultConfig +} + +// Complex Function Overloads +export function processData(data: string): string +export function processData(data: number): number +export function processData(data: boolean): boolean +export function processData(data: T): T +export function processData(data: unknown): unknown { + return data +} + +// Complex Async Patterns -> due to isolatedDeclarations, we can assume the return type here +export async function* complexAsyncGenerator(): any { + const results = await Promise.all([ + fetchUsers(), + getProduct(1), + authenticate('user', 'pass'), + ]) + + for (const result of results) { + yield result + } +} + +// Type Assertions and Guards +export function isUser(value: unknown): value is User { + return ( + typeof value === 'object' + && value !== null + && 'id' in value + && 'email' in value + ) +} + +/** + * Extract complete function signature using regex + */ +export 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 functionMatch = cleanDeclaration.match(functionPattern) + + const name = functionMatch[4] + let rest = cleanDeclaration.slice(cleanDeclaration.indexOf(name) + name.length).trim() + + // Extract generics + let generics = '' + if (rest.startsWith('<')) { + const genericsResult = extractBalancedSymbols(rest, '<', '>') + if (genericsResult) { + generics = genericsResult.content + rest = genericsResult.rest.trim() + } + } + + // Extract parameters + let params = '' + if (rest.startsWith('(')) { + const paramsResult = extractBalancedSymbols(rest, '(', ')') + if (paramsResult) { + params = paramsResult.content.slice(1, -1).trim() + rest = paramsResult.rest.trim() + } + } + + // Extract return type - keep it exactly as specified + let returnType = 'void' + if (rest.startsWith(':')) { + const match = rest.match(/^:\s*([^{]+)/) + if (match) { + returnType = match[1].trim() + } + } + + return { + name, + params, + returnType: normalizeType(returnType), + generics, + } +} + +// TODO: Type Inference in Functions +// export function createApi any>>( +// endpoints: T +// ): { [K in keyof T]: ReturnType extends Promise ? R : ReturnType } { +// return {} as any +// } diff --git a/fixtures/tbd/interface.ts b/fixtures/tbd/interface.ts new file mode 100644 index 0000000..376884f --- /dev/null +++ b/fixtures/tbd/interface.ts @@ -0,0 +1,89 @@ +/** + * Example of interface declaration + * with another comment in an extra line + */ +export interface User { + id: number + name: string + email: string +} + +export interface ApiResponse { + status: number + message: string + data: T +} + +/** + * Example of an interface declaration + * + * with multiple lines of comments, including an empty line + */ +export interface ResponseData { + success: boolean + data: User[] +} + +export interface Product { + id: number + name: string + price: number +} + +export interface AuthResponse { + token: string + expiresIn: number +} + +interface Options { + name: string + cwd?: string + defaultConfig: T +} + +// Complex Generic Types +export interface ComplexGeneric, K extends keyof T> { + data: T + key: K + value: T[K] + transform: (input: T[K]) => string + nested: Array> +} + +// Default Type Parameters +export interface DefaultGeneric< + T = string, + K extends keyof any = string, + V extends Record = Record +> { + key: K + value: T + record: V +} + +/** + * Regular expression patterns used throughout the module + */ +interface RegexPatterns { + /** Import type declarations */ + readonly typeImport: RegExp + /** Regular import declarations */ + readonly regularImport: RegExp + /** Async function declarations */ + readonly asyncFunction: RegExp + /** Generic type parameters */ + readonly functionOverload: RegExp + /** Module declaration pattern */ + readonly moduleDeclaration: RegExp + /** + * Module augmentation pattern + */ + readonly moduleAugmentation: RegExp +} + +export interface ImportTrackingState { + typeImports: Map> + valueImports: Map> + usedTypes: Set + usedValues: Set +} diff --git a/fixtures/tbd/module.ts b/fixtures/tbd/module.ts new file mode 100644 index 0000000..c36e6e1 --- /dev/null +++ b/fixtures/tbd/module.ts @@ -0,0 +1,12 @@ +declare module '@stacksjs/some-module' { + interface DtsGenerationConfig { + customPlugins?: Array<{ + name: string + transform: (code: string) => string + }> + } + + interface DtsGenerationResult { + customPluginResults?: Record + } +} diff --git a/fixtures/tbd/type.ts b/fixtures/tbd/type.ts new file mode 100644 index 0000000..6ee27b9 --- /dev/null +++ b/fixtures/tbd/type.ts @@ -0,0 +1,67 @@ +import type { DtsGenerationOption } from '@stacksjs/dtsx' + +export type AuthStatus = 'authenticated' | 'unauthenticated' + +// Intersection and Union Types +export type ComplexUnionIntersection = + | (User & { role: 'admin' }) + | (Product & { category: string }) + & { + metadata: Record + } + +export type { DtsGenerationOption } + +// Mapped and Conditional Types +export type ReadonlyDeep = { + readonly [P in keyof T]: T[P] extends object ? ReadonlyDeep : T[P] +} + +export type ConditionalResponse = T extends Array + ? ApiResponse + : T extends object + ? ApiResponse + : ApiResponse + +export type EventType = 'click' | 'focus' | 'blur' +export type ElementType = 'button' | 'input' | 'form' +export type EventHandler = `on${Capitalize}${Capitalize}` + +// Recursive Types +export type RecursiveObject = { + id: string + children?: RecursiveObject[] + parent?: RecursiveObject + metadata: Record +} + +// Branded Types +export type UserId = string & { readonly __brand: unique symbol } +export type ProductId = number & { + readonly __brand: unique symbol +} + +// Utility Type Implementations +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial +} : T + +export type DeepRequired = T extends object ? { + [P in keyof T]-?: DeepRequired +} : T + +// Polymorphic Types +export type PolymorphicComponent

= { + ( + props: { as?: C } & Omit, keyof P> & P + ): React.ReactElement | null +} + +// Complex Index Types +export type DynamicRecord = { + [P in K]: P extends number + ? Array + : P extends string + ? Record + : never +}