jsii-reflect
Version:
strongly-typed reflection library and tools for jsii
313 lines • 10 kB
JavaScript
;
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