@ts-defold/type-gen
Version:
Typescript type generator for Defold
226 lines • 10.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = void 0;
const schema = __importStar(require("./schema"));
function comment(input) {
let out = '\t' + `/**` + '\n';
input.split('\n').forEach(line => {
out += '\t' + `* ${line}` + '\n';
});
out += '\t' + `*/` + '\n';
return out;
}
function docs(input, TAB = '\t') {
const desc = input.description || input.brief;
let out = TAB + `/**` + '\n';
desc.split('\n').forEach(line => {
out += TAB + `* ${line}` + '\n';
});
input.parameters.forEach(p => {
out += TAB + `* @param ${p.name} ${p.doc}` + '\n';
});
input.returnvalues.forEach(p => {
out += TAB + `* @return ${p.name} ${p.doc}` + '\n';
});
out += TAB + `*/` + '\n';
return out;
}
function type(input) {
const types = input.map(t => {
let type = Object.values(schema.EDocParamType)[Object.keys(schema.EDocParamType).indexOf(t)];
if (type == schema.EDocParamType.Unknown)
type = schema.EDocParamType.Any;
if (type == schema.EDocParamType.Table)
type = schema.EDocParamType.Any;
if (type == schema.EDocParamType.Constant)
type = schema.EDocParamType.Any;
if (type === undefined)
type = schema.EDocParamType.Any;
return type;
});
// Unsure the parameter names are unique (no dupes), and if a single param is any, then the union is unnecessary
const unique = [...new Set(types)];
return (unique.includes(schema.EDocParamType.Any) ? [schema.EDocParamType.Any] : unique).join(' | ');
}
function isOverloadedFunction(e) {
if (e.parameters.length && e.returnvalues.length) {
const params = e.parameters.map(p => p.type).concat(e.returnvalues.map(r => r.type));
let isOverload = true;
let reqCount = 1;
let overloadRun = -1;
// a function overload is 1 or more single value params followed by at least 2 multi-value params
for (let i = 0; i < params.length; i++) {
const p = params[i];
if (reqCount <= 1 && p.length >= reqCount) {
reqCount = p.length;
overloadRun = i;
}
if (reqCount > 1 && p.length != reqCount) {
isOverload = false;
break;
}
}
if (isOverload && overloadRun + 1 < params.length) {
for (let i = overloadRun + 1; i < params.length; i++) {
if (params[i].toString() != params[overloadRun].toString())
return { overload: false, start: -1 };
}
return { overload: true, start: overloadRun };
}
}
return { overload: false, start: -1 };
}
function isReserved(name) {
const reserved = ["delete"];
return reserved.includes(name) ? { alt: name + '$', name } : null;
}
function ensureUnique(set, e) {
if (!set.has(e.name)) {
set.set(e.name, 0);
return e;
}
const count = set.get(e.name);
set.set(e.name, (count ? count : 0) + 1);
return Object.assign({}, e, { name: `${e.name}${set.get(e.name)}` });
}
function hr() {
return `// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= //` + '\n\n';
}
function generate(input, info, types) {
let output = "";
// Header
output += `/** @noSelfInFile */` + '\n';
output += `/// <reference types="lua-types/5.1" />` + '\n';
output += '\n';
output += `// DEFOLD. ${info.channel} version ${info.tag} (${info.sha1})` + '\n';
output += hr();
// Types
if (types) {
types.forEach(t => {
if (t.namespace)
output += '\n' + `declare namespace ${t.namespace} {` + '\n';
const tab = t.namespace ? '\t' : '';
const exp = t.namespace ? "export" : "declare";
t.types.forEach(d => {
const unionOrIntersect = d.unions.length ? `${d.unions.join(' | ')} ` : d.intersection ? `${d.intersection} & ` : "";
output += '\n';
output += tab + `${exp} type ${d.name} = ${unionOrIntersect}{` + '\n';
for (const k in d.definition) {
output += tab + '\t' + `${k}: ${d.definition[k]},` + '\n';
}
output += tab + `}` + '\n';
});
if (t.namespace)
output += `}` + '\n';
});
}
output += hr();
// Globals to the front
const globals = input.filter(i => i.info.namespace === "");
globals.forEach(g => input.splice(input.indexOf(g), 1));
const sorted = globals.concat(input);
// strip empty modules
const printable = [schema.EDocElemType.Variable, schema.EDocElemType.Message, schema.EDocElemType.Function];
const modules = sorted.filter(m => {
const el = m.elements.find(e => printable.includes(e.type));
if (el)
return m;
});
// Modules
modules.forEach(i => {
const TAB = i.info.namespace ? '\t' : '';
const exp = i.info.namespace ? "export" : "declare";
output += '\n';
if (i.info.namespace)
output += `declare namespace ${i.info.namespace} {` + '\n';
const elements = i.elements.sort((a, b) => Object.keys(schema.EDocElemType).indexOf(a.type) - Object.keys(schema.EDocElemType).indexOf(b.type));
elements.forEach(e => {
const name = e.name.replace(`${i.info.namespace}.`, "");
output += '\n';
switch (e.type) {
case schema.EDocElemType.Variable:
{
output += comment(e.brief);
output += TAB + `${exp} let ${name}: ${schema.EDocParamType.Any}` + '\n';
}
break;
case schema.EDocElemType.Message:
{
output += comment(e.description);
output += TAB + `${exp} type ${name} = "${name}"` + '\n';
}
break;
case schema.EDocElemType.Property:
{
output += comment(e.description);
output += TAB + `${exp} let ${name}: ${schema.EDocParamType.Any}` + '\n';
}
break;
case schema.EDocElemType.Function:
{
const scriptFunctions = ["final", "init", "on_input", "on_message", "on_reload", "update"];
const reserved = isReserved(name);
const funcName = reserved ? reserved.alt : name;
const funcExp = reserved ? "" : exp;
if (i.info.group == schema.EDocGroup.System && scriptFunctions.includes(funcName)) {
output.slice(0, -1); // skip script functions
}
else {
output += docs(e, TAB);
const { overload, start } = isOverloadedFunction(e);
if (overload && e.returnvalues.length) {
e.returnvalues[0].type.forEach((ret, i) => {
const set = new Map();
let params = [];
e.parameters.forEach((p, j) => {
const uniqueParam = ensureUnique(set, p);
params.push(`${uniqueParam.name}${uniqueParam.optional ? "?" : ""}: ${type(j < start ? uniqueParam.type : [uniqueParam.type[i]])}`);
});
const optional = e.returnvalues[0].optional;
output += TAB + `${funcExp} function ${funcName}(${params.join(', ')}): ${type([ret])}${optional ? " | undefined" : ""}` + '\n';
if (reserved)
output += '\t' + `export { ${reserved.alt} as ${reserved.name} }` + '\n';
});
}
else {
const set = new Map();
const params = e.parameters.map(p => ensureUnique(set, p)).map(p => `${p.name}${p.optional ? "?" : ""}: ${type(p.type)}`).join(', ');
const retValues = e.returnvalues.length > 0 ? e.returnvalues[0].type : ['Void']; // need a string of the key, to match parser
const retOptional = e.returnvalues.length > 0 ? e.returnvalues[0].optional : false;
output += TAB + `${funcExp} function ${funcName}(${params}): ${type(retValues)}${retOptional ? " | undefined" : ""}` + '\n';
if (reserved)
output += '\t' + `export { ${reserved.alt} as ${reserved.name} }` + '\n';
}
}
}
break;
}
});
output += '\n';
if (i.info.namespace)
output += `}` + '\n';
output += hr();
});
return output;
}
exports.generate = generate;
//# sourceMappingURL=generator.js.map