All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.codemodder.remediation.DefaultFixCandidateSearcher Maven / Gradle / Ivy

There is a newer version: 0.97.9
Show newest version
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;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy