Skip to content

Commit

Permalink
chore: add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Oct 20, 2024
1 parent 3be1fcf commit f8caae7
Show file tree
Hide file tree
Showing 23 changed files with 424 additions and 6 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions fixtures/input/example-0007.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { DtsGenerationConfig } from './types'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import process from 'node:process'
import { deepMerge } from './utils'

interface Options<T> {
name: string
cwd?: string
defaultConfig: T
}

export async function loadConfig<T extends Record<string, unknown>>({ name, cwd, defaultConfig }: Options<T>): Promise<T> {
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
export const config: 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',
},
})
65 changes: 65 additions & 0 deletions fixtures/input/example-0008.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { DtsGenerationConfig, DtsGenerationOption } from './types'
import { mkdir, rm } from 'node:fs/promises'
import { dirname, join, parse, relative } from 'node:path'
import { glob } from 'tinyglobby'
import { config } from './config'
import { extractTypeFromSource } from './extract'
import { checkIsolatedDeclarations, getAllTypeScriptFiles, writeToFile } from './utils'

export async function generateDeclarationsFromFiles(options?: DtsGenerationConfig): Promise<void> {
// console.log('Generating declaration files...', options)
try {
// Check for isolatedModules setting
const isIsolatedDeclarations = await checkIsolatedDeclarations(options)
if (!isIsolatedDeclarations) {
console.error('Error: isolatedModules must be set to true in your tsconfig.json. Ensure `tsc --noEmit` does not output any errors.')
return
}

if (options?.clean) {
// console.log('Cleaning output directory...')
await rm(options.outdir, { recursive: true, force: true })
}

let files: string[]
if (options?.entrypoints) {
files = await glob(options.entrypoints, { cwd: options.root ?? options.cwd, absolute: true })
}
else {
files = await getAllTypeScriptFiles(options?.root)
}

// console.log('Found the following TypeScript files:', files)

for (const file of files) {
// console.log(`Processing file: ${file}`)
const fileDeclarations = await extractTypeFromSource(file)

if (fileDeclarations) {
const relativePath = relative(options?.root ?? './src', file)
const parsedPath = parse(relativePath)
const outputPath = join(options?.outdir ?? './dist', `${parsedPath.name}.d.ts`)

// Ensure the directory exists
await mkdir(dirname(outputPath), { recursive: true })

// Write the declarations without additional formatting
await writeToFile(outputPath, fileDeclarations)

// console.log(`Generated ${outputPath}`)
}
else {
console.warn(`No declarations extracted for ${file}`)
}
}

// console.log('Declaration file generation complete')
}
catch (error) {
console.error('Error generating declarations:', error)
}
}

export async function generate(options?: DtsGenerationOption): Promise<void> {
await generateDeclarationsFromFiles({ ...config, ...options })
}
5 changes: 5 additions & 0 deletions fixtures/input/example-0009.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { config } from './config'
export * from './extract'
export * from './generate'
export * from './types'
export * from './utils'
28 changes: 28 additions & 0 deletions fixtures/input/example-0010.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* DtsGenerationConfig
*
* This is the configuration object for the DTS generation process.
*/
export interface DtsGenerationConfig {
cwd: string
root: string
entrypoints: string[]
outdir: string
keepComments: boolean
clean: boolean
tsconfigPath: string
}

/**
* DtsGenerationOption
*
* This is the configuration object for the DTS generation process.
*/
export type DtsGenerationOption = Partial<DtsGenerationConfig>

/**
* DtsGenerationOptions
*
* This is the configuration object for the DTS generation process.
*/
export type DtsGenerationOptions = DtsGenerationOption | DtsGenerationOption[]
114 changes: 114 additions & 0 deletions fixtures/input/example-0011.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { DtsGenerationConfig } from './types'
import { readdir, readFile } from 'node:fs/promises'
import { extname, join } from 'node:path'
import { config } from './config'

export async function writeToFile(filePath: string, content: string): Promise<void> {
await Bun.write(filePath, content)
}

export async function getAllTypeScriptFiles(directory?: string): Promise<string[]> {
const dir = directory ?? config.root
const entries = await readdir(dir, { withFileTypes: true })

const files = await Promise.all(entries.map((entry) => {
const res = join(dir, entry.name)
return entry.isDirectory() ? getAllTypeScriptFiles(res) : res
}))

return Array.prototype.concat(...files).filter(file => extname(file) === '.ts')
}

export async function checkIsolatedDeclarations(options?: DtsGenerationConfig): Promise<boolean> {
try {
const tsconfigPath = options?.tsconfigPath || join(options?.root ?? process.cwd(), 'tsconfig.json')
const tsconfigContent = await readFile(tsconfigPath, 'utf-8')
const tsconfig = JSON.parse(tsconfigContent)

return tsconfig.compilerOptions?.isolatedDeclarations === true
}
catch (error) {
// eslint-disable-next-line no-console
console.log('Error reading tsconfig.json:', error)
return false
}
}

export function formatDeclarations(declarations: string): string {
const lines = declarations.split('\n')
const formattedLines = lines.map((line) => {
// Trim trailing spaces
line = line.trimEnd()

// Handle interface and type declarations
if (line.startsWith('export interface') || line.startsWith('export type')) {
const parts = line.split('{')
if (parts.length > 1) {
return `${parts[0].trim()} {${parts[1]}`
}
}

// Remove semicolons from the end of lines
if (line.endsWith(';')) {
line = line.slice(0, -1)
}

return line
})

// Join lines and ensure only one blank line between declarations
let result = formattedLines.join('\n')
result = result.replace(/\n{3,}/g, '\n\n')

// Format comments
result = result.replace(/\/\*\*\n([^*]*)(\n \*\/)/g, (match, content) => {
const formattedContent = content
.split('\n')
.map((line: string) => ` *${line.trim() ? ` ${line.trim()}` : ''}`)
.join('\n')
return `/**\n${formattedContent}\n */`
})

return `${result.trim()}\n`
}

export function formatComment(comment: string): string {
const lines = comment.split('\n')
return lines
.map((line, index) => {
if (index === 0)
return '/**'
if (index === lines.length - 1)
return ' */'
const trimmedLine = line.replace(/^\s*\*?\s?/, '').trim()
return ` * ${trimmedLine}`
})
.join('\n')
}

export function deepMerge<T extends object>(target: T, ...sources: Array<Partial<T>>): T {
if (!sources.length)
return target

const source = sources.shift()

if (isObject(target) && isObject(source)) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const sourceValue = source[key]
if (isObject(sourceValue) && isObject(target[key])) {
target[key] = deepMerge(target[key] as any, sourceValue as any)
}
else {
(target as any)[key] = sourceValue
}
}
}
}

return deepMerge(target, ...sources)
}

function isObject(item: unknown): item is Record<string, unknown> {
return (item && typeof item === 'object' && !Array.isArray(item)) as boolean
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions fixtures/output/example-0007.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { DtsGenerationConfig } from './types'

export declare function loadConfig<T extends Record<string, unknown>>({ name, cwd, defaultConfig }: Options<T>): Promise<T>

export declare const config: DtsGenerationConfig
5 changes: 5 additions & 0 deletions fixtures/output/example-0008.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { DtsGenerationConfig, DtsGenerationOption } from './types'

export declare function generateDeclarationsFromFiles(options?: DtsGenerationConfig): Promise<void>

export declare function generate(options?: DtsGenerationOption): Promise<void>
5 changes: 5 additions & 0 deletions fixtures/output/example-0009.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { config } from './config'
export * from './extract'
export * from './generate'
export * from './types'
export * from './utils'
28 changes: 28 additions & 0 deletions fixtures/output/example-0010.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* DtsGenerationConfig
*
* This is the configuration object for the DTS generation process.
*/
export interface DtsGenerationConfig {
cwd: string
root: string
entrypoints: string[]
outdir: string
keepComments: boolean
clean: boolean
tsconfigPath: string
}

/**
* DtsGenerationOption
*
* This is the configuration object for the DTS generation process.
*/
export type DtsGenerationOption = Partial<DtsGenerationConfig>

/**
* DtsGenerationOptions
*
* This is the configuration object for the DTS generation process.
*/
export type DtsGenerationOptions = DtsGenerationOption | DtsGenerationOption[]
13 changes: 13 additions & 0 deletions fixtures/output/example-0011.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { DtsGenerationConfig } from './types'

export declare function writeToFile(filePath: string, content: string): Promise<void>

export declare function getAllTypeScriptFiles(directory?: string): Promise<string[]>

export declare function checkIsolatedDeclarations(options?: DtsGenerationConfig): Promise<boolean>

export declare function formatDeclarations(declarations: string): string

export declare function formatComment(comment: string): string

export declare function deepMerge<T extends object>(target: T, ...sources: Array<Partial<T>>): T
Loading

0 comments on commit f8caae7

Please sign in to comment.