Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Oct 17, 2024
1 parent caedf34 commit 78f51b3
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 108 deletions.
110 changes: 110 additions & 0 deletions src/extract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { readFile } from 'node:fs/promises'

async function extractTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''
let usedTypes = new Set<string>()
let importMap = new Map<string, Set<string>>()

// Capture all imported types
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
let importMatch
while ((importMatch = importRegex.exec(fileContent)) !== null) {
const types = importMatch[1].split(',').map(t => t.trim())
const from = importMatch[2]
if (!importMap.has(from)) importMap.set(from, new Set())
types.forEach(type => importMap.get(from)!.add(type))
}

// Function to add used types
const addUsedType = (type: string) => {
const cleanType = type.replace(/[\[\]?]/g, '').trim() // Remove brackets and question marks
if (/^[A-Z]/.test(cleanType)) { // Only add if it starts with a capital letter (likely a type)
usedTypes.add(cleanType)
}
}

// Handle exported functions with comments
const exportedFunctionRegex = /(\/\*\*[\s\S]*?\*\/\s*)?(export\s+(async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*:\s*([^{]+))/g
let match
while ((match = exportedFunctionRegex.exec(fileContent)) !== null) {
const [, comment, , isAsync, name, params, returnType] = match
const cleanParams = params.replace(/\s*=\s*[^,)]+/g, '')
const declaration = `${comment || ''}export declare ${isAsync || ''}function ${name}(${cleanParams}): ${returnType.trim()}`
declarations += `${declaration}\n\n`

// Check for types used in parameters
const paramTypes = params.match(/:\s*([^,)]+)/g) || []
paramTypes.forEach(type => addUsedType(type.slice(1).trim()))

// Check for return type
addUsedType(returnType.trim())
}

// Handle other exports (interface, type, const)
const otherExportRegex = /(\/\*\*[\s\S]*?\*\/\s*)?(export\s+((?:interface|type|const)\s+\w+(?:\s*=\s*[^;]+|\s*\{[^}]*\})));?/gs
while ((match = otherExportRegex.exec(fileContent)) !== null) {
const [, comment, exportStatement, declaration] = match
declarations += `${comment || ''}${exportStatement}\n\n`

// Check for types used in the declaration
const typeRegex = /\b([A-Z]\w+)\b/g
let typeMatch
while ((typeMatch = typeRegex.exec(declaration)) !== null) {
addUsedType(typeMatch[1])
}
}

// Generate import statements for used types
let importDeclarations = ''
importMap.forEach((types, path) => {
const usedTypesFromPath = [...types].filter(type => usedTypes.has(type))
if (usedTypesFromPath.length > 0) {
importDeclarations += `import type { ${usedTypesFromPath.join(', ')} } from '${path}'\n`
}
})

if (importDeclarations) {
declarations = importDeclarations + '\n' + declarations
}

return declarations.trim() + '\n'
}

export async function extractConfigTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''

// Handle type imports
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
let importMatch
while ((importMatch = importRegex.exec(fileContent)) !== null) {
const types = importMatch[1].split(',').map(t => t.trim())
const from = importMatch[2]
declarations += `import type { ${types.join(', ')} } from '${from}'\n\n` // Add two newlines here
}

// Handle exports
const exportRegex = /export\s+const\s+(\w+)\s*:\s*([^=]+)\s*=/g
let exportMatch
while ((exportMatch = exportRegex.exec(fileContent)) !== null) {
const [, name, type] = exportMatch
declarations += `export declare const ${name}: ${type.trim()}\n`
}

return declarations.trim() + '\n'
}

export async function extractIndexTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''

// Handle re-exports
const reExportRegex = /export\s*(?:\*|\{[^}]*\})\s*from\s*['"]([^'"]+)['"]/g
let match
while ((match = reExportRegex.exec(fileContent)) !== null) {
declarations += `${match[0]}\n`
}

return declarations.trim() + '\n'
}
110 changes: 2 additions & 108 deletions src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Result } from 'neverthrow'
import type { DtsGenerationConfig, DtsGenerationOption } from './types'
import { readFile, rm, mkdir } from 'node:fs/promises'
import { rm, mkdir } from 'node:fs/promises'
import { join, relative, dirname } from 'node:path'
import { err, ok } from 'neverthrow'
import { config } from './config'
import { writeToFile, getAllTypeScriptFiles, checkIsolatedDeclarations } from './utils'
import { extractTypeFromSource, extractConfigTypeFromSource, extractIndexTypeFromSource } from './extract'

export async function generateDeclarationsFromFiles(options: DtsGenerationConfig = config): Promise<void> {
// Check for isolatedModules setting
Expand Down Expand Up @@ -65,113 +66,6 @@ export async function generate(options?: DtsGenerationOption): Promise<void> {
await generateDeclarationsFromFiles({ ...config, ...options })
}

async function extractTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''
let imports = new Set<string>()

// Handle imported types
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
let importMatch
while ((importMatch = importRegex.exec(fileContent)) !== null) {
const types = importMatch[1].split(',').map(t => t.trim())
const from = importMatch[2]
types.forEach(type => imports.add(`${type}:${from}`))
}

// Handle exported functions with comments
const exportedFunctionRegex = /(\/\*\*[\s\S]*?\*\/\s*)?(export\s+(async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*:\s*([^{]+))/g
let match
while ((match = exportedFunctionRegex.exec(fileContent)) !== null) {
const [, comment, , isAsync, name, params, returnType] = match
const cleanParams = params.replace(/\s*=\s*[^,)]+/g, '')
const declaration = `${comment || ''}export declare ${isAsync || ''}function ${name}(${cleanParams}): ${returnType.trim()}`
declarations += `${declaration}\n\n`

// Check for types used in the declaration and add them to imports
const usedTypes = [...params.matchAll(/(\w+):\s*([A-Z]\w+)/g), ...returnType.matchAll(/\b([A-Z]\w+)\b/g)]
usedTypes.forEach(([, , type]) => {
if (type) imports.add(type)
})
}

// Handle other exports (interface, type, const)
const otherExportRegex = /(\/\*\*[\s\S]*?\*\/\s*)?(export\s+((?:interface|type|const)\s+\w+(?:\s*=\s*[^;]+|\s*\{[^}]*\})));?/gs
while ((match = otherExportRegex.exec(fileContent)) !== null) {
const [, comment, exportStatement, declaration] = match
if (declaration.startsWith('interface') || declaration.startsWith('type')) {
declarations += `${comment || ''}${exportStatement}\n\n`
} else if (declaration.startsWith('const')) {
const [, name, type] = declaration.match(/const\s+(\w+):\s*([^=]+)/) || []
if (name && type) {
declarations += `${comment || ''}export declare const ${name}: ${type.trim()}\n\n`
}
}

// Check for types used in the declaration and add them to imports
const usedTypes = declaration.match(/\b([A-Z]\w+)\b/g) || []
usedTypes.forEach(type => imports.add(type))
}

// Generate import statements for used types
let importDeclarations = ''
const importMap = new Map()
imports.forEach(typeWithPath => {
const [type, path] = typeWithPath.split(':')
if (path) {
if (!importMap.has(path)) importMap.set(path, new Set())
importMap.get(path).add(type)
}
})
importMap.forEach((types, path) => {
importDeclarations += `import type { ${Array.from(types).join(', ')} } from '${path}'\n`
})

if (importDeclarations) {
declarations = importDeclarations + '\n' + declarations
}

return declarations.trim() + '\n'
}

async function extractConfigTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''

// Handle type imports
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
let importMatch
while ((importMatch = importRegex.exec(fileContent)) !== null) {
const types = importMatch[1].split(',').map(t => t.trim())
const from = importMatch[2]
declarations += `import type { ${types.join(', ')} } from '${from}'\n\n` // Add two newlines here
}

// Handle exports
const exportRegex = /export\s+const\s+(\w+)\s*:\s*([^=]+)\s*=/g
let exportMatch
while ((exportMatch = exportRegex.exec(fileContent)) !== null) {
const [, name, type] = exportMatch
declarations += `export declare const ${name}: ${type.trim()}\n`
}

return declarations.trim() + '\n'
}

async function extractIndexTypeFromSource(filePath: string): Promise<string> {
const fileContent = await readFile(filePath, 'utf-8')
let declarations = ''

// Handle re-exports
const reExportRegex = /export\s*(?:\*|\{[^}]*\})\s*from\s*['"]([^'"]+)['"]/g
let match
while ((match = reExportRegex.exec(fileContent)) !== null) {
declarations += `${match[0]}\n`
}

return declarations.trim() + '\n'
}

function formatDeclarations(declarations: string, isConfigFile: boolean): string {
if (isConfigFile) {
return declarations
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { config } from './config'
export * from './generate'
export * from './types'
export * from './utils'

0 comments on commit 78f51b3

Please sign in to comment.