UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

319 lines (316 loc) 11.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveDefinition = exports.resolveReferences = exports.resolveProperty = exports.getFullKey = exports.minimize = exports.updateObjectByHierarchy = void 0; exports.getValueByHierarchy = getValueByHierarchy; exports.insertIntoObjectByHierarchy = insertIntoObjectByHierarchy; exports.arrayToLookup = arrayToLookup; exports.expandContext = expandContext; exports.findBestOneOf = findBestOneOf; exports.isSchemaDefinition = isSchemaDefinition; exports.resolveReferenceFromPath = resolveReferenceFromPath; exports.pickBestOneOf = pickBestOneOf; const Log_1 = __importDefault(require("../../../../core/Log")); const retryLimit = 10; function getValueByHierarchy(object, hierarchy) { return hierarchy.reduce((prev, entry) => prev && descend(prev, entry), object); } function descend(object, entry) { if (!object) { Log_1.default.debug("[descend]: attempted to descend into undefined object: " + entry); return undefined; } return object[entry.key]; } const updateObjectByHierarchy = (object, value, hierarchy) => { const key = hierarchy[hierarchy.length - 1]?.key; if (!key) { throw new Error("[updateObjectByHierarchy]: Failed to find valid key when updating object by hierarchy"); } // Walk the hierarchy, materializing intermediate objects/arrays if any are missing. // Without this, leaf inputs (e.g. number fields like `min` / `max` on a range // property) crash with "Cannot set properties of undefined (setting 'min')" // when the user starts typing before the parent object has been initialized // by a render pass. Insertion semantics match `insertIntoObjectByHierarchy`: // arrays are created when the next hierarchy entry is an array item, objects // otherwise. let current = object; for (let i = 0; i < hierarchy.length - 1; ++i) { const entry = hierarchy[i]; if (current[entry.key] === undefined || current[entry.key] === null) { const nextIsArrayItem = hierarchy[i + 1]?.isArrayItem; current[entry.key] = nextIsArrayItem ? [] : {}; } current = current[entry.key]; } current[key] = value; }; exports.updateObjectByHierarchy = updateObjectByHierarchy; /* Similar to update but will insert objects as necessary */ function insertIntoObjectByHierarchy(object, value, hierarchy) { let current = object; for (let i = 0; i < hierarchy.length; ++i) { const entry = hierarchy[i]; if (i === hierarchy.length - 1) { current[entry.key] = value; return; } if (current[entry.key] === undefined) { const hasItems = hierarchy[i + 1]?.isArrayItem; current[entry.key] = hasItems ? [] : {}; } current = current[entry.key]; } } const minimize = (input) => { return Array.isArray(input) ? minimizeArray(input) : minimizeObject(input); }; exports.minimize = minimize; const minimizeArray = (input) => { if (!input || !input.length) { return undefined; } const output = []; input.forEach((item, index) => { if (Array.isArray(item)) { output[index] = minimizeArray(item); } else if (typeof item === "object") { output[index] = minimizeObject(item); } else { output[index] = item; } }); return output; }; const minimizeObject = (input, removeEmptyObjects = false) => { if (!input) { return undefined; } // short-circuit the check if N/A const removeRemoveDueToEmpty = removeEmptyObjects && Object.values(input).every((value) => value === undefined || (Array.isArray(value) && !value.length)); if (removeRemoveDueToEmpty) { return undefined; } const output = {}; Object.entries(input).forEach(([key, value]) => { if (Array.isArray(value)) { output[key] = minimizeArray(value); } else if (typeof value === "object") { output[key] = minimizeObject(value); } else { output[key] = value; } }); return output; }; function arrayToLookup(arr) { return arr.reduce((dic, val) => dic.set(val, true), new Map()); } function expandContext(context, options) { return { ...context, hierarchy: !!options?.entry ? [...context.hierarchy, options?.entry] : context.hierarchy, required: options?.required ? arrayToLookup(options?.required || []) : context.required, }; } const getFullKey = (hierarchy, limit) => { return hierarchy .slice(0, (limit ?? hierarchy.length) + 1) .reduce((value, entry) => value + (value && ".") + entry.key, ""); }; exports.getFullKey = getFullKey; const resolveProperty = (property, context) => { const [key, definition] = property; const resolved = (0, exports.resolveDefinition)(definition, context.references); return [key, resolved.definition]; }; exports.resolveProperty = resolveProperty; // resolves references without choosing from oneOfs, will instead return a list of definitions based on oneOfs const resolveReferences = (definition, references) => { const narrowedDef = definition; if (typeof narrowedDef === "boolean") { throw new Error("bools not supported"); } let lookupDefinition = narrowedDef; let lookupKey = undefined; for (let tries = 0; !!lookupDefinition.$ref; tries += 1) { const reference = lookupReference(lookupDefinition.$ref, references); if (!!reference) { const [key, def] = reference; lookupKey = key; lookupDefinition = def; } if (tries++ >= retryLimit) { throw new Error(`Failed to resolve references`); } } let lookupOneOfs = []; const options = lookupDefinition.oneOf || lookupDefinition.anyOf; if (!!options?.length) { lookupOneOfs = options.map((def) => (0, exports.resolveReferences)(def, references)).map((x) => x.definition); } return { definition: lookupDefinition, schemaKey: lookupKey, oneOfs: lookupOneOfs, }; }; exports.resolveReferences = resolveReferences; // attempts to resolve to one definition, by choosing the best available oneOfs where applicable const resolveDefinition = (definition, references, oneOfChoice) => { let counter = 0; // const lets us narrow the type const narrowedDef = definition; if (typeof narrowedDef === "boolean") { throw new Error("bools not supported"); } let lookupDefinition = narrowedDef; let lookupKey = undefined; while (!!lookupDefinition.oneOf || !!lookupDefinition.$ref) { if (!!lookupDefinition.oneOf?.length) { let foundDefinition; if (oneOfChoice) { foundDefinition = lookupDefinition.oneOf[oneOfChoice]; } else { const result = findBestOneOf(lookupDefinition.oneOf, references); foundDefinition = result?.definition; } if (!!foundDefinition) { if (typeof foundDefinition === "boolean") { throw new Error("boolean not supported", { cause: foundDefinition }); } lookupDefinition = foundDefinition; } } // try get ref if (!!lookupDefinition.$ref) { if (lookupDefinition.$ref.endsWith(".json")) { throw new Error("external references not supported: " + lookupDefinition.$ref); } const reference = lookupReference(lookupDefinition.$ref, references); if (!!reference) { const [key, def] = reference; lookupKey = key; lookupDefinition = def; } } if (counter++ >= retryLimit) { throw new Error(`Failed to resolve references`); } } return { schemaKey: lookupKey, definition: lookupDefinition, }; }; exports.resolveDefinition = resolveDefinition; // OBSOLETE: replaced by `determineOneOf` - still used in a couple cases, those should be removed soon // // sorts OneOf options to return the single 'best' option. // // More versatile, comprehensive options are preferred // e.g. `int[]` is preferred over a single `int`, since int can be represented as a list of one function findBestOneOf(oneOfs, references, parent) { if (!oneOfs) { return null; } const sorted = oneOfs .map((item) => (0, exports.resolveDefinition)(item, references)) .map((ref) => ({ rank: rankOneOf(ref.definition, parent), ref })) .sort((refA, refB) => refB.rank - refA.rank) .map((value) => value.ref); return sorted[0]; } //removes bools function isSchemaDefinition(definition) { return typeof definition !== "boolean"; } function rankOneOf(definition, parent) { if (typeof definition === "boolean") { return 0; } if (!!definition && definition === parent) { return 1; } let score = 0; if (definition.type === "array") { score += 1000; score += rankType(definition.items); } else { score = rankType(definition.type); } return score; } function rankType(type) { // an array of types in this context likely means an array with mixed types allowed // we want to avoid these (for now)... if (!type || type === "boolean" || Array.isArray(type)) { return 0; } if (type === "object") { return 100; } if (type === "string") { return 80; } return 10; } function resolveReferenceFromPath(referencePath, references) { const reference = lookupReference(referencePath, references); if (!reference) { return null; } const [, def] = reference; const resolved = (0, exports.resolveDefinition)(def, references); return resolved; } function lookupReference(referencePath, references) { const definitionName = extractNameFromReferencePath(referencePath); const reference = references.find(([name]) => name === definitionName) || null; return reference; } function extractNameFromReferencePath(referencePath) { const index = referencePath.lastIndexOf("/"); return index < 0 ? null : referencePath.substring(index + 1); } /* Picks a single definition from among supplied `oneOfs` based on a static set of criteria. The criteria attempts to determine the flexible definition, for example if oneOfs allow for an object or an array of objects, then the array should be preferred as the array as it is more versatile option - an array of one item is roughly equivalent to the single item, but a single object does not allow for multiple items. */ function pickBestOneOf(oneOfs) { let bestDef = oneOfs.find((candidate) => candidate.type === "array"); if (!!bestDef) { return bestDef; } bestDef = oneOfs.find((candidate) => candidate.type === "object"); if (!!bestDef) { return bestDef; } bestDef = oneOfs.find((candidate) => candidate.type === "string"); if (!!bestDef) { return bestDef; } bestDef = oneOfs.find((candidate) => candidate.type === "number"); if (!!bestDef) { return bestDef; } bestDef = oneOfs.find((candidate) => typeof candidate.type === "string" && candidate.type !== "null" && candidate.type !== "any"); if (!!bestDef) { return bestDef; } Log_1.default.debug("Defaulting oneOf type."); return oneOfs[0]; }