UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

145 lines 7.05 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 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