@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
220 lines • 14.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.printSignatureHelp = printSignatureHelp;
exports.pushFunction = pushFunction;
exports.pushPackage = pushPackage;
exports.pushMatches = pushMatches;
exports.pushPackages = pushPackages;
exports.pushSummary = pushSummary;
const ansi_1 = require("../../../util/text/ansi");
const signature_query_executor_1 = require("./signature-query-executor");
const r_base_packages_1 = require("../../../util/r-base-packages");
const arrays_1 = require("../../../util/collections/arrays");
/** print an in-repl usage guide for the signature query */
function printSignatureHelp(output) {
const f = output.formatter;
const ex = (cmd, desc) => output.stdout(` ${(0, ansi_1.bold)(cmd, f)}\n ${(0, ansi_1.italic)(desc, f)}`);
output.stdout((0, ansi_1.bold)('Signature Database Query', f) + (0, ansi_1.italic)(' (inspects the databases that resolve library()/`::` calls)', f));
output.stdout('');
output.stdout(`${(0, ansi_1.bold)('Usage', f)} :query @signature [<package>[@<version>][::<function>] [<function>]] [--param <name>]... [--required <n>] [--cg]`);
output.stdout('');
output.stdout((0, ansi_1.bold)('Examples', f));
ex(':query @signature', 'summarize the loaded databases');
ex(':query @signature ggplot2', 'a package: version, exports, dependencies, links');
ex(':query @signature ggplot2::aes', 'one function (or `ggplot2 aes`, `ggplot2@3.5.0 aes`)');
ex(':query @signature gg* geom_*', 'glob search (versions also take ranges: >=4.0.0, 4.x)');
ex(':query @signature gg*@* geom_*', 'search every release in the history, not just the latest');
ex(':query @signature ggplot2@<=2021.05', 'select releases by date (YYYY.MM.DD: <=2026, >=2021.05)');
ex(':query @signature ggplot2 * --param data --param mapping', 'functions with both parameters (repeat/comma-separate --param; alone it searches all packages)');
ex(':query @signature stats * --required 3', 'functions with exactly 3 required parameters');
ex(':query @signature dplyr::lead --cg', 'a function plus its transitive call graph as a mermaid.live link');
output.stdout('');
output.stdout(`${(0, ansi_1.bold)('Signature', f)} ${(0, ansi_1.color)('required', 3 /* Colors.Yellow */, f)} params (no default) are yellow, ${(0, ansi_1.italic)('non-forced', f)} (lazily evaluated) italic, defaults dimmed`);
output.stdout((0, ansi_1.italic)(':query* dumps the full JSON (every function, the whole match set).', f));
}
/** how many names to show inline before an `+N more`: a short sample for a package's functions, more for lists */
const SampleFns = 5;
const MaxList = 25;
const baseSet = new Set((0, r_base_packages_1.baseRPackages)());
/** a package name linked to its CRAN page (unless it is base R / the `R` language pseudo-package) */
function linkPackage(name, f) {
return name === 'R' || baseSet.has(name) ? name : f.hyperlink(name, (0, signature_query_executor_1.cranPageUrl)(name), true);
}
/** a `file:line` location, linked to its source when a url is known */
function linkLocation(file, line, url, f) {
const text = `${file}${line !== undefined ? `:${line}` : ''}`;
return url ? f.hyperlink(text, url, true) : text;
}
/** render one parameter: required (no default) in yellow, non-forced (lazily evaluated) italicised, default dimmed */
function renderParameter(f, p) {
if (p.name === '...') {
return p.name;
}
const lazy = p.forced ? {} : { style: 3 /* FontStyles.Italic */ };
const name = p.default === undefined ? (0, ansi_1.color)(p.name, 3 /* Colors.Yellow */, f, lazy) : p.forced ? p.name : (0, ansi_1.italic)(p.name, f);
return p.default !== undefined ? `${name} = ${(0, ansi_1.faint)(p.default, f)}` : name;
}
/** render a function signature as `name(a, b = default, ...)`; see {@link renderParameter} for the per-parameter styling */
function renderSignature(f, fn) {
return `${(0, ansi_1.bold)(fn.name, f)}(${fn.parameters.map(p => renderParameter(f, p)).join(', ')})`;
}
/** render the full view of a single function into `result` */
function pushFunction(result, f, fn) {
const generic = fn.s3generic ? ` ${(0, ansi_1.color)('S3 generic', 5 /* Colors.Magenta */, f, { style: 1 /* FontStyles.Bold */ })}` : '';
result.push(` ╰ ${(0, ansi_1.color)(fn.package, 6 /* Colors.Cyan */, f, { style: 1 /* FontStyles.Bold */ })}::${(0, ansi_1.bold)(fn.name, f)}${fn.version ? ` ${(0, ansi_1.color)('v' + fn.version, 2 /* Colors.Green */, f)}` : ''}${generic}`);
result.push(` ╰ ${renderSignature(f, fn)}`);
if (fn.flowrOnly) {
// nothing below comes from the database, so say so instead of rendering its empty fields as facts
result.push(` ╰ ${(0, ansi_1.italic)('only flowR knows this one, the signature database has no entry', f)}`);
}
else {
const tags = [fn.exported ? (0, ansi_1.color)('exported', 2 /* Colors.Green */, f) : (0, ansi_1.color)('internal', 3 /* Colors.Yellow */, f),
...fn.properties.filter(p => p !== 'exported').map(p => (0, ansi_1.italic)(p, f))];
result.push(` ╰ ${tags.join(' ')}`);
}
if (fn.file) {
const loc = `${fn.file}${fn.line !== undefined ? `:${fn.line}` : ''}`;
result.push(` ╰ ${(0, ansi_1.italic)('source', f)} ${fn.sourceUrl ? `${loc} ${f.hyperlink(fn.sourceUrl, fn.sourceUrl)}` : loc}`);
}
if (fn.docUrl || fn.manUrl) {
const links = [
...(fn.docUrl ? [f.hyperlink('rdrr.io', fn.docUrl)] : []),
...(fn.manUrl ? [f.hyperlink(fn.version ? `man v${fn.version}` : 'man', fn.manUrl)] : [])
];
result.push(` ╰ ${(0, ansi_1.italic)('docs', f)} ${links.join(' ')}`);
}
if (fn.s3method) {
result.push(` ╰ ${(0, ansi_1.italic)('S3 method of', f)} ${(0, ansi_1.color)(`${fn.s3method.package}::${fn.s3method.generic}`, 5 /* Colors.Magenta */, f)} ${(0, ansi_1.italic)(`(class ${fn.s3method.class})`, f)}`);
}
if (fn.flowr) {
const args = (fn.flowr.args ?? []).map(a => `${a.name}${a.roles.length > 0 ? `: ${a.roles.join('+')}` : ''}`);
const props = fn.flowr.props.map(p => (0, ansi_1.color)(p, 4 /* Colors.Blue */, f)).join(', ');
const returns = fn.flowr.returns ? (0, ansi_1.italic)(`returns ${fn.flowr.returns}`, f) : '';
const line = [props, args.length > 0 ? (0, ansi_1.italic)(`(${args.join(', ')})`, f) : '', returns].filter(s => s !== '').join(' ');
if (line !== '') {
result.push(` ╰ ${(0, ansi_1.italic)('flowR', f)} ${line}`);
}
if (fn.flowr.parameters && !fn.flowrOnly) {
result.push(` ╰ ${(0, ansi_1.italic)('flowR', f)} ${(0, ansi_1.italic)('reads it as', f)} ${(0, ansi_1.bold)(fn.name, f)}(${fn.flowr.parameters.join(', ')})`);
}
}
const listLine = (label, items, max) => {
if (!items.length) {
return;
}
const more = items.length > max ? (0, ansi_1.italic)(` (+${items.length - max} more)`, f) : '';
result.push(` ╰ ${(0, ansi_1.italic)(label, f)} (${items.length}): ${items.slice(0, max).join(', ')}${more}`);
};
listLine('dispatches to', fn.s3methods ?? [], MaxList);
listLine('calls', fn.callees, MaxList);
if (fn.callGraph) {
result.push(` ╰ ${(0, ansi_1.italic)('call graph', f)} ${f.hyperlink('mermaid', fn.callGraph, true)}`);
}
}
/** render the full view of a single package into `result` */
function pushPackage(result, f, p) {
const kind = p.base ? (0, ansi_1.color)(' base R', 3 /* Colors.Yellow */, f, { style: 1 /* FontStyles.Bold */ })
: p.cran ? (0, ansi_1.color)(' CRAN', 4 /* Colors.Blue */, f, { style: 1 /* FontStyles.Bold */ }) : '';
result.push(` ╰ ${(0, ansi_1.color)(p.name, 6 /* Colors.Cyan */, f, { style: 1 /* FontStyles.Bold */ })} ${(0, ansi_1.color)('v' + p.version, 2 /* Colors.Green */, f)}${p.resolved ? (0, ansi_1.italic)(` (analyzer resolved ${p.resolved})`, f) : ''}${kind}`);
if (p.releaseDate) {
result.push(` ╰ ${(0, ansi_1.italic)(`released ${p.releaseDate}`, f)}`);
}
if (p.cranPage) {
result.push(` ╰ ${(0, ansi_1.italic)('docs', f)} ${f.hyperlink(p.cranPage, p.cranPage)}`);
}
const links = [];
if (p.repoUrl) {
links.push(f.hyperlink('mirror', p.repoUrl));
}
if (p.cranUrl) {
links.push(f.hyperlink('tarball', p.cranUrl));
}
if (links.length) {
result.push(` ╰ ${links.join(' ')}`);
}
if (p.coreVersions?.length) {
const first = p.coreVersions[0], last = p.coreVersions[p.coreVersions.length - 1];
result.push(` ╰ ${(0, ansi_1.italic)('in R versions', f)}: ${first === last ? first : `${first} to ${last}`} (${p.coreVersions.length})`);
}
result.push(` ╰ ${(0, ansi_1.italic)('exports', f)} (${p.exportsTotal}): ${p.functionCount} functions, ${p.constants.length} constants, ${p.internalCount} internal, ${p.deprecated.length} deprecated`);
if (p.constants.length) {
const shown = p.constants.slice(0, MaxList);
const more = p.constants.length > shown.length ? (0, ansi_1.italic)(` … +${p.constants.length - shown.length} more`, f) : '';
result.push(` ╰ ${(0, ansi_1.italic)('constants', f)}: ${shown.join(', ')}${more}`);
}
if (p.dependencies.length) {
const byType = new Map();
for (const d of p.dependencies) {
const list = byType.get(d.type) ?? [];
list.push(`${linkPackage(d.name, f)}${d.constraint ? (0, ansi_1.italic)(` ${d.constraint}`, f) : ''}`);
byType.set(d.type, list);
}
result.push(` ╰ ${(0, ansi_1.italic)('dependencies', f)} (${p.dependencies.length})`);
for (const [type, list] of byType) {
result.push(` ╰ ${(0, ansi_1.italic)(type, f)}: ${list.join(', ')}`);
}
}
if (p.functions.length) {
result.push(` ╰ ${(0, ansi_1.italic)('functions', f)} (${p.functions.length})`);
const sample = p.functions.slice(0, SampleFns).map(fn => (0, ansi_1.bold)(fn.name, f)).join(', ');
const more = p.functions.length > SampleFns ? (0, ansi_1.italic)(` … +${p.functions.length - SampleFns} more (:query* for the full JSON)`, f) : '';
result.push(` ╰ ${(0, ansi_1.italic)('e.g.', f)} ${sample}${more}`);
}
}
/** render the function hits of a wildcard search into `result` */
function pushMatches(result, f, out) {
const matches = out.matches ?? [];
const cap = out.truncated ? (0, ansi_1.italic)(` (capped at ${matches.length})`, f) : '';
const scanned = out.searched !== undefined && out.searched > (out.matchCount ?? matches.length)
? (0, ansi_1.faint)(` (of ${out.searched.toLocaleString()} searched)`, f) : '';
const onlyLatest = out.latestOnly && out.searched !== undefined
? (0, ansi_1.faint)(' latest versions only, add ', f) + (0, ansi_1.italic)('@*', f) + (0, ansi_1.faint)(' to the package to search the history', f) : '';
result.push(` ╰ ${(0, ansi_1.bold)(String(out.matchCount ?? matches.length), f)} function${matches.length === 1 ? '' : 's'} matched${scanned}${cap}${onlyLatest}`);
for (const m of matches) {
const matched = new Set(m.matchedParameters ?? []);
const params = m.parameters?.length
? `${(0, ansi_1.italic)('(', f)}${m.parameters.map(p => matched.has(p) ? (0, ansi_1.color)(p, 3 /* Colors.Yellow */, f, { style: 1 /* FontStyles.Bold */ }) : (0, ansi_1.italic)(p, f)).join((0, ansi_1.italic)(', ', f))}${(0, ansi_1.italic)(')', f)}`
: '';
const loc = m.file ? ` ${linkLocation(m.file, m.line, m.sourceUrl, f)}` : '';
// one link on a search hit: the version-exact help page when there is one, else the rdrr.io page
const doc = m.manUrl ? ` ${f.hyperlink('man', m.manUrl)}` : (m.docUrl ? ` ${f.hyperlink('docs', m.docUrl)}` : '');
result.push(` ╰ ${(0, ansi_1.color)(m.package, 6 /* Colors.Cyan */, f)}::${(0, ansi_1.bold)(m.name, f)}${params}${m.version ? (0, ansi_1.italic)(` v${m.version}`, f) : ''}${loc}${doc}`);
}
}
/** render the package hits of a wildcard package search into `result` */
function pushPackages(result, f, out) {
const packages = out.packages ?? [];
const cap = out.truncated ? (0, ansi_1.italic)(` (capped at ${packages.length})`, f) : '';
result.push(` ╰ ${(0, ansi_1.bold)(String(packages.length), f)} package${packages.length === 1 ? '' : 's'} matched${cap}`);
for (const pm of packages) {
const name = pm.cranPage ? f.hyperlink((0, ansi_1.color)(pm.name, 6 /* Colors.Cyan */, f), pm.cranPage, true) : (0, ansi_1.color)(pm.name, 6 /* Colors.Cyan */, f);
const kind = pm.base ? (0, ansi_1.italic)(' base R', f) : pm.cran ? (0, ansi_1.italic)(' CRAN', f) : '';
const vers = pm.versions ? `: ${pm.versions.join(', ')}` : pm.latest ? ` ${(0, ansi_1.color)('v' + pm.latest, 2 /* Colors.Green */, f)}` : '';
result.push(` ╰ ${name}${vers}${kind}`);
}
}
/** render the summary of the loaded databases into `result` */
function pushSummary(result, f, out) {
if (out.databases.length === 0 && out.sourceCount === 0) {
result.push(` ╰ ${(0, ansi_1.italic)('No signature databases are loaded (the solver may be disabled or no bundle was found).', f)}`);
return;
}
result.push(` ╰ ${(0, ansi_1.bold)(String(out.packageCount), f)} packages across ${out.sourceCount} source${out.sourceCount === 1 ? '' : 's'}`);
for (const db of out.databases) {
result.push(` ╰ ${(0, ansi_1.color)(db.scope, 6 /* Colors.Cyan */, f)}${db.version ? ` v${db.version}` : ''}${db.date ? (0, ansi_1.italic)(` (${db.date})`, f) : ''}`);
}
if (out.shards && out.shards.length > 0) {
const accessed = out.shards.filter(s => s.accessed).length;
const unpacked = out.shards.filter(s => s.unpacked).length;
result.push(` ╰ ${(0, ansi_1.italic)('shards', f)}: ${accessed}/${out.shards.length} accessed, ${unpacked}/${out.shards.length} unpacked`);
const byScope = [...(0, arrays_1.arraysGroupBy)(out.shards, s => s.id.split('-', 1)[0])].sort(([a], [b]) => a.localeCompare(b));
for (const [scope, inScope] of byScope) {
const list = inScope.map(s => {
const state = s.accessed ? (0, ansi_1.color)('accessed', 2 /* Colors.Green */, f) : s.unpacked ? (0, ansi_1.color)('unpacked', 3 /* Colors.Yellow */, f) : (0, ansi_1.faint)('on disk', f);
return `${(0, ansi_1.color)(s.id, 6 /* Colors.Cyan */, f)} ${state}`;
}).join(', ');
result.push(` ╰ ${(0, ansi_1.italic)(scope, f)}: ${list}`);
}
}
}
//# sourceMappingURL=signature-query-render.js.map