UNPKG

untyped

Version:

[![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![Github Actions][github-actions-src]][github-actions-href] [![Codecov][codecov-src]][codecov-href] [![bundle][bundle-src]][bundle-href]

286 lines (281 loc) 7.32 kB
function escapeKey(val) { return /^\w+$/.test(val) ? val : `"${val}"`; } function getType(val) { const type = typeof val; if (type === "undefined" || val === null) { return void 0; } if (Array.isArray(val)) { return "array"; } return type; } function isObject(val) { return val !== null && !Array.isArray(val) && typeof val === "object"; } function nonEmpty(arr) { return arr.filter(Boolean); } function unique(arr) { return Array.from(new Set(arr)); } function joinPath(a, b = "", sep = ".") { return a ? a + sep + b : b; } function setValue(obj, path, val) { const keys = path.split("."); const _key = keys.pop(); for (const key of keys) { if (!(key in obj)) { obj[key] = {}; } obj = obj[key]; } if (_key) { obj[_key] = val; } } function getValue(obj, path) { for (const key of path.split(".")) { if (!(key in obj)) { return void 0; } obj = obj[key]; } return obj; } function normalizeTypes(val) { const arr = unique(val.filter((str) => str)); if (!arr.length || arr.includes("any")) { return void 0; } return arr.length > 1 ? arr : arr[0]; } function resolveSchema(obj, defaults) { const schema = _resolveSchema(obj, "", { root: obj, defaults, resolveCache: {} }); return schema; } function _resolveSchema(input, id, ctx) { if (id in ctx.resolveCache) { return ctx.resolveCache[id]; } if (!isObject(input)) { const schema2 = { type: getType(input), default: input }; normalizeSchema(schema2); ctx.resolveCache[id] = schema2; if (ctx.defaults && getValue(ctx.defaults, id) === void 0) { setValue(ctx.defaults, id, schema2.default); } return schema2; } const node = {...input}; const schema = ctx.resolveCache[id] = { ...node.$schema, id: "#" + id.replace(/\./g, "/") }; for (const key in node) { if (key === "$resolve" || key === "$schema" || key === "$default") { continue; } schema.properties = schema.properties || {}; if (!schema.properties[key]) { schema.properties[key] = _resolveSchema(node[key], joinPath(id, key), ctx); } } if (ctx.defaults) { schema.default = getValue(ctx.defaults, id); } if (schema.default === void 0 && "$default" in node) { schema.default = node.$default; } if (typeof node.$resolve === "function") { schema.default = node.$resolve(schema.default, (key) => { return _resolveSchema(getValue(ctx.root, key), key, ctx).default; }); } if (ctx.defaults) { setValue(ctx.defaults, id, schema.default); } if (!schema.type) { schema.type = getType(schema.default) || (schema.properties ? "object" : "any"); } normalizeSchema(schema); if (ctx.defaults && getValue(ctx.defaults, id) === void 0) { setValue(ctx.defaults, id, schema.default); } return schema; } function applyDefaults(ref, input) { resolveSchema(ref, input); return input; } function normalizeSchema(schema) { if (schema.type === "array" && !("items" in schema)) { schema.items = { type: nonEmpty(unique(schema.default.map((i) => getType(i)))) }; if (!schema.items.type.length) { schema.items.type = "any"; } } if (schema.default === void 0 && (schema.type === "object" || schema.type === "any")) { schema.default = {}; } } const TYPE_MAP = { array: "any[]", bigint: "bigint", boolean: "boolean", number: "number", object: "any", any: "any", string: "string", symbol: "Symbol", function: "Function" }; const SCHEMA_KEYS = [ "items", "default", "resolve", "properties", "title", "description", "$schema", "type", "tags", "args", "id", "returns" ]; function generateTypes(schema, name = "Untyped") { return `interface ${name} { ` + _genTypes(schema, " ").join("\n ") + "\n}"; } function _genTypes(schema, spaces) { const buff = []; for (const key in schema.properties) { const val = schema.properties[key]; buff.push(...generateJSDoc(val)); if (val.type === "object") { buff.push(`${escapeKey(key)}: {`, ..._genTypes(val, spaces + " "), "},\n"); } else { let type; if (val.type === "array") { type = `Array<${getTsType(val.items)}>`; } else if (val.type === "function") { type = genFunctionType(val); } else { type = getTsType(val); } buff.push(`${escapeKey(key)}: ${type}, `); } } if (buff.length) { const last = buff.pop() || ""; buff.push(last.substr(0, last.length - 1)); } else { buff.push("[key: string]: any"); } return buff.map((i) => spaces + i); } function getTsType(type) { if (Array.isArray(type)) { return [].concat(normalizeTypes(type.map((t) => getTsType(t)))).join("|") || "any"; } if (!type || !type.type) { return "any"; } if (Array.isArray(type.type)) { return type.type.map((t) => TYPE_MAP[t]).join("|"); } if (type.type === "array") { return `Array<${getTsType(type.items)}>`; } return TYPE_MAP[type.type] || type.type; } function genFunctionType(schema) { return `(${genFunctionArgs(schema.args)}) => ${getTsType(schema.returns)}`; } function genFunctionArgs(args) { return (args == null ? void 0 : args.map((arg) => { let argStr = arg.name; if (arg.optional || arg.default) { argStr += "?"; } if (arg.type) { argStr += `: ${getTsType(arg)}`; } return argStr; }).join(", ")) || ""; } function generateJSDoc(schema) { let buff = []; if (schema.title) { buff.push(schema.title); } if (schema.description) { buff.push(schema.description); } if (schema.type !== "object" && schema.type !== "any" && !(Array.isArray(schema.default) && schema.default.length === 0)) { const stringified = JSON.stringify(schema.default); if (stringified) { buff.push(`@default ${stringified.replace(/\*\//g, "*\\/")}`); } } for (const key in schema) { if (!SCHEMA_KEYS.includes(key)) { buff.push("", `@${key} ${schema[key]}`); } } if (Array.isArray(schema.tags)) { for (const tag of schema.tags) { if (tag !== "@untyped") { buff.push("", tag); } } } buff = buff.map((i) => i.split("\n")).flat(); if (buff.length) { return buff.length === 1 ? ["/** " + buff[0] + " */"] : ["/**", ...buff.map((i) => ` * ${i}`), "*/"]; } return []; } function generateMarkdown(schema) { return _generateMarkdown(schema, "", "").join("\n"); } function _generateMarkdown(schema, title, level) { const lines = []; lines.push(`${level} ${title}`); if (schema.type === "object") { for (const key in schema.properties) { const val = schema.properties[key]; lines.push("", ..._generateMarkdown(val, `\`${key}\``, level + "#")); } return lines; } lines.push(`- **Type**: \`${schema.type}\``); if ("default" in schema) { lines.push(`- **Default**: \`${JSON.stringify(schema.default)}\``); } lines.push(""); if (schema.title) { lines.push("> " + schema.title, ""); } if (schema.type === "function") { lines.push("```ts", genFunctionType(schema), "```", ""); } if (schema.description) { lines.push("", schema.description, ""); } return lines; } export { applyDefaults, generateMarkdown, generateTypes, resolveSchema };