@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
145 lines • 7.05 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.definedFunctions = exports.AllDefinitionsFileBase = void 0;
const post_process_1 = require("./post-process");
const range_1 = require("../../../../util/range");
const assert_1 = require("../../../../util/assert");
const edge_1 = require("../../../../dataflow/graph/edge");
const type_1 = require("../../../../r-bridge/lang-4.x/ast/model/type");
const statistics_file_1 = require("../../../output/statistics-file");
const vertex_1 = require("../../../../dataflow/graph/vertex");
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 initialFunctionDefinitionInfo = {
/** all, anonymous, assigned, non-assigned, ... */
total: 0,
/** how many are really using OP-Lambda? */
lambdasOnly: 0,
/** using `<<-`, `<-`, `=`, `->` `->>` */
assignedFunctions: 0,
nestedFunctions: 0,
/** functions that in some easily detectable way call themselves */
recursive: 0,
deepestNesting: 0
};
exports.AllDefinitionsFileBase = 'all-definitions';
function retrieveAllCallsites(input, node, recursiveCalls) {
const dfStart = input.dataflow.graph.outgoingEdges(node.info.id);
const callsites = [];
for (const [target, edge] of dfStart ?? []) {
if (!edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Calls)) {
continue;
}
const loc = input.normalizedRAst.idMap.get(target)?.location;
if (loc) {
callsites.push(range_1.SourceRange.getStart(loc));
}
}
for (const call of recursiveCalls) {
const loc = call.location;
if (loc) {
callsites.push(range_1.SourceRange.getStart(loc));
}
}
return callsites;
}
function visitDefinitions(info, input) {
const definitionStack = [];
const allDefinitions = [];
r_project_1.RProject.visitAst(input.normalizedRAst.ast, node => {
if (node.type !== type_1.RType.FunctionDefinition) {
return;
}
const graph = input.dataflow.graph;
const dfNode = graph.get(node.info.id, true);
if (dfNode === undefined) {
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'no-dataflow-node-found', [node], input.filepath);
return;
}
const [fnDefinition] = dfNode;
(0, assert_1.guard)(fnDefinition.tag === vertex_1.VertexType.FunctionDefinition, () => `Dataflow node is not a function definition (${JSON.stringify(fnDefinition)}))})`);
const returnTypes = fnDefinition.exitPoints.map(ep => graph.get(ep.nodeId, true)).filter(assert_1.isNotUndefined)
.map(([vertex]) => {
const l = graph.idMap?.get(vertex.id)?.location;
return {
location: l ? range_1.SourceRange.getStart(l) : range_1.SourcePosition.invalid()
};
});
if (definitionStack.length > 0) {
info.nestedFunctions++;
info.deepestNesting = Math.max(info.deepestNesting, definitionStack.length);
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'nested-definitions', [model_1.RNode.lexeme(node)], input.filepath);
}
// parameter names:
const parameterNames = node.parameters.map(model_1.RNode.lexeme);
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'usedParameterNames', parameterNames, input.filepath);
const isLambda = node.lexeme.startsWith('\\');
if (isLambda) {
info.lambdasOnly++;
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'allLambdas', [model_1.RNode.lexeme(node)], input.filepath);
}
definitionStack.push(node);
// we find definitions with silly defined-by edges
const assigned = new Set();
const edges = input.dataflow.graph.ingoingEdges(node.info.id);
if (edges !== undefined) {
for (const [targetId, edge] of edges) {
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.DefinedBy)) {
const target = input.normalizedRAst.idMap.get(targetId);
(0, assert_1.guard)(target !== undefined, 'Dataflow edge points to unknown node');
const name = model_1.RNode.lexeme(target);
if (name) {
assigned.add(name);
}
info.assignedFunctions++;
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'assignedFunctions', [name ?? '<unknown>'], input.filepath);
}
if (edge_1.DfEdge.includesType(edge, edge_1.EdgeType.Calls)) {
const target = input.normalizedRAst.idMap.get(targetId);
(0, assert_1.guard)(target !== undefined, 'Dataflow edge points to unknown node');
}
}
}
// track all calls with the same name that do not already have a bound calls edge, superfluous if recursive tracking is explicit
const recursiveCalls = [];
model_1.RNode.visitAst(node.body, n => {
if (n.type === type_1.RType.FunctionCall && n.named && assigned.has(n.functionName.lexeme)) {
recursiveCalls.push(n);
}
});
// one recursive definition, but we record all
info.recursive += recursiveCalls.length > 0 ? 1 : 0;
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'recursive', recursiveCalls.map(n => model_1.RNode.lexeme(n) ?? 'unknown'), input.filepath);
const lexeme = node.info.fullLexeme;
const lexemeSplit = lexeme?.split('\n');
allDefinitions.push({
location: range_1.SourceRange.getStart(node.location),
callsites: retrieveAllCallsites(input, node, recursiveCalls),
numberOfParameters: node.parameters.length,
returns: returnTypes,
length: {
lines: lexemeSplit?.length ?? -1,
characters: lexeme?.length ?? -1,
nonWhitespaceCharacters: lexeme?.replaceAll(/\s/g, '').length ?? 0
}
});
}, node => {
// drop again :D
if (node.type === type_1.RType.FunctionDefinition) {
definitionStack.pop();
}
});
info.total += allDefinitions.length;
(0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, exports.AllDefinitionsFileBase, allDefinitions, input.filepath);
}
exports.definedFunctions = {
name: 'Defined Functions',
description: 'All functions defined within the document',
process(existing, input) {
visitDefinitions(existing, input);
return existing;
},
initialValue: initialFunctionDefinitionInfo,
postProcess: post_process_1.postProcess
};
//# sourceMappingURL=defined-functions.js.map