Initial check-in with two inspections working.

This commit is contained in:
2019-03-10 18:19:46 +01:00
parent 39ba01adec
commit 80104004d0
34 changed files with 1063 additions and 2 deletions
@@ -0,0 +1,23 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.psi.CommonClassNames
import com.siyeh.ig.callMatcher.CallMatcher
open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
companion object {
private const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert"
private const val IS_EQUAL_TO = "isEqualTo"
private const val IS_NOT_EQUAL_TO = "isNotEqualTo"
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_NOT_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
}
override fun getGroupDisplayName(): String {
return "AssertJ"
}
}
@@ -0,0 +1,62 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import org.jetbrains.annotations.NonNls
class AssertThatObjectIsNotNullInspection : AbstractAssertJInspection() {
companion object {
@NonNls
private val DISPLAY_NAME = "Comparing to non-null"
@NonNls
private val INSPECTION_MESSAGE = "isNotEqualTo(null) can be simplified to isNotNull()"
@NonNls
private val QUICKFIX_DESCRIPTION = "Replace isNotEqualTo(null) with isNotNull()"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
return
}
if (expression.argumentList.expressions[0].type == PsiType.NULL) {
holder.registerProblem(
expression,
INSPECTION_MESSAGE,
ProblemHighlightType.INFORMATION,
null as TextRange?,
ReplaceWithIsNotNullQuickFix()
)
}
}
}
}
private class ReplaceWithIsNotNullQuickFix : LocalQuickFix {
override fun getFamilyName() = QUICKFIX_DESCRIPTION
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val factory = JavaPsiFacade.getElementFactory(element.project)
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val oldQualifier = methodCallExpression.methodExpression.qualifierExpression ?: return
val isNotNullExpression =
factory.createExpressionFromText("a.isNotNull()", null) as PsiMethodCallExpression
isNotNullExpression.methodExpression.qualifierExpression!!.replace(oldQualifier)
element.replace(isNotNullExpression)
}
}
}
@@ -0,0 +1,62 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import org.jetbrains.annotations.NonNls
class AssertThatObjectIsNullInspection : AbstractAssertJInspection() {
companion object {
@NonNls
private val DISPLAY_NAME = "Comparing to null"
@NonNls
private val INSPECTION_MESSAGE = "isEqualTo(null) can be simplified to isNull()"
@NonNls
private val QUICKFIX_DESCRIPTION = "Replace isEqualTo(null) with isNull()"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!IS_EQUAL_TO_OBJECT.test(expression)) {
return
}
if (expression.argumentList.expressions[0].type == PsiType.NULL) {
holder.registerProblem(
expression,
INSPECTION_MESSAGE,
ProblemHighlightType.INFORMATION,
null as TextRange?,
ReplaceWithIsNullQuickFix()
)
}
}
}
}
private class ReplaceWithIsNullQuickFix : LocalQuickFix {
override fun getFamilyName() = QUICKFIX_DESCRIPTION
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val factory = JavaPsiFacade.getElementFactory(element.project)
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val oldQualifier = methodCallExpression.methodExpression.qualifierExpression ?: return
val isNullExpression =
factory.createExpressionFromText("a.isNull()", null) as PsiMethodCallExpression
isNullExpression.methodExpression.qualifierExpression!!.replace(oldQualifier)
element.replace(isNullExpression)
}
}
}
+29
View File
@@ -0,0 +1,29 @@
<idea-plugin>
<id>de.platon42.cajon</id>
<name>Concise AssertJ Optimizing Nitpicker (Cajon)</name>
<vendor email="chrisly@platon42.de" url="http://platon42.de">Platon Computer Products</vendor>
<description><![CDATA[
This plugin will add inspections and quick fixes to fully make use of the AssertJ methods
to make the intention clear and concise.
]]></description>
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
<idea-version since-build="182.0"/>
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
on how to target different products -->
<depends>com.intellij.modules.lang</depends>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.java</depends>
<extensions defaultExtensionNs="com.intellij">
<localInspection groupPath="Java" shortName="AssertThatObjectIsNull" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectIsNullInspection"/>
<localInspection groupPath="Java" shortName="AssertThatObjectIsNotNull" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectIsNotNullInspection"/>
</extensions>
<actions>
</actions>
</idea-plugin>
@@ -0,0 +1,6 @@
<html>
<body>
Turns assertThat(object).isNotEqualTo(null) into assertThat(object).isNotNull().
<!-- tooltip end -->
</body>
</html>
@@ -0,0 +1,6 @@
<html>
<body>
Turns assertThat(object).isEqualTo(null) into assertThat(object).isNull().
<!-- tooltip end -->
</body>
</html>
@@ -0,0 +1,10 @@
package de.platon42.intellij.jupiter;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AddLocalJarToModule {
Class[] value();
}
@@ -0,0 +1,170 @@
package de.platon42.intellij.jupiter;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;
import java.util.stream.Stream;
public class LightCodeInsightExtension implements ParameterResolver, AfterTestExecutionCallback {
private static final Logger LOG = Logger.getLogger(LightCodeInsightExtension.class.getName());
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
Parameter parameter = parameterContext.getParameter();
return parameter.isAnnotationPresent(MyFixture.class)
|| parameter.isAnnotationPresent(MyTestCase.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
LightCodeInsightFixtureTestCaseWrapper testCase = getWrapper(extensionContext);
Parameter parameter = parameterContext.getParameter();
if (parameter.isAnnotationPresent(MyFixture.class)) {
return testCase.getMyFixture();
} else if (parameter.isAnnotationPresent(MyTestCase.class)) {
return testCase;
}
return null;
}
private LightCodeInsightFixtureTestCaseWrapper getWrapper(ExtensionContext extensionContext) {
Store store = getStore(extensionContext);
return (LightCodeInsightFixtureTestCaseWrapper) store.getOrComputeIfAbsent("testCase",
key -> {
LightCodeInsightFixtureTestCaseWrapper wrapper = new LightCodeInsightFixtureTestCaseWrapper(extensionContext);
try {
wrapper.setUp();
} catch (Exception e) {
LOG.severe("Exception during setUp(): " + e);
throw new IllegalStateException("Exception during setUp()", e);
}
return wrapper;
});
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
Store store = getStore(context);
LightCodeInsightFixtureTestCaseWrapper testCase = (LightCodeInsightFixtureTestCaseWrapper) store.get("testCase");
if (testCase != null) {
testCase.tearDown();
}
}
private static Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(LightCodeInsightExtension.class, context.getRequiredTestMethod()));
}
private static class LightCodeInsightFixtureTestCaseWrapper extends LightCodeInsightFixtureTestCase {
private final ExtensionContext extensionContext;
private LightCodeInsightFixtureTestCaseWrapper(ExtensionContext extensionContext) {
this.extensionContext = extensionContext;
}
@Override
public void setUp() throws Exception {
super.setUp();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
UsefulTestCase.clearFields(this);
if (myFixture != null && getProject() != null && !getProject().isDisposed()) {
Disposer.dispose(getProject());
}
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
TestJdk testJdk = getMethodOrClassAnnotation(TestJdk.class);
if (testJdk == null) {
return super.getProjectDescriptor();
}
return new ProjectDescriptor(testJdk.value(), testJdk.annotations()) {
@Override
public Sdk getSdk() {
return testJdk.useInternal()
? JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk()
: super.getSdk();
}
@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
super.configureModule(module, model, contentEntry);
AddLocalJarToModule methodOrClassAnnotation = getMethodOrClassAnnotation(AddLocalJarToModule.class);
if (methodOrClassAnnotation != null) {
Stream.of(methodOrClassAnnotation.value()).forEach(it -> addJarContaining(model, it));
}
}
};
}
protected void addJarContaining(ModifiableRootModel model, Class clazz) {
try {
Path jarPath = Paths.get(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
VirtualFile jarFile = LocalFileSystem.getInstance().findFileByIoFile(jarPath.toFile());
myFixture.allowTreeAccessForFile(jarFile);
PsiTestUtil.addLibrary(
myFixture.getModule(),
model,
jarPath.getFileName().toString().replace(".jar", ""),
jarPath.getParent().toString(),
jarPath.getFileName().toString()
);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Class URL malformed", e);
}
}
@Override
protected String getTestDataPath() {
TestDataPath testDataPath = getMethodOrClassAnnotation(TestDataPath.class);
if (testDataPath == null) {
return super.getTestDataPath();
}
TestDataSubPath testDataSubPath = getMethodOrClassAnnotation(TestDataSubPath.class);
if (testDataSubPath == null) {
return testDataPath.value();
}
return Paths.get(testDataPath.value(), testDataSubPath.value()).toString();
}
public JavaCodeInsightTestFixture getMyFixture() {
return myFixture;
}
private <T extends Annotation> T getMethodOrClassAnnotation(Class<T> clazz) {
T annotation = extensionContext.getRequiredTestMethod().getAnnotation(clazz);
if (annotation == null) {
annotation = extensionContext.getRequiredTestClass().getAnnotation(clazz);
}
return annotation;
}
}
}
@@ -0,0 +1,11 @@
package de.platon42.intellij.jupiter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFixture {
}
@@ -0,0 +1,11 @@
package de.platon42.intellij.jupiter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestCase {
}
@@ -0,0 +1,10 @@
package de.platon42.intellij.jupiter;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestDataPath {
String value();
}
@@ -0,0 +1,10 @@
package de.platon42.intellij.jupiter;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestDataSubPath {
String value();
}
@@ -0,0 +1,16 @@
package de.platon42.intellij.jupiter;
import com.intellij.pom.java.LanguageLevel;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestJdk {
LanguageLevel value();
boolean annotations() default false;
boolean useInternal() default false;
}
@@ -0,0 +1,44 @@
package de.platon42.intellij.jupiter;
import com.intellij.testFramework.EdtTestUtilKt;
import com.intellij.testFramework.TestLoggerFactory;
import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy;
import org.jetbrains.annotations.NotNull;
// See https://github.com/junit-team/junit5/issues/157
public class WorkaroundUntilJupiterGetsExecutorProvider {
protected static void runTest(Runnable test) throws Throwable {
Throwable[] throwables = new Throwable[1];
Runnable runnable = () -> {
try {
TestLoggerFactory.onTestStarted();
test.run();
TestLoggerFactory.onTestFinished(true);
} catch (Throwable e) {
TestLoggerFactory.onTestFinished(false);
e.fillInStackTrace();
throwables[0] = e;
}
};
invokeTestRunnable(runnable);
if (throwables[0] != null) {
throw throwables[0];
}
}
private static void invokeTestRunnable(@NotNull Runnable runnable) {
IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current();
if (policy != null && !policy.runInDispatchThread()) {
runnable.run();
} else {
EdtTestUtilKt.runInEdtAndWait(() -> {
runnable.run();
return null;
});
}
}
}
@@ -0,0 +1,18 @@
package de.platon42.intellij.playground;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
public class Playground {
private void sizeOfList() {
assertThat(new ArrayList<>().size()).isEqualTo(1);
}
private void sizeOfArray() {
assertThat(new String[1].length).isLessThanOrEqualTo(1);
assertThat(new String[1]).hasSameSizeAs(new Object());
assertThat("").isEqualTo(null);
}
}
@@ -0,0 +1,67 @@
package de.platon42.intellij.plugins.cajon
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.TestLoggerFactory
import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.runInEdtAndWait
import de.platon42.intellij.jupiter.AddLocalJarToModule
import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.TestDataPath
import de.platon42.intellij.jupiter.TestJdk
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.extension.ExtendWith
import java.lang.reflect.InvocationTargetException
@ExtendWith(LightCodeInsightExtension::class)
@TestDataPath("src/test/resources")
@TestJdk(LanguageLevel.JDK_1_8, annotations = true, useInternal = true)
@AddLocalJarToModule(Assertions::class)
abstract class AbstractCajonTest {
// See https://github.com/junit-team/junit5/issues/157
protected fun runTest(body: () -> Unit) {
val throwables = arrayOfNulls<Throwable>(1)
invokeTestRunnable {
try {
TestLoggerFactory.onTestStarted()
body()
TestLoggerFactory.onTestFinished(true)
} catch (e: InvocationTargetException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e.targetException
} catch (e: IllegalAccessException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e
} catch (e: Throwable) {
TestLoggerFactory.onTestFinished(false)
throwables[0] = e
}
}
if (throwables[0] != null) {
throw throwables[0]!!
}
}
private fun invokeTestRunnable(runnable: () -> Unit) {
val policy = IdeaTestExecutionPolicy.current()
if (policy != null && !policy.runInDispatchThread()) {
runnable()
} else {
runInEdtAndWait {
runnable()
}
}
}
protected fun executeQuickFixes(myFixture: JavaCodeInsightTestFixture, expectedFixes: Int) {
val quickfixes = myFixture.getAllQuickFixes()
assertThat(quickfixes).hasSize(expectedFixes)
quickfixes.forEach(myFixture::launchAction)
}
}
@@ -0,0 +1,21 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatObjectIsNotNullInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/ObjectIsNotNull")
internal fun assertThat_with_isNotEqualTo_null_can_use_isNotNull(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatObjectIsNotNullInspection::class.java)
myFixture.configureByFile("ObjectIsNotNullBefore.java")
executeQuickFixes(myFixture, 3)
myFixture.checkResultByFile("ObjectIsNotNullAfter.java")
}
}
}
@@ -0,0 +1,21 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatObjectIsNullInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/ObjectIsNull")
internal fun assertThat_with_isEqualTo_null_can_use_isNull(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatObjectIsNullInspection::class.java)
myFixture.configureByFile("ObjectIsNullBefore.java")
executeQuickFixes(myFixture, 3)
myFixture.checkResultByFile("ObjectIsNullAfter.java")
}
}
}
@@ -0,0 +1,10 @@
import static org.assertj.core.api.Assertions.assertThat;
public class ObjectIsNotNull {
private void objectIsNotNull() {
assertThat("").isNotNull();
assertThat("").as("nah").isNotNull();
assertThat(new Object).isNotNull();
}
}
@@ -0,0 +1,10 @@
import static org.assertj.core.api.Assertions.assertThat;
public class ObjectIsNotNull {
private void objectIsNotNull() {
assertThat("").isNotEqualTo(null);
assertThat("").as("nah").isNotEqualTo(null);
assertThat(new Object).isNotEqualTo(null);
}
}
@@ -0,0 +1,10 @@
import static org.assertj.core.api.Assertions.assertThat;
public class ObjectIsNull {
private void objectIsNull() {
assertThat("").isNull();
assertThat("").as("nah").isNull();
assertThat(new Object).isNull();
}
}
@@ -0,0 +1,10 @@
import static org.assertj.core.api.Assertions.assertThat;
public class ObjectIsNull {
private void objectIsNull() {
assertThat("").isEqualTo(null);
assertThat("").as("nah").isEqualTo(null);
assertThat(new Object).isEqualTo(null);
}
}