UNPKG

jsii-reflect

Version:

strongly-typed reflection library and tools for jsii

313 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Predicate = void 0; exports.jsiiQuery = jsiiQuery; exports.parseExpression = parseExpression; exports.renderElement = renderElement; exports.renderDocs = renderDocs; /* eslint-disable @typescript-eslint/no-implied-eval */ require("@jsii/check-node/run"); const lib_1 = require("../lib"); const hierarchical_set_1 = require("./hierarchical-set"); const JSII_TREE_SUPPORTED_FEATURES = [ 'intersection-types', 'class-covariant-overrides', ]; async function jsiiQuery(options) { const typesys = new lib_1.TypeSystem(); if (options.closure) { await typesys.loadNpmDependencies(options.fileName, { validate: false, supportedFeatures: JSII_TREE_SUPPORTED_FEATURES, }); } else { await typesys.load(options.fileName, { validate: false, supportedFeatures: JSII_TREE_SUPPORTED_FEATURES, }); } const universe = selectAll(typesys); const selectedElements = selectApiElements(universe, options.expressions); expandSelectToParentsAndChildren(universe, selectedElements, options); // The keys are sortable, so sort them, then get the original API elements back // and return only those that were asked for. // Then retain only the kinds we asked for, and sort them return Array.from(selectedElements) .sort() .map((key) => universe.get(stringFromKey(key))) .filter((x) => (isType(x) && options.returnTypes) || (isMember(x) && options.returnMembers)); } // - if we are asking for types, include any type that's a parent of any of the selected members // - if we are asking for members, include all members that are a child of any of the selected types function expandSelectToParentsAndChildren(universe, selected, options) { if (options.returnTypes) { // All type keys from either type keys or member keys selected.addAll(Array.from(selected).map(typeKey)); } if (options.returnMembers) { const allElements = new hierarchical_set_1.HierarchicalSet(Array.from(universe.keys()).map(keyFromString)); // Add all member keys that are members of a selected type selected.addAll(new hierarchical_set_1.HierarchicalSet(allElements).intersect(selected)); } } function isType(x) { return (x instanceof lib_1.ClassType || x instanceof lib_1.InterfaceType || x instanceof lib_1.EnumType); } function isMember(x) { return x instanceof lib_1.Callable || x instanceof lib_1.Property; } /** * Returns a unique key per API element, used because the jsii-reflect members don't guarantee uniqueness at the object level * * Keys have the property that parent keys are a prefix of child keys, and that the keys are in sort order */ function apiElementKey(x) { if (isType(x)) { return [`${x.fqn}`]; } if (isMember(x)) { const sort = x instanceof lib_1.Method && x.static ? '000' : x instanceof lib_1.Initializer ? '001' : '002'; return [`${x.parentType.fqn}`, `${sort}${x.name}`]; } throw new Error('huh'); } function stringFromKey(x) { return x.map((s) => `${s}#`).join(''); } function keyFromString(x) { return x.split('#').slice(0, -1); } /** * Given a type or member key, return the type key */ function typeKey(x) { return [x[0]]; } function selectApiElements(universe, expressions) { const allKeys = new hierarchical_set_1.HierarchicalSet(Array.from(universe.keys()).map(keyFromString)); const currentSelection = expressions.length === 0 || expressions[0].op === 'filter' ? new hierarchical_set_1.HierarchicalSet(allKeys) : new hierarchical_set_1.HierarchicalSet(); for (const expr of expressions) { const thisQuery = Array.from(filterElements(universe, allKeys, expr.kind, expr.expression)); if (expr.op === 'filter' && expr.remove) { currentSelection.remove(thisQuery); } else if (expr.op === 'filter') { currentSelection.intersect(new hierarchical_set_1.HierarchicalSet(thisQuery)); } else { currentSelection.addAll(thisQuery); } } return currentSelection; } function* filterElements(universe, elements, kind, expression) { const pred = new Predicate(expression); for (const key of elements) { const el = universe.get(stringFromKey(key)); if (!el) { throw new Error(`Key not in universe: ${stringFromKey(key)}`); } if (matches(el, kind, pred)) { yield key; } } } class Predicate { constructor(expr) { if (!expr) { this.fn = () => true; } else { const args = API_ELEMENT_ATTRIBUTES.join(','); const body = `return Boolean(${expr});`; try { this.fn = Function(args, body); } catch (e) { throw new Error(`Syntax error in selector: ${body}: ${e}`); } } } apply(context) { return this.fn(...API_ELEMENT_ATTRIBUTES.map((attr) => context[attr])); } } exports.Predicate = Predicate; /** * Whether a given API element matches the filter */ function matches(el, kind, pred) { const context = {}; if (el instanceof lib_1.ClassType) { if (!['type', 'class'].includes(kind)) return false; context.kind = 'class'; context.fqn = el.fqn; } if (el instanceof lib_1.InterfaceType) { const moreSpecificInterfaceType = el.datatype ? 'struct' : 'interface'; if (!['type', moreSpecificInterfaceType].includes(kind)) return false; context.kind = moreSpecificInterfaceType; context.fqn = el.fqn; } if (el instanceof lib_1.EnumType) { if (!['type', 'enum'].includes(kind)) return false; context.kind = 'enum'; context.fqn = el.fqn; } if (el instanceof lib_1.Property) { if (!['member', 'property'].includes(kind)) return false; context.kind = 'property'; context.fqn = `${el.parentType.fqn}#${el.name}`; } if (el instanceof lib_1.Callable) { const moreSpecificCallable = el instanceof lib_1.Initializer ? 'initializer' : 'method'; if (!['member', moreSpecificCallable].includes(kind)) return false; context.kind = moreSpecificCallable; context.fqn = `${el.parentType.fqn}#${el.name}`; } Object.assign(context, Object.fromEntries(API_ELEMENT_ATTRIBUTES.map((attr) => [attr, el[attr]]))); const ret = pred.apply(context); return ret; } function selectAll(typesys) { return new Map([ ...typesys.classes, ...typesys.interfaces, ...typesys.enums, ...typesys.methods, ...typesys.properties, ].map((el) => [stringFromKey(apiElementKey(el)), el])); } const KIND_ALIASES = { t: 'type', c: 'class', i: 'interface', s: 'struct', e: 'enum', p: 'property', prop: 'property', mem: 'member', m: 'method', init: 'initializer', ctr: 'initializer', constructor: 'initializer', }; function parseExpression(expr) { let op; if (expr[0].match(/[a-z]/i)) { op = '.'; } else { op = expr[0]; expr = expr.slice(1); } if (!['-', '+', '.'].includes(op)) { throw new Error(`Invalid operator: ${op} (must be +, - or .)`); } const operator = op; const [kind_, ...expressionParts] = expr.split(':'); const kind = (KIND_ALIASES[kind_] ?? kind_); if (!VALID_KINDS.includes(kind)) { throw new Error(`Invalid kind: ${kind} (must be one of ${VALID_KINDS.join(', ')})`); } return { op: operator === '+' ? 'select' : 'filter', remove: operator === '-', kind, expression: expressionParts?.join(':'), }; } function renderElement(el) { if (el instanceof lib_1.ClassType) { return combine(el.abstract ? 'abstract' : '', 'class', el.fqn); } if (el instanceof lib_1.InterfaceType) { if (el.spec.datatype) { return combine('struct', el.fqn); } return combine('interface', el.fqn); } if (el instanceof lib_1.EnumType) { return combine('enum', el.fqn); } if (el instanceof lib_1.Property) { const opt = el.optional ? '?' : ''; return combine(el.static ? 'static' : '', el.immutable ? 'readonly' : '', `${el.parentType.fqn}#${el.name}${opt}: ${el.type.toString()}`); } if (el instanceof lib_1.Method) { return combine(el.static ? 'static' : '', `${el.parentType.fqn}#${el.name}(${renderParams(el.parameters)}): ${el.returns.toString()}`); } if (el instanceof lib_1.Initializer) { return `${el.parentType.fqn}(${renderParams(el.parameters)}`; } return '???'; } function renderDocs(el) { return el.docs.toString(); } function renderParams(ps) { return (ps ?? []) .map((p) => { const opt = p.optional ? '?' : ''; const varia = p.variadic ? '...' : ''; const arr = p.variadic ? '[]' : ''; return `${varia}${p.name}${opt}: ${p.type.toString()}${arr}`; }) .join(', '); } function combine(...xs) { return xs.filter((x) => x).join(' '); } // A list of all valid API element kinds const VALID_KINDS = [ // Types 'type', 'interface', 'class', 'enum', 'struct', // Members 'member', 'property', 'method', 'initializer', ]; const API_ELEMENT_ATTRIBUTES = [ 'kind', 'fqn', // Types 'ancestors', 'abstract', 'base', 'datatype', 'docs', 'interfaces', 'name', // Members 'initializer', 'optional', 'overrides', 'protected', 'returns', 'parameters', 'static', 'variadic', 'type', ]; //# sourceMappingURL=jsii-query.js.map