From 7ab17e555258b9cfb5e6109612171a56225bb970 Mon Sep 17 00:00:00 2001 From: Stephan Schroevers Date: Sun, 14 Aug 2022 14:36:15 +0200 Subject: [PATCH] Build test code using JDK 17 This enables usage of text blocks, which in combination with IntelliJ IDEA's _language injection_ functionality greatly simplify writing `BugChecker` tests. --- .mvn/jvm.config | 1 + README.md | 18 +- .../ErrorProneTestHelperSourceFormat.java | 179 +++++++++++++---- .../bugpatterns/util/SourceCode.java | 37 ++++ .../bugpatterns/AutowiredConstructorTest.java | 3 + .../ErrorProneTestHelperSourceFormatTest.java | 190 ++++++++++++++++++ pom.xml | 34 +++- to-11.sh | 5 + with-11.sh | 5 + 9 files changed, 424 insertions(+), 48 deletions(-) create mode 100755 to-11.sh create mode 100755 with-11.sh diff --git a/.mvn/jvm.config b/.mvn/jvm.config index b79f9a23841..e203deb3cce 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -4,6 +4,7 @@ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED diff --git a/README.md b/README.md index 090ba532dda..e7cf9c6c3e8 100644 --- a/README.md +++ b/README.md @@ -176,17 +176,11 @@ Some other commands one may find relevant: the current working directory does not contain unstaged or uncommited changes. -When running the project's tests in IntelliJ IDEA, you might see the following -error: - -``` -java: exporting a package from system module jdk.compiler is not allowed with --release -``` - -If this happens, go to _Settings -> Build, Execution, Deployment -> Compiler -> -Java Compiler_ and deselect the option _Use '--release' option for -cross-compilation (Java 9 and later)_. See [IDEA-288052][idea-288052] for -details. +The `BugChecker` implementations provided by this project are tested using +Error Prone's `CompilationTestHelper` and `BugCheckerRefactoringTestHelper` +classes. These utilities accept text blocks containing inline Java source code. +To ease modification of this inline source code, consider using IntelliJ IDEA's +[language injection][idea-language-injection] feature. ## 💡 How it works @@ -213,7 +207,7 @@ guidelines][contributing]. [github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml/badge.svg [github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml?query=branch%3Amaster [google-java-format]: https://github.com/google/google-java-format -[idea-288052]: https://youtrack.jetbrains.com/issue/IDEA-288052 +[idea-language-injection]: https://www.jetbrains.com/help/idea/using-language-injections.html [license-badge]: https://img.shields.io/github/license/PicnicSupermarket/error-prone-support [license]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/LICENSE.md [maven-central-badge]: https://img.shields.io/maven-central/v/tech.picnic.error-prone-support/error-prone-support?color=blue diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormat.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormat.java index cec01c623da..c3ca0cfd18c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormat.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormat.java @@ -5,12 +5,15 @@ import static com.google.errorprone.BugPattern.StandardTags.STYLE; import static com.google.errorprone.matchers.Matchers.anyOf; import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.sun.tools.javac.util.Position.NOPOS; import static java.util.stream.Collectors.joining; import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; import com.google.auto.service.AutoService; +import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.errorprone.BugPattern; +import com.google.errorprone.ErrorProneFlags; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; @@ -26,9 +29,9 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import com.sun.tools.javac.util.Position; import java.util.List; import java.util.Optional; +import tech.picnic.errorprone.bugpatterns.util.SourceCode; /** * A {@link BugChecker} that flags improperly formatted Error Prone test code. @@ -44,13 +47,16 @@ * are not able to) remove imports that become obsolete as a result of applying their suggested * fix(es). */ -// XXX: Once we target JDK 17 (optionally?) suggest text block fixes. +// XXX: The check does not flag well-formatted text blocks with insufficient indentation. Cover this +// using an generic check. // XXX: GJF guesses the line separator to be used by inspecting the source. When using text blocks // this may cause the current unconditional use of `\n` not to be sufficient when building on // Windows; TBD. @AutoService(BugChecker.class) @BugPattern( - summary = "Test code should follow the Google Java style", + summary = + "Test code should follow the Google Java style (and when targeting JDK 15+ be " + + "specified using a single text block)", link = BUG_PATTERNS_BASE_URL + "ErrorProneTestHelperSourceFormat", linkType = CUSTOM, severity = SUGGESTION, @@ -58,6 +64,10 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker implements MethodInvocationTreeMatcher { private static final long serialVersionUID = 1L; + private static final String FLAG_AVOID_TEXT_BLOCKS = + "ErrorProneTestHelperSourceFormat:AvoidTextBlocks"; + private static final String FLAG_IGNORE_MALFORMED_CODE = + "ErrorProneTestHelperSourceFormat:IgnoreMalformedCode"; private static final Formatter FORMATTER = new Formatter(); private static final Matcher INPUT_SOURCE_ACCEPTING_METHOD = anyOf( @@ -71,9 +81,27 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker instanceMethod() .onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput") .named("addOutputLines"); + // XXX: Proper name for this? + private static final String TEXT_BLOCK_MARKER = "\"\"\""; + private static final String DEFAULT_TEXT_BLOCK_INDENTATION = " ".repeat(12); - /** Instantiates a new {@link ErrorProneTestHelperSourceFormat} instance. */ - public ErrorProneTestHelperSourceFormat() {} + private final boolean avoidTextBlocks; + private final boolean ignoreMalformedCode; + + /** Instantiates a default {@link ErrorProneTestHelperSourceFormat} instance. */ + public ErrorProneTestHelperSourceFormat() { + this(ErrorProneFlags.empty()); + } + + /** + * Instantiates a customized {@link ErrorProneTestHelperSourceFormat}. + * + * @param flags Any provided command line flags. + */ + public ErrorProneTestHelperSourceFormat(ErrorProneFlags flags) { + avoidTextBlocks = flags.getBoolean(FLAG_AVOID_TEXT_BLOCKS).orElse(Boolean.FALSE); + ignoreMalformedCode = flags.getBoolean(FLAG_IGNORE_MALFORMED_CODE).orElse(Boolean.FALSE); + } @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { @@ -88,53 +116,130 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return buildDescription(tree).setMessage("No source code provided").build(); } - int startPos = ASTHelpers.getStartPosition(sourceLines.get(0)); - int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1)); - /* Attempt to format the source code only if it fully consists of constant expressions. */ return getConstantSourceCode(sourceLines) - .map(source -> flagFormattingIssues(startPos, endPos, source, isOutputSource, state)) + .map(source -> flagFormattingIssues(sourceLines, source, isOutputSource, state)) .orElse(Description.NO_MATCH); } private Description flagFormattingIssues( - int startPos, int endPos, String source, boolean retainUnusedImports, VisitorState state) { - Tree methodInvocation = state.getPath().getLeaf(); + List sourceLines, + String source, + boolean retainUnusedImports, + VisitorState state) { + MethodInvocationTree methodInvocation = (MethodInvocationTree) state.getPath().getLeaf(); String formatted; try { - formatted = formatSourceCode(source, retainUnusedImports).trim(); + String gjfResult = formatSourceCode(source, retainUnusedImports); + formatted = canUseTextBlocks(state) ? gjfResult : gjfResult.stripTrailing(); } catch (FormatterException e) { - return buildDescription(methodInvocation) - .setMessage(String.format("Source code is malformed: %s", e.getMessage())) - .build(); + return ignoreMalformedCode + ? Description.NO_MATCH + : buildDescription(methodInvocation) + .setMessage(String.format("Source code is malformed: %s", e.getMessage())) + .build(); } - if (source.trim().equals(formatted)) { + boolean isFormatted = source.equals(formatted); + boolean hasStringLiteralMismatch = shouldUpdateStringLiteralFormat(sourceLines, state); + + if (isFormatted && !hasStringLiteralMismatch) { return Description.NO_MATCH; } - if (startPos == Position.NOPOS || endPos == Position.NOPOS) { - /* - * We have insufficient source information to emit a fix, so we only flag the fact that the - * code isn't properly formatted. - */ - return describeMatch(methodInvocation); - } + int startPos = ASTHelpers.getStartPosition(sourceLines.get(0)); + int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1)); + boolean hasNewlineMismatch = + !isFormatted && source.stripTrailing().equals(formatted.stripTrailing()); /* - * The code isn't properly formatted; replace all lines with the properly formatted - * alternatives. + * The source code is not properly formatted and/or not specified using a single text block. + * Report the more salient of the violations, and suggest a fix if sufficient source information + * is available. */ - return describeMatch( - methodInvocation, - SuggestedFix.replace( - startPos, - endPos, - Splitter.on('\n') - .splitToStream(formatted) - .map(state::getConstantExpression) - .collect(joining(", ")))); + return buildDescription(methodInvocation) + .setMessage( + isFormatted || (hasNewlineMismatch && hasStringLiteralMismatch) + ? String.format( + "Test code should %sbe specified using a single text block", + avoidTextBlocks ? "not " : "") + : String.format( + "Test code should follow the Google Java style%s", + hasNewlineMismatch ? " (pay attention to trailing newlines)" : "")) + .addFix( + (startPos == NOPOS || endPos == NOPOS) + ? SuggestedFix.emptyFix() + : SuggestedFix.replace( + startPos, + endPos, + canUseTextBlocks(state) + ? toTextBlockExpression(methodInvocation, formatted, state) + : toLineEnumeration(formatted, state))) + .build(); + } + + private boolean shouldUpdateStringLiteralFormat( + List sourceLines, VisitorState state) { + return canUseTextBlocks(state) + ? sourceLines.size() > 1 || !SourceCode.isTextBlock(sourceLines.get(0), state) + : sourceLines.stream().anyMatch(tree -> SourceCode.isTextBlock(tree, state)); + } + + private boolean canUseTextBlocks(VisitorState state) { + return !avoidTextBlocks && SourceCode.isTextBlockSupported(state); + } + + private static String toTextBlockExpression( + MethodInvocationTree tree, String source, VisitorState state) { + String indentation = suggestTextBlockIndentation(tree, state); + + // XXX: Verify trailing """ on new line. + return TEXT_BLOCK_MARKER + + '\n' + + indentation + + source + .replace("\n", '\n' + indentation) + .replace("\\", "\\\\") + .replace(TEXT_BLOCK_MARKER, "\"\"\\\"") + + TEXT_BLOCK_MARKER; + } + + private static String toLineEnumeration(String source, VisitorState state) { + return Splitter.on('\n') + .splitToStream(source) + .map(state::getConstantExpression) + .collect(joining(", ")); + } + + // XXX: This doesn't seem fully correct; see `EmptyMethodTest#replacement`. + private static String suggestTextBlockIndentation( + MethodInvocationTree target, VisitorState state) { + CharSequence sourceCode = state.getSourceCode(); + if (sourceCode == null) { + return DEFAULT_TEXT_BLOCK_INDENTATION; + } + + String source = sourceCode.toString(); + return getIndentation(target.getArguments().get(1), source) + .or(() -> getIndentation(target.getArguments().get(0), source)) + .or(() -> getIndentation(target.getMethodSelect(), source)) + .orElse(DEFAULT_TEXT_BLOCK_INDENTATION); + } + + private static Optional getIndentation(Tree tree, String source) { + int startPos = ASTHelpers.getStartPosition(tree); + if (startPos == NOPOS) { + return Optional.empty(); + } + + int finalNewLine = source.lastIndexOf('\n', startPos); + if (finalNewLine < 0) { + return Optional.empty(); + } + + return Optional.of(source.substring(finalNewLine + 1, startPos)) + .filter(CharMatcher.whitespace()::matchesAllOf); } private static String formatSourceCode(String source, boolean retainUnusedImports) @@ -152,12 +257,16 @@ private static Optional getConstantSourceCode( StringBuilder source = new StringBuilder(); for (ExpressionTree sourceLine : sourceLines) { + if (source.length() > 0) { + source.append('\n'); + } + Object value = ASTHelpers.constValue(sourceLine); if (value == null) { return Optional.empty(); } - source.append(value).append('\n'); + source.append(value); } return Optional.of(source.toString()); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/SourceCode.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/SourceCode.java index 9691a8a2f20..3d43574bfa9 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/SourceCode.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/SourceCode.java @@ -1,7 +1,9 @@ package tech.picnic.errorprone.bugpatterns.util; import com.google.errorprone.VisitorState; +import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; +import com.sun.tools.javac.jvm.Target; /** * A collection of Error Prone utility methods for dealing with the source code representation of @@ -9,8 +11,43 @@ */ // XXX: Can we locate this code in a better place? Maybe contribute it upstream? public final class SourceCode { + // XXX: Proper name for this? + private static final String TEXT_BLOCK_MARKER = "\"\"\""; + private SourceCode() {} + /** + * Tells whether the targeted Java language level supports text blocks. + * + * @param state A {@link VisitorState} from which the targeted Java language level can be derived. + * @return {@code true} iff text block expressions are supported. + */ + // XXX: Add tests! + public static boolean isTextBlockSupported(VisitorState state) { + // XXX: String comparison is for JDK 11 compatibility. Is there a better way? + return Target.instance(state.context).toString().compareTo("JDK1_15") >= 0; + // return Target.instance(state.context).compareTo(Target.JDK1_15) >= 0; + } + + /** + * Tells whether the given expression is a text block. + * + * @param tree The AST node of interest. + * @param state A {@link VisitorState} describing the context in which the given {@link + * ExpressionTree} is found. + * @return {@code true} iff the given expression is a text block. + */ + // XXX: Add tests! + public static boolean isTextBlock(ExpressionTree tree, VisitorState state) { + if (tree.getKind() != Tree.Kind.STRING_LITERAL) { + return false; + } + + /* If the source code is unavailable then we assume that this literal is _not_ a text block. */ + String src = state.getSourceForNode(tree); + return src != null && src.startsWith(TEXT_BLOCK_MARKER); + } + /** * Returns a string representation of the given {@link Tree}, preferring the original source code * (if available) over its prettified representation. diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AutowiredConstructorTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AutowiredConstructorTest.java index 459e765b72c..75d6fc21839 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AutowiredConstructorTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AutowiredConstructorTest.java @@ -69,6 +69,9 @@ void identification() { .doTest(); } + // XXX: Drop this suppression once + // https://github.com/PicnicSupermarket/error-prone-support/pull/347 is merged. + @SuppressWarnings("RegexpMultiline" /* Check may introduce empty line at start of code block. */) @Test void replacement() { refactoringTestHelper diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormatTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormatTest.java index be6e20306af..58d7fa9e80a 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormatTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ErrorProneTestHelperSourceFormatTest.java @@ -4,17 +4,97 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; import com.google.errorprone.CompilationTestHelper; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; final class ErrorProneTestHelperSourceFormatTest { + // XXX: Add tests cases for `ErrorProneTestHelperSourceFormat:IgnoreMalformedCode`. private final CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass()); + private final CompilationTestHelper avoidTextBlocksCompilationTestHelper = + CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass()) + .setArgs("-XepOpt:ErrorProneTestHelperSourceFormat:AvoidTextBlocks=true"); private final BugCheckerRefactoringTestHelper refactoringTestHelper = BugCheckerRefactoringTestHelper.newInstance( ErrorProneTestHelperSourceFormat.class, getClass()); + private final BugCheckerRefactoringTestHelper avoidTextBlocksRefactoringTestHelper = + BugCheckerRefactoringTestHelper.newInstance( + ErrorProneTestHelperSourceFormat.class, getClass()) + .setArgs("-XepOpt:ErrorProneTestHelperSourceFormat:AvoidTextBlocks=true"); + + // XXX: Consider reducing the `@DisabledForJreRange(max = JRE.JAVA_14)` test scope by moving the + // text blocks to smaller test methods. + @DisabledForJreRange(max = JRE.JAVA_14) @Test void identification() { compilationTestHelper + .addSourceLines( + "A.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import tech.picnic.errorprone.bugpatterns.EmptyMethod;", + "", + "class A {", + " private final CompilationTestHelper compilationTestHelper =", + " CompilationTestHelper.newInstance(EmptyMethod.class, getClass());", + " private final BugCheckerRefactoringTestHelper refactoringTestHelper =", + " BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());", + "", + " void m() {", + " compilationTestHelper", + " // BUG: Diagnostic contains: No source code provided", + " .addSourceLines(\"A.java\")", + " // BUG: Diagnostic contains: Source code is malformed:", + " .addSourceLines(\"B.java\", \"class B {\")", + " // BUG: Diagnostic contains: Test code should be specified using a single text block", + " .addSourceLines(\"C.java\", \"class C {}\")", + " // Malformed code, but not compile-time constant, so not flagged.", + " .addSourceLines(\"D.java\", \"class D {\" + getClass())", + " // BUG: Diagnostic contains: Test code should follow the Google Java style", + " .addSourceLines(\"E.java\", \"class E { }\")", + " // Well-formed code, so not flagged.", + " .addSourceLines(\"F.java\", \"\"\"", + " class F {}", + " \"\"\")", + " // BUG: Diagnostic contains: Test code should follow the Google Java style (pay attention to", + " // trailing newlines)", + " .addSourceLines(\"G.java\", \"\"\"", + " class G {}\"\"\")", + " .doTest();", + "", + " refactoringTestHelper", + " // BUG: Diagnostic contains: Test code should follow the Google Java style", + " .addInputLines(\"in/A.java\", \"class A { }\")", + " // BUG: Diagnostic contains: Test code should follow the Google Java style", + " .addOutputLines(\"out/A.java\", \"class A { }\")", + " // BUG: Diagnostic contains: Test code should follow the Google Java style", + " .addInputLines(", + " \"in/B.java\",", + " \"\"\"", + " import java.util.Map;", + "", + " class B {}", + " \"\"\")", + " // Unused import, but in an output file, so not flagged.", + " .addOutputLines(", + " \"out/B.java\",", + " \"\"\"", + " import java.util.Map;", + "", + " class B {}", + " \"\"\")", + " .doTest(TestMode.TEXT_MATCH);", + " }", + "}") + .doTest(); + } + + @DisabledForJreRange(max = JRE.JAVA_14) + @Test + void identificationAvoidTextBlocks() { + avoidTextBlocksCompilationTestHelper .addSourceLines( "A.java", "import com.google.errorprone.BugCheckerRefactoringTestHelper;", @@ -40,6 +120,13 @@ void identification() { " .addSourceLines(\"D.java\", \"class D {\" + getClass())", " // BUG: Diagnostic contains: Test code should follow the Google Java style", " .addSourceLines(\"E.java\", \"class E { }\")", + " // BUG: Diagnostic contains: Test code should not be specified using a single text block", + " .addSourceLines(\"F.java\", \"\"\"", + " class F {}", + " \"\"\")", + " // BUG: Diagnostic contains: Test code should follow the Google Java style (pay attention to", + " // trailing newlines)", + " .addSourceLines(\"G.java\", \"class G {}\", \"\")", " .doTest();", "", " refactoringTestHelper", @@ -57,6 +144,8 @@ void identification() { .doTest(); } + // XXX: Add `replacement` test. + @DisabledForJreRange(max = JRE.JAVA_14) @Test void replacement() { /* @@ -81,6 +170,107 @@ void replacement() { " compilationTestHelper", " .addSourceLines(", " \"A.java\",", + " \"\"\"", + " import java.util.Map;", + " import java.util.Collection;", + " import java.util.List;", + "", + " interface A extends List, Map { }\"\"\")", + " .doTest();", + "", + " refactoringTestHelper", + " .addInputLines(", + " \"in/A.java\",", + " \"\"\"", + " import java.util.Map;", + " import java.util.Collection;", + " import java.util.List;", + "", + " interface A extends List, Map { }\"\"\")", + " .addOutputLines(", + " \"out/A.java\",", + " \"\"\"", + " import java.util.Map;", + " import java.util.Collection;", + " import java.util.List;", + "", + " interface A extends List, Map { }\"\"\")", + " .doTest(TestMode.TEXT_MATCH);", + " }", + "}") + .addOutputLines( + "out/A.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import tech.picnic.errorprone.bugpatterns.EmptyMethod;", + "", + "class A {", + " private final CompilationTestHelper compilationTestHelper =", + " CompilationTestHelper.newInstance(EmptyMethod.class, getClass());", + " private final BugCheckerRefactoringTestHelper refactoringTestHelper =", + " BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());", + "", + " void m() {", + " compilationTestHelper", + " .addSourceLines(", + " \"A.java\",", + " \"\"\"", + " import java.util.List;", + " import java.util.Map;", + "", + " interface A extends List, Map {}", + " \"\"\")", + " .doTest();", + "", + " refactoringTestHelper", + " .addInputLines(", + " \"in/A.java\",", + " \"\"\"", + " import java.util.List;", + " import java.util.Map;", + "", + " interface A extends List, Map {}", + " \"\"\")", + " .addOutputLines(", + " \"out/A.java\",", + " \"\"\"", + " import java.util.Collection;", + " import java.util.List;", + " import java.util.Map;", + "", + " interface A extends List, Map {}", + " \"\"\")", + " .doTest(TestMode.TEXT_MATCH);", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } + + @Test + void replacementAvoidTextBlocks() { + /* + * Verifies that import sorting and code formatting is performed unconditionally, while unused + * imports are removed unless part of a `BugCheckerRefactoringTestHelper` expected output file. + */ + avoidTextBlocksRefactoringTestHelper + .addInputLines( + "in/A.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import tech.picnic.errorprone.bugpatterns.EmptyMethod;", + "", + "class A {", + " private final CompilationTestHelper compilationTestHelper =", + " CompilationTestHelper.newInstance(EmptyMethod.class, getClass());", + " private final BugCheckerRefactoringTestHelper refactoringTestHelper =", + " BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());", + "", + " void m() {", + " compilationTestHelper", + " .addSourceLines(", + " \"A.java\",", " \"import java.util.Map;\",", " \"import java.util.Collection;\",", " \"import java.util.List;\",", diff --git a/pom.xml b/pom.xml index 7eb41047c30..f91c42afad7 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED @@ -153,6 +154,7 @@ 0.1.16 1.0 11 + 17 3.8.6 4.8.1 1.0.1 @@ -877,6 +879,7 @@ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED @@ -888,6 +891,8 @@ true ${version.jdk} ${version.jdk} + ${version.jdk.test} + ${version.jdk.test} false @@ -931,7 +936,7 @@ src/main/resources/**/*.properties,src/test/resources/**/*.properties - ${version.jdk} + ${version.jdk.test} ${version.maven} @@ -1786,5 +1791,32 @@ + + + idea + + + idea.maven.embedder.version + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${version.jdk.test} + + + + + + diff --git a/to-11.sh b/to-11.sh new file mode 100755 index 00000000000..d7a609ceb7a --- /dev/null +++ b/to-11.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail + +mvn clean test-compile fmt:format -T 1.0C -Perror-prone -Perror-prone-fork -Ppatch -Pself-check -Derror-prone.patch-checks=ErrorProneTestHelperSourceFormat -Derror-prone.self-check-args='-XepOpt:ErrorProneTestHelperSourceFormat:AvoidTextBlocks=true -Xep:MethodReferenceUsage:OFF' -Dverification.skip diff --git a/with-11.sh b/with-11.sh new file mode 100755 index 00000000000..30320c8ae33 --- /dev/null +++ b/with-11.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail + +chjdk 11 && mvn clean install -Dversion.jdk.test=11 && mvn clean install -Perror-prone-fork -Pnon-maven-central -Pself-check -Dversion.jdk.test=11 -Derror-prone.self-check-args='-XepOpt:ErrorProneTestHelperSourceFormat:IgnoreMalformedCode=true -Xep:MethodReferenceUsage:OFF'