@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
194 lines • 11.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DependenciesQueryDefinition = exports.DefaultDependencyCategories = exports.Constant = exports.Unknown = void 0;
exports.getAllCategories = getAllCategories;
const ansi_1 = require("../../../util/text/ansi");
const time_1 = require("../../../util/text/time");
const joi_1 = __importDefault(require("joi"));
const dependencies_query_executor_1 = require("./dependencies-query-executor");
const library_functions_1 = require("./function-info/library-functions");
const source_functions_1 = require("./function-info/source-functions");
const remote_functions_1 = require("./function-info/remote-functions");
const read_functions_1 = require("./function-info/read-functions");
const write_functions_1 = require("./function-info/write-functions");
const visualize_functions_1 = require("./function-info/visualize-functions");
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
const test_functions_1 = require("./function-info/test-functions");
const identifier_1 = require("../../../dataflow/environments/identifier");
const r_project_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-project");
const model_1 = require("../../../r-bridge/lang-4.x/ast/model/model");
const objects_1 = require("../../../util/objects");
const query_fn_props_1 = require("../../../dataflow/environments/query-fn-props");
const built_in_props_1 = require("../../../dataflow/environments/built-in-props");
const vertex_1 = require("../../../dataflow/graph/vertex");
const df_helper_1 = require("../../../dataflow/graph/df-helper");
/** The value could not be resolved, e.g. a path assembled at runtime. Such a dependency may be missing or fetchable. */
exports.Unknown = 'unknown';
/** The value resolved, but to data given inline rather than to a path (`matrix(0, 2, 2)`, `data.frame(a = 1)`). */
exports.Constant = 'constant';
exports.DefaultDependencyCategories = {
'library': {
queryDisplayName: 'Libraries',
functions: library_functions_1.LibraryFunctions,
defaultValue: exports.Unknown,
/* for libraries, we have to additionally track all uses of `::` and `:::`, for this we currently simply traverse all uses */
additionalAnalysis: async (data, ignoreDefault, _functions, _queryResults, result) => {
if (!ignoreDefault) {
r_project_1.RProject.visitAst((await data.analyzer.normalize()).ast, node => {
let ns;
if (node.type === type_1.RType.Symbol && (ns = identifier_1.Identifier.getNamespace(node.content)) !== undefined) {
const dep = data.analyzer.inspectContext().deps.getDependency(ns);
/* we should improve the identification of ':::' */
result.push({
nodeId: node.info.id,
functionName: model_1.RNode.lexeme(node).includes(':::') ? ':::' : '::',
value: ns,
versionConstraints: dep?.versionConstraints,
derivedRange: dep?.effectiveRange,
namespaceInfo: dep?.namespaceInfo
});
}
});
}
}
},
'remote': {
queryDisplayName: 'Remote Installs',
functions: remote_functions_1.RemoteFunctions,
defaultValue: exports.Unknown,
/* the value is the reference as written, which names the package only implicitly */
additionalAnalysis: (_data, _ignoreDefault, _functions, _queryResults, result) => {
for (const [at, info] of result.entries()) {
const target = (0, remote_functions_1.remoteTarget)(info.value);
if (target !== undefined) {
result[at] = { ...info, ...target };
}
}
}
},
'source': {
queryDisplayName: 'Sourced Files',
functions: source_functions_1.SourceFunctions,
defaultValue: exports.Unknown
},
'read': {
queryDisplayName: 'Read Data',
functions: read_functions_1.ReadFunctions,
defaultValue: exports.Unknown
},
'write': {
queryDisplayName: 'Written Data',
functions: write_functions_1.WriteFunctions,
defaultValue: 'stdout'
},
'print': {
queryDisplayName: 'Auto-printed Results',
functions: [],
defaultValue: 'stdout',
additionalAnalysis: async (data, ignoreDefault, _functions, queryResults, result) => {
if (ignoreDefault) {
return;
}
const [{ ast }, dataflow] = await Promise.all([data.analyzer.normalize(), data.analyzer.dataflow()]);
const accountedFor = new Set(Object.values(queryResults?.kinds ?? {})
.flatMap(k => Object.values(k.subkinds).flat()).map(r => r.id));
for (const file of ast.files) {
for (const statement of file.root.children) {
if (statement.type !== type_1.RType.FunctionCall || accountedFor.has(statement.info.id)) {
continue;
}
const vertex = dataflow.graph.getVertex(statement.info.id);
const functionName = vertex_1.FunctionCallVertex.is(vertex)
? df_helper_1.Dataflow.qualify(statement.info.id, dataflow.graph, false) ?? vertex.name : undefined;
if (functionName === undefined) {
continue;
}
const props = (0, query_fn_props_1.queryFnProps)(functionName, { environment: dataflow.environment })?.props ?? 0;
if ((props & (built_in_props_1.CallProp.Invisible | built_in_props_1.CallProp.Graphics)) !== 0 || test_functions_1.ExpectFunctionNames.test(identifier_1.Identifier.getName(functionName))) {
continue;
}
result.push({ nodeId: statement.info.id, functionName, value: 'stdout' });
}
}
}
},
'visualize': {
queryDisplayName: 'Visualizations',
functions: visualize_functions_1.VisualizeFunctions
},
'test': {
queryDisplayName: 'Tests',
functions: test_functions_1.TestFunctions
}
};
function printResultSection(title, infos, result, formatter) {
if (infos.length <= 0) {
return;
}
result.push(` ${(0, ansi_1.bold)(title, formatter)} ${(0, ansi_1.faint)(`(${infos.length})`, formatter)}`);
// one line per dependency: the value (package/file) up front, its function + node as a faint provenance suffix
for (const i of infos) {
const fn = identifier_1.Identifier.getName(i.functionName);
/* neither names a resource: inline data is resolved but no path, `unknown` is a path we could not resolve */
const stands = i.value === exports.Constant ? '<inline data>' : i.value === exports.Unknown || i.value === undefined ? '<unresolved>' : undefined;
const value = stands !== undefined ? (0, ansi_1.faint)(stands, formatter) : (0, ansi_1.bold)(i.value, formatter);
const version = i.derivedRange !== undefined ? ` ${(0, ansi_1.faint)(i.derivedRange.format(), formatter)}` : '';
const linked = i.linkedIds ? `, linked ${i.linkedIds.join(', ')}` : '';
result.push(` ${value}${version} ${(0, ansi_1.faint)(`via ${fn} (node ${i.nodeId}${linked})`, formatter)}`);
}
}
/**
* Gets all dependency categories, including user-defined additional categories.
* A category named like a built-in one extends it instead of replacing it.
*/
function getAllCategories(queries) {
const categories = { ...exports.DefaultDependencyCategories };
for (const query of queries) {
for (const [name, settings] of Object.entries(query.additionalCategories ?? {})) {
const known = categories[name];
categories[name] = known === undefined ? settings : {
...known,
...(0, objects_1.compactRecord)(settings),
functions: [...known.functions, ...settings.functions]
};
}
}
return categories;
}
const functionInfoSchema = joi_1.default.array().items(joi_1.default.object({
name: joi_1.default.string().required().description('The name of the library function.'),
package: joi_1.default.string().optional().description('The package name of the library function'),
argIdx: joi_1.default.number().optional().description('The index of the argument that contains the library name.'),
argName: joi_1.default.string().optional().description('The name of the argument that contains the library name.'),
})).optional();
exports.DependenciesQueryDefinition = {
title: 'Dependencies Query',
executor: dependencies_query_executor_1.executeDependenciesQuery,
asciiSummarizer: (formatter, _analyzer, queryResults, result, queries) => {
const out = queryResults;
result.push(`Query: ${(0, ansi_1.bold)('dependencies', formatter)} (${(0, time_1.printAsMs)(out['.meta'].timing, 0)})`);
for (const [category, value] of Object.entries(getAllCategories(queries))) {
printResultSection(value.queryDisplayName ?? category, out[category] ?? [], result, formatter);
}
return true;
},
schema: joi_1.default.object({
type: joi_1.default.string().valid('dependencies').required().description('The type of the query.'),
ignoreDefaultFunctions: joi_1.default.boolean().optional().description('Should the set of functions that are detected by default be ignored/skipped? Defaults to false.'),
...Object.fromEntries(Object.keys(exports.DefaultDependencyCategories).map(c => [`${c}Functions`, functionInfoSchema.description(`The set of ${c} functions to search for.`)])),
enabledCategories: joi_1.default.array().optional().items(joi_1.default.string()).description('A set of flags that determines what types of dependencies are searched for. If unset, all dependency types are searched for.'),
additionalCategories: joi_1.default.object().allow(joi_1.default.object({
queryDisplayName: joi_1.default.string().description('The display name in the query result.'),
functions: functionInfoSchema.description('The functions that this additional category should search for.'),
defaultValue: joi_1.default.string().description('The default value to return when there is no value to gather from the function information.').optional()
})).description('A set of additional, user-supplied dependency categories, whose results will be included in the query return value. Using the name of a built-in category extends it instead of replacing it.').optional()
}).description('The dependencies query retrieves and returns the set of all dependencies in the dataflow graph, which includes libraries, sourced files, read data, and written data.'),
flattenInvolvedNodes: (queryResults, query) => {
const out = queryResults;
return Object.keys(getAllCategories(query)).flatMap(c => out[c] ?? []).map(o => o.nodeId);
}
};
//# sourceMappingURL=dependencies-query-format.js.map