Skip to content

Commit 8598729

Browse files
committed
chore: wip
chore: wip chore: wip
1 parent d3a42cf commit 8598729

File tree

3 files changed

+91
-96
lines changed

3 files changed

+91
-96
lines changed

src/extract.ts

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile } from 'node:fs/promises'
2-
import { formatComment } from './utils'
2+
import { formatComment, formatDeclarations } from './utils'
33

44
export async function extractTypeFromSource(filePath: string): Promise<string> {
55
const fileContent = await readFile(filePath, 'utf-8')
@@ -12,13 +12,16 @@ export async function extractTypeFromSource(filePath: string): Promise<string> {
1212
let importMatch
1313
while ((importMatch = importRegex.exec(fileContent)) !== null) {
1414
const [, isTypeImport, namedImports1, defaultImport1, namedImports2, defaultImport2, from] = importMatch
15+
if (!importMap.has(from)) {
16+
importMap.set(from, new Set())
17+
}
18+
1519
const processImports = (imports: string | undefined, isType: boolean) => {
1620
if (imports) {
1721
const types = imports.replace(/[{}]/g, '').split(',').map(t => {
1822
const [name, alias] = t.split(' as ').map(s => s.trim())
1923
return { name: name.replace(/^type\s+/, ''), alias: alias || name.replace(/^type\s+/, '') }
2024
})
21-
if (!importMap.has(from)) importMap.set(from, new Set())
2225
types.forEach(({ name, alias }) => {
2326
importMap.get(from)!.add(name)
2427
})
@@ -32,11 +35,11 @@ export async function extractTypeFromSource(filePath: string): Promise<string> {
3235
}
3336

3437
// Handle exports with comments
35-
const exportRegex = /(\/\*\*[\s\S]*?\*\/)?\s*(export\s+(?:async\s+)?(?:function|const|let|var|class|interface|type)\s+\w+[\s\S]*?)(?=\n\s*(?:\/\*\*|export|$))/g;
38+
const exportRegex = /(\/\*\*[\s\S]*?\*\/\s*)?(export\s+(?:async\s+)?(?:function|const|let|var|class|interface|type)\s+\w+[\s\S]*?)(?=\n\s*(?:\/\*\*|export|$))/g;
3639
let match
3740
while ((match = exportRegex.exec(fileContent)) !== null) {
3841
const [, comment, exportStatement] = match
39-
const formattedComment = comment ? formatComment(comment) : ''
42+
const formattedComment = comment ? formatComment(comment.trim()) : ''
4043
let formattedExport = exportStatement.trim()
4144

4245
if (formattedExport.startsWith('export function') || formattedExport.startsWith('export async function')) {
@@ -51,17 +54,9 @@ export async function extractTypeFromSource(filePath: string): Promise<string> {
5154
} else if (formattedExport.startsWith('export const') || formattedExport.startsWith('export let') || formattedExport.startsWith('export var')) {
5255
formattedExport = formattedExport.replace(/^export\s+(const|let|var)/, 'export declare $1')
5356
formattedExport = formattedExport.split('=')[0].trim() + ';'
54-
} else if (formattedExport.startsWith('export interface')) {
55-
// Keep interface declarations as they are, including their content
56-
formattedExport = formattedExport.replace(/\s*\{\s*$/m, ' {')
57-
.replace(/^\s*}/m, '}')
58-
.replace(/;\s*$/g, '')
59-
} else if (formattedExport.startsWith('export type')) {
60-
// Keep type declarations as they are
61-
formattedExport = formattedExport.replace(/;\s*$/, '')
6257
}
6358

64-
declarations += `${formattedComment}${formattedExport}\n\n`
59+
declarations += `${formattedComment}\n${formattedExport}\n\n`
6560

6661
// Add types used in the export to usedTypes
6762
const typeRegex = /\b([A-Z]\w+)(?:<[^>]*>)?/g
@@ -76,39 +71,55 @@ export async function extractTypeFromSource(filePath: string): Promise<string> {
7671
importMap.forEach((types, path) => {
7772
const usedTypesFromPath = [...types].filter(type => usedTypes.has(type))
7873
if (usedTypesFromPath.length > 0) {
79-
importDeclarations += `import type { ${usedTypesFromPath.join(', ')} } from '${path}';\n`
74+
importDeclarations += `import type { ${usedTypesFromPath.join(', ')} } from '${path}'\n`
8075
}
8176
})
8277

8378
if (importDeclarations) {
8479
declarations = importDeclarations + '\n' + declarations
8580
}
8681

87-
return declarations.trim() + '\n'
82+
// Apply final formatting
83+
const formattedDeclarations = formatDeclarations(declarations, false)
84+
85+
console.log(`Unformatted declarations for ${filePath}:`, declarations)
86+
console.log(`Extracted declarations for ${filePath}:`, formattedDeclarations)
87+
88+
return formattedDeclarations
8889
}
8990

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

94-
// Handle type imports
95-
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
96-
let importMatch
97-
while ((importMatch = importRegex.exec(fileContent)) !== null) {
98-
const types = importMatch[1].split(',').map(t => t.trim())
99-
const from = importMatch[2]
100-
declarations += `import type { ${types.join(', ')} } from '${from}'\n\n` // Add two newlines here
101-
}
95+
try {
96+
// Handle type imports
97+
const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g
98+
let importMatch
99+
while ((importMatch = importRegex.exec(fileContent)) !== null) {
100+
const [, types, from] = importMatch
101+
const typeList = types.split(',').map(t => t.trim())
102+
declarations += `import type { ${typeList.join(', ')} } from '${from}'\n`
103+
}
102104

103-
// Handle exports
104-
const exportRegex = /export\s+const\s+(\w+)\s*:\s*([^=]+)\s*=/g
105-
let exportMatch
106-
while ((exportMatch = exportRegex.exec(fileContent)) !== null) {
107-
const [, name, type] = exportMatch
108-
declarations += `export declare const ${name}: ${type.trim()}\n`
109-
}
105+
if (declarations) {
106+
declarations += '\n'
107+
}
110108

111-
return declarations.trim() + '\n'
109+
// Handle exports
110+
const exportRegex = /export\s+const\s+(\w+)\s*:\s*([^=]+)\s*=/g
111+
let exportMatch
112+
while ((exportMatch = exportRegex.exec(fileContent)) !== null) {
113+
const [, name, type] = exportMatch
114+
declarations += `export declare const ${name}: ${type.trim()}\n`
115+
}
116+
117+
console.log(`Extracted config declarations for ${filePath}:`, declarations)
118+
return declarations.trim() + '\n'
119+
} catch (error) {
120+
console.error(`Error extracting config declarations from ${filePath}:`, error)
121+
return ''
122+
}
112123
}
113124

114125
export async function extractIndexTypeFromSource(filePath: string): Promise<string> {
@@ -122,5 +133,6 @@ export async function extractIndexTypeFromSource(filePath: string): Promise<stri
122133
declarations += `${match[0]}\n`
123134
}
124135

136+
console.log(`Extracted index declarations for ${filePath}:`, declarations)
125137
return declarations.trim() + '\n'
126138
}

src/generate.ts

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,57 +8,46 @@ import { writeToFile, getAllTypeScriptFiles, checkIsolatedDeclarations, formatDe
88
import { extractTypeFromSource, extractConfigTypeFromSource, extractIndexTypeFromSource } from './extract'
99

1010
export async function generateDeclarationsFromFiles(options: DtsGenerationConfig = config): Promise<void> {
11-
// Check for isolatedModules setting
12-
const isIsolatedDeclarations = await checkIsolatedDeclarations(options)
13-
if (!isIsolatedDeclarations) {
14-
console.error('Error: isolatedModules must be set to true in your tsconfig.json. Ensure `tsc --noEmit` does not output any errors.')
15-
return
16-
}
17-
18-
if (options.clean) {
19-
console.log('Cleaning output directory...')
20-
await rm(options.outdir, { recursive: true, force: true })
21-
}
22-
23-
const validationResult = validateOptions(options)
11+
try {
12+
// Check for isolatedModules setting
13+
const isIsolatedDeclarations = await checkIsolatedDeclarations(options)
14+
if (!isIsolatedDeclarations) {
15+
console.error('Error: isolatedModules must be set to true in your tsconfig.json. Ensure `tsc --noEmit` does not output any errors.')
16+
return
17+
}
2418

25-
if (validationResult.isErr()) {
26-
console.error(validationResult.error.message)
27-
return
28-
}
19+
if (options.clean) {
20+
console.log('Cleaning output directory...')
21+
await rm(options.outdir, { recursive: true, force: true })
22+
}
2923

30-
const files = await getAllTypeScriptFiles(options.root)
31-
console.log('Found the following TypeScript files:', files)
24+
const files = await getAllTypeScriptFiles(options.root)
25+
console.log('Found the following TypeScript files:', files)
3226

33-
for (const file of files) {
34-
console.log(`Processing file: ${file}`)
35-
let fileDeclarations
36-
const isConfigFile = file.endsWith('config.ts')
37-
const isIndexFile = file.endsWith('index.ts')
38-
if (isConfigFile) {
39-
fileDeclarations = await extractConfigTypeFromSource(file)
40-
} else if (isIndexFile) {
41-
fileDeclarations = await extractIndexTypeFromSource(file)
42-
} else {
43-
fileDeclarations = await extractTypeFromSource(file)
44-
}
27+
for (const file of files) {
28+
console.log(`Processing file: ${file}`)
29+
const fileDeclarations = await extractTypeFromSource(file)
4530

46-
if (fileDeclarations) {
47-
const relativePath = relative(options.root, file)
48-
const outputPath = join(options.outdir, relativePath.replace(/\.ts$/, '.d.ts'))
31+
if (fileDeclarations) {
32+
const relativePath = relative(options.root, file)
33+
const outputPath = join(options.outdir, relativePath.replace(/\.ts$/, '.d.ts'))
4934

50-
// Ensure the directory exists
51-
await mkdir(dirname(outputPath), { recursive: true })
35+
// Ensure the directory exists
36+
await mkdir(dirname(outputPath), { recursive: true })
5237

53-
// Format and write the declarations
54-
const formattedDeclarations = formatDeclarations(fileDeclarations, isConfigFile)
55-
await writeToFile(outputPath, formattedDeclarations)
38+
// Write the declarations without additional formatting
39+
await writeToFile(outputPath, fileDeclarations)
5640

57-
console.log(`Generated ${outputPath}`)
41+
console.log(`Generated ${outputPath}`)
42+
} else {
43+
console.warn(`No declarations extracted for ${file}`)
44+
}
5845
}
59-
}
6046

61-
console.log('Declaration file generation complete')
47+
console.log('Declaration file generation complete')
48+
} catch (error) {
49+
console.error('Error generating declarations:', error)
50+
}
6251
}
6352

6453
export async function generate(options?: DtsGenerationOption): Promise<void> {

src/utils.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readdir, readFile } from 'node:fs/promises'
22
import { extname, join } from 'node:path'
3+
import { formatComment } from './utils'
34
import { config } from './config'
45
import { type DtsGenerationConfig } from './types'
56

@@ -33,36 +34,29 @@ export async function checkIsolatedDeclarations(options: DtsGenerationConfig): P
3334

3435
export function formatDeclarations(declarations: string, isConfigFile: boolean): string {
3536
if (isConfigFile) {
36-
return declarations
37-
.replace(/\n{3,}/g, '\n\n')
38-
.replace(/(\w+):\s+/g, '$1: ')
39-
.trim() + '\n'
37+
return declarations.trim() + '\n'
4038
}
4139

4240
return declarations
4341
.replace(/\n{3,}/g, '\n\n')
44-
.replace(/(\w+):\s+/g, '$1: ')
45-
.replace(/\s*\n\s*/g, '\n')
46-
.replace(/\{\s*\n\s*\n/g, '{\n')
42+
.replace(/;\n/g, '\n')
43+
.replace(/export (interface|type) ([^\{]+)\s*\{\s*\n/g, 'export $1 $2 {\n')
4744
.replace(/\n\s*\}/g, '\n}')
48-
.replace(/;\s*\n/g, '\n')
49-
.replace(/export interface ([^\{]+)\{/g, 'export interface $1{ ')
50-
.replace(/^(\s*\w+:.*(?:\n|$))/gm, ' $1')
51-
.replace(/}\n\n(?=\/\*\*|export (interface|type))/g, '}\n\n')
52-
.replace(/^(import .*\n)+/m, match => match.trim() + '\n\n')
53-
.replace(/(\/\*\*[\s\S]*?\*\/\s*)(export\s+(?:interface|type|const))/g, '$1\n$2')
54-
.replace(/\*\/\n\n/g, '*/\n') // Remove extra newline after comment
55-
.replace(/\* \//g, '*/') // Ensure proper closing of comments
45+
.replace(/\/\*\*\n([^*]*)(\n \*\/)/g, (match, content) => {
46+
const formattedContent = content.split('\n').map(line => ` *${line.trim() ? ' ' + line.trim() : ''}`).join('\n')
47+
return `/**\n${formattedContent}\n */`
48+
})
5649
.trim() + '\n'
5750
}
5851

5952
export function formatComment(comment: string): string {
60-
return comment
61-
.replace(/^\/\*\*\s*\n?/, '/**\n * ')
62-
.replace(/^[\t ]*\*[\t ]?/gm, ' * ')
63-
.replace(/\s*\*\/\s*$/, '\n */')
64-
.replace(/^\s*\* $/gm, ' *')
65-
.replace(/\* \//g, '*/')
66-
.replace(/\n \* \n/g, '\n *\n')
67-
.trim();
53+
const lines = comment.split('\n')
54+
return lines
55+
.map((line, index) => {
56+
if (index === 0) return '/**'
57+
if (index === lines.length - 1) return ' */'
58+
const trimmedLine = line.replace(/^\s*\*?\s?/, '').trim()
59+
return ` * ${trimmedLine}`
60+
})
61+
.join('\n')
6862
}

0 commit comments

Comments
 (0)