UNPKG

@ts-defold/type-gen

Version:

Typescript type generator for Defold

320 lines 14.1 kB
"use strict"; 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("../types/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, isReturnTypes = false) { 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; }); // Is we are handling return types we may need to emit a LuaMultiReturn type if (isReturnTypes && types.length > 1) { return `LuaMultiReturn<[${types.join(', ')}]>`; } // 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.types) .concat(e.returnvalues.map((r) => r.types)); 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', 'null']; 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, overrides) { let output = ''; // Header output += `/** @noSelfInFile */` + '\n'; output += `/// <reference types="lua-types/5.1" />` + '\n'; output += `/// <reference types="@typescript-to-lua/language-extensions" />` + '\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.intersections.length ? `${d.intersections.join(' & ')} & ` : ''; 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; }); // Apply overrides overrides === null || overrides === void 0 ? void 0 : overrides.forEach((o) => { modules.forEach((m) => { if (o.info.namespace == m.info.namespace) { o.elements.forEach((overrideElem) => { const targetElem = m.elements.find((elem) => elem.name == overrideElem.name); if (targetElem) { targetElem.parameters = overrideElem.parameters; targetElem.returnvalues = overrideElem.returnvalues; targetElem.type = overrideElem.type; if (overrideElem.brief) targetElem.brief = overrideElem.brief; if (overrideElem.description) targetElem.description = overrideElem.description; } }); } }); }); // 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'; // Sort element output by name then type const elements = i.elements.sort((a, b) => { const keys = Object.values(schema.EDocElemType); return ((a.name > b.name ? 1 : a.name < b.name ? -1 : 0) || keys.indexOf(a.type) - keys.indexOf(b.type)); }); elements.forEach((e) => { const name = e.name.replace(`${i.info.namespace}.`, ''); // TODO: Nested namespaces. Collect in parser? if (name.includes('.') || name.includes(':')) return; output += '\n'; switch (e.type) { case schema.EDocElemType.Variable: { const reserved = isReserved(name); const varName = reserved ? reserved.alt : name; const varExp = reserved ? '' : exp; output += comment(e.brief); output += TAB + `${varExp} let ${varName}: ${schema.EDocParamType.Any}` + '\n'; if (reserved) { output += '\t' + `export { ${reserved.alt} as ${reserved.name} }` + '\n'; } } break; case schema.EDocElemType.Message: { output += comment(e.description); output += TAB + `${exp} type ${name} = "${name}"` + '\n'; } break; case schema.EDocElemType.Property: { const reserved = isReserved(name); const propName = reserved ? reserved.alt : name; const propExp = reserved ? '' : exp; output += comment(e.description); output += TAB + `${propExp} let ${propName}: ${schema.EDocParamType.Any}` + '\n'; if (reserved) { output += '\t' + `export { ${reserved.alt} as ${reserved.name} }` + '\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].types.forEach((ret, i) => { const set = new Map(); const params = []; e.parameters.forEach((p, j) => { const uniqueParam = ensureUnique(set, p); params.push(`${uniqueParam.name}${uniqueParam.optional ? '?' : ''}: ${type(j < start ? uniqueParam.types : [uniqueParam.types[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.types)}`) .join(', '); const retValues = e.returnvalues.length > 0 ? e.returnvalues.map((r) => r.types).flat() : ['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, true)}${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