@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
206 lines • 8.71 kB
JavaScript
;
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