Skip to content

Instantly share code, notes, and snippets.

@betafcc
Last active February 6, 2025 13:07
Show Gist options
  • Save betafcc/3fde408c76c68bc8e9f1d0f4a0dd5cc5 to your computer and use it in GitHub Desktop.
Save betafcc/3fde408c76c68bc8e9f1d0f4a0dd5cc5 to your computer and use it in GitHub Desktop.
Expand type aliases from a typescript file
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import JSON5 from 'json5'
import ts from 'typescript'
import * as prettier from 'prettier'
/**
* reveal all top level `type` declarations from a file
*/
export const reveal = async (sourcePath: string) => {
const program = ts.createProgram([sourcePath], getConfig())
const checker = program.getTypeChecker()
const revealed = program
.getSourceFile(sourcePath)!
.getChildren()
.flatMap(c => c.getChildren())
.filter(ts.isTypeAliasDeclaration)
.map(c => getFullDeclarationText(checker, c))
.join('\n\n')
return await prettier.format(revealed, {
parser: 'typescript',
semi: false,
})
}
const getFullDeclarationText = (
checker: ts.TypeChecker,
c: ts.TypeAliasDeclaration,
) => getTypeDeclarationText(c) + typeToString(checker, c)
const typeToString = (checker: ts.TypeChecker, c: ts.TypeAliasDeclaration) =>
checker.typeToString(
checker.getTypeAtLocation(c.type),
undefined,
ts.TypeFormatFlags.InTypeAlias | ts.TypeFormatFlags.NoTruncation,
)
const getTypeDeclarationText = (c: ts.TypeAliasDeclaration) =>
c.getText().slice(0, c.getText().lastIndexOf(c.type.getText()))
const getConfig = () => {
const configFile = ts.findConfigFile(
'.',
ts.sys.fileExists,
'tsconfig.json',
) as string
// return JSON5.parse(readFileSync(configFile, { encoding: 'utf-8' }))
return JSON5.parse(readFileSync(configFile) as any)
}
/**
* I usually put this file in a scratch folder in the project and run as
*
* ```bash
* npx ts-node-dev --respawn -T --compiler-options '{"module": "commonjs"}' ./scratch/reveal.ts ./test.ts
* ```
*
* In order for this to rerun when the sourcePath file changes,
* a good trick is to `import` it here, so ts-node-dev will know
* to rerun this, also is better to run `reveal` only after
* the file is fully imported, I had problems with reveal getting
* previous source if not
*/
const sourcePath = resolve(process.argv.at(-1)!)
import(sourcePath).then(() => reveal(sourcePath)).then(console.log)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment