io.codemodder.remediation.DefaultFixCandidateSearcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codemodder-base Show documentation
Show all versions of codemodder-base Show documentation
Base framework for writing codemods in Java
package io.codemodder.remediation;
import com.github.javaparser.Position;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import io.codemodder.codetf.DetectorRule;
import io.codemodder.codetf.UnfixedFinding;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.VisibleForTesting;
/**
* Maps issues of type T to relevant nodes in the AST. Relevant nodes are decided with matchers.
*
* @param
*/
final class DefaultFixCandidateSearcher implements FixCandidateSearcher {
private final List> matchers;
DefaultFixCandidateSearcher(final List> matchers) {
this.matchers = matchers;
}
@Override
public FixCandidateSearchResults search(
final CompilationUnit cu,
final String path,
final DetectorRule rule,
final List issuesForFile,
final Function getKey,
final Function getStartLine,
final Function> getEndLine,
final Function> getColumn) {
List unfixedFindings = new ArrayList<>();
List nodes =
cu.findAll(Node.class).stream()
// filter by matchers
.filter(n -> matchers.stream().allMatch(m -> m.test(n)))
.toList();
Map> fixCandidateToIssueMapping = new HashMap<>();
for (T issue : issuesForFile) {
String findingId = getKey.apply(issue);
int issueStartLine = getStartLine.apply(issue);
Optional maybeEndLine = getEndLine.apply(issue);
Optional maybeColumn = getColumn.apply(issue);
List nodesForIssue =
nodes.stream()
.filter(
n -> {
int nodeStartLine = n.getRange().orElseThrow().begin.line;
// if issue end line info is present, match the node line with the issue range
return maybeEndLine
.map(issueEndLine -> matches(issueStartLine, nodeStartLine, issueEndLine))
.orElse(matches(issueStartLine, nodeStartLine));
})
// if column info is present, check if the column is contained in the node's range
.filter(
n ->
maybeColumn
.map(
column ->
n.getRange()
.orElseThrow()
.contains(new Position(issueStartLine, column)))
.orElse(true))
.toList();
if (nodesForIssue.isEmpty()) {
unfixedFindings.add(
new UnfixedFinding(
findingId, rule, path, issueStartLine, RemediationMessages.noNodesAtThatLocation));
continue;
} else if (nodesForIssue.size() > 1) {
unfixedFindings.add(
new UnfixedFinding(
findingId, rule, path, issueStartLine, RemediationMessages.multipleNodesFound));
continue;
}
Node node = nodesForIssue.get(0);
fixCandidateToIssueMapping.computeIfAbsent(node, k -> new ArrayList<>()).add(issue);
}
List> fixCandidates =
fixCandidateToIssueMapping.entrySet().stream()
.map(entry -> new FixCandidate<>(entry.getKey(), entry.getValue()))
.toList();
return new FixCandidateSearchResults() {
@Override
public List unfixableFindings() {
return unfixedFindings;
}
@Override
public List> fixCandidates() {
return fixCandidates;
}
};
}
@VisibleForTesting
static boolean matches(
final int issueStartLine, final int startNodeLine, final int issueEndLine) {
// if the issue spans multiple lines, the node must be within that range
return matches(issueStartLine, startNodeLine)
&& isInBetween(startNodeLine, issueStartLine, issueEndLine);
}
static boolean matches(final int issueStartLine, final int startNodeLine) {
// if the issue is on a single line, the node must be on that line
return startNodeLine == issueStartLine;
}
private static boolean isInBetween(int number, int lowerBound, int upperBound) {
return number >= lowerBound && number <= upperBound;
}
}