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

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

The newest version!
package io.codemodder.remediation;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.nodeTypes.NodeWithRange;
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;

  private final NodePositionMatcher nodePositionMatcher;

  DefaultFixCandidateSearcher(final List> matchers) {
    this.matchers = matchers;
    this.nodePositionMatcher = new DefaultNodePositionMatcher();
  }

  DefaultFixCandidateSearcher(
      final List> matchers, final NodePositionMatcher nodePositionMatcher) {
    this.matchers = matchers;
    this.nodePositionMatcher = nodePositionMatcher;
  }

  @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 IdentityHashMap<>();
    Set matchedIssues = new HashSet<>();

    for (T issue : issuesForFile) {
      String findingId = getKey.apply(issue);
      int issueStartLine = getStartLine.apply(issue);
      int issueEndLine = getEndLine.apply(issue).orElse(issueStartLine);
      Optional maybeColumn = getColumn.apply(issue);
      List nodesForIssue =
          nodes.stream()
              .filter(NodeWithRange::hasRange)
              // if column info is present, check if the node starts after the issue start
              // coordinates
              .filter(
                  n ->
                      maybeColumn
                          .map(
                              column ->
                                  nodePositionMatcher.match(
                                      n, issueStartLine, issueEndLine, column))
                          .orElse(nodePositionMatcher.match(n, issueStartLine, issueEndLine)))
              .toList();
      if (nodesForIssue.isEmpty()) {
        continue;
      }
      matchedIssues.add(issue);
      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();

    // remove any issue that had a match
    return new FixCandidateSearchResults() {
      @Override
      public List unfixableFindings() {
        return unfixedFindings;
      }

      @Override
      public List unmatchedIssues() {
        return issuesForFile.stream().filter(i -> !matchedIssues.contains(i)).toList();
      }

      @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 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 - 2025 Weber Informatics LLC | Privacy Policy