Skip to content

Commit

Permalink
Make insertion coalescing in Replacements#add explicit
Browse files Browse the repository at this point in the history
Instead of silently concatenating overlapping insertions together,
require callers of Replacements#add to explicitly requires a policy for
concatenating them (either existing-first, or new-first). The default
behaviour now throws an exception for overlapping insertions.

MOE_MIGRATED_REVID=212536627
  • Loading branch information
cushon committed Sep 13, 2018
1 parent f3c3a4c commit 60e9f64
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 6 deletions.
Expand Up @@ -119,7 +119,8 @@ public void applyDifferences(SourceFile sourceFile) throws DiffNotApplicableExce
Replacement.create( Replacement.create(
importStatements.getStartPos(), importStatements.getStartPos(),
importStatements.getEndPos(), importStatements.getEndPos(),
importStatements.toString())); importStatements.toString()),
Replacements.CoalescePolicy.REPLACEMENT_FIRST);
} }
} }
for (Replacement replacement : replacements.descending()) { for (Replacement replacement : replacements.descending()) {
Expand Down
Expand Up @@ -53,7 +53,46 @@ public int compare(Range<Integer> o1, Range<Integer> o2) {
private final RangeMap<Integer, Replacement> overlaps = TreeRangeMap.create(); private final RangeMap<Integer, Replacement> overlaps = TreeRangeMap.create();
private final TreeSet<Integer> zeroLengthRanges = new TreeSet<>(); private final TreeSet<Integer> zeroLengthRanges = new TreeSet<>();


/** A policy for handling overlapping insertions. */
public enum CoalescePolicy {
/** Reject overlapping insertions and throw an {@link IllegalArgumentException}. */
REJECT {
@Override
public String coalesce(String replacement, String existing) {
throw new IllegalArgumentException(
String.format("%s conflicts with existing replacement %s", replacement, existing));
}
},
/** Accept overlapping insertions, with the new insertion before the existing one. */
REPLACEMENT_FIRST {
@Override
public String coalesce(String replacement, String existing) {
return replacement + existing;
}
},
/** Accept overlapping insertions, with the existing insertion before the new one. */
EXISTING_FIRST {
@Override
public String coalesce(String replacement, String existing) {
return existing + replacement;
}
};

/**
* Handle an overlapping insert.
*
* @param replacement the replacement being added.
* @param existing the existing insert at this range.
* @return the coalesced replacement.
*/
public abstract String coalesce(String replacement, String existing);
}

public Replacements add(Replacement replacement) { public Replacements add(Replacement replacement) {
return add(replacement, CoalescePolicy.REJECT);
}

public Replacements add(Replacement replacement, CoalescePolicy coalescePolicy) {
if (replacements.containsKey(replacement.range())) { if (replacements.containsKey(replacement.range())) {
Replacement existing = replacements.get(replacement.range()); Replacement existing = replacements.get(replacement.range());
if (!existing.equals(replacement)) { if (!existing.equals(replacement)) {
Expand All @@ -64,7 +103,7 @@ public Replacements add(Replacement replacement) {
Replacement.create( Replacement.create(
existing.startPosition(), existing.startPosition(),
existing.endPosition(), existing.endPosition(),
existing.replaceWith() + replacement.replaceWith()); coalescePolicy.coalesce(replacement.replaceWith(), existing.replaceWith()));
} else { } else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("%s conflicts with existing replacement %s", replacement, existing)); String.format("%s conflicts with existing replacement %s", replacement, existing));
Expand Down
Expand Up @@ -87,7 +87,8 @@ public Set<Replacement> getReplacements(EndPosTable endPositions) {
} }
Replacements replacements = new Replacements(); Replacements replacements = new Replacements();
for (FixOperation fix : fixes) { for (FixOperation fix : fixes) {
replacements.add(fix.getReplacement(endPositions)); replacements.add(
fix.getReplacement(endPositions), Replacements.CoalescePolicy.EXISTING_FIRST);
} }
return replacements.descending(); return replacements.descending();
} }
Expand Down
Expand Up @@ -23,6 +23,7 @@
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import com.google.errorprone.fixes.Replacements.CoalescePolicy;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
Expand Down Expand Up @@ -96,24 +97,54 @@ public void identicalDuplicatesOK() {
} }


@Test @Test
public void multipleInsertionsAtSamePointAreCoalescedInOrder() { public void coalesceExistingFirst() {
// A replacement of an empty region represents an insertion. // A replacement of an empty region represents an insertion.
// Multiple, differing insertions at the same insertion point are allowed, and will be // Multiple, differing insertions at the same insertion point are allowed, and will be
// coalesced into a single Replacement at that insertion point. // coalesced into a single Replacement at that insertion point.
assertThat( assertThat(
new Replacements() new Replacements()
.add(Replacement.create(42, 42, "hello;")) .add(Replacement.create(42, 42, "hello;"))
.add(Replacement.create(42, 42, "goodbye;")) .add(Replacement.create(42, 42, "goodbye;"), CoalescePolicy.EXISTING_FIRST)
.descending()) .descending())
.containsExactly(Replacement.create(42, 42, "hello;goodbye;")); .containsExactly(Replacement.create(42, 42, "hello;goodbye;"));
assertThat( assertThat(
new Replacements() new Replacements()
.add(Replacement.create(42, 42, "goodbye;")) .add(Replacement.create(42, 42, "goodbye;"))
.add(Replacement.create(42, 42, "hello;")) .add(Replacement.create(42, 42, "hello;"), CoalescePolicy.EXISTING_FIRST)
.descending()) .descending())
.containsExactly(Replacement.create(42, 42, "goodbye;hello;")); .containsExactly(Replacement.create(42, 42, "goodbye;hello;"));
} }


@Test
public void coalesceReplacementFirst() {
// A replacement of an empty region represents an insertion.
// Multiple, differing insertions at the same insertion point are allowed, and will be
// coalesced into a single Replacement at that insertion point.
assertThat(
new Replacements()
.add(Replacement.create(42, 42, "hello;"))
.add(Replacement.create(42, 42, "goodbye;"), CoalescePolicy.REPLACEMENT_FIRST)
.descending())
.contains(Replacement.create(42, 42, "goodbye;hello;"));
assertThat(
new Replacements()
.add(Replacement.create(42, 42, "goodbye;"))
.add(Replacement.create(42, 42, "hello;"), CoalescePolicy.REPLACEMENT_FIRST)
.descending())
.containsExactly(Replacement.create(42, 42, "hello;goodbye;"));
}

@Test
public void coalesceReject() {
assertThrows(
IllegalArgumentException.class,
() ->
new Replacements()
.add(Replacement.create(42, 42, "hello;"))
.add(Replacement.create(42, 42, "goodbye;"), CoalescePolicy.REJECT)
.descending());
}

@Test @Test
public void multipleInsertionsAreDeduplicated() { public void multipleInsertionsAreDeduplicated() {
assertThat( assertThat(
Expand Down
Expand Up @@ -57,6 +57,7 @@
import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.DCTree; import com.sun.tools.javac.tree.DCTree;
import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree;
import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -1229,4 +1230,38 @@ public void removeAddModifier_rangesCompatible() {
.addOutputLines("out/Test.java", "abstract class Test {}") .addOutputLines("out/Test.java", "abstract class Test {}")
.doTest(); .doTest();
} }

/** A bugchecker for testing suggested fixes. */
@BugPattern(
name = "PrefixAddImportCheck",
summary = "A bugchecker for testing suggested fixes.",
severity = ERROR,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public static class PrefixAddImportCheck extends BugChecker implements ClassTreeMatcher {
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
return describeMatch(
tree,
SuggestedFix.builder()
.prefixWith(tree, "@Deprecated\n")
.addImport("java.util.List")
.build());
}
}

@Test
public void prefixAddImport() throws IOException {
BugCheckerRefactoringTestHelper.newInstance(new PrefixAddImportCheck(), getClass())
.addInputLines(
"in/Test.java", //
"package p;",
"class Test {}")
.addOutputLines(
"out/Test.java", //
"package p;",
"import java.util.List;",
"@Deprecated",
"class Test {}")
.doTest();
}
} }

0 comments on commit 60e9f64

Please sign in to comment.