Initial check-in with two inspections working.

This commit is contained in:
Chris Hodges 2019-03-10 18:19:46 +01:00
parent 39ba01adec
commit 80104004d0
34 changed files with 1063 additions and 2 deletions

View File

@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SaveActionSettings">
<option name="actions">
<set>
<option value="activate" />
<option value="organizeImports" />
<option value="reformat" />
<option value="missingOverrideAnnotation" />
<option value="unnecessaryThis" />
<option value="finalPrivateMethod" />
<option value="unnecessaryFinalOnLocalVariableOrParameter" />
<option value="explicitTypeCanBeDiamond" />
<option value="suppressAnnotation" />
<option value="unnecessarySemicolon" />
</set>
</option>
<option name="configurationPath" value="" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,2 +1,64 @@
# ConciseAssertJPlugin
IntelliJ Plugin for shortening and optimizing AssertJ assertions.
# Cajon - Concise AssertJ Optimizing Nitpicker
Cajon is an IntelliJ Plugin for shortening and optimizing AssertJ assertions.
## Why?
First, code is easier to read, when is concise and reflects the intention clearly.
AssertJ has plenty of different convenience methods that make describing the various intentions.
Why write longer, more complex code that can be expressed in brevity?
Second, AssertJ is able to output more meaningful descriptions when an assertion fails.
This makes finding bugs and fixing failed tests easier.
## Implemented
- AssertThatObjectIsNull
> from: assertThat(object).isEqualTo(null);
> to: assertThat(object).isNull();
- AssertThatObjectIsNotNull
> from: assertThat(object).isNotEqualTo(null);
> to: assertThat(object).isNotNull();
## TODO
- AssertThatBooleanIsTrueOrFalse
> from: assertThat(booleanValue).isEqualTo(true/false/Boolean.TRUE/Boolean.FALSE);
> to: assertThat(booleanValue).isTrue()/isFalse();
- AssertThatStringEmpty
> from: assertThat(string).isEqualTo("")
> to: assertThat(string).isEmpty();
- AssertThatArrayHasLiteralSize
> from: assertThat(array.length).isEqualTo(literal); literal > 0
> to: assertThat(array).hasSize(literal);
- AssertThatArrayHasEqualSize
> from: assertThat(array.length).isEqualTo(anotherArray.length);
> to: assertThat(array).hasSameSizeAs(anotherArray);
> from: assertThat(array.length).isEqualTo(iterable.size());
> to: assertThat(array).hasSameSizeAs(iterable);
- AssertThatArrayIsEmpty
> from: assertThat(array.length).isEqualTo(0);
> from: assertThat(array.length).isLessThanOrEqualTo(0);
> from: assertThat(array.length).isLessThan(1);
> from: assertThat(array).hasSize(0);
> to: assertThat(array).isEmpty();
- AssertThatArrayIsNotEmpty
> from: assertThat(array.length).isGreaterThan(0);
> to: assertThat(array).isNotEmpty();
- AssertThatIterableHasLiteralSize
> from: assertThat(iterable.size()).isEqualTo(literal); literal > 0
> to: assertThat(iterable).hasSize(literal);
- AssertThatIterableHasEqualSize
> from: assertThat(iterable.size()).isEqualTo(anotherArray.length);
> to: assertThat(iterable).hasSameSizeAs(anotherArray);
> from: assertThat(iterable.size()).isEqualTo(iterable.size());
> to: assertThat(iterable).hasSameSizeAs(iterable);
- AssertThatIterableIsNotEmpty
> from: assertThat(array.length).isGreaterThan(0);
> from: assertThat(array.length).isGreaterThanOrEqualTo(1);
> to: assertThat(array).isNotEmpty();
- AssertThatIterableIsEmpty
> from: assertThat(iterable.size()).isEqualTo(0);
> from: assertThat(iterable.size()).isLessThanOrEqualTo(0);
> from: assertThat(iterable.size()).isLessThan(1);
> from: assertThat(iterable).hasSize(0);
> to: assertThat(iterable).isEmpty();

55
build.gradle Normal file
View File

@ -0,0 +1,55 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '0.4.3'
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
}
group 'de.platon42'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
/*
To run tests in IntelliJ use these VM Options for run configuration
-ea -Didea.system.path=build/idea-sandbox/system-test -Didea.config.path=build/idea-sandbox/config-test -Didea.plugins.path=build/idea-sandbox/plugins-test
*/
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile "org.assertj:assertj-core:3.11.1"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.4.0'
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
intellij {
version '2018.3.4'
// pluginName 'Concise AssertJ Optimizing Nitpicker (Cajon)'
updateSinceUntilBuild false
}
patchPluginXml {
changeNotes """
<h2>V0.1 (10-Mar-2019)
<ul>
<li>Initial release.
</ul>
"""
}
test {
useJUnitPlatform()
// testLogging {
// events "passed", "skipped", "failed"
// }
}

2
gradle.properties Normal file
View File

@ -0,0 +1,2 @@
kotlin.code.style=official
kotlin.incremental=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Feb 21 17:35:51 CET 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip

172
gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'cajon-plugin'

View File

@ -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"
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

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>

View File

@ -0,0 +1,6 @@
<html>
<body>
Turns assertThat(object).isNotEqualTo(null) into assertThat(object).isNotNull().
<!-- tooltip end -->
</body>
</html>

View File

@ -0,0 +1,6 @@
<html>
<body>
Turns assertThat(object).isEqualTo(null) into assertThat(object).isNull().
<!-- tooltip end -->
</body>
</html>

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;
});
}
}
}

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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")
}
}
}

View File

@ -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")
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}