UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

194 lines 11.5 kB
"use strict"; 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