Skip to content

Instantly share code, notes, and snippets.

@Alhadis
Created June 18, 2019 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alhadis/ced2d65488450f943a3afe044a3dbf34 to your computer and use it in GitHub Desktop.
Save Alhadis/ced2d65488450f943a3afe044a3dbf34 to your computer and use it in GitHub Desktop.
JSDoc helpers
{
"source": {
"includePattern": "\\.+(mjs|js(on|x)?)$"
}
}
#!/usr/bin/env node
"use strict";
// Usage: ./to-typescript.js /path/to/library.mjs
const {join} = require("path");
const {exec} = require("alhadis.utils");
const {readFileSync} = require("fs");
loadFile(input).then(doclets => {
const chunks = [];
for(const doc of doclets){
switch(doc.kind){
case "function":
if(!doc.memberof)
chunks.push(parseFunction(doc));
break;
case "typedef":
chunks.unshift(parseTypedef(doc));
break;
}
}
process.stdout.write(chunks.join("\n") + "\n");
});
async function loadFile(path){
const result = await exec("jsdoc", ["-c", join(__dirname, "config.json"), "-X", path]);
const source = readFileSync(path, "utf8");
return JSON.parse(result.stdout)
.filter(doc => !doc.undocumented && "package" !== doc.kind)
.map(doc => {
// Stupid hack to fix missing info in `export async function…`
if("function" === doc.kind){
const {range} = doc.meta;
const fnDef = source.substring(range[0], range[1]);
if(/^\s*export\s+async\s/.test(fnDef))
doc.async = true;
}
return doc;
});
}
function parseTypedef(obj){
if(obj.properties && obj.properties.length && obj.type.names.some(n => /^Object$/i.test(n))){
obj.type.names = obj.type.names.filter(n => !/^Object$/i.test(n));
const props = obj.properties.map(parseProp).join("\n").replace(/^/gm, "\t");
return `declare type ${obj.name} = `
+ (obj.type.names.length ? parseType(obj.type) + " | " : "")
+ `{\n${props}\n};`;
}
return `declare type ${obj.name} = ${parseType(obj.type)};`
}
function parseProp(obj){
return (obj.readonly ? "readonly " : "")
+ obj.name
+ (obj.optional ? "?" : "")
+ ": " + parseType(obj.type)
+ ";";
}
function parseFunction(obj, exported = true){
return (exported ? "export declare " : "declare ")
+ `${obj.async ? "async " : ""}function ${obj.name}(`
+ obj.params.map(arg => parseParam(arg)).join(", ")
+ `): ${parseReturnType(obj)};`;
}
function parseParam(obj){
let result = obj.variable
? ("..." + obj.name)
: obj.name + (obj.optional ? "?" : "");
result += ": " + parseType(obj.type, obj.variable);
if("defaultvalue" in obj)
result += ` = ${String(obj.defaultvalue)}`;
return result;
}
function parseReturnType(obj){
if(!obj || !obj.returns)
return "void";
const names = new Set();
for(const {type} of obj.returns)
names.add(...type.names);
return names.size
? parseType({names: [...names]})
: "void";
}
function parseType(obj, variadic = false){
const primitives = /^(?:Boolean|Number|String|Function|Object|Symbol|Null|Undefined)(?!\$)\b/i;
return obj.names.map(name => {
if(/^Array\.?<([^<>]+)>$/i.test(name))
name = RegExp.$1 + "[]";
name = name.replace(primitives, s => s.toLowerCase()).replace(/\*/g, "any");
if(variadic) name += "[]";
return name.replace(/\.</g, "<").replace(/^Promise$/, "$&<any>");
}).join(" | ");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment