-
-
Save Lupus/7980a1211ce51a1bdb0f7c05070eddb0 to your computer and use it in GitHub Desktop.
Half-arsed highly-PoC level typescript => gen_js_api bindings generator
This file contains 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
all: | |
node index.js > out.mli | |
ocamlformat -i out.mli | |
ocamlfind gen_js_api/gen_js_api out.mli | |
ocamlformat -i out.ml |
This file contains 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
{ | |
"name": "ts_to_mli", | |
"version": "1.0.0", | |
"main": "index.js", | |
"license": "MIT", | |
"dependencies": { | |
"@types/node": "^13.7.4", | |
"cassandra-driver": "^4.4.0", | |
"dts-bundle": "^0.7.3", | |
"dts-generator": "^3.0.0", | |
"flowgen": "^1.10.0", | |
"glob": "^7.1.6", | |
"gulp": "^4.0.2", | |
"json-cyclic": "^0.0.3", | |
"json-stringify-safe": "^5.0.1", | |
"long": "^4.0.0", | |
"ts-dox": "^0.0.7", | |
"tsc": "^1.20150623.0", | |
"typescript": "^3.8.2" | |
} | |
} |
This file contains 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
const path = require("path"); | |
const glob = require("glob"); | |
const ts = require("typescript"); | |
const crypto = require("crypto"); | |
var program = ts.createProgram(glob.sync("sample-ts/**/*.d.ts"), {}); | |
var checker = program.getTypeChecker(); | |
let indent = " "; | |
var output = ""; | |
var header = ""; | |
function out_start(level = 0) { | |
output += indent.repeat(level); | |
} | |
function out(s) { | |
output += s; | |
} | |
function out_end(s = "") { | |
output += s + "\n"; | |
} | |
function outl(s, level = 0) { | |
out_start(level); | |
out_end(s); | |
} | |
function hdr_start(level = 0) { | |
header += indent.repeat(level); | |
} | |
function hdr(s) { | |
header += s; | |
} | |
function hdr_end(s = "") { | |
header += s + "\n"; | |
} | |
function hdrl(s, level = 0) { | |
hdr_start(level); | |
hdr_end(s); | |
} | |
function to_snake_case(str, upper_case_first_char = false) { | |
var str = str | |
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) | |
.map(x => x.toLowerCase()) | |
.join("_"); | |
if (upper_case_first_char) { | |
return str.replace(/^[a-z]/, x => x.toUpperCase()); | |
} else { | |
return str; | |
} | |
} | |
const keywords = { | |
and: true, | |
as: true, | |
assert: true, | |
asr: true, | |
begin: true, | |
class: true, | |
constraint: true, | |
do: true, | |
done: true, | |
downto: true, | |
else: true, | |
end: true, | |
exception: true, | |
externalfalse: true, | |
for: true, | |
fun: true, | |
function: true, | |
functor: true, | |
if: true, | |
in: true, | |
include: true, | |
inherit: true, | |
initializer: true, | |
land: true, | |
lazy: true, | |
let: true, | |
lor: true, | |
lsl: true, | |
lsr: true, | |
lxor: true, | |
match: true, | |
method: true, | |
mod: true, | |
module: true, | |
mutable: true, | |
new: true, | |
nonrec: true, | |
object: true, | |
of: true, | |
open: true, | |
or: true, | |
private: true, | |
rec: true, | |
sig: true, | |
struct: true, | |
then: true, | |
to: true, | |
true: true, | |
try: true, | |
type: true, | |
val: true, | |
virtual: true, | |
when: true, | |
while: true, | |
with: true | |
}; | |
function escape_keyword(s) { | |
if (keywords[s.toLowerCase()]) return s + "_"; | |
return s; | |
} | |
var get_flags = function(e, flags) { | |
var selected = []; | |
for (var flag in e) { | |
if (typeof flag == "string") { | |
var val = e[flag]; | |
if ((flags & val) === val) selected.push(flag); | |
} | |
} | |
return selected.join(" | "); | |
}; | |
var get_symbol_flags = function(flags) { | |
return get_flags(ts.SymbolFlags, flags); | |
}; | |
var get_type_flags = function(flags) { | |
return get_flags(ts.TypeFlags, flags); | |
}; | |
var get_object_flags = function(flags) { | |
return get_flags(ts.ObjectFlags, flags); | |
}; | |
function ordered_for_each(x, f) { | |
var a = []; | |
x.forEach(v => a.push(v)); | |
a.sort(function(a, b) { | |
return a.declarations[0].pos - b.declarations[0].pos; | |
}).forEach(f); | |
} | |
var this_module_escaped_name; | |
var module_path = []; | |
var module_aliases = {}; | |
var module_requires = {}; | |
var current_js_path = ""; | |
var js_path_aliases = {}; | |
function prepend_js_path(x) { | |
if (current_js_path != "") { | |
return current_js_path + "." + x; | |
} | |
return x; | |
} | |
function resolve_js_path(x) { | |
for (const s in js_path_aliases) { | |
if (x.startsWith(s)) { | |
return js_path_aliases[s] + x.substr(s.length); | |
} | |
} | |
return x; | |
} | |
function resolve_js_path_for_symbol(s) { | |
var qn = resolve_js_path(checker.getFullyQualifiedName(s)); | |
var parts = qn.split("."); | |
var mdl = parts.shift().replace(/["]/g, ""); | |
if (!module_requires[mdl]) { | |
var hash = crypto.createHash("md5"); | |
hash.update(mdl); | |
var require_id = `__ts2mli_${to_snake_case(mdl)}_${hash | |
.digest("hex") | |
.substr(26)}`; | |
hdrl(`let () = Ojs.set Ojs.global "${require_id}" (node_require "${mdl}")`); | |
module_requires[mdl] = require_id; | |
} | |
parts.unshift(module_requires[mdl]); | |
return parts.join("."); | |
} | |
function symbol_path(s, last_part) { | |
var file_modules = s.valueDeclaration | |
? s.valueDeclaration.getSourceFile().ambientModuleNames | |
: []; | |
var ambients = checker.getAmbientModules(); | |
file_modules = file_modules.map(fm => { | |
var found = ambients.find(m => m.valueDeclaration.name.text == fm); | |
if (!found) { | |
throw "unable to find ambient module for " + fm; | |
} | |
return found; | |
}); | |
var parts = [s.escapedName]; | |
var parent = s.parent; | |
while (parent) { | |
var m = file_modules.find(m => { | |
var alias = m.exports.get("export="); | |
if (alias && alias.flags & ts.SymbolFlags.Alias) { | |
return checker.getAliasedSymbol(alias) == parent; | |
} else { | |
return false; | |
} | |
}); | |
if (m) { | |
parts.unshift(m.escapedName); | |
} else { | |
parts.unshift(parent.escapedName); | |
} | |
parent = parent.parent; | |
} | |
var path = parts | |
.filter(x => x != this_module_escaped_name) | |
.map(x => x.replace(/["]/g, "")) | |
.map(x => | |
x.startsWith(process.cwd()) ? x.substring(process.cwd().length) : x | |
) | |
.map(x => to_snake_case(x, true)) | |
.join("."); | |
for (const s in module_aliases) { | |
if (path.startsWith(s)) { | |
path = module_aliases[s] + path.substr(s.length); | |
break; | |
} | |
} | |
if (last_part && path != "") path = `${path}.${last_part}`; | |
for (const s of module_path) { | |
if (path.startsWith(s + ".")) { | |
path = path.substr(s.length + 1); | |
} | |
} | |
return path; | |
} | |
function map_type(typ) { | |
if (typ.flags & ts.TypeFlags.Object) { | |
if ( | |
typ.objectFlags & ts.ObjectFlags.Reference || | |
typ.objectFlags & ts.ObjectFlags.ClassOrInterface | |
) { | |
if ( | |
(typ.symbol.escapedName == "Array" || | |
typ.symbol.escapedName == "Set") && | |
checker.getTypeArguments(typ).length == 1 | |
) { | |
return `(${map_type(checker.getTypeArguments(typ)[0])} list)`; | |
} | |
return `${symbol_path(typ.symbol, "t")}`; | |
} | |
} else if ( | |
typ.flags & ts.TypeFlags.Any || | |
typ.flags & ts.TypeFlags.UnknownAny | |
) { | |
return `Ojs.t (* any *)`; | |
} else if ( | |
typ.flags & ts.TypeFlags.String || | |
typ.flags & ts.TypeFlags.StringLike | |
) { | |
return `string`; | |
} else if ( | |
typ.flags & ts.TypeFlags.Number || | |
typ.flags & ts.TypeFlags.NumberLike | |
) { | |
return `int`; | |
} else if ( | |
typ.flags & ts.TypeFlags.Undefined || | |
typ.flags & ts.TypeFlags.Null || | |
typ.flags & ts.TypeFlags.Void || | |
typ.flags & ts.TypeFlags.VoidLike || | |
typ.flags & ts.TypeFlags.Unit || | |
typ.flags & ts.TypeFlags.Never | |
) { | |
return `unit`; | |
} else if ( | |
typ.flags & ts.TypeFlags.Boolean || | |
typ.flags & ts.TypeFlags.BooleanLike | |
) { | |
return `bool`; | |
} else if (typ.flags & ts.TypeFlags.TypeParameter) { | |
return symbol_path(typ.symbol, "t"); | |
} | |
/* | |
Enum = 32, | |
BigInt = 64, | |
StringLiteral = 128, | |
NumberLiteral = 256, | |
BooleanLiteral = 512, | |
EnumLiteral = 1024, | |
BigIntLiteral = 2048, | |
ESSymbol = 4096, | |
UniqueESSymbol = 8192, | |
TypeParameter = 262144, | |
Object = 524288, | |
Union = 1048576, | |
Intersection = 2097152, | |
Index = 4194304, | |
IndexedAccess = 8388608, | |
Conditional = 16777216, | |
Substitution = 33554432, | |
NonPrimitive = 67108864, | |
Literal = 2944, | |
StringOrNumberLiteral = 384, | |
PossiblyFalsy = 117724, | |
BigIntLike = 2112, | |
BooleanLike = 528, | |
EnumLike = 1056, | |
ESSymbolLike = 12288, | |
UnionOrIntersection = 3145728, | |
StructuredType = 3670016, | |
TypeVariable = 8650752, | |
InstantiableNonPrimitive = 58982400, | |
InstantiablePrimitive = 4194304, | |
Instantiable = 63176704, | |
StructuredOrInstantiable = 66846720, | |
Narrowable = 133970943, | |
NotUnionOrUnit = 67637251, | |
*/ | |
return `Ojs.t (* ${get_type_flags(typ.flags)} *)`; | |
} | |
function dump_property(p, level) { | |
var typ = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration); | |
outl( | |
`(* ${checker.symbolToString(p)} : ${checker.typeToString(typ)} *)`, | |
level | |
); | |
outl( | |
`method ${escape_keyword(to_snake_case(p.escapedName))} : ${map_type( | |
typ | |
)} [@@js.get "${p.escapedName}"]`, | |
level | |
); | |
} | |
function dump_heritage(x, level) { | |
var n = 0; | |
if (x.valueDeclaration && x.valueDeclaration.heritageClauses) { | |
x.valueDeclaration.heritageClauses.forEach(clause => { | |
if (clause.token == ts.SyntaxKind.ExtendsKeyword) { | |
clause.types.forEach(t => { | |
var ts = ts; | |
var typ = checker.getTypeAtLocation(t.expression); | |
outl(`inherit ${symbol_path(typ.symbol, "t")}`, level); | |
n++; | |
}); | |
} | |
}); | |
} | |
if (n == 0) { | |
outl(`inherit Ojs.obj`, level); | |
} | |
} | |
function serialize_symbol(symbol) { | |
return ( | |
symbol.getName() + | |
(checker.isOptionalParameter(symbol.valueDeclaration) ? "?" : "") + | |
": " + | |
checker.typeToString( | |
checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration) | |
) | |
); | |
} | |
function serialize_signature(signature) { | |
var parts = signature.parameters.map(p => { | |
var name = escape_keyword(to_snake_case(p.getName())); | |
var q = checker.isOptionalParameter(p.valueDeclaration) ? "?" : ""; | |
var typ = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration); | |
if (typ.members && typ.members.has("__call")) { | |
return `${q}${name}:(${serialize_signature(typ.callSignatures[0])})`; | |
} else { | |
return `${q}${name}:${map_type(typ)}`; | |
} | |
}); | |
if (parts.length == 0 || parts[parts.length - 1].startsWith("?")) { | |
parts.push("unit"); | |
} | |
parts.push(map_type(signature.getReturnType())); | |
return parts.join(" -> "); | |
} | |
function dump_indexes(s, level) { | |
var typ = checker.getDeclaredTypeOfSymbol(s); | |
var str_index = checker.getIndexInfoOfType(typ, ts.IndexKind.String); | |
if (str_index) { | |
outl(`module String_index : sig`, level); | |
{ | |
var level = level + 1; | |
outl(`val get : t -> string -> ${map_type(str_index.type)}`, level); | |
outl(`[@@js.custom`, level); | |
{ | |
var level = level + 1; | |
outl( | |
`val cast_output_val: Ojs.t -> ${map_type( | |
str_index.type | |
)} [@@js.cast]`, | |
level | |
); | |
outl( | |
`let get t prop = Ojs.get (t |> js_of_t) prop |> cast_output_val`, | |
level | |
); | |
} | |
outl(`]`, level); | |
} | |
outl(`end`, level); | |
} | |
var num_index = checker.getIndexInfoOfType(typ, ts.IndexKind.Number); | |
if (num_index) { | |
outl(`module Number_index : sig`, level); | |
{ | |
var level = level + 1; | |
outl(`val get : t -> string -> Ojs.t`, level); | |
outl(`[@@js.custom`, level); | |
{ | |
var level = level + 1; | |
outl(`let get t idx = Ojs.array_get (t |> js_of_t) idx`, level); | |
} | |
outl(`]`, level); | |
} | |
outl(`end`, level); | |
} | |
} | |
function dump_constructors(cls, level) { | |
var c; | |
cls.members.forEach(m => { | |
if (m.flags & ts.SymbolFlags.Constructor) c = m; | |
}); | |
if (!c) return; | |
outl(`(* constructors *)`, level); | |
let parent_type = checker.getTypeOfSymbolAtLocation( | |
c.parent, | |
c.parent.valueDeclaration | |
); | |
var signatures = parent_type.getConstructSignatures(); | |
var n = signatures.length > 1 ? 1 : undefined; | |
signatures.forEach(s => { | |
outl(`(* signature: ${checker.signatureToString(s)} *)`, level); | |
outl( | |
`val create${n ? "_" + n : ""} : ${serialize_signature( | |
s | |
)}[@@js.new "${resolve_js_path_for_symbol(cls)}"]`, | |
level | |
); | |
if (n) n++; | |
}); | |
} | |
function dump_members(x, level) { | |
ordered_for_each(x.members, m => { | |
if (m.flags & ts.SymbolFlags.Property) { | |
dump_property(m, level); | |
} else if (m.flags & ts.SymbolFlags.Method) { | |
outl(`(* method '${m.escapedName}' *)`, level); | |
var typ = checker.getTypeOfSymbolAtLocation(m, m.valueDeclaration); | |
var signatures = checker.getSignaturesOfType(typ, ts.SignatureKind.Call); | |
var n = signatures.length > 1 ? 1 : undefined; | |
signatures.forEach(s => { | |
outl(`(* signature: ${checker.signatureToString(s)} *)`, level); | |
outl( | |
`method ${escape_keyword(to_snake_case(m.escapedName))}${ | |
n ? "_" + n : "" | |
} : ${serialize_signature(s)}[@@js.call "${m.escapedName}"]`, | |
level | |
); | |
if (n) n++; | |
}); | |
} else if (m.flags & ts.SymbolFlags.Constructor) { | |
} else if ( | |
m.flags & ts.SymbolFlags.Signature && | |
m.escapedName == "__index" | |
) { | |
} else { | |
outl( | |
`(* member? ${m.escapedName}, flags: ${get_symbol_flags(m.flags)} *)`, | |
level | |
); | |
} | |
}); | |
} | |
function out_class(level, cb) { | |
outl(`class t :`, level + 1); | |
outl(`Ojs.t`, level + 2); | |
outl(`-> object`, level + 2); | |
cb(level + 3); | |
outl(`end`, level + 2); | |
} | |
function out_module(s, level, cb) { | |
var name = to_snake_case(s.escapedName, true); | |
outl(`module ${name} : sig`, level); | |
module_path.push(name); | |
cb(level + 1); | |
module_path.pop(); | |
outl(`end`, level); | |
} | |
function dump_symbol(s, level) { | |
if (s.flags & ts.SymbolFlags.Function) { | |
outl(`(* function '${s.escapedName}' *)`, level); | |
var typ = checker.getTypeOfSymbolAtLocation(s, s.valueDeclaration); | |
var signatures = checker.getSignaturesOfType(typ, ts.SignatureKind.Call); | |
var n = signatures.length > 1 ? 1 : undefined; | |
signatures.forEach(sig => { | |
outl(`(* signature: ${checker.signatureToString(sig)} *)`, level); | |
outl( | |
`val ${escape_keyword(to_snake_case(s.escapedName))}${ | |
n ? "_" + n : "" | |
} : ${serialize_signature( | |
sig | |
)}[@@js.global "${resolve_js_path_for_symbol(s)}"]`, | |
level | |
); | |
if (n) n++; | |
}); | |
} else if (s.flags & ts.SymbolFlags.Interface) { | |
outl(`(* interface '${s.escapedName}' *)`, level); | |
out_module(s, level, level => { | |
out_class(level + 1, level => { | |
dump_heritage(s, level); | |
dump_members(s, level); | |
}); | |
outl(`val js_of_t : t -> Ojs.t [@@js.cast]`, level + 1); | |
outl(`val t_of_js : Ojs.t -> t [@@js.cast]`, level + 1); | |
dump_indexes(s, level); | |
}); | |
} else if (s.flags & ts.SymbolFlags.Class) { | |
outl(`(* class '${s.escapedName}' *)`, level); | |
out_module(s, level, level => { | |
out_class(level, level => { | |
dump_heritage(s, level); | |
dump_members(s, level); | |
}); | |
outl(`val js_of_t : t -> Ojs.t [@@js.cast]`, level + 1); | |
outl(`val t_of_js : Ojs.t -> t [@@js.cast]`, level + 1); | |
dump_constructors(s, level); | |
dump_indexes(s, level); | |
}); | |
} else if (s.flags & ts.SymbolFlags.Alias) { | |
var a = checker.getAliasedSymbol(s); | |
if (a.flags & ts.SymbolFlags.ValueModule) { | |
outl( | |
`(* re-export of '${checker.getFullyQualifiedName( | |
a | |
)}' as '${symbol_path(s)}' *)`, | |
level | |
); | |
out_module(s, level, level => { | |
dump_module_symbol(a, level); | |
}); | |
} else { | |
outl( | |
`(* Alias??? ${s.escapedName}, flags: ${get_symbol_flags(s.flags)} *)`, | |
level | |
); | |
} | |
} else if (s.flags & ts.SymbolFlags.ValueModule) { | |
outl(`(* ValueModule '${s.escapedName}' *)`, level); | |
out_module(s, level, level => { | |
dump_module_symbol(s, level); | |
}); | |
} else { | |
outl( | |
`(* ??? ${s.escapedName}, flags: ${get_symbol_flags(s.flags)} *)`, | |
level | |
); | |
} | |
} | |
function extract_alias(s) { | |
if (s.flags & ts.SymbolFlags.Alias) { | |
var a = checker.getAliasedSymbol(s); | |
if (a.flags & ts.SymbolFlags.ValueModule) { | |
module_aliases[symbol_path(a)] = symbol_path(s); | |
js_path_aliases[checker.getFullyQualifiedName(a)] = resolve_js_path( | |
checker.getFullyQualifiedName(s) | |
); | |
} | |
} | |
} | |
function dump_module_symbol(m, level = 0) { | |
var exports = checker.getExportsOfModule(m); | |
exports.forEach(e => { | |
extract_alias(e); | |
}); | |
ordered_for_each(exports, e => { | |
dump_symbol(e, level + 1); | |
}); | |
} | |
function dump_ambient_module(name) { | |
var m = checker | |
.getAmbientModules() | |
.find(m => m.valueDeclaration.name.text == name); | |
if (m) { | |
this_module_escaped_name = m.escapedName; | |
dump_module_symbol(m); | |
} | |
} | |
function dump_file_module(filename) { | |
var filename = path.resolve(filename); | |
var m = program.getSourceFiles().find(f => f.path == filename).symbol; | |
if (m) { | |
this_module_escaped_name = m.escapedName; | |
dump_module_symbol(m); | |
} | |
} | |
const cassandra_ts = "sample-ts/cassandra/index.d.ts"; | |
const policies_ts = "sample-ts/cassandra/lib/policies/index.d.ts"; | |
const http_ts = "sample-ts/node-v12/http.d.ts"; | |
hdrl(`[@@@js.implem`); | |
hdrl( | |
`val node_require : string -> Ojs.t [@@js.global "process.mainModule.require"]` | |
); | |
dump_file_module(cassandra_ts); | |
//dump_ambient_module("http"); | |
hdrl(`]`); | |
process.stdout.write(header); | |
process.stdout.write(output); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment