UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

220 lines 14.9 kB
"use strict"; 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