Created
May 20, 2020 04:05
-
-
Save Gerrit0/874824caec91c0aa4aea2c3d3989313e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//@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