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
JavaScript
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 };