Skip to content

Instantly share code, notes, and snippets.

@Gerrit0
Created May 20, 2020 04:05
Show Gist options
  • Save Gerrit0/874824caec91c0aa4aea2c3d3989313e to your computer and use it in GitHub Desktop.
Save Gerrit0/874824caec91c0aa4aea2c3d3989313e to your computer and use it in GitHub Desktop.
//@ts-check
'use strict';
const fs = require('fs');
const ts = require('typescript');
const path = require('path');
const Module = require('module');
// These are declared as globals
const SKIP_MODULES = new Set([
'console',
'module',
'process'
])
// Set up a file that brings all node modules into a source file
// You could do this without hitting the file system, but its more complicated.
// ts-morph makes it easy, haven't figured out how to do it myself.
const lines = Module.builtinModules
.filter(mod => !mod.startsWith('_') && !SKIP_MODULES.has(mod))
.map(mod => `const ${mod} = require('${mod}')`)
fs.writeFileSync('intermediate.ts', lines.join('\n'))
/** @type {import('typescript').FormatDiagnosticsHost} */
const diagnosticHost = {
getCurrentDirectory: () => process.cwd(),
getCanonicalFileName: file => path.resolve(file),
getNewLine: () => '\n'
}
const program = ts.createProgram(['intermediate.ts'], {
lib: ['lib.esnext.d.ts']
})
// The compiler API behaves badly if there are errors... so check just in case.
const errors = [
...program.getOptionsDiagnostics(),
...program.getSyntacticDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics()
]
if (errors.length) {
console.error(ts.formatDiagnosticsWithColorAndContext(errors, diagnosticHost))
process.exit(1)
}
const sourceFile = program.getSourceFile('intermediate.ts')
const typeChecker = program.getTypeChecker()
const symbols = typeChecker.getSymbolsInScope(sourceFile, ts.SymbolFlags.Value)
/** @type {Map<string, string[][]>} */
const outputs = new Map()
// No infinite recursion please
/** @type {Set<import('typescript').Symbol>} */
const SEEN = new Set()
const SKIP_GLOBALS = new Set(['name'])
for (const symbol of symbols) {
if (SKIP_GLOBALS.has(symbol.name)) continue;
addSignatures('global', symbol);
(symbol.members || []).forEach(member => {
addSignatures(`${symbol.name}.prototype`, member);
});
}
console.log(outputs);
/**
* @param {string} scope
* @param {import('typescript').Symbol} symbol
*/
function addSignatures(scope, symbol, recurse=true) {
// TS puts quotes on module names
const name = symbol.name.startsWith('"') ? symbol.name.substr(1, symbol.name.length - 2) : symbol.name;
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, sourceFile);
/** @type {string[][]} */
const signatures = []
for (const signature of type.getCallSignatures()) {
signatures.push(signature.parameters.map(getParamName));
}
for (const signature of type.getConstructSignatures()) {
signatures.push(signature.parameters.map(getParamName));
}
if (recurse) {
for (const property of type.getProperties()) {
if (property.name.startsWith('__@')) {
continue; // Computed property name like __@iterator
}
addSignatures(name, property, false);
}
}
if (signatures.length) {
outputs.set(`${scope}.${name}`, signatures);
}
}
/** @param {import('typescript').Symbol} symbol */
function getParamName(symbol) {
const param = /** @type {import('typescript').ParameterDeclaration} */ (symbol.valueDeclaration)
if (param.questionToken) {
return `?${symbol.name}`
}
if (param.dotDotDotToken) {
return `...${symbol.name}`
}
return symbol.name
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment