UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

206 lines 8.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.descriptionPathInfo = descriptionPathInfo; exports.schemaPathInfo = schemaPathInfo; exports.firstUnknownSchemaSegment = firstUnknownSchemaSegment; exports.descriptionPathKeys = descriptionPathKeys; exports.descriptionPaths = descriptionPaths; exports.describeSchema = describeSchema; exports.genericDescription = genericDescription; exports.headerLine = headerLine; exports.describeObject = describeObject; const ansi_1 = require("./text/ansi"); /** * A `.pattern()` object (`versionOverrides`, `specializeConfig`, ...) as `{ value, keyValids? }`: the value schema * any key maps to, plus the keys the pattern restricts to when its key schema is a `.valid(...)` enum (e.g. * `specializeConfig`'s ProjectKinds); `keyValids` is absent for a free-string key. `undefined` when not a pattern * object. Only the first pattern is considered -- the config schema uses a single pattern per object. */ function patternObject(node) { const patterns = node?.patterns; if (!patterns || patterns.length === 0) { return undefined; } const allow = patterns[0].schema?.allow; return allow && allow.length > 0 ? { value: patterns[0].rule, keyValids: allow } : { value: patterns[0].rule }; } /** * Descend one key into a Joi {@link Joi.Description|description}: an explicit sub-key, or -- for a `.pattern()` * object -- the {@link patternObject|pattern's value schema}, since any key matches there. */ function descendDescription(node, key) { return node?.keys?.[key] ?? patternObject(node)?.value; } /** walk a Joi schema {@link Joi.Description|description} down a key path (see {@link descendDescription}) */ function descendDescriptionPath(description, path) { let node = description; for (const key of path) { node = descendDescription(node, key); if (node === undefined) { return undefined; } } return node; } /** walk a Joi schema {@link Joi.Description|description} down a key path, returning the leaf's {@link SchemaPathInfo} (empty if the path does not exist) */ function descriptionPathInfo(description, path) { const node = descendDescriptionPath(description, path); if (node === undefined) { return {}; } const allow = node.allow; return { type: node.type, description: node.flags?.description, valids: allow && allow.length > 0 ? allow : undefined }; } /** {@link descriptionPathInfo} straight from a schema (describes it once per call) */ function schemaPathInfo(schema, path) { return descriptionPathInfo(schema.describe(), path); } /** Joi types that cannot have sub-keys, so a config path may not descend into them. */ const ScalarSchemaTypes = new Set(['boolean', 'number', 'string', 'array', 'date', 'binary', 'symbol', 'function']); /** * The first path segment the schema does not accept, with the keys that ARE accepted there and the path to that * point -- or `undefined` when the whole path is a settable key. Used to reject typo'd config keys. A `.pattern()` * object accepts any key (validation continues into its value schema) and an `.unknown(true)` object (e.g. a * `specializeConfig` entry, which may overwrite any config key) accepts any key, so neither is reported as unknown. */ function firstUnknownSchemaSegment(description, path) { let node = description; for (let i = 0; i < path.length; i++) { const explicit = node?.keys?.[path[i]]; if (explicit === undefined) { const pattern = patternObject(node); if (pattern !== undefined) { if (pattern.keyValids !== undefined && !pattern.keyValids.includes(path[i])) { return { segment: path[i], available: pattern.keyValids.map(String), at: path.slice(0, i) }; } node = pattern.value; continue; } } if (node?.flags?.['unknown'] === true) { return undefined; // `.unknown(true)`: any further key is accepted here } const keys = node?.keys; if (keys === undefined) { if (node?.type !== undefined && ScalarSchemaTypes.has(node.type)) { return { segment: path[i], available: [], at: path.slice(0, i) }; } return undefined; } const next = keys[path[i]]; if (next === undefined) { return { segment: path[i], available: Object.keys(keys), at: path.slice(0, i) }; } node = next; } return undefined; } /** * The keys a schema {@link Joi.Description|description} offers below `path`, i.e. every option that may be set there. * In contrast to the keys of a value, this covers the optional ones that are unset as well. */ function descriptionPathKeys(description, path) { const node = descendDescriptionPath(description, path); if (node === undefined) { return []; } const keys = Object.keys((node.keys ?? {})); // a `.pattern()` object declares no keys, but a `.valid(...)` key schema restricts them to a known set const valids = patternObject(node)?.keyValids?.map(String) ?? []; return [...keys, ...valids.filter(v => !keys.includes(v))]; } /** every settable path in a schema description, intermediate objects included */ function descriptionPaths(description, path = []) { const result = []; for (const key of descriptionPathKeys(description, path)) { const childPath = [...path, key]; result.push(childPath); if (descriptionPathInfo(description, childPath).type === 'object') { result.push(...descriptionPaths(description, childPath)); } } return result; } /** * Describes a Joi schema in a human-readable way. */ function describeSchema(schema, f = ansi_1.formatter) { const description = schema.describe(); const lines = genericDescription(1, f, f.format('.', { effect: ansi_1.ColorEffect.Foreground, color: 7 /* Colors.White */ }), description); const indent = ' '.repeat(4); return lines.map(line => `${indent.repeat(line.level - 1)}${line.text}`).join('\n'); } /** * Provides a generic description for any Joi schema. * You probably want to use {@link describeSchema}. */ function genericDescription(level, formatter, name, desc) { if (!desc) { return []; } const lines = [...headerLine(level, formatter, name, desc.type ?? 'unknown', desc.flags)]; if ('allow' in desc) { lines.push({ level: level + 1, text: `Only allows: ${desc['allow'].map(v => "'" + v + "'").join(', ')}` }); } switch (desc.type) { case 'object': lines.push(...describeObject(level, formatter, desc)); break; case 'alternatives': if ('matches' in desc) { lines.push(...desc['matches'] .flatMap(({ schema }) => genericDescription(level + 1, formatter, '.', schema))); } break; case 'array': if ('items' in desc) { lines.push({ text: 'Valid item types:', level: level }); lines.push(...desc['items'] .flatMap(desc => genericDescription(level + 1, formatter, '.', desc))); } break; default: /* specific support for others if needed */ break; } return lines; } function printFlags(flags) { if (!flags || Object.keys(flags).length === 0) { return ''; } let flagText = ''; if ('presence' in flags) { flagText += String(flags['presence']); } return flagText.trim().length > 0 ? '[' + flagText + '] ' : ''; } /** * Creates the header line(s) for a schema description. */ function headerLine(level, formatter, name, type, flags) { const fnam = name === '.' ? '' : (0, ansi_1.bold)(name, formatter) + ' '; const fdesc = flags && 'description' in flags ? (0, ansi_1.italic)(flags['description'], formatter) + ' ' : ''; const text = `- ${fnam}${printFlags(flags)}${fdesc}(${formatter.format(type, { effect: ansi_1.ColorEffect.Foreground, color: 7 /* Colors.White */ })})`; return [{ level, text }]; } /** * Describes a Joi object schema. */ function describeObject(level, formatter, desc) { let lines = []; if (!('keys' in desc)) { return lines; } for (const key in desc.keys) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const keySchema = desc.keys[key]; lines = lines.concat(genericDescription(level + 1, formatter, key, keySchema)); } return lines; } //# sourceMappingURL=schema.js.map