UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

108 lines 6.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UNUSED_DEFINITION = void 0; const linter_format_1 = require("../linter-format"); const flowr_search_builder_1 = require("../../search/flowr-search-builder"); const range_1 = require("../../util/range"); const linter_tags_1 = require("../linter-tags"); const assert_1 = require("../../util/assert"); const vertex_1 = require("../../dataflow/graph/vertex"); const edge_1 = require("../../dataflow/graph/edge"); const flowr_search_filters_1 = require("../../search/flowr-search-filters"); const InterestingEdgesVariable = edge_1.EdgeType.Reads | edge_1.EdgeType.Calls | edge_1.EdgeType.DefinesOnCall; const InterestingEdgesFunction = edge_1.EdgeType.Reads | edge_1.EdgeType.Calls; // include read as this could print the function definition const InterestingEdgesTargets = edge_1.EdgeType.SideEffectOnCall; function getDefinitionArguments(def, dfg) { return dfg.outgoingEdges(def)?.entries().filter(([, e]) => edge_1.DfEdge.includesType(e, edge_1.EdgeType.DefinedBy)) .map(([target]) => target).toArray() ?? []; } function buildQuickFix(variable, dfg, ast) { // first we check whether any of the 'Defined by' targets have any obligations - if so, we can not remove the definition // otherwise we can automatically remove the full definition! if (variable.info.role === "acc" /* RoleInParent.Accessed */ || variable.info.role === "for-var" /* RoleInParent.ForVariable */) { // this is an access or a for variable, we can not remove it currently return undefined; } const definedBys = getDefinitionArguments(variable.info.id, dfg); const hasImportantArgs = definedBys.some(d => dfg.unknownSideEffects.has(d)) || definedBys.flatMap(e => Array.from(dfg.outgoingEdges(e) ?? [])) .some(([target, e]) => { return edge_1.DfEdge.includesType(e, InterestingEdgesTargets) || dfg.unknownSideEffects.has(target); }); if (hasImportantArgs) { return undefined; // we can not remove this definition, it has important arguments } const totalRangeToRemove = range_1.SourceLocation.merge([...definedBys.map(d => { const vertex = ast.idMap.get(d); return vertex ? range_1.SourceLocation.fromNode(vertex) : undefined; }), variable.info.fullRange ?? variable.location]); return [{ type: 'remove', loc: totalRangeToRemove ?? range_1.SourceLocation.invalid(), description: `Remove unused definition of \`${variable.lexeme}\`` }]; } /** * consider `x <- function() ...` if we say `x` is unused and propose to remove everything, there should be no separate quick fix for the function definition */ function onlyKeepSupersetOfUnused(elements) { const locs = elements.flatMap(e => e.quickFix?.map(q => q.loc) ?? [e.loc]); if (locs.length <= 1) { return elements; // nothing to filter, only one element } return elements.filter(e => { const otherLoc = range_1.SourceLocation.merge((e.quickFix?.map(q => q.loc) ?? [e.loc])) ?? range_1.SourceLocation.invalid(); return !locs.some(r => range_1.SourceLocation.compare(r, otherLoc) !== 0 && range_1.SourceLocation.isSubsetOf(otherLoc, r)); // there is no smaller remove }); } exports.UNUSED_DEFINITION = { /* this can be done better once we have types */ createSearch: config => flowr_search_builder_1.Q.all().filter(config.includeFunctionDefinitions ? flowr_search_filters_1.FlowrFilterCombinator.is(vertex_1.VertexType.VariableDefinition).or(vertex_1.VertexType.FunctionDefinition) : vertex_1.VertexType.VariableDefinition), processSearchResult: (elements, config, data) => { const metadata = { totalConsidered: 0 }; return { results: onlyKeepSupersetOfUnused(elements.getElements().flatMap(element => { metadata.totalConsidered++; const dfgVertex = data.dataflow.graph.getVertex(element.node.info.id); if (!dfgVertex || (!(0, vertex_1.isVariableDefinitionVertex)(dfgVertex) && (0, vertex_1.isFunctionDefinitionVertex)(dfgVertex) && !config.includeFunctionDefinitions)) { return undefined; } const ingoingEdges = data.dataflow.graph.ingoingEdges(dfgVertex.id); const interestedIn = (0, vertex_1.isVariableDefinitionVertex)(dfgVertex) ? InterestingEdgesVariable : InterestingEdgesFunction; const ingoingInteresting = ingoingEdges?.values().some(e => edge_1.DfEdge.includesType(e, interestedIn)); if (ingoingInteresting) { return undefined; } // found an unused definition const variableName = element.node.lexeme; return [{ certainty: linter_format_1.LintingResultCertainty.Uncertain, variableName, involvedId: element.node.info.id, loc: range_1.SourceLocation.fromNode(element.node) ?? range_1.SourceLocation.invalid(), quickFix: buildQuickFix(element.node, data.dataflow.graph, data.normalize) }]; }).filter(assert_1.isNotUndefined)), '.meta': metadata }; }, prettyPrint: { [linter_format_1.LintingPrettyPrintContext.Query]: result => `Definition of \`${result.variableName}\` at ${range_1.SourceLocation.format(result.loc)}`, [linter_format_1.LintingPrettyPrintContext.Full]: result => `Definition of \`${result.variableName}\` at ${range_1.SourceLocation.format(result.loc)} is unused` }, info: { name: 'Unused Definitions', description: 'Checks for unused definitions.', tags: [linter_tags_1.LintingRuleTag.Readability, linter_tags_1.LintingRuleTag.Smell, linter_tags_1.LintingRuleTag.QuickFix], // our limited analysis causes unused definitions involving complex reflection etc. not to be included in our result, but unused definitions are correctly validated certainty: linter_format_1.LintingRuleCertainty.BestEffort, defaultConfig: { includeFunctionDefinitions: true } } }; //# sourceMappingURL=unused-definition.js.map