UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

144 lines 7 kB
"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 visitor_1 = require("../../../../r-bridge/lang-4.x/ast/model/processing/visitor"); const statistics_file_1 = require("../../../output/statistics-file"); const vertex_1 = require("../../../../dataflow/graph/vertex"); 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 (!(0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Calls)) { continue; } const loc = input.normalizedRAst.idMap.get(target)?.location; if (loc) { callsites.push((0, range_1.getRangeStart)(loc)); } } for (const call of recursiveCalls) { const loc = call.location; if (loc) { callsites.push((0, range_1.getRangeStart)(loc)); } } return callsites; } function visitDefinitions(info, input) { const definitionStack = []; const allDefinitions = []; (0, visitor_1.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, true)).filter(assert_1.isNotUndefined) .map(([vertex]) => { const l = graph.idMap?.get(vertex.id)?.location; return { location: l ? (0, range_1.getRangeStart)(l) : [-1, -1] }; }); if (definitionStack.length > 0) { info.nestedFunctions++; info.deepestNesting = Math.max(info.deepestNesting, definitionStack.length); (0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'nested-definitions', [node.info.fullLexeme ?? node.lexeme], input.filepath); } // parameter names: const parameterNames = node.parameters.map(p => p.info.fullLexeme ?? p.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', [node.info.fullLexeme ?? node.lexeme], 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 ((0, edge_1.edgeIncludesType)(edge.types, 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 = target.info.fullLexeme ?? target.lexeme; if (name) { assigned.add(name); } info.assignedFunctions++; (0, statistics_file_1.appendStatisticsFile)(exports.definedFunctions.name, 'assignedFunctions', [name ?? '<unknown>'], input.filepath); } if ((0, edge_1.edgeIncludesType)(edge.types, 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 = []; (0, visitor_1.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 => n.info.fullLexeme ?? n.lexeme ?? 'unknown'), input.filepath); const lexeme = node.info.fullLexeme; const lexemeSplit = lexeme?.split('\n'); allDefinitions.push({ location: (0, range_1.getRangeStart)(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