Compare commits

...

90 Commits
v0.3 ... master

Author SHA1 Message Date
a71253142b Last update 1.14. Reworked so much without success it was just painful. 2024-02-19 22:23:56 +01:00
855dc41a7f Rename .java to .kt 2024-02-19 22:17:02 +01:00
06b17d32ac Giving up on workaround, minimum version now 2019.3.1. 2022-08-18 12:25:38 +02:00
f3bc2c1a0b Changed workaround according to recommendation by Jetbrains. 2022-08-17 20:28:18 +02:00
1ee532b577 Updated Travis-Image. 2022-08-16 11:11:18 +02:00
d5a81bf84a Attempt to fix out-of-order problem with coveralls task being executed before jacocoTestReport. 2022-08-16 08:34:01 +02:00
355ee1d29d Fixed standalone test build. 2022-08-15 23:57:07 +02:00
a3185e3277 Changed to JDK 17 for CI build. 2022-08-15 23:25:55 +02:00
a657d2bfaa Updated readme for release. Typofix. 2022-08-15 23:13:31 +02:00
bb7497b494 Fix for issue #6. 2022-08-15 23:04:20 +02:00
1ba1363dd2 Added AssertThatIsZeroOneInspection. 2022-08-15 22:44:45 +02:00
c29d644f56 Updated dependencies, attempt to fix NPE, workaround for scheduled removal of a static field in PsiParserFacade.
Added PluginVerifier to build.gradle.
2022-08-15 21:39:32 +02:00
2d92d71af0 Travis-CI.com now? Really? 2021-07-16 21:44:04 +02:00
01c6b141ee Updated various dependencies (Kotlin 1.5.21) and AssertJ 3.20.2. Latest Gradle. Migrated to latest intellij-gradle-plugin. Slightly fixed a situation in LightCodeInsightExtension, where the fields were cleared in super-Method before they had been evaluated. 2021-07-16 19:59:38 +02:00
32eb8be1e3 Typofix. 2021-06-14 13:56:27 +02:00
6692ded98a Updated various dependencies (Kotlin 1.50.0) and AssertJ 3.19.0. Fixed issue#3 reported by hankem where usingRecursiveComparison() was not considered a complex transformation. 2021-05-06 12:42:23 +02:00
146465328c Cajon is written in Kotlin 1.4. 2020-10-02 23:14:23 +02:00
47dcc7ec5e Disable TSL 1.3 due to JDK11 bug for sending data to coveralls. 2020-10-02 23:06:38 +02:00
ce0b5ce872 Added inversion of boolean conditions inside isEqualTo() and isNotEqualTo() for InvertedBooleanCondition inspection. 2020-10-02 22:44:08 +02:00
d48c71d4ea Added several cases for hasSizeGreaterThan/LessThan/OrEqualTo() for EnumerablesEmpty inspection. 2020-10-02 19:24:22 +02:00
1340a34782 Fixed ImplicitAssertion crashing the plugin with IntelliJ 2020.3 EAP, added support for singleElement(). Unrelated: Documentation grammar fixes. 2020-10-02 18:54:23 +02:00
fbde6b3387 Upgraded gradle to 6.6.1.
Upgraded kotlin to 1.4.10.
Upgraded assertj to 3.17.2.
Upgraded junit-jupiter to 5.7.0.
Other minor upgrades.
2020-10-02 18:52:50 +02:00
b9110d226e Added a note about the plugin being for Java language projects only. 2020-10-02 16:21:29 +02:00
45307b364d Fixed two possible index out of bounds exceptions in ExtractorReferenceContributor and BogusAssertionInspection. Prepared release. 2020-07-31 17:01:13 +02:00
66b725c4d2 Updated libraries to the latest versions (including AssertJ 3.16.1 and Kotlin 1.40-rc).
Now uses Gradle 6.5.1.
2020-07-30 21:01:34 +02:00
48349a6528 Prepared for release. 2020-02-25 14:39:30 +01:00
9b25e50183 Improvements for TwistedAssertionInspection to no longer report .class types as constant to bother, nor matches() and doesNotMatch() for regexps. If both sides are constants, they will only show as weak problems. 2020-02-17 20:17:33 +01:00
e8ce8ce2c6 BogusAssertionInspection will no longer warn if the expression contains method calls and now tries to avoid valid hashCode() and equals() tests. 2020-02-17 18:33:11 +01:00
62f59b0fe2 Added new TwistedAssertion inspection that will warn about assertions with the actual expression being a constant indicating swapped use of actual and expected expressions.
Added new BogusAssertion inspection that showing typical copy and paste errors where actual and expected expressions are the same.
2020-02-14 13:36:04 +01:00
58298fabc6 Extended Testing-Framework to work around IntelliJ IDEA introducing an unwanted assertj-core dependency conflicting with our newer one. 2020-02-08 16:33:38 +01:00
77d3608fd3 Minor NPE Bugfix. Fixed use of "experimental API". Upgraded dependencies. 2020-02-04 20:01:05 +01:00
a0909d8c39 Prepared release. 2019-11-19 19:47:40 +01:00
5113cc15ab Added hasSize(), isEmpty() and isNotEmpty() for AssertThatFileExpression when using AssertJ >= 3.14.0. 2019-11-18 21:13:30 +01:00
ae2076a425 Bumped to AssertJ 3.14.0, AssertJ-Guava 3.3.0, Kotlin 1.3.60. IntelliJ-Plugin 0.4.13, Jacoco 0.8.5 2019-11-18 21:10:45 +01:00
42429c0f72 Added AssertThatComparableExpression for funny compareTo() uses. 2019-11-17 21:21:45 +01:00
8133f3850f Added first version of AssertThatPathExpression for a limited number transformations (more stuff is possible, but requires detection and transformation of static Files-methods). 2019-11-17 19:10:41 +01:00
8d03b3734c Fixed a lapsuus in AssertThatFileExpression also transforming listFiles() with a filter argument. 2019-11-17 17:14:28 +01:00
2f0d855d1e Minor documentation fixes. Prepared release. 2019-09-30 22:17:23 +02:00
6dab8ad552 Added several transformations to AssertThatStringExpression inspection. Specifically, uses of matches(), compareToIgnoreCase(), indexOf(), and trim(). 2019-09-30 20:27:43 +02:00
1983750077 Internal refactoring: Reduced code duplication by moving stuff into a common base class. 2019-09-30 17:25:16 +02:00
a0ed4eab76 Added new AssertThatFileExpression to move out many common methods from inside the assertThat() expression (exists(), getName(), getParent(), and many more). 2019-09-29 21:56:36 +02:00
acc81863f5 Fixed a bug in AssertThatBinaryExpression inspection for assertThat(null != expression) and related that would not correctly invert the condition on transformation. 2019-09-29 12:32:32 +02:00
2b97494c17 Really fixed AssertThatGuavaOptional inspections to avoid conversions from .get() to .contains() for array types. 2019-09-25 18:38:35 +02:00
e3444db213 Fixes to AssertThatSize inspection after extending it for Maps in previous release as not all combinations for .hasSameSizeAs() are supported.
Bumped to IntelliJ IDEA 2019.2.3.
2019-09-24 22:55:02 +02:00
855fb03f7c One more option for AssertThatCollectionOrMap inspection for warnings without quickfix.
AssertThatGuavaOptional inspections will now avoid conversions from .get() to .contains() for array types (currently not correctly supported by AssertJ-Guava).
2019-09-24 22:16:11 +02:00
8d678411b5 Fix for AssertThatCollectionOrMap inspection sometimes causing an index out of bounds exception.
Added an settings option for AssertThatCollectionOrMap inspection respecting the degenerated case of maps with null values.
Upgrade to JUnit Jupiter 5.5.2.
2019-09-09 23:13:45 +02:00
341d1877df Prepared for 1.4 release. 2019-08-25 18:40:58 +02:00
05dc4905ca Extended AssertThatCollectionOrMap inspection for several assertThat(map.get()) cases as suggested by Stefan H. 2019-08-23 19:58:11 +02:00
315b93d90d Extended AssertThatSize inspection to Maps, too. 2019-08-23 18:21:08 +02:00
36f63d26d2 Minor fix for highlighting of JoinVarArgsContains inspection.
Upgraded to kotlin 1.3.50, intellij plugin 0.4.10, assertj 3.13.2, idea 2019.2.1
2019-08-23 18:04:07 +02:00
a4535afbbb Fixed references to wrong AssertJ version 13.2.0 instead of 3.12.0, courtesy of Bernhard R. 2019-08-04 18:29:59 +02:00
df11939589 New JoinVarArgsContains inspection that will detect multiple .contains(), .doesNotContain(), and .containsOnlyOnce() calls within the same statement that could be joined together using variadic arguments. 2019-08-03 20:57:34 +02:00
4420a0a392 AssertThatJava8Optional and AssertThatGuavaOptional inspections do not longer try to fix assertThat(optional).isEqualTo(Optional.fromNullable(expression)) to contains() when expression is not a non-null constant expression. 2019-07-30 20:02:40 +02:00
8fb3ecce95 Bumped kotlin to 1.3.41, jupiter 5.5.1, added compatibility with IDEA 2019.2 and fixed regressions with new AssertJ 3.13.0. 2019-07-30 19:43:40 +02:00
5048c898ec Fixed broken code in test classes (missing imports, undefined variables). 2019-07-30 19:39:16 +02:00
b84a781134 Reverted removed method (identical to default implementation) that causes compatibility problem with older IDEA versions (again, sigh)... 2019-06-23 19:24:36 +02:00
095345a456 The JoinAssertThatStatements inspection will now add line breaks on joining statements.
Unrelated: Upgraded Gradle to 5.4.1. Upgraded kotlin to 1.3.40. Upgraded jacoco to 0.8.4. Minor style change for immediate returns. Increased test/branch coverage.
2019-06-23 18:51:12 +02:00
5c455c3ca9 Fix for multiple JUnit Conversions in batch mode with and without delta creating an exception.
Added new AssertThatObjectExpression inspection for toString() and hashCode() and moved equals() from AssertThatBinaryExpression there.
2019-06-09 21:35:23 +02:00
6795622202 Finally was able to remove JUnit workaround for test invokation with availability of JUnit 5.5.0-RC1. 2019-06-09 14:17:44 +02:00
828e61a73b New ImplicitAssertion inspection for implicit isNotNull(), isNotEmpty() and isPresent() assertions that will be covered by chained assertions. 2019-06-09 14:04:01 +02:00
178b7d368a Extended JUnitAssertToAssertJ inspection to convert JUnit assume-Statements, too.
Improved JUnitAssertToAssertJ quick fix to swap expected and actual expressions if the actual one is a constant.
2019-05-17 12:17:01 +02:00
e797dc2515 Extended documentation about assumeThat() preconditions. 2019-05-16 20:06:14 +02:00
7133c55710 Added Guava Optional opt.orNull() == null case. Added Java 8 Optional opt.orElse(null) == null case, too. 2019-05-15 20:50:30 +02:00
94c695617a Improved JoinAssertThatStatements detection of expressions with side-effects and added pre/post-increment/decrement detection. 2019-05-11 13:40:37 +02:00
ecb5029154 Fixed a NPE in AssumeThatInsteadOfReturn inspection quickfix for empty else branches.
Fixed missing description for AssumeThatInsteadOfReturn inspection.
Added new AssertThatCollectionOrMapExpression inspection that tries to pull out methods such as isEmpty() or contains() out of an actual assertThat() expression. Added more method calls that are considered extension points.
2019-05-06 20:17:11 +02:00
0e2007641c Compatibility fixes for IDEA <= 2019.1 :-/ 2019-05-05 20:06:12 +02:00
eab50f590b Added a first version of a new inspection that tries to detect bogus uses of return statements in test methods and replaces them by assumeThat() calls. Removed oraclejdk8 from travis-ci. Added custom Jupiter DisplayNameGenerator for user friendly test names in report. Extended documentation. Removed Playground. 2019-05-05 19:02:35 +02:00
66f1467b23 Added coveralls. 2019-05-04 16:40:05 +02:00
6a36294a2b Added travis Build Status to readme. 2019-05-04 16:15:36 +02:00
0b79a6d7dc Added early bail-outs via simple text matchings to optimize speed of false positive detection. 2019-05-04 15:54:27 +02:00
6fb23ea89c Heavily reworked inspections for edge cases, such as multiple isEqualTo() calls inside a single statement. Corrected highlighting for most inspections. Lots of refactorings, tweakings. Added travis-ci file (untested). Added jacoco. 2019-05-04 15:36:04 +02:00
362c4210a5 Fixed isEmpty() for enumerables and strings and isNull() for object conversions to be applied only if it is the terminal method call as isEmpty() and isNull() return void. 2019-05-01 13:15:31 +02:00
e55acf9c74 Extended test cases for AssertThatBooleanCondition and AssertThatObjectIsNullOrNotNull. 2019-04-29 20:10:55 +02:00
3ece81b024 Minor fixes to descriptions. 2019-04-29 19:58:05 +02:00
0b2ce470db Fixed missing description for JoinAssertThatStatements and detection of equivalent expressions. 2019-04-29 19:58:00 +02:00
941ddfdb5e Implemented first version of JoinAssertThatStatements inspection that will try to merge assertThat() statements with the same actual object together, preserving comments. 2019-04-28 20:43:42 +02:00
666e373405 Extended AssertThatSize inspection to transform hasSize() into hasSameSizeAs(), if possible. 2019-04-23 20:16:43 +02:00
8b0da63f86 Another fix for AssertThatGuavaOptional inspection regarding using the same family name for slightly different quick fix executions (really, Jetbrains, this sucks for no reason). Consolidated location of methods. 2019-04-23 18:19:47 +02:00
66508ceb2c New AssertThatInstanceOf inspection that moves instanceof expressions out of assertThat(). Fixes to documentation. 2019-04-22 19:03:59 +02:00
db02f7fb93 New AssertThatInvertedBooleanCondition inspection that will remove inverted boolean expressions inside assertThat().
Renamed a few inspections to better/shorter names (and fixed file and directory names accordingly).
2019-04-20 18:40:38 +02:00
a707eee9ad Extended AssertThatSize intention to take strings and CharSequences into account, too. 2019-04-20 09:04:38 +02:00
533c20906a Added a note on referencing. Moved AssertThatStringExpression to a more appropriate place in documentation. 2019-04-19 19:44:40 +02:00
da83f7f101 New AssertThatStringExpression that will move isEmpty(), equals(), equalsIgnoreCase(), contains(), startsWith(), and endsWith() out of actual expression. Bumped intellij-plugin to latest version. 2019-04-19 19:33:14 +02:00
faeb509797 Fixed default method not available in IDEA <2018.3. Sigh :-( 2019-04-18 22:29:30 +02:00
9f91fb3ccf Added support for referencing and refactoring inside ``.extracting()`` methods with fields, properties and methods (though getter renaming does not work that perfect, but I'm giving up for now as the IntelliJ SDK docs are seriously lacking).
Fixed an exception in batch mode if the description string was the same but for different fixes. Now descriptions are different for quick fixes triggered by AssertThatJava8OptionalInspection and AssertThatGuavaOptionalInspection. Minor refactorings. Extended documentation.
2019-04-18 22:12:48 +02:00
b58d8cfd2f Fix for incompatible method call regarding IDEA <2018.2.
Heavy refactoring regarding quick fixes (using more extensions).
Even more refactoring.
Fixed/extended AssertThatGuavaOptionalInspection regarding static.
imports and wrong reference to core assertThat() where Guava assertThat() actually was necessary.
2019-04-13 18:35:49 +02:00
4e6d53b3dc Reduced minimal supported IDEA version from 2018.2 to 2017.2.
Added AssertThatGuavaOptional inspection. Prepared next release.
Bumped to Kotlin 1.3.30.
2019-04-11 23:55:35 +02:00
2cdc242125 Refactored Code to avoid string duplications... added Boolean.map() extension. 2019-04-11 21:09:10 +02:00
d49b7bf17b Optimized even more code. Fixed BinaryExpression for Boolean.TRUE/FALSE. Added new AssertThatJava8Optional inspection. Added changelog to README. 2019-04-11 16:26:11 +02:00
c1d8ade7b1 Optimized code. More kotlin-style stuff. Made detecting of Boolean.TRUE/FALSE available to all constant evaluations. 2019-04-08 11:56:13 +02:00
202 changed files with 10134 additions and 1692 deletions

View File

@ -18,6 +18,7 @@
<option name="configurationPath" value="" />
<option name="exclusions">
<set>
<option value=".*\.md" />
<option value="src/test/resources/.*" />
</set>
</option>

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
language: java
jdk:
- openjdk11
before_script:
- chmod +x gradlew
- chmod +x gradle/wrapper/gradle-wrapper.jar
script:
- ./gradlew clean test jacocoTestReport coveralls

View File

@ -1,4 +1,4 @@
Copyright 2019 Chris Hodges <chrisly@platon42.de>
Copyright 2019-2024 Chris Hodges <chrisly@platon42.de>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.

953
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +0,0 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '0.4.3'
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
}
group 'de.platon42'
version '0.3'
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.12.2"
testCompile "org.assertj:assertj-guava:3.2.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 '2019.1'
// pluginName 'Concise AssertJ Optimizing Nitpicker (Cajon)'
updateSinceUntilBuild false
}
patchPluginXml {
changeNotes """
<h4>V0.3 (07-Apr-19)</h4>
<ul>
<li>New inspection AssertThatBinaryExpressionIsTrueOrFalse that will find and fix common binary expressions and equals() statements (more than 150 combinations) inside assertThat().
<li>Merged AssertThatObjectIsNull and AssertThatObjectIsNotNull to AssertThatObjectIsNullOrNotNull.
<li>Support for hasSizeLessThan(), hasSizeLessThanOrEqualTo(), hasSizeGreaterThanOrEqualTo(), and hasSizeGreaterThan() for AssertThatSizeInspection (with AssertJ >=13.2.0).
<li>Really fixed highlighting for JUnit conversion. Sorry.
</ul>
<h4>V0.2 (01-Apr-19)</h4>
<ul>
<li>Fixed descriptions and quick fix texts.
<li>Fixed highlighting of found problems and also 'Run inspection by Name' returning nothing.
</ul>
<h4>V0.1 (31-Mar-19)</h4>
<ul>
<li>Initial release.
</ul>
"""
}
test {
useJUnitPlatform()
// testLogging {
// events "passed", "skipped", "failed"
// }
}
publishPlugin {
token intellijPublishToken
}

131
build.gradle.kts Normal file
View File

@ -0,0 +1,131 @@
plugins {
id("java")
id("org.jetbrains.intellij") version "1.17.1"
id("org.jetbrains.kotlin.jvm") version "1.9.22"
id("jacoco")
id("com.github.kt3k.coveralls") version "2.12.2"
}
group = "de.platon42"
version = "1.14"
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"
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.assertj:assertj-guava:3.25.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.junit.platform:junit-platform-launcher:1.10.2")
// testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
}
intellij {
version.set("2022.2") // LATEST-EAP-SNAPSHOT
//pluginName.set(provider { 'Concise AssertJ Optimizing Nitpicker (Cajon)' })
updateSinceUntilBuild.set(false)
plugins.set(listOf("com.intellij.java"))
}
tasks {
withType<JavaCompile> {
sourceCompatibility = "11"
targetCompatibility = "11"
}
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
prepareSandbox {
enabled = true
}
verifyPlugin {
enabled = true
}
verifyPluginConfiguration {
enabled = true
}
patchPluginXml {
enabled = true
sinceBuild.set("222")
changeNotes.set(
"""
<h4>V1.14 (19-Feb-24)</h4>
<ul>
<li>Now requires minimum version 2022.2.
<li>Maintenance. Updated various dependencies (Kotlin 1.9.22) and AssertJ 3.25.3 and AssertJ-Guava 3.25.3.
<li>Reworked JUnit 5 test framework to work again. However, all the tests are broken.
I spent several days trying to figure out what is going on, but I'm giving up on this pile of crap called IntelliJ.
Jetbrains keeps breaking the APIs and implementations every year and I just cannot be bothered anymore.
<li>This is very likely the last version.
</ul>
<h4>V1.13 (18-Aug-22)</h4>
<ul>
<li>API change in IntelliJ platforms now requires minimum version 2019.3.1. Sorry, giving up to maintain compatibility after four attempts.
<li>Maintenance. Updated various dependencies (Kotlin 1.7.10) and AssertJ 3.23.1 and AssertJ-Guava 3.5.0.
<li>Tried to fix unreproducible issue #9.
<li>Added AssertThatIsZeroOne inspection demanded by issue #5.
<li>Fix for wrongly joining statements that cannot be trivially joined (e.g. with filteredOn). Fixes issue #6.
</ul>
<p>Full changelog available at <a href="https://git.platon42.de/chrisly42/cajon-plugin#changelog">Gitea project site</a>.</p>
"""
)
}
publishPlugin {
token.set(System.getProperty("org.gradle.project.intellijPublishToken"))
}
runIde {
enabled = true
if (project.hasProperty("ideDir")) {
ideDir.set(file(project.property("ideDir")!!))
jbrVersion.set(project.property("ideJBR")!! as String)
}
autoReloadPlugins.set(false)
}
runPluginVerifier {
ideVersions.set(listOf("IC-222.4167.29", "IC-233.14015.106")) // 2022.2.2 - 2023.3.3
downloadDir.set(System.getenv("user.home") + "/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea/verifier")
}
}
//tasks.coveralls {
// dependsOn(jacocoTestReport)
//}
//jacoco {
// toolVersion = '0.8.8'
//}
//
//jacocoTestReport {
// reports {
// xml.required.set(true)
// csv.required.set(false)
// }
//}

View File

@ -1,3 +1,4 @@
kotlin.code.style=official
kotlin.incremental=true
intellijPublishToken=perm:dummy
intellijPublishToken=perm:dummy
systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"

Binary file not shown.

View File

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

55
gradlew vendored
View File

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -28,7 +44,7 @@ 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=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -56,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
MINGW* )
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
@ -66,6 +82,7 @@ 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
@ -109,10 +126,11 @@ 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
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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
@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $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" ;;
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
@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
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" "$@"

43
gradlew.bat vendored
View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@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=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@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
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,28 +64,14 @@ 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%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -0,0 +1,112 @@
package de.platon42.intellij.plugins.cajon
import org.jetbrains.annotations.NonNls
class AssertJClassNames {
companion object {
@NonNls
const val ASSERTIONS_CLASSNAME = "org.assertj.core.api.Assertions"
@NonNls
const val ASSUMPTIONS_CLASSNAME = "org.assertj.core.api.Assumptions"
@NonNls
const val DESCRIPTABLE_INTERFACE = "org.assertj.core.api.Descriptable"
@NonNls
const val EXTENSION_POINTS_INTERFACE = "org.assertj.core.api.ExtensionPoints"
@NonNls
const val ENUMERABLE_ASSERT_INTERFACE = "org.assertj.core.api.EnumerableAssert"
@NonNls
const val OBJECT_ENUMERABLE_ASSERT_INTERFACE = "org.assertj.core.api.ObjectEnumerableAssert"
@NonNls
const val ASSERT_INTERFACE = "org.assertj.core.api.Assert"
@NonNls
const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert"
@NonNls
const val ABSTRACT_OBJECT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractObjectAssert"
@NonNls
const val ABSTRACT_BOOLEAN_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanAssert"
@NonNls
const val ABSTRACT_SHORT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractShortAssert"
@NonNls
const val ABSTRACT_INTEGER_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntegerAssert"
@NonNls
const val ABSTRACT_LONG_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractLongAssert"
@NonNls
const val ABSTRACT_FLOAT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractFloatAssert"
@NonNls
const val ABSTRACT_DOUBLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractDoubleAssert"
@NonNls
const val ABSTRACT_COMPARABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractComparableAssert"
@NonNls
const val ABSTRACT_STRING_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractStringAssert"
@NonNls
const val ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharSequenceAssert"
@NonNls
const val ABSTRACT_MAP_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractMapAssert"
@NonNls
const val ABSTRACT_BOOLEAN_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanArrayAssert"
@NonNls
const val ABSTRACT_BYTE_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractByteArrayAssert"
@NonNls
const val ABSTRACT_SHORT_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractShortArrayAssert"
@NonNls
const val ABSTRACT_INT_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntArrayAssert"
@NonNls
const val ABSTRACT_LONG_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractLongArrayAssert"
@NonNls
const val ABSTRACT_FLOAT_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractFloatArrayAssert"
@NonNls
const val ABSTRACT_DOUBLE_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractDoubleArrayAssert"
@NonNls
const val ABSTRACT_CHAR_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharArrayAssert"
@NonNls
const val ABSTRACT_OBJECT_ARRAY_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractObjectArrayAssert"
@NonNls
const val ABSTRACT_ITERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIterableAssert"
@NonNls
const val ABSTRACT_FILE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractFileAssert"
@NonNls
const val ABSTRACT_OPTIONAL_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractOptionalAssert"
@NonNls
const val EXTRACTORS_CLASSNAME = "org.assertj.core.extractor.Extractors"
@NonNls
const val GUAVA_OPTIONAL_CLASSNAME = "com.google.common.base.Optional"
@NonNls
const val GUAVA_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.Assertions"
@NonNls
const val GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.OptionalAssert"
}
}

View File

@ -0,0 +1,79 @@
package de.platon42.intellij.plugins.cajon
import com.intellij.psi.CommonClassNames
import com.siyeh.ig.callMatcher.CallMatcher
val CORE_ASSERT_THAT_MATCHER = CallMatcher.staticCall(AssertJClassNames.ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)!!
val GUAVA_ASSERT_THAT_MATCHER = CallMatcher.staticCall(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)!!
val ALL_ASSERT_THAT_MATCHERS = CallMatcher.anyOf(CORE_ASSERT_THAT_MATCHER, GUAVA_ASSERT_THAT_MATCHER)!!
val EXTRACTING_FROM_OBJECT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ASSERT_CLASSNAME, "extracting")!!
val EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extracting")!!
val FLAT_EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "flatExtracting")!!
val EXTRACTING_RESULT_OF_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extractingResultOf")!!
val EXTRACTING_CALL_MATCHERS = CallMatcher.anyOf(
EXTRACTING_FROM_OBJECT,
EXTRACTING_FROM_ITERABLE,
FLAT_EXTRACTING_FROM_ITERABLE,
EXTRACTING_RESULT_OF_FROM_ITERABLE
)!!
val DESCRIBED_AS = CallMatcher.instanceCall(AssertJClassNames.DESCRIPTABLE_INTERFACE, MethodNames.DESCRIBED_AS, MethodNames.AS)!!
val WITH_REPRESENTATION_AND_SUCH = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, "withRepresentation", "withThreadDumpOnError")!!
val USING_COMPARATOR = CallMatcher.anyOf(
CallMatcher.instanceCall(
AssertJClassNames.ASSERT_INTERFACE,
"usingComparator",
"usingDefaultComparator"
),
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ASSERT_CLASSNAME, "usingRecursiveComparison"),
CallMatcher.instanceCall(
AssertJClassNames.ABSTRACT_OBJECT_ASSERT_CLASSNAME,
"usingComparatorForFields",
"usingComparatorForType"
)
)!!
val IN_HEXADECIMAL_OR_BINARY = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ASSERT_CLASSNAME, MethodNames.IN_HEXADECIMAL, MethodNames.IN_BINARY)!!
val EXTENSION_POINTS = CallMatcher.instanceCall(
AssertJClassNames.EXTENSION_POINTS_INTERFACE,
"is", "isNot", "has", "doesNotHave",
"satisfies"
)!!
val MORE_EXTENSION_POINTS = CallMatcher.instanceCall(
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
"are", "areNot", "have", "doNotHave",
"areAtLeast", "areAtLeastOne", "areAtMost", "areExactly",
"haveAtLeastOne", "haveAtLeast", "haveAtMost", "haveExactly",
"singleElement", "hasOnlyOneElementSatisfying", "anyMatch", "noneMatch", "anySatisfy", "noneSatisfy"
)!!
val FILTERED_ON = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "filteredOn", "filteredOnNull", "filteredOnAssertions")!!
val COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY = CallMatcher.anyOf(
EXTRACTING_CALL_MATCHERS,
DESCRIBED_AS,
WITH_REPRESENTATION_AND_SUCH,
FILTERED_ON,
USING_COMPARATOR,
IN_HEXADECIMAL_OR_BINARY
)!!
val COMPLEX_STUFF_THAT_MAKES_JOINING_IMPOSSIBLE = CallMatcher.anyOf(
EXTRACTING_CALL_MATCHERS,
WITH_REPRESENTATION_AND_SUCH,
FILTERED_ON,
USING_COMPARATOR,
IN_HEXADECIMAL_OR_BINARY
)!!
val NOT_ACTUAL_ASSERTIONS = CallMatcher.anyOf(
ALL_ASSERT_THAT_MATCHERS,
COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY
)!!
val KNOWN_METHODS_WITH_SIDE_EFFECTS = CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "next")
)!!

View File

@ -0,0 +1,183 @@
package de.platon42.intellij.plugins.cajon
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.inspections.AbstractAssertJInspection
val PsiMethodCallExpression.qualifierExpression: PsiExpression get() = methodExpression.qualifierExpression!!
val PsiMethodCallExpression.firstArg: PsiExpression get() = getArg(0)
fun PsiElement.hasAssertThat(): Boolean {
val elementText = text
return elementText.startsWith("${MethodNames.ASSERT_THAT}(") || elementText.contains(".${MethodNames.ASSERT_THAT}(")
}
fun PsiMethodCallExpression.replaceQualifier(qualifier: PsiElement) {
qualifierExpression.replace(qualifier)
}
fun PsiMethodCallExpression.replaceQualifierFromMethodCall(oldMethodCall: PsiMethodCallExpression) {
qualifierExpression.replace(oldMethodCall.qualifierExpression)
}
fun PsiElement.findOutmostMethodCall(): PsiMethodCallExpression? {
val statement = PsiTreeUtil.getParentOfType(this, PsiStatement::class.java, false) ?: return null
return PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java)
}
fun PsiElement.findStaticMethodCall(): PsiMethodCallExpression? {
var elem: PsiElement? = this
while (elem != null) {
if ((elem is PsiMethodCallExpression) && (elem.resolveMethod()?.hasModifierProperty(PsiModifier.STATIC) == true)) {
return elem
}
elem = elem.firstChild
}
return null
}
fun PsiElement.gatherAssertionCalls(): List<PsiMethodCallExpression> {
val assertThatMethodCall = findStaticMethodCall() ?: return emptyList()
return assertThatMethodCall.collectMethodCallsUpToStatement()
.filterNot(NOT_ACTUAL_ASSERTIONS::test)
.toList()
}
fun PsiMethodCallExpression.collectMethodCallsUpToStatement(): Sequence<PsiMethodCallExpression> {
return generateSequence(this) { PsiTreeUtil.getParentOfType(it, PsiMethodCallExpression::class.java, true, PsiStatement::class.java) }
}
fun PsiMethodCallExpression.findFluentCallTo(matcher: CallMatcher): PsiMethodCallExpression? {
return collectMethodCallsUpToStatement().find { matcher.test(it) }
}
fun PsiMethodCallExpression.getArg(n: Int): PsiExpression = PsiUtil.skipParenthesizedExprDown(argumentList.expressions[n])!!
fun PsiMethodCallExpression.getArgOrNull(n: Int): PsiExpression? = argumentList.expressions.getOrNull(n)
fun <T> Boolean.map(forTrue: T, forFalse: T) = if (this) forTrue else forFalse
fun PsiMethod.addAsStaticImport(context: PsiElement, vararg allowedClashes: String) {
val factory = JavaPsiFacade.getElementFactory(context.project)
val methodName = name
val containingClass = containingClass ?: return
val importList = (context.containingFile as PsiJavaFile).importList ?: return
val notImportedStatically = importList.importStaticStatements.none {
val targetClass = it.resolveTargetClass() ?: return@none false
((it.referenceName == methodName) && !allowedClashes.contains(targetClass.qualifiedName))
|| (it.isOnDemand && (targetClass == containingClass))
}
if (notImportedStatically) {
importList.add(factory.createImportStaticStatement(containingClass, methodName))
}
}
fun PsiElement.shortenAndReformat() {
val codeStyleManager = JavaCodeStyleManager.getInstance(project)
codeStyleManager.shortenClassReferences(this)
CodeStyleManager.getInstance(project).reformat(this)
}
fun PsiMethodCallExpression.getExpectedBooleanResult(): Boolean? {
val isTrue = AbstractAssertJInspection.IS_TRUE.test(this)
val isFalse = AbstractAssertJInspection.IS_FALSE.test(this)
if (isTrue || isFalse) {
return isTrue
} else {
val isEqualTo = AbstractAssertJInspection.IS_EQUAL_TO_BOOLEAN.test(this) || AbstractAssertJInspection.IS_EQUAL_TO_OBJECT.test(this)
val isNotEqualTo =
AbstractAssertJInspection.IS_NOT_EQUAL_TO_BOOLEAN.test(this) || AbstractAssertJInspection.IS_NOT_EQUAL_TO_OBJECT.test(this)
if (isEqualTo || isNotEqualTo) {
val constValue = calculateConstantParameterValue(0) as? Boolean ?: return null
return isNotEqualTo xor constValue
}
}
return null
}
fun PsiMethodCallExpression.getExpectedNullNonNullResult(): Boolean? {
val isNull = AbstractAssertJInspection.IS_NULL.test(this)
val isNotNull = AbstractAssertJInspection.IS_NOT_NULL.test(this)
if (isNull || isNotNull) {
return isNotNull
} else {
val isEqualTo = CallMatcher.anyOf(AbstractAssertJInspection.IS_EQUAL_TO_OBJECT, AbstractAssertJInspection.IS_EQUAL_TO_STRING).test(this)
val isNotEqualTo = AbstractAssertJInspection.IS_NOT_EQUAL_TO_OBJECT.test(this)
if ((isEqualTo || isNotEqualTo) && firstArg.type == PsiType.NULL) {
return isNotEqualTo
}
}
return null
}
fun PsiMethodCallExpression.calculateConstantParameterValue(argIndex: Int): Any? {
return getArgOrNull(argIndex)?.calculateConstantValue()
}
fun PsiExpression.calculateConstantValue(): Any? {
val constantEvaluationHelper = JavaPsiFacade.getInstance(project).constantEvaluationHelper
val value = constantEvaluationHelper.computeConstantExpression(this)
if (value == null) {
val field = (this as? PsiReferenceExpression)?.resolve() as? PsiField
if (field?.containingClass?.qualifiedName == CommonClassNames.JAVA_LANG_BOOLEAN) {
return when (field.name) {
"TRUE" -> true
"FALSE" -> false
else -> null
}
}
}
return value
}
fun PsiExpression.getAllTheSameExpectedBooleanConstants(): Boolean? {
val assertThatMethodCall = findStaticMethodCall() ?: return null
var lockedResult: Boolean? = null
val methodsToView = generateSequence(assertThatMethodCall) { PsiTreeUtil.getParentOfType(it, PsiMethodCallExpression::class.java) }
for (methodCall in methodsToView) {
val expectedResult = methodCall.getExpectedBooleanResult()
if (expectedResult != null) {
if ((lockedResult != null) && (lockedResult != expectedResult)) return null
lockedResult = expectedResult
} else {
val isNotConstant = CallMatcher.anyOf(
EXTENSION_POINTS,
MORE_EXTENSION_POINTS,
AbstractAssertJInspection.IS_EQUAL_TO_BOOLEAN,
AbstractAssertJInspection.IS_EQUAL_TO_OBJECT,
AbstractAssertJInspection.IS_NOT_EQUAL_TO_BOOLEAN,
AbstractAssertJInspection.IS_NOT_EQUAL_TO_OBJECT
).test(methodCall)
if (isNotConstant) return null
}
}
return lockedResult
}
fun PsiExpression.getAllTheSameNullNotNullConstants(): Boolean? {
val assertThatMethodCall = findStaticMethodCall() ?: return null
var lockedResult: Boolean? = null
val methodsToView = generateSequence(assertThatMethodCall) { PsiTreeUtil.getParentOfType(it, PsiMethodCallExpression::class.java) }
for (methodCall in methodsToView) {
val expectedResult = methodCall.getExpectedNullNonNullResult()
if (expectedResult != null) {
if ((lockedResult != null) && (lockedResult != expectedResult)) return null
lockedResult = expectedResult
} else {
val isNotConstant = CallMatcher.anyOf(
EXTENSION_POINTS,
MORE_EXTENSION_POINTS,
AbstractAssertJInspection.IS_EQUAL_TO_OBJECT,
AbstractAssertJInspection.IS_NOT_EQUAL_TO_OBJECT
).test(methodCall)
if (isNotConstant) return null
}
}
return lockedResult
}

View File

@ -0,0 +1,44 @@
package de.platon42.intellij.plugins.cajon
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.search.GlobalSearchScope
fun createAssertThat(context: PsiElement, actualExpression: PsiExpression): PsiMethodCallExpression {
return createAssertThat(context, AssertJClassNames.ASSERTIONS_CLASSNAME, actualExpression)
}
fun createGuavaAssertThat(context: PsiElement, actualExpression: PsiExpression): PsiMethodCallExpression {
return createAssertThat(context, AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, actualExpression)
}
fun createAssertThat(context: PsiElement, baseclass: String, actualExpression: PsiExpression): PsiMethodCallExpression {
return createMethodCall(context, "$baseclass.${MethodNames.ASSERT_THAT}", actualExpression)
}
fun createAssumeThat(context: PsiElement, actualExpression: PsiExpression): PsiMethodCallExpression {
return createMethodCall(context, "${AssertJClassNames.ASSUMPTIONS_CLASSNAME}.${MethodNames.ASSUME_THAT}", actualExpression)
}
fun createExpectedMethodCall(context: PsiElement, methodName: String, vararg arguments: PsiElement): PsiMethodCallExpression {
return createMethodCall(context, "a.$methodName", *arguments)
}
fun createMethodCall(context: PsiElement, fullQualifiedMethodName: String, vararg arguments: PsiElement): PsiMethodCallExpression {
val factory = JavaPsiFacade.getElementFactory(context.project)
val argString = generateSequence('b') { it + 1 }.take(arguments.size).joinToString(", ")
val expectedExpression = factory.createExpressionFromText(
"$fullQualifiedMethodName($argString)", context
) as PsiMethodCallExpression
arguments.forEachIndexed { index, newArg -> expectedExpression.getArg(index).replace(newArg) }
return expectedExpression
}
fun hasAssertJMethod(element: PsiElement, classname: String, methodname: String): Boolean {
val findClass =
JavaPsiFacade.getInstance(element.project).findClass(classname, GlobalSearchScope.allScope(element.project))
?: return false
return findClass.allMethods.any { it.name == methodname }
}

View File

@ -0,0 +1,180 @@
package de.platon42.intellij.plugins.cajon
import org.jetbrains.annotations.NonNls
class MethodNames {
companion object {
@NonNls
const val EQUALS = "equals"
@NonNls
const val ASSERT_THAT = "assertThat"
@NonNls
const val ASSUME_THAT = "assumeThat"
@NonNls
const val AS = "as"
@NonNls
const val DESCRIBED_AS = "describedAs"
@NonNls
const val IN_HEXADECIMAL = "inHexadecimal"
@NonNls
const val IN_BINARY = "inBinary"
@NonNls
const val IS_EQUAL_TO = "isEqualTo"
@NonNls
const val IS_NOT_EQUAL_TO = "isNotEqualTo"
@NonNls
const val IS_SAME_AS = "isSameAs"
@NonNls
const val IS_NOT_SAME_AS = "isNotSameAs"
@NonNls
const val HAS_TO_STRING = "hasToString"
@NonNls
const val IS_GREATER_THAN = "isGreaterThan"
@NonNls
const val IS_GREATER_THAN_OR_EQUAL_TO = "isGreaterThanOrEqualTo"
@NonNls
const val IS_LESS_THAN = "isLessThan"
@NonNls
const val IS_LESS_THAN_OR_EQUAL_TO = "isLessThanOrEqualTo"
@NonNls
const val IS_ZERO = "isZero"
@NonNls
const val IS_NOT_ZERO = "isNotZero"
@NonNls
const val IS_ONE = "isOne"
@NonNls
const val IS_TRUE = "isTrue"
@NonNls
const val IS_FALSE = "isFalse"
@NonNls
const val IS_NULL = "isNull" // terminal, returns void
@NonNls
const val IS_NOT_NULL = "isNotNull"
@NonNls
const val IS_CLOSE_TO = "isCloseTo"
@NonNls
const val IS_NOT_CLOSE_TO = "isNotCloseTo"
@NonNls
const val IS_INSTANCE_OF = "isInstanceOf"
@NonNls
const val IS_NOT_INSTANCE_OF = "isNotInstanceOf"
@NonNls
const val IS_NULL_OR_EMPTY = "isNullOrEmpty" // terminal, returns void
@NonNls
const val IS_EMPTY = "isEmpty" // terminal, returns void
@NonNls
const val IS_NOT_EMPTY = "isNotEmpty"
@NonNls
const val HAS_SIZE = "hasSize"
@NonNls
const val HAS_SIZE_LESS_THAN = "hasSizeLessThan"
@NonNls
const val HAS_SIZE_LESS_THAN_OR_EQUAL_TO = "hasSizeLessThanOrEqualTo"
@NonNls
const val HAS_SIZE_GREATER_THAN = "hasSizeGreaterThan"
@NonNls
const val HAS_SIZE_GREATER_THAN_OR_EQUAL_TO = "hasSizeGreaterThanOrEqualTo"
@NonNls
const val HAS_SAME_SIZE_AS = "hasSameSizeAs"
@NonNls
const val CONTAINS = "contains"
@NonNls
const val CONTAINS_ONLY_ONCE = "containsOnlyOnce"
@NonNls
const val DOES_NOT_CONTAIN = "doesNotContain"
@NonNls
const val CONTAINS_EXACTLY = "containsExactly"
@NonNls
const val CONTAINS_ALL = "containsAll"
@NonNls
const val CONTAINS_KEY = "containsKey"
@NonNls
const val DOES_NOT_CONTAIN_KEY = "doesNotContainKey"
@NonNls
const val CONTAINS_VALUE = "containsValue"
@NonNls
const val DOES_NOT_CONTAIN_VALUE = "doesNotContainValue"
@NonNls
const val CONTAINS_ENTRY = "containsEntry"
@NonNls
const val DOES_NOT_CONTAIN_ENTRY = "doesNotContainEntry"
@NonNls
const val IS_EQUAL_TO_IC = "isEqualToIgnoringCase"
@NonNls
const val IS_NOT_EQUAL_TO_IC = "isNotEqualToIgnoringCase"
@NonNls
const val STARTS_WITH = "startsWith"
@NonNls
const val ENDS_WITH = "endsWith"
@NonNls
const val DOES_NOT_START_WITH = "doesNotStartWith"
@NonNls
const val DOES_NOT_END_WITH = "doesNotEndWith"
@NonNls
const val CONTAINS_SAME = "containsSame"
@NonNls
const val IS_PRESENT = "isPresent"
@NonNls
const val IS_NOT_PRESENT = "isNotPresent"
@NonNls
const val IS_ABSENT = "isAbsent"
}
}

View File

@ -1,108 +1,217 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTypesUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_DOUBLE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_FLOAT_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_INTEGER_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_LONG_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_SHORT_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_STRING_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ASSERTIONS_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ASSERT_INTERFACE
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ENUMERABLE_ASSERT_INTERFACE
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_OPTIONAL_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSimpleMethodCallQuickFix
import org.jetbrains.annotations.NonNls
open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
abstract class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
companion object {
const val SIMPLIFY_MESSAGE_TEMPLATE = "%s can be simplified to %s"
const val SIMPLIFY_MESSAGE_TEMPLATE = "%s() can be simplified to %s()"
const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s() would be more concise than %s()"
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s with %s"
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s() with %s()"
const val REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE = "Unwrap expected expression and replace %s() with %s()"
const val MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE = "Remove %s() of actual expression and use assertThat().%s() instead"
const val MOVING_OUT_MESSAGE_TEMPLATE = "Moving %s() expression out of assertThat() would be more concise"
@NonNls
const val ASSERTIONS_CLASSNAME = "org.assertj.core.api.Assertions"
val TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP = mapOf<IElementType, String>(
JavaTokenType.EQEQ to MethodNames.IS_EQUAL_TO,
JavaTokenType.NE to MethodNames.IS_NOT_EQUAL_TO,
JavaTokenType.GT to MethodNames.IS_GREATER_THAN,
JavaTokenType.GE to MethodNames.IS_GREATER_THAN_OR_EQUAL_TO,
JavaTokenType.LT to MethodNames.IS_LESS_THAN,
JavaTokenType.LE to MethodNames.IS_LESS_THAN_OR_EQUAL_TO
)
@NonNls
const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert"
@NonNls
const val ABSTRACT_BOOLEAN_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanAssert"
@NonNls
const val ABSTRACT_INTEGER_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntegerAssert"
@NonNls
const val ABSTRACT_COMPARABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractComparableAssert"
@NonNls
const val ABSTRACT_STRING_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractStringAssert"
@NonNls
const val ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharSequenceAssert"
@NonNls
const val ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.EnumerableAssert"
val TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS = mapOf<IElementType, String>(
JavaTokenType.EQEQ to MethodNames.IS_SAME_AS,
JavaTokenType.NE to MethodNames.IS_NOT_SAME_AS
)
@NonNls
const val ASSERT_THAT_METHOD = "assertThat"
@NonNls
const val IS_EQUAL_TO_METHOD = "isEqualTo"
@NonNls
const val IS_NOT_EQUAL_TO_METHOD = "isNotEqualTo"
@NonNls
const val IS_GREATER_THAN_METHOD = "isGreaterThan"
@NonNls
const val IS_GREATER_THAN_OR_EQUAL_TO_METHOD = "isGreaterThanOrEqualTo"
@NonNls
const val IS_LESS_THAN_METHOD = "isLessThan"
@NonNls
const val IS_LESS_THAN_OR_EQUAL_TO_METHOD = "isLessThanOrEqualTo"
@NonNls
const val IS_ZERO_METHOD = "isZero"
@NonNls
const val IS_NOT_ZERO_METHOD = "isNotZero"
@NonNls
const val IS_TRUE_METHOD = "isTrue"
@NonNls
const val IS_FALSE_METHOD = "isFalse"
@NonNls
const val HAS_SIZE_METHOD = "hasSize"
val SWAP_SIDE_OF_BINARY_OPERATOR = mapOf<IElementType, IElementType>(
JavaTokenType.GT to JavaTokenType.LT,
JavaTokenType.GE to JavaTokenType.LE,
JavaTokenType.LT to JavaTokenType.GT,
JavaTokenType.LE to JavaTokenType.GE
)
val ASSERT_THAT_INT = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
val INVERT_BINARY_OPERATOR = mapOf<IElementType, IElementType>(
JavaTokenType.EQEQ to JavaTokenType.NE,
JavaTokenType.NE to JavaTokenType.EQEQ,
JavaTokenType.GT to JavaTokenType.LE,
JavaTokenType.GE to JavaTokenType.LT,
JavaTokenType.LT to JavaTokenType.GE,
JavaTokenType.LE to JavaTokenType.GT
)
val ASSERT_THAT_INT = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes("int")!!
val ASSERT_THAT_BOOLEAN = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
val ASSERT_THAT_BOOLEAN = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes("boolean")!!
val ASSERT_THAT_BOOLEAN_OBJ = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes(CommonClassNames.JAVA_LANG_BOOLEAN)!!
val ASSERT_THAT_ANY = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterCount(1)!!
val ASSERT_THAT_JAVA8_OPTIONAL = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes(CommonClassNames.JAVA_UTIL_OPTIONAL)!!
val GUAVA_ASSERT_THAT_ANY = CallMatcher.staticCall(GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterCount(1)!!
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_EQUAL_TO_STRING = CallMatcher.instanceCall(ABSTRACT_STRING_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!
val IS_EQUAL_TO_SHORT = CallMatcher.instanceCall(ABSTRACT_SHORT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("short")!!
val IS_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("int")!!
val IS_EQUAL_TO_LONG = CallMatcher.instanceCall(ABSTRACT_LONG_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("long")!!
val IS_EQUAL_TO_FLOAT = CallMatcher.instanceCall(ABSTRACT_FLOAT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("float")!!
val IS_EQUAL_TO_DOUBLE = CallMatcher.instanceCall(ABSTRACT_DOUBLE_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("double")!!
val IS_EQUAL_TO_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("boolean")!!
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_EQUAL_TO_METHOD)
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_NOT_EQUAL_TO_METHOD)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_EQUAL_TO_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, IS_EQUAL_TO_METHOD)
val IS_NOT_EQUAL_TO_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("boolean")!!
val IS_NOT_EQUAL_TO_BOOLEAN =
CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, IS_NOT_EQUAL_TO_METHOD)
.parameterTypes("boolean")!!
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, HAS_SIZE_METHOD)
val IS_NOT_EQUAL_TO_SHORT = CallMatcher.instanceCall(ABSTRACT_SHORT_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("short")!!
val IS_NOT_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("int")!!
val IS_NOT_EQUAL_TO_LONG = CallMatcher.instanceCall(ABSTRACT_LONG_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("long")!!
val IS_NOT_EQUAL_TO_FLOAT = CallMatcher.instanceCall(ABSTRACT_FLOAT_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("float")!!
val IS_NOT_EQUAL_TO_DOUBLE = CallMatcher.instanceCall(ABSTRACT_DOUBLE_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("double")!!
val IS_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_GREATER_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_GREATER_THAN_METHOD)
.parameterTypes("int")!!
val IS_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_GREATER_THAN_OR_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_SAME_AS)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_SAME_AS)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_LESS_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_LESS_THAN_METHOD)
.parameterTypes("int")!!
val IS_LESS_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_LESS_THAN_OR_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_ZERO = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, IS_ZERO_METHOD)
val IS_NULL = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NULL)
.parameterCount(0)!!
val IS_NOT_ZERO = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, IS_NOT_ZERO_METHOD)
val IS_NOT_NULL = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_NULL)
.parameterCount(0)!!
val IS_TRUE = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, IS_TRUE_METHOD)
val IS_EMPTY = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.IS_EMPTY)
.parameterCount(0)!!
val IS_FALSE = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, IS_FALSE_METHOD)
val IS_NOT_EMPTY = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.IS_NOT_EMPTY)
.parameterCount(0)!!
val HAS_SIZE = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE)
.parameterTypes("int")!!
val HAS_SIZE_GREATER_THAN_INT = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE_GREATER_THAN)
.parameterTypes("int")!!
val HAS_SIZE_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO)
.parameterTypes("int")!!
val HAS_SIZE_LESS_THAN_INT = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE_LESS_THAN)
.parameterTypes("int")!!
val HAS_SIZE_LESS_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE_LESS_THAN_OR_EQUAL_TO)
.parameterTypes("int")!!
val IS_GREATER_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN)
.parameterTypes("int")!!
val IS_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN_OR_EQUAL_TO)
.parameterTypes("int")!!
val IS_LESS_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_LESS_THAN)
.parameterTypes("int")!!
val IS_LESS_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_LESS_THAN_OR_EQUAL_TO)
.parameterTypes("int")!!
val IS_ZERO_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, MethodNames.IS_ZERO)
.parameterCount(0)!!
val IS_NOT_ZERO_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, MethodNames.IS_NOT_ZERO)
.parameterCount(0)!!
val IS_ZERO_LONG = CallMatcher.instanceCall(ABSTRACT_LONG_ASSERT_CLASSNAME, MethodNames.IS_ZERO)
.parameterCount(0)!!
val IS_NOT_ZERO_LONG = CallMatcher.instanceCall(ABSTRACT_LONG_ASSERT_CLASSNAME, MethodNames.IS_NOT_ZERO)
.parameterCount(0)!!
val IS_ONE_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, "isOne")
.parameterCount(0)!!
val IS_NEGATIVE_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, "isNegative")
.parameterCount(0)!!
val IS_NOT_NEGATIVE_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, "isNotNegative")
.parameterCount(0)!!
val IS_POSITIVE_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, "isPositive")
.parameterCount(0)!!
val IS_NOT_POSITIVE_INT = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, "isNotPositive")
.parameterCount(0)!!
val IS_TRUE = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_TRUE)
.parameterCount(0)!!
val IS_FALSE = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_FALSE)
.parameterCount(0)!!
val COLLECTION_SIZE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "size")
.parameterCount(0)!!
val OBJECT_EQUALS = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "equals")
val MAP_SIZE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "size")
.parameterCount(0)!!
val CHAR_SEQUENCE_LENGTH = CallMatcher.instanceCall("java.lang.CharSequence", "length")
.parameterCount(0)!!
val OBJECT_EQUALS = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, MethodNames.EQUALS)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val OPTIONAL_GET = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "get")
.parameterCount(0)!!
val OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, MethodNames.IS_PRESENT)
.parameterCount(0)!!
val OPTIONAL_OR_ELSE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElse")
.parameterCount(1)!!
val OPTIONAL_OF = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "of")
.parameterCount(1)!!
val OPTIONAL_OF_NULLABLE = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable")
.parameterCount(1)!!
val OPTIONAL_EMPTY = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "empty")
.parameterCount(0)!!
val GUAVA_OPTIONAL_GET = CallMatcher.instanceCall(GUAVA_OPTIONAL_CLASSNAME, "get")
.parameterCount(0)!!
val GUAVA_OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(GUAVA_OPTIONAL_CLASSNAME, MethodNames.IS_PRESENT)
.parameterCount(0)!!
val GUAVA_OPTIONAL_OR_NULL = CallMatcher.instanceCall(GUAVA_OPTIONAL_CLASSNAME, "orNull")
.parameterCount(0)!!
val GUAVA_OPTIONAL_OF = CallMatcher.staticCall(GUAVA_OPTIONAL_CLASSNAME, "of")
.parameterCount(1)!!
val GUAVA_OPTIONAL_FROM_NULLABLE = CallMatcher.staticCall(GUAVA_OPTIONAL_CLASSNAME, "fromNullable")
.parameterCount(1)!!
val GUAVA_OPTIONAL_ABSENT = CallMatcher.staticCall(GUAVA_OPTIONAL_CLASSNAME, "absent")
.parameterCount(0)!!
}
override fun getGroupDisplayName(): String {
@ -110,8 +219,8 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
}
protected fun checkAssertedType(expression: PsiMethodCallExpression, classname: String): Boolean {
var assertedType = expression.methodExpression.qualifierExpression?.type ?: return false
if (assertedType is PsiCapturedWildcardType) {
var assertedType = expression.qualifierExpression.type ?: return false
while (assertedType is PsiCapturedWildcardType) {
assertedType = assertedType.upperBound
}
val assertedClass = PsiTypesUtil.getPsiClass(assertedType) ?: return false
@ -120,8 +229,7 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
return assertedClass.isEquivalentTo(expectedClass) || assertedClass.isInheritor(expectedClass, true)
}
protected fun getOriginalMethodName(expression: PsiMethodCallExpression) =
expression.resolveMethod()?.name?.plus("()")
protected fun getOriginalMethodName(expression: PsiMethodCallExpression) = expression.resolveMethod()?.name
protected fun registerSimplifyMethod(
holder: ProblemsHolder,
@ -131,25 +239,72 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
val originalMethod = getOriginalMethodName(expression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = SIMPLIFY_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod)
holder.registerProblem(
expression,
message,
ReplaceSimpleMethodCallQuickFix(description, replacementMethod)
)
val quickFix = ReplaceSimpleMethodCallQuickFix(description, replacementMethod)
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
holder.registerProblem(expression, textRange, message, quickFix)
}
protected fun calculateConstantParameterValue(expression: PsiMethodCallExpression, argIndex: Int): Any? {
if (argIndex >= expression.argumentList.expressionCount) return null
val valueExpression = expression.argumentList.expressions[argIndex] ?: return null
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
return constantEvaluationHelper.computeConstantExpression(valueExpression)
protected fun registerMoveOutMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
oldActualExpression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
val originalMethod = getOriginalMethodName(oldActualExpression) ?: return
val description = MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MOVING_OUT_MESSAGE_TEMPLATE.format(originalMethod)
val quickfix = quickFixSupplier(description, replacementMethod)
holder.registerProblem(expression, message, quickfix)
}
protected fun hasAssertJMethod(element: PsiElement, classAndMethod: String): Boolean {
val classname = "org.assertj.core.api.${classAndMethod.substringBeforeLast(".")}"
val findClass =
JavaPsiFacade.getInstance(element.project).findClass(classname, GlobalSearchScope.allScope(element.project))
?: return false
return findClass.findMethodsByName(classAndMethod.substringAfterLast(".")).isNotEmpty()
protected fun registerMoveOutMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
oldActualExpression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String) -> List<LocalQuickFix>
) {
val originalMethod = getOriginalMethodName(oldActualExpression) ?: return
val description = MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MOVING_OUT_MESSAGE_TEMPLATE.format(originalMethod)
val quickfixes = quickFixSupplier(description)
holder.registerProblem(expression, message, *quickfixes.toTypedArray())
}
}
protected fun registerReplaceMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
oldExpectedCallExpression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
registerConciseMethod(REPLACE_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod, quickFixSupplier)
}
protected fun registerConciseMethod(
descriptionTemplate: String,
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
oldExpectedCallExpression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
val originalMethod = getOriginalMethodName(oldExpectedCallExpression) ?: return
val description = descriptionTemplate.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
val quickfix = quickFixSupplier(description, replacementMethod)
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
holder.registerProblem(expression, textRange, message, quickfix)
}
protected fun registerRemoveExpectedOutmostMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
oldExpectedCallExpression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
registerConciseMethod(REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod, quickFixSupplier)
}
}

View File

@ -4,40 +4,59 @@ import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.psi.PsiMethodCallExpression
import org.jetbrains.annotations.NonNls
open class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool() {
abstract class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool() {
companion object {
const val CONVERT_MESSAGE_TEMPLATE = "%s can be converted to AssertJ style"
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s with assertThat().%s"
@NonNls
const val JUNIT_ASSERT_CLASSNAME = "org.junit.Assert"
@NonNls
const val JUNIT_ASSUME_CLASSNAME = "org.junit.Assume"
@NonNls
const val ASSERT_TRUE_METHOD = "assertTrue"
@NonNls
const val ASSERT_FALSE_METHOD = "assertFalse"
@NonNls
const val ASSERT_NULL_METHOD = "assertNull"
@NonNls
const val ASSERT_NOT_NULL_METHOD = "assertNotNull"
@NonNls
const val ASSERT_EQUALS_METHOD = "assertEquals"
@NonNls
const val ASSERT_NOT_EQUALS_METHOD = "assertNotEquals"
@NonNls
const val ASSERT_SAME_METHOD = "assertSame"
@NonNls
const val ASSERT_NOT_SAME_METHOD = "assertNotSame"
@NonNls
const val ASSERT_ARRAY_EQUALS_METHOD = "assertArrayEquals"
@NonNls
const val ASSUME_TRUE_METHOD = "assumeTrue"
@NonNls
const val ASSUME_FALSE_METHOD = "assumeFalse"
@NonNls
const val ASSUME_NOT_NULL_METHOD = "assumeNotNull"
@NonNls
const val ASSUME_NO_EXCEPTION = "assumeNoException"
}
override fun getGroupDisplayName(): String {
return "AssertJ"
}
protected fun getOriginalMethodName(expression: PsiMethodCallExpression) =
expression.resolveMethod()?.name?.plus("()")
protected fun getOriginalMethodName(expression: PsiMethodCallExpression) = expression.resolveMethod()?.name
}

View File

@ -0,0 +1,64 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiExpressionStatement
import com.intellij.psi.PsiMethodCallExpression
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
abstract class AbstractMoveOutInspection : AbstractAssertJInspection() {
protected fun createInspectionsForMappings(
statement: PsiExpressionStatement,
holder: ProblemsHolder,
mappings: List<MoveOutMapping>
) {
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
val assertThatArgument = staticMethodCall.getArgOrNull(0) as? PsiMethodCallExpression ?: return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
for (mapping in mappings.filter { it.callMatcher.test(assertThatArgument) }) {
if (mapping.expectBoolean && ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
val expectedBooleanResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: continue
if (mapping.additionalCondition?.invoke(statement, expectedCallExpression) == false) continue
val replacementMethod = if (expectedBooleanResult) mapping.replacementForTrue else mapping.replacementForFalse ?: return
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method)
}
} else if (mapping.expectNullNonNull != null) {
val expectedNullNonNullResult = expectedCallExpression.getExpectedNullNonNullResult() ?: continue
if (mapping.additionalCondition?.invoke(statement, expectedCallExpression) == false) continue
val replacementMethod = if (expectedNullNonNullResult xor mapping.expectNullNonNull) mapping.replacementForTrue else mapping.replacementForFalse ?: continue
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, useNullNonNull = true)
}
} else if (mapping.expectedMatcher?.test(expectedCallExpression) == true) {
if (mapping.additionalCondition?.invoke(statement, expectedCallExpression) == false) continue
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, mapping.replacementForTrue) { desc, method ->
MoveOutMethodCallExpressionQuickFix(
desc, method,
replaceOnlyThisMethod = mapping.expectedMatcher,
replaceFromOriginalMethod = mapping.replaceFromOriginalMethod,
noExpectedExpression = mapping.noExpectedExpression
)
}
}
}
}
class MoveOutMapping(
val callMatcher: CallMatcher,
val replacementForTrue: String,
val replacementForFalse: String? = null,
val expectBoolean: Boolean = false,
val expectNullNonNull: Boolean? = null,
val expectedMatcher: CallMatcher? = null,
val replaceFromOriginalMethod: Boolean = false,
val noExpectedExpression: Boolean = false,
val additionalCondition: ((PsiExpressionStatement, PsiMethodCallExpression) -> Boolean)? = null
)
}

View File

@ -0,0 +1,86 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.SplitBinaryExpressionMethodCallQuickFix
class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a binary expression"
private const val SPLIT_EXPRESSION_DESCRIPTION_TEMPLATE = "Split binary expression out of assertThat()"
private const val MORE_MEANINGFUL_MESSAGE_TEMPLATE = "Moving binary expression out of assertThat() would be more meaningful"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
val binaryExpression = staticMethodCall.firstArg as? PsiBinaryExpression ?: return
val leftType = binaryExpression.lOperand.type ?: return
val rightType = binaryExpression.rOperand?.type ?: return
val bothTypes = listOf(leftType, rightType)
val (isLeftNull, isRightNull) = bothTypes.map(TypeConversionUtil::isNullType)
if (isLeftNull && isRightNull) return
if (isLeftNull || isRightNull) {
val expectedResultOnOp =
when (binaryExpression.operationTokenType) {
JavaTokenType.EQEQ -> expectedResult
JavaTokenType.NE -> !expectedResult
else -> return
}
val replacementMethod = expectedResultOnOp.map(MethodNames.IS_NULL, MethodNames.IS_NOT_NULL)
registerSplitMethod(holder, expectedCallExpression, replacementMethod) { desc, method ->
SplitBinaryExpressionMethodCallQuickFix(desc, method, pickRightOperand = isLeftNull, noExpectedExpression = true)
}
return
}
val isPrimitive = bothTypes.all(TypeConversionUtil::isPrimitiveAndNotNull)
val isNumericType = bothTypes.all(TypeConversionUtil::isNumericType)
val constantEvaluationHelper = JavaPsiFacade.getInstance(statement.project).constantEvaluationHelper
val swapExpectedAndActual = constantEvaluationHelper.computeConstantExpression(binaryExpression.lOperand) != null
val tokenType = binaryExpression.operationTokenType
.let {
if (swapExpectedAndActual) SWAP_SIDE_OF_BINARY_OPERATOR.getOrDefault(it, it) else it
}
.let {
if (expectedResult) it else INVERT_BINARY_OPERATOR.getOrDefault(it, it)
}
val mappingToUse =
(isPrimitive || isNumericType).map(TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP, TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS)
val replacementMethod = mappingToUse[tokenType] ?: return
registerSplitMethod(holder, expectedCallExpression, replacementMethod) { desc, method ->
SplitBinaryExpressionMethodCallQuickFix(desc, method, pickRightOperand = swapExpectedAndActual)
}
}
}
}
private fun registerSplitMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
val quickfix = quickFixSupplier(SPLIT_EXPRESSION_DESCRIPTION_TEMPLATE, replacementMethod)
holder.registerProblem(expression, MORE_MEANINGFUL_MESSAGE_TEMPLATE, quickfix)
}
}

View File

@ -1,144 +0,0 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.quickfixes.SplitBinaryExpressionMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.SplitEqualsExpressionMethodCallQuickFix
class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a binary expression"
private const val SPLIT_BINARY_EXPRESSION_DESCRIPTION = "Split binary expression out of assertThat()"
private const val SPLIT_EQUALS_EXPRESSION_DESCRIPTION = "Split equals() expression out of assertThat()"
private const val BINARY_MORE_MEANINGFUL_MESSAGE = "Moving binary expression out of assertThat() would be more meaningful"
private const val EQUALS_MORE_MEANINGFUL_MESSAGE = "Moving equals() expression out of assertThat() would be more meaningful"
private val PRIMITIVE_MAPPINGS = listOf(
Mapping(JavaTokenType.EQEQ, "isEqualTo()", "isNotEqualTo()"),
Mapping(JavaTokenType.NE, "isNotEqualTo()", "isEqualTo()"),
Mapping(JavaTokenType.GT, "isGreaterThan()", "isLessThanOrEqualTo()"),
Mapping(JavaTokenType.GE, "isGreaterThanOrEqualTo()", "isLessThan()"),
Mapping(JavaTokenType.LT, "isLessThan()", "isGreaterThanOrEqualTo()"),
Mapping(JavaTokenType.LE, "isLessThanOrEqualTo()", "isGreaterThan()")
)
private val PRIMITIVE_MAPPINGS_SWAPPED = listOf(
Mapping(JavaTokenType.EQEQ, "isEqualTo()", "isNotEqualTo()"),
Mapping(JavaTokenType.NE, "isNotEqualTo()", "isEqualTo()"),
Mapping(JavaTokenType.GT, "isLessThan()", "isGreaterThanOrEqualTo()"),
Mapping(JavaTokenType.GE, "isLessThanOrEqualTo()", "isGreaterThan()"),
Mapping(JavaTokenType.LT, "isGreaterThan()", "isLessThanOrEqualTo()"),
Mapping(JavaTokenType.LE, "isGreaterThanOrEqualTo()", "isLessThan()")
)
private val OBJECT_MAPPINGS = listOf(
Mapping(JavaTokenType.EQEQ, "isSameAs()", "isNotSameAs()"),
Mapping(JavaTokenType.NE, "isNotSameAs()", "isSameAs()")
)
}
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 (!ASSERT_THAT_BOOLEAN.test(expression)) {
return
}
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val isInverted = getExpectedResult(expectedCallExpression) ?: return
val assertThatArgument = expression.argumentList.expressions[0] ?: return
if (assertThatArgument is PsiMethodCallExpression && OBJECT_EQUALS.test(assertThatArgument)) {
val replacementMethod = if (isInverted) "isNotEqualTo()" else "isEqualTo()"
holder.registerProblem(
expression,
EQUALS_MORE_MEANINGFUL_MESSAGE,
SplitEqualsExpressionMethodCallQuickFix(SPLIT_EQUALS_EXPRESSION_DESCRIPTION, replacementMethod)
)
return
}
val binaryExpression = assertThatArgument as? PsiBinaryExpression ?: return
val leftType = binaryExpression.lOperand.type ?: return
val rightType = binaryExpression.rOperand?.type ?: return
val isLeftNull = TypeConversionUtil.isNullType(leftType)
val isRightNull = TypeConversionUtil.isNullType(rightType)
if (isLeftNull && isRightNull) {
return
} else if (isLeftNull || isRightNull) {
registerSplitBinaryExpressionMethod(
holder,
expression,
if (isInverted) "isNotNull()" else "isNull()",
pickRightOperand = isLeftNull,
noExpectedExpression = true
)
return
}
val isPrimitive = TypeConversionUtil.isPrimitiveAndNotNull(leftType) && TypeConversionUtil.isPrimitiveAndNotNull(rightType)
val isNumericType = TypeConversionUtil.isNumericType(leftType) && TypeConversionUtil.isNumericType(rightType)
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
val swapExpectedAndActual = constantEvaluationHelper.computeConstantExpression(binaryExpression.lOperand) != null
val tokenType = binaryExpression.operationSign.tokenType
val mappingToUse =
if (isPrimitive || isNumericType) {
if (swapExpectedAndActual) PRIMITIVE_MAPPINGS_SWAPPED else PRIMITIVE_MAPPINGS
} else {
OBJECT_MAPPINGS
}
val mapping = mappingToUse.find { it.tokenType == tokenType } ?: return
val replacementMethod = if (isInverted) mapping.replacementInverted else mapping.replacement
registerSplitBinaryExpressionMethod(holder, expression, replacementMethod, pickRightOperand = swapExpectedAndActual)
}
private fun getExpectedResult(expectedCallExpression: PsiMethodCallExpression): Boolean? {
val isTrue = IS_TRUE.test(expectedCallExpression)
val isFalse = IS_FALSE.test(expectedCallExpression)
if (isTrue || isFalse) {
return isFalse
} else {
val isEqualTo = IS_EQUAL_TO_BOOLEAN.test(expectedCallExpression)
val isNotEqualTo = IS_NOT_EQUAL_TO_BOOLEAN.test(expectedCallExpression)
if (isEqualTo || isNotEqualTo) {
val constValue = calculateConstantParameterValue(expectedCallExpression, 0) as? Boolean ?: return null
return isEqualTo xor constValue
}
}
return null
}
private fun registerSplitBinaryExpressionMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String,
pickRightOperand: Boolean = false,
noExpectedExpression: Boolean = false
) {
holder.registerProblem(
expression,
BINARY_MORE_MEANINGFUL_MESSAGE,
SplitBinaryExpressionMethodCallQuickFix(SPLIT_BINARY_EXPRESSION_DESCRIPTION, replacementMethod, pickRightOperand, noExpectedExpression)
)
}
}
}
private class Mapping(
val tokenType: IElementType,
val replacement: String,
val replacementInverted: String
)
}

View File

@ -0,0 +1,43 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME
class AssertThatBooleanConditionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a boolean condition"
}
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 (!expression.hasAssertThat()) return
val isEqualToObject = IS_EQUAL_TO_OBJECT.test(expression)
val isEqualToPrimitive = IS_EQUAL_TO_BOOLEAN.test(expression)
val isNotEqualToObject = IS_NOT_EQUAL_TO_OBJECT.test(expression)
val isNotEqualToPrimitive = IS_NOT_EQUAL_TO_BOOLEAN.test(expression)
if (!(isEqualToObject || isEqualToPrimitive || isNotEqualToObject || isNotEqualToPrimitive)) return
if (!checkAssertedType(expression, ABSTRACT_BOOLEAN_ASSERT_CLASSNAME)) return
val expectedExpression = expression.firstArg
if (!TypeConversionUtil.isBooleanType(expectedExpression.type)) return
val expectedResult = expression.calculateConstantParameterValue(0) as? Boolean ?: return
val flippedBooleanTest = isNotEqualToObject || isNotEqualToPrimitive
val replacementMethod = (expectedResult xor flippedBooleanTest).map(MethodNames.IS_TRUE, MethodNames.IS_FALSE)
registerSimplifyMethod(holder, expression, replacementMethod)
}
}
}
}

View File

@ -1,53 +0,0 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.util.TypeConversionUtil
class AssertThatBooleanIsTrueOrFalseInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting true or false"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
val isEqualToObject = IS_EQUAL_TO_OBJECT.test(expression)
val isEqualToBoolean = IS_EQUAL_TO_BOOLEAN.test(expression)
val isNotEqualToObject = IS_NOT_EQUAL_TO_OBJECT.test(expression)
val isNotEqualToBoolean = IS_NOT_EQUAL_TO_BOOLEAN.test(expression)
val normalBooleanTest = isEqualToObject || isEqualToBoolean
val flippedBooleanTest = isNotEqualToObject || isNotEqualToBoolean
if (!(normalBooleanTest || flippedBooleanTest)) {
return
}
if (!checkAssertedType(expression, ABSTRACT_BOOLEAN_ASSERT_CLASSNAME)) {
return
}
val equalToExpression = expression.argumentList.expressions[0] ?: return
if (!TypeConversionUtil.isBooleanType(equalToExpression.type)) {
return
}
var value = calculateConstantParameterValue(expression, 0)
if (value == null) {
val field = (equalToExpression as? PsiReferenceExpression)?.resolve() as? PsiField
if (field?.containingClass?.qualifiedName == CommonClassNames.JAVA_LANG_BOOLEAN) {
when {
field.name == "TRUE" -> value = true
field.name == "FALSE" -> value = false
}
}
}
val expectedResult = value as? Boolean ?: return
val replacementMethod = if (expectedResult xor flippedBooleanTest) "isTrue()" else "isFalse()"
registerSimplifyMethod(holder, expression, replacementMethod)
}
}
}
}

View File

@ -0,0 +1,158 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.ui.ComboBox
import com.intellij.psi.*
import com.intellij.util.ui.FormBuilder
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
import java.awt.BorderLayout
import javax.swing.JComponent
import javax.swing.JPanel
class AssertThatCollectionOrMapExpressionInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a collection or map specific expression"
private const val DEFAULT_MAP_VALUES_NEVER_NULL = 2
private val MAP_GET_MATCHER = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "get").parameterCount(1)
private val ANY_IS_EQUAL_TO_MATCHER = CallMatcher.anyOf(
IS_EQUAL_TO_OBJECT,
IS_EQUAL_TO_STRING,
IS_EQUAL_TO_INT,
IS_EQUAL_TO_LONG,
IS_EQUAL_TO_FLOAT,
IS_EQUAL_TO_DOUBLE,
IS_EQUAL_TO_BOOLEAN
)
private val ANY_IS_NOT_EQUAL_TO_MATCHER = CallMatcher.anyOf(
IS_NOT_EQUAL_TO_OBJECT,
IS_NOT_EQUAL_TO_INT,
IS_NOT_EQUAL_TO_LONG,
IS_NOT_EQUAL_TO_FLOAT,
IS_NOT_EQUAL_TO_DOUBLE,
IS_NOT_EQUAL_TO_BOOLEAN
)
private val MAPPINGS = listOf(
MoveOutMapping(
CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, MethodNames.IS_EMPTY).parameterCount(0),
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, MethodNames.IS_EMPTY).parameterCount(0)
),
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, MethodNames.CONTAINS).parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, MethodNames.CONTAINS_ALL).parameterCount(1),
MethodNames.CONTAINS_ALL, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, MethodNames.CONTAINS_KEY).parameterCount(1),
MethodNames.CONTAINS_KEY, MethodNames.DOES_NOT_CONTAIN_KEY, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, MethodNames.CONTAINS_VALUE).parameterCount(1),
MethodNames.CONTAINS_VALUE, MethodNames.DOES_NOT_CONTAIN_VALUE, expectBoolean = true
)
)
}
override fun getDisplayName() = DISPLAY_NAME
@JvmField
var behaviorForMapValueEqualsNull: Int = DEFAULT_MAP_VALUES_NEVER_NULL
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
val assertThatArgument = staticMethodCall.getArgOrNull(0) as? PsiMethodCallExpression ?: return
if (MAP_GET_MATCHER.test(assertThatArgument)) {
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val nullOrNotNull = expectedCallExpression.getAllTheSameNullNotNullConstants()
if (nullOrNotNull == true) {
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.CONTAINS_KEY) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, useNullNonNull = true)
}
} else if (nullOrNotNull == false) {
when (behaviorForMapValueEqualsNull) {
1 -> // warning only
registerMoveOutMethod(
holder,
expectedCallExpression,
assertThatArgument,
""
) { _ -> emptyList() }
2 -> // as doesNotContainKey(key)
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.DOES_NOT_CONTAIN_KEY) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, useNullNonNull = true)
}
3 -> // as containsEntry(key, null)
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.CONTAINS_ENTRY) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, keepExpectedAsSecondArgument = true, useNullNonNull = true)
}
4 -> // both
registerMoveOutMethod(
holder,
expectedCallExpression,
assertThatArgument,
MethodNames.DOES_NOT_CONTAIN_KEY + "/" + MethodNames.CONTAINS_ENTRY
) { _ ->
listOf(
MoveOutMethodCallExpressionQuickFix(
"Remove get() of actual expression and use assertThat().doesNotContainKey() instead (regular map)",
MethodNames.DOES_NOT_CONTAIN_KEY,
useNullNonNull = true
),
MoveOutMethodCallExpressionQuickFix(
"Remove get() of actual expression and use assertThat().containsEntry(key, null) instead (degenerated map)",
MethodNames.CONTAINS_ENTRY,
keepExpectedAsSecondArgument = true,
useNullNonNull = true
)
)
}
}
} else {
if (ANY_IS_EQUAL_TO_MATCHER.test(expectedCallExpression)) {
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.CONTAINS_ENTRY) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, keepExpectedAsSecondArgument = true)
}
} else if (ANY_IS_NOT_EQUAL_TO_MATCHER.test(expectedCallExpression)) {
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.DOES_NOT_CONTAIN_ENTRY) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, keepExpectedAsSecondArgument = true)
}
}
}
} else {
createInspectionsForMappings(statement, holder, MAPPINGS)
}
}
}
}
override fun createOptionsPanel(): JComponent {
val comboBox = ComboBox(
arrayOf("ignore", "warning only, no fixes", "as doesNotContainKey(key)", "as containsEntry(key, null)", "both choices")
)
comboBox.selectedIndex = behaviorForMapValueEqualsNull
comboBox.addActionListener { behaviorForMapValueEqualsNull = comboBox.selectedIndex }
val panel = JPanel(BorderLayout())
panel.add(
FormBuilder.createFormBuilder().addLabeledComponent("Fix get() on maps expecting null values:", comboBox).panel,
BorderLayout.NORTH
)
return panel
}
}

View File

@ -0,0 +1,115 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantValue
import de.platon42.intellij.plugins.cajon.firstArg
class AssertThatComparableInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a compareTo() expression"
private val ARG_IS_ZERO_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == 0 }
private val ARG_IS_PLUS_ONE_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == 1 }
private val ARG_IS_MINUS_ONE_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == -1 }
private val COMPARABLE_COMPARE_TO =
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_COMPARABLE, "compareTo").parameterCount(1)
private val MAPPINGS = listOf(
MoveOutMapping(
COMPARABLE_COMPARE_TO,
"isEqualByComparingTo", expectedMatcher = IS_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
"isEqualByComparingTo", expectedMatcher = IS_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
"isNotEqualByComparingTo", expectedMatcher = IS_NOT_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
"isNotEqualByComparingTo", expectedMatcher = IS_NOT_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN_OR_EQUAL_TO, expectedMatcher = IS_GREATER_THAN_OR_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN_OR_EQUAL_TO, expectedMatcher = CallMatcher.anyOf(IS_NOT_EQUAL_TO_INT, IS_GREATER_THAN_INT), replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN_OR_EQUAL_TO, expectedMatcher = IS_NOT_NEGATIVE_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN, expectedMatcher = CallMatcher.anyOf(IS_EQUAL_TO_INT, IS_GREATER_THAN_OR_EQUAL_TO_INT), replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_PLUS_ONE_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN, expectedMatcher = IS_GREATER_THAN_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_GREATER_THAN, expectedMatcher = CallMatcher.anyOf(IS_POSITIVE_INT, IS_ONE_INT), replaceFromOriginalMethod = true
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN_OR_EQUAL_TO, expectedMatcher = IS_LESS_THAN_OR_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN_OR_EQUAL_TO, expectedMatcher = CallMatcher.anyOf(IS_NOT_EQUAL_TO_INT, IS_LESS_THAN_INT), replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_PLUS_ONE_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN_OR_EQUAL_TO, expectedMatcher = IS_NOT_POSITIVE_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN, expectedMatcher = CallMatcher.anyOf(IS_EQUAL_TO_INT, IS_LESS_THAN_OR_EQUAL_TO_INT), replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN, expectedMatcher = IS_LESS_THAN_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
COMPARABLE_COMPARE_TO,
MethodNames.IS_LESS_THAN, expectedMatcher = IS_NEGATIVE_INT, replaceFromOriginalMethod = true
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
createInspectionsForMappings(statement, holder, MAPPINGS)
}
}
}
}

View File

@ -4,11 +4,16 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
import de.platon42.intellij.plugins.cajon.hasAssertThat
class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting an empty enumerable"
private const val DISPLAY_NAME = "Asserting an empty or not empty enumerable"
}
override fun getDisplayName() = DISPLAY_NAME
@ -17,14 +22,19 @@ class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
val hasSize = HAS_SIZE.test(expression)
if (!hasSize) {
return
}
if (!expression.hasAssertThat()) return
val isLastExpression = expression.parent is PsiStatement
val value = expression.calculateConstantParameterValue(0) ?: return
val value = calculateConstantParameterValue(expression, 0) ?: return
if (value == 0) {
registerSimplifyMethod(holder, expression, "isEmpty()")
val isEmpty = (CallMatcher.anyOf(HAS_SIZE, HAS_SIZE_LESS_THAN_OR_EQUAL_TO_INT).test(expression) && (value == 0)) ||
(HAS_SIZE_LESS_THAN_INT.test(expression) && (value == 1))
val isNotEmpty = (HAS_SIZE_GREATER_THAN_INT.test(expression) && (value == 0)) ||
(HAS_SIZE_GREATER_THAN_OR_EQUAL_TO_INT.test(expression) && (value == 1))
if (isEmpty && isLastExpression) {
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
} else if (isNotEmpty) {
registerSimplifyMethod(holder, expression, MethodNames.IS_NOT_EMPTY)
}
}
}

View File

@ -0,0 +1,119 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
class AssertThatFileExpressionInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a file specific expression"
private val ARG_IS_ZERO_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == 0 }
private val ARG_IS_NOT_ZERO_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call ->
val constant =
call.firstArg.calculateConstantValue()
(constant != null) && (constant != 0)
}
private val MAPPINGS = listOf(
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "canRead").parameterCount(0),
"canRead", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "canWrite").parameterCount(0),
"canWrite", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "exists").parameterCount(0),
"exists", "doesNotExist", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "isAbsolute").parameterCount(0),
"isAbsolute", "isRelative", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "isDirectory").parameterCount(0),
"isDirectory", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "isFile").parameterCount(0),
"isFile", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "getName").parameterCount(0),
"hasName",
expectedMatcher = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING)
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "getParent", "getParentFile").parameterCount(0),
"hasNoParent", expectNullNonNull = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "getParent").parameterCount(0),
"hasParent",
expectedMatcher = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING)
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "getParentFile").parameterCount(0),
"hasParent",
expectedMatcher = IS_EQUAL_TO_OBJECT
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "list", "listFiles").parameterCount(0),
"isEmptyDirectory",
expectedMatcher = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ARRAY_ASSERT_CLASSNAME, MethodNames.IS_EMPTY)
.parameterCount(0)
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "list", "listFiles").parameterCount(0),
"isNotEmptyDirectory",
expectedMatcher = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ARRAY_ASSERT_CLASSNAME, MethodNames.IS_NOT_EMPTY)
.parameterCount(0)
)
)
private val MAPPINGS_SINCE_ASSERTJ_3_14_0 = listOf(
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "length").parameterCount(0),
MethodNames.IS_EMPTY, expectedMatcher = IS_ZERO_LONG, noExpectedExpression = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "length").parameterCount(0),
MethodNames.IS_EMPTY, expectedMatcher = IS_EQUAL_TO_LONG, noExpectedExpression = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "length").parameterCount(0),
MethodNames.IS_NOT_EMPTY, expectedMatcher = IS_NOT_ZERO_LONG, noExpectedExpression = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "length").parameterCount(0),
MethodNames.IS_NOT_EMPTY, expectedMatcher = IS_NOT_EQUAL_TO_LONG, noExpectedExpression = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_IO_FILE, "length").parameterCount(0),
MethodNames.HAS_SIZE, expectedMatcher = IS_EQUAL_TO_LONG,
additionalCondition = ARG_IS_NOT_ZERO_CONST
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
createInspectionsForMappings(statement, holder, MAPPINGS)
if (hasAssertJMethod(statement, AssertJClassNames.ABSTRACT_FILE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE)) {
createInspectionsForMappings(statement, holder, MAPPINGS_SINCE_ASSERTJ_3_14_0)
}
}
}
}
}

View File

@ -0,0 +1,116 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.*
class AssertThatGuavaOptionalInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting an Optional (Guava)"
private const val REPLACE_GUAVA_DESCRIPTION_TEMPLATE = "Replace %s() with Guava assertThat().%s()"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!checkPreconditions(staticMethodCall)) return
val actualExpression = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
if (GUAVA_OPTIONAL_GET.test(actualExpression)) {
if (actualExpression.type is PsiArrayType) return
val expectedCallExpression = staticMethodCall.gatherAssertionCalls().singleOrNull() ?: return
if (CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING).test(expectedCallExpression)) {
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS) { desc, method ->
QuickFixWithPostfixDelegate(
RemoveActualOutmostMethodCallQuickFix(desc, method),
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
)
}
}
} else if (GUAVA_OPTIONAL_IS_PRESENT.test(actualExpression)) {
val expectedPresence = outmostMethodCall.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_ABSENT)
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
QuickFixWithPostfixDelegate(
MoveOutMethodCallExpressionQuickFix(desc, method),
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
)
}
} else if (GUAVA_OPTIONAL_OR_NULL.test(actualExpression)) {
val expectedPresence = outmostMethodCall.getAllTheSameNullNotNullConstants() ?: return
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_ABSENT)
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
QuickFixWithPostfixDelegate(
MoveOutMethodCallExpressionQuickFix(desc, method, useNullNonNull = true),
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
)
}
}
}
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
val staticMethodCall = expression.findStaticMethodCall() ?: return
if (!checkPreconditions(staticMethodCall)) return
// We're not calling an assertThat() from Guava, but a core-AssertJ one!
// We need to replace that by the Guava one, if we want to apply a formally correct fix.
if (IS_EQUAL_TO_OBJECT.test(expression)) {
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
if (CallMatcher.anyOf(GUAVA_OPTIONAL_OF, GUAVA_OPTIONAL_FROM_NULLABLE).test(innerExpectedCall)) {
if (GUAVA_OPTIONAL_FROM_NULLABLE.test(innerExpectedCall)) {
innerExpectedCall.firstArg.calculateConstantValue() ?: return
}
registerRemoveExpectedOutmostMethod(holder, expression, expression, MethodNames.CONTAINS) { desc, method ->
QuickFixWithPostfixDelegate(
UnwrapExpectedStaticMethodCallQuickFix(desc, method),
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
)
}
} else if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_ABSENT)
}
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_PRESENT)
}
}
}
private fun checkPreconditions(staticMethodCall: PsiMethodCallExpression): Boolean {
if (CallMatcher.anyOf(ASSERT_THAT_ANY, GUAVA_ASSERT_THAT_ANY).test(staticMethodCall)) {
JavaPsiFacade.getInstance(staticMethodCall.project)
.findClass(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(staticMethodCall.project)) ?: return false
return true
}
return false
}
}
}
private fun registerSimplifyForGuavaMethod(holder: ProblemsHolder, expression: PsiMethodCallExpression, replacementMethod: String) {
val originalMethod = getOriginalMethodName(expression) ?: return
val description = REPLACE_GUAVA_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = SIMPLIFY_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod)
val quickFix = QuickFixWithPostfixDelegate(
ReplaceSimpleMethodCallQuickFix(description, replacementMethod),
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
)
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
holder.registerProblem(expression, textRange, message, quickFix)
}
}

View File

@ -0,0 +1,48 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutInstanceOfExpressionQuickFix
class AssertThatInstanceOfInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a class instance"
private const val REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE = "Remove instanceof in actual expression and use assertThat().%s() instead"
private const val MOVE_OUT_INSTANCEOF_MESSAGE = "instanceof expression could be moved out of assertThat()"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
if (staticMethodCall.firstArg is PsiInstanceOfExpression) {
val replacementMethod = expectedResult.map(MethodNames.IS_INSTANCE_OF, MethodNames.IS_NOT_INSTANCE_OF)
registerMoveOutInstanceOfMethod(holder, expectedCallExpression, replacementMethod, ::MoveOutInstanceOfExpressionQuickFix)
}
}
}
}
private fun registerMoveOutInstanceOfMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
val description = REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE.format(replacementMethod)
val quickfix = quickFixSupplier(description, replacementMethod)
holder.registerProblem(expression, MOVE_OUT_INSTANCEOF_MESSAGE, quickfix)
}
}

View File

@ -0,0 +1,55 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.TypeConversionUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.InvertUnaryExpressionQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.InvertUnaryStatementQuickFix
class AssertThatInvertedBooleanConditionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting an inverted boolean condition"
private const val INVERT_CONDITION_MESSAGE = "Condition inside assertThat() could be inverted"
private const val REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE = "Invert condition in %s() and use %s() instead"
private const val INVERT_OUTSIDE_CONDITION_MESSAGE = "Condition outside assertThat() could be inverted"
}
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 (!expression.hasAssertThat()) return
val staticMethodCall = expression.findStaticMethodCall() ?: return
if (expression.getExpectedBooleanResult() != null) {
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) return
val prefixExpression = staticMethodCall.firstArg as? PsiPrefixExpression ?: return
if (prefixExpression.operationTokenType == JavaTokenType.EXCL) {
val outmostMethodCall = expression.findOutmostMethodCall() ?: return
holder.registerProblem(outmostMethodCall, INVERT_CONDITION_MESSAGE, InvertUnaryStatementQuickFix())
}
} else {
if (!CallMatcher.anyOf(ASSERT_THAT_BOOLEAN, ASSERT_THAT_BOOLEAN_OBJ).test(staticMethodCall)) return
val isEqualTo = CallMatcher.anyOf(IS_EQUAL_TO_BOOLEAN, IS_EQUAL_TO_OBJECT).test(expression)
val isNotEqualTo = CallMatcher.anyOf(IS_NOT_EQUAL_TO_BOOLEAN, IS_NOT_EQUAL_TO_OBJECT).test(expression)
if (!(isEqualTo || isNotEqualTo)) return
val prefixExpression = expression.firstArg as? PsiPrefixExpression ?: return
val operand = PsiUtil.skipParenthesizedExprDown(prefixExpression.operand) ?: return
if (!TypeConversionUtil.isPrimitiveAndNotNull(operand.type)) return
val originalMethod = getOriginalMethodName(expression) ?: return
val replacementMethod = isEqualTo.map(MethodNames.IS_NOT_EQUAL_TO, MethodNames.IS_EQUAL_TO)
val description = REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val textRange = TextRange(staticMethodCall.textLength, expression.textLength)
holder.registerProblem(expression, textRange, INVERT_OUTSIDE_CONDITION_MESSAGE, InvertUnaryExpressionQuickFix(description, replacementMethod))
}
}
}
}
}

View File

@ -0,0 +1,81 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.TypeConversionUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
class AssertThatIsZeroOneInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a zero or one value"
}
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 (!expression.hasAssertThat()) return
val isEqualTo = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_SHORT, IS_EQUAL_TO_INT, IS_EQUAL_TO_LONG, IS_EQUAL_TO_FLOAT, IS_EQUAL_TO_DOUBLE).test(expression)
val isNotEqualTo =
CallMatcher.anyOf(IS_NOT_EQUAL_TO_OBJECT, IS_NOT_EQUAL_TO_SHORT, IS_NOT_EQUAL_TO_INT, IS_NOT_EQUAL_TO_LONG, IS_NOT_EQUAL_TO_FLOAT, IS_NOT_EQUAL_TO_DOUBLE)
.test(expression)
if (!(isEqualTo || isNotEqualTo)) return
val expectedExpression = expression.firstArg
if (!TypeConversionUtil.isNumericType(expectedExpression.type)) return
val expectedResult = expression.calculateConstantParameterValue(0) ?: return
var isZero = false
var isOne = false
when (expectedResult) {
is Short -> {
isZero = (expectedResult == 0.toShort())
isOne = (expectedResult == 1.toShort())
}
is Int -> {
isZero = (expectedResult == 0)
isOne = (expectedResult == 1)
}
is Long -> {
isZero = (expectedResult == 0L)
isOne = (expectedResult == 1L)
}
is Float -> {
isZero = (expectedResult == 0.0f)
isOne = (expectedResult == 1.0f)
}
is Double -> {
isZero = (expectedResult == 0.0)
isOne = (expectedResult == 1.0)
}
}
if (isZero || isOne) {
val numericBaseClass = listOf(
AssertJClassNames.ABSTRACT_SHORT_ASSERT_CLASSNAME,
AssertJClassNames.ABSTRACT_INTEGER_ASSERT_CLASSNAME,
AssertJClassNames.ABSTRACT_LONG_ASSERT_CLASSNAME,
AssertJClassNames.ABSTRACT_FLOAT_ASSERT_CLASSNAME,
AssertJClassNames.ABSTRACT_DOUBLE_ASSERT_CLASSNAME
).any { checkAssertedType(expression, it) }
if (!numericBaseClass) return
}
if (isZero) {
registerSimplifyMethod(holder, expression, isEqualTo.map(MethodNames.IS_ZERO, MethodNames.IS_NOT_ZERO))
} else if (isOne && isEqualTo) {
registerSimplifyMethod(holder, expression, MethodNames.IS_ONE)
}
}
}
}
}

View File

@ -0,0 +1,79 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.UnwrapExpectedStaticMethodCallQuickFix
class AssertThatJava8OptionalInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting an Optional (Java 8)"
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_ANY.test(staticMethodCall)) return
val actualExpression = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
if (OPTIONAL_GET.test(actualExpression)) {
val expectedCallExpression = staticMethodCall.gatherAssertionCalls().singleOrNull() ?: return
if (CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING).test(expectedCallExpression)) {
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS) { desc, method ->
RemoveActualOutmostMethodCallQuickFix(desc, method)
}
} else if (IS_SAME_AS_OBJECT.test(expectedCallExpression)) {
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS_SAME) { desc, method ->
RemoveActualOutmostMethodCallQuickFix(desc, method)
}
}
} else if (OPTIONAL_IS_PRESENT.test(actualExpression)) {
val expectedPresence = outmostMethodCall.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT)
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method)
}
} else if (OPTIONAL_OR_ELSE.test(actualExpression) && (actualExpression.firstArg.type == PsiType.NULL)) {
val expectedPresence = outmostMethodCall.getAllTheSameNullNotNullConstants() ?: return
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT)
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method, useNullNonNull = true, noExpectedExpression = true)
}
}
}
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
val staticMethodCall = expression.findStaticMethodCall() ?: return
if (!ASSERT_THAT_JAVA8_OPTIONAL.test(staticMethodCall)) return
if (IS_EQUAL_TO_OBJECT.test(expression)) {
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
if (CallMatcher.anyOf(OPTIONAL_OF, OPTIONAL_OF_NULLABLE).test(innerExpectedCall)) {
if (OPTIONAL_OF_NULLABLE.test(innerExpectedCall)) {
innerExpectedCall.firstArg.calculateConstantValue() ?: return
}
registerRemoveExpectedOutmostMethod(holder, expression, expression, MethodNames.CONTAINS, ::UnwrapExpectedStaticMethodCallQuickFix)
} else if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expression, MethodNames.IS_NOT_PRESENT)
}
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expression, MethodNames.IS_PRESENT)
}
}
}
}
}
}

View File

@ -0,0 +1,53 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.HasHashCodeQuickFix
class AssertThatObjectExpressionInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting equals(), toString(), or hashCode()"
private const val HASHCODE_MESSAGE_TEMPLATE = "Replacing hashCode() expressions by hasSameHashCode() would be more concise"
private val OBJECT_TO_STRING = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "toString").parameterCount(0)
private val OBJECT_HASHCODE = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "hashCode").parameterCount(0)
private val MAPPINGS = listOf(
MoveOutMapping(
OBJECT_EQUALS,
MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO, expectBoolean = true
),
MoveOutMapping(
OBJECT_TO_STRING,
MethodNames.HAS_TO_STRING, expectedMatcher = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING)
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
val assertThatArgument = staticMethodCall.getArgOrNull(0) as? PsiMethodCallExpression ?: return
if (OBJECT_HASHCODE.test(assertThatArgument)) {
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val isEqualTo = staticMethodCall.findFluentCallTo(IS_EQUAL_TO_INT) ?: return
val expectedExpression = isEqualTo.firstArg as? PsiMethodCallExpression ?: return
if (OBJECT_HASHCODE.test(expectedExpression)) {
holder.registerProblem(expectedCallExpression, HASHCODE_MESSAGE_TEMPLATE, HasHashCodeQuickFix())
}
} else {
createInspectionsForMappings(statement, holder, MAPPINGS)
}
}
}
}
}

View File

@ -1,10 +1,12 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiType
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.hasAssertThat
import de.platon42.intellij.plugins.cajon.map
class AssertThatObjectIsNullOrNotNullInspection : AbstractAssertJInspection() {
@ -18,16 +20,16 @@ class AssertThatObjectIsNullOrNotNullInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
val isNotEqualTo = IS_NOT_EQUAL_TO_OBJECT.test(expression)
val isEqualTo = IS_EQUAL_TO_OBJECT.test(expression)
if (!(isEqualTo || isNotEqualTo)) {
return
}
val isEqualTo = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING).test(expression)
val isLastExpression = expression.parent is PsiStatement
if (!((isEqualTo && isLastExpression) || isNotEqualTo)) return
if (expression.argumentList.expressions[0].type == PsiType.NULL) {
registerSimplifyMethod(holder, expression, if (isEqualTo) "isNull()" else "isNotNull()")
if (expression.firstArg.type == PsiType.NULL) {
registerSimplifyMethod(holder, expression, isEqualTo.map(MethodNames.IS_NULL, MethodNames.IS_NOT_NULL))
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiExpressionStatement
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.MethodNames
class AssertThatPathExpressionInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a path specific expression"
private const val JAVA_NIO_PATH = "java.nio.file.Path"
private val MAPPINGS = listOf(
MoveOutMapping(
CallMatcher.instanceCall(JAVA_NIO_PATH, "isAbsolute").parameterCount(0),
"isAbsolute", "isRelative", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(JAVA_NIO_PATH, MethodNames.STARTS_WITH).parameterTypes(JAVA_NIO_PATH),
"startsWithRaw", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(JAVA_NIO_PATH, MethodNames.ENDS_WITH).parameterTypes(JAVA_NIO_PATH),
"endsWithRaw", expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(JAVA_NIO_PATH, "getParent").parameterCount(0),
"hasParentRaw",
expectedMatcher = IS_EQUAL_TO_OBJECT
),
MoveOutMapping(
CallMatcher.instanceCall(JAVA_NIO_PATH, "getParent").parameterCount(0),
"hasNoParentRaw", expectNullNonNull = true
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
createInspectionsForMappings(statement, holder, MAPPINGS)
}
}
}
}

View File

@ -2,99 +2,147 @@ package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ITERABLE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_MAP_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceHasSizeMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSizeMethodCallQuickFix
class AssertThatSizeInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting the size of an collection or array"
private const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s would be more concise than %s"
private const val DISPLAY_NAME = "Asserting the size of an collection, map, array or string"
private const val REMOVE_SIZE_DESCRIPTION_TEMPLATE = "Remove size determination of expected expression and replace %s() with %s()"
private const val REMOVE_ALL_MESSAGE = "Try to operate on the iterable itself rather than its size"
private val BONUS_EXPRESSIONS_CALL_MATCHER_MAP = listOf(
IS_LESS_THAN_INT to MethodNames.HAS_SIZE_LESS_THAN,
IS_LESS_THAN_OR_EQUAL_TO_INT to MethodNames.HAS_SIZE_LESS_THAN_OR_EQUAL_TO,
IS_GREATER_THAN_INT to MethodNames.HAS_SIZE_GREATER_THAN,
IS_GREATER_THAN_OR_EQUAL_TO_INT to MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO
)
private fun isCharSequenceLength(expression: PsiExpression) = (expression is PsiMethodCallExpression) && CHAR_SEQUENCE_LENGTH.test(expression)
private fun isCollectionSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && COLLECTION_SIZE.test(expression)
private fun isMapSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && MAP_SIZE.test(expression)
private fun isArrayLength(expression: PsiExpression): Boolean {
val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false
return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType)
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
}
fun getMatch(expression: PsiMethodCallExpression, isForArrayOrCollection: Boolean, isForMap: Boolean, isForString: Boolean): Match? {
val isLastExpression = expression.parent is PsiStatement
val constValue = expression.calculateConstantParameterValue(0)
if (IS_EQUAL_TO_INT.test(expression)) {
return if ((constValue == 0) && isLastExpression) {
Match(expression, MethodNames.IS_EMPTY, noExpectedExpression = true)
} else {
val equalToExpression = expression.firstArg
val equalsArrayOrCollectionSize = isArrayLength(equalToExpression) ||
isCollectionSize(equalToExpression)
if ((isForArrayOrCollection && equalsArrayOrCollectionSize)
|| (isForMap && (equalsArrayOrCollectionSize || isMapSize(equalToExpression)))
|| (isForString && (equalsArrayOrCollectionSize || isCharSequenceLength(equalToExpression)))
) {
Match(expression, MethodNames.HAS_SAME_SIZE_AS, expectedIsCollection = true)
} else {
Match(expression, MethodNames.HAS_SIZE)
}
}
} else {
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expression) && (constValue == 1))
|| IS_ZERO_INT.test(expression))
val isTestForNotEmpty = ((IS_GREATER_THAN_INT.test(expression) && (constValue == 0))
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expression) && (constValue == 1))
|| IS_NOT_ZERO_INT.test(expression))
if ((isTestForEmpty && isLastExpression) || isTestForNotEmpty) {
val replacementMethod = isTestForEmpty.map(MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY)
return Match(expression, replacementMethod, noExpectedExpression = true)
} else if (hasAssertJMethod(expression, ABSTRACT_ITERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE_LESS_THAN)) {
// new stuff in AssertJ 3.12.0
val replacementMethod = BONUS_EXPRESSIONS_CALL_MATCHER_MAP.find { it.first.test(expression) }?.second ?: return null
return Match(expression, replacementMethod)
}
return null
}
}
}
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 (!ASSERT_THAT_INT.test(expression)) {
return
}
val actualExpression = expression.argumentList.expressions[0] ?: return
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_INT.test(staticMethodCall)) return
if (isArrayLength(actualExpression) || isCollectionSize(actualExpression)) {
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val constValue = calculateConstantParameterValue(expectedCallExpression, 0)
if (IS_EQUAL_TO_INT.test(expectedCallExpression)) {
if (constValue == 0) {
registerSizeMethod(holder, expression, expectedCallExpression, "isEmpty()", noExpectedExpression = true)
return
val actualExpression = staticMethodCall.firstArg
val isForArrayOrCollection = isArrayLength(actualExpression) || isCollectionSize(actualExpression)
val isForMap = isMapSize(actualExpression)
val isForString = isCharSequenceLength(actualExpression)
if (!(isForArrayOrCollection || isForMap || isForString)) return
val matches = staticMethodCall.collectMethodCallsUpToStatement()
.mapNotNull { getMatch(it, isForArrayOrCollection, isForMap, isForString) }
.toList()
if (matches.isNotEmpty()) {
if (matches.size == 1) {
val match = matches.single()
val expression = match.methodCall
registerReplaceMethod(
holder,
expression,
expression,
match.replacementMethod
)
{ desc, method ->
ReplaceSizeMethodCallQuickFix(desc, method, noExpectedExpression = match.noExpectedExpression, expectedIsCollection = match.expectedIsCollection)
}
val equalToExpression = expectedCallExpression.argumentList.expressions[0]
if (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) {
registerSizeMethod(holder, expression, expectedCallExpression, "hasSameSizeAs()", expectedIsCollection = true)
return
}
registerSizeMethod(holder, expression, expectedCallExpression, "hasSize()")
} else {
if ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_ZERO.test(expectedCallExpression)
) {
registerSizeMethod(holder, expression, expectedCallExpression, "isEmpty()", noExpectedExpression = true)
return
}
if ((IS_GREATER_THAN_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_NOT_ZERO.test(expectedCallExpression)
) {
registerSizeMethod(holder, expression, expectedCallExpression, "isNotEmpty()", noExpectedExpression = true)
return
}
// new stuff in AssertJ 13.2.0
if (hasAssertJMethod(expression, "AbstractIterableAssert.hasSizeLessThan")) {
val matchedMethod = listOf(
Pair(IS_GREATER_THAN_INT, "hasSizeGreaterThan()"),
Pair(IS_GREATER_THAN_OR_EQUAL_TO_INT, "hasSizeGreaterThanOrEqualTo()"),
Pair(IS_LESS_THAN_OR_EQUAL_TO_INT, "hasSizeLessThanOrEqualTo()"),
Pair(IS_LESS_THAN_INT, "hasSizeLessThan()")
).find { it.first.test(expectedCallExpression) }?.second
if (matchedMethod != null) {
registerSizeMethod(holder, expression, expectedCallExpression, matchedMethod)
return
}
}
// I could try to create a quickfix for this, too, but it's probably not worth the effort
holder.registerProblem(statement, REMOVE_ALL_MESSAGE)
}
}
}
private fun isCollectionSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && COLLECTION_SIZE.test(expression)
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
if (!HAS_SIZE.test(expression)) return
val actualExpression = expression.firstArg
private fun isArrayLength(expression: PsiExpression): Boolean {
val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false
return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType)
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
}
val isForArrayOrCollection = isArrayLength(actualExpression) || isCollectionSize(actualExpression)
val isForMap = isMapSize(actualExpression)
val isForString = isCharSequenceLength(actualExpression)
if (!(isForArrayOrCollection
|| (isForMap && checkAssertedType(expression, ABSTRACT_MAP_ASSERT_CLASSNAME))
|| (isForString && checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)))
) return
private fun registerSizeMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression,
replacementMethod: String,
noExpectedExpression: Boolean = false,
expectedIsCollection: Boolean = false
) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
holder.registerProblem(
registerConciseMethod(
REMOVE_SIZE_DESCRIPTION_TEMPLATE,
holder,
expression,
message,
ReplaceSizeMethodCallQuickFix(description, replacementMethod, noExpectedExpression, expectedIsCollection)
expression,
MethodNames.HAS_SAME_SIZE_AS,
::ReplaceHasSizeMethodCallQuickFix
)
}
}
}
class Match(
val methodCall: PsiMethodCallExpression,
val replacementMethod: String,
val noExpectedExpression: Boolean = false,
val expectedIsCollection: Boolean = false
)
}

View File

@ -0,0 +1,152 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantValue
import de.platon42.intellij.plugins.cajon.firstArg
class AssertThatStringExpressionInspection : AbstractMoveOutInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a string specific expression"
private val ARG_IS_ZERO_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == 0 }
private val ARG_IS_MINUS_ONE_CONST: (PsiExpressionStatement, PsiMethodCallExpression) -> Boolean = { _, call -> call.firstArg.calculateConstantValue() == -1 }
private val STRING_COMPARE_TO_IGNORE_CASE =
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "compareToIgnoreCase").parameterTypes(CommonClassNames.JAVA_LANG_STRING)
private val STRING_INDEX_OF = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "indexOf").parameterTypes(CommonClassNames.JAVA_LANG_STRING)
private val STRING_TRIM = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "trim").parameterCount(0)
private val MAPPINGS = listOf(
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, MethodNames.IS_EMPTY).parameterCount(0),
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY, expectBoolean = true
),
MoveOutMapping(
CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, MethodNames.EQUALS).parameterCount(1),
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contentEquals").parameterCount(1)
),
MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equalsIgnoreCase").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.IS_EQUAL_TO_IC, MethodNames.IS_NOT_EQUAL_TO_IC, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, MethodNames.CONTAINS).parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, MethodNames.STARTS_WITH).parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.STARTS_WITH, MethodNames.DOES_NOT_START_WITH, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, MethodNames.ENDS_WITH).parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.ENDS_WITH, MethodNames.DOES_NOT_END_WITH, expectBoolean = true
),
MoveOutMapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "matches").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
"matches", "doesNotMatch", expectBoolean = true
),
MoveOutMapping(
STRING_COMPARE_TO_IGNORE_CASE,
MethodNames.IS_EQUAL_TO_IC, expectedMatcher = IS_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_COMPARE_TO_IGNORE_CASE,
MethodNames.IS_EQUAL_TO_IC, expectedMatcher = IS_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_COMPARE_TO_IGNORE_CASE,
MethodNames.IS_NOT_EQUAL_TO_IC, expectedMatcher = IS_NOT_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_COMPARE_TO_IGNORE_CASE,
MethodNames.IS_NOT_EQUAL_TO_IC, expectedMatcher = IS_NOT_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.STARTS_WITH, expectedMatcher = IS_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.STARTS_WITH, expectedMatcher = IS_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_START_WITH, expectedMatcher = IS_NOT_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_START_WITH, expectedMatcher = IS_NOT_ZERO_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.CONTAINS, expectedMatcher = IS_NOT_NEGATIVE_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.CONTAINS, expectedMatcher = IS_NOT_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.CONTAINS, expectedMatcher = IS_GREATER_THAN_OR_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.CONTAINS, expectedMatcher = IS_GREATER_THAN_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_CONTAIN, expectedMatcher = IS_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_CONTAIN, expectedMatcher = IS_NEGATIVE_INT, replaceFromOriginalMethod = true
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_CONTAIN, expectedMatcher = IS_LESS_THAN_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_ZERO_CONST
),
MoveOutMapping(
STRING_INDEX_OF,
MethodNames.DOES_NOT_CONTAIN, expectedMatcher = IS_LESS_THAN_OR_EQUAL_TO_INT, replaceFromOriginalMethod = true,
additionalCondition = ARG_IS_MINUS_ONE_CONST
),
MoveOutMapping(
STRING_TRIM,
"isNotBlank", expectedMatcher = IS_NOT_EMPTY
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
createInspectionsForMappings(statement, holder, MAPPINGS)
}
}
}
}

View File

@ -4,6 +4,12 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
import de.platon42.intellij.plugins.cajon.hasAssertThat
class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
@ -17,19 +23,17 @@ class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
val isEqual = IS_EQUAL_TO_OBJECT.test(expression)
if (!expression.hasAssertThat()) return
val isEqual = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING).test(expression)
val hasSize = HAS_SIZE.test(expression)
if (!(isEqual || hasSize)) {
return
}
val isLastExpression = expression.parent is PsiStatement
if (!((isEqual || hasSize) && isLastExpression)) return
if (!checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)) {
return
}
if (!checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)) return
val value = calculateConstantParameterValue(expression, 0) ?: return
val value = expression.calculateConstantParameterValue(0) ?: return
if ((isEqual && (value == "")) || (hasSize && (value == 0))) {
registerSimplifyMethod(holder, expression, "isEmpty()")
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
}
}
}

View File

@ -0,0 +1,144 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import de.platon42.intellij.plugins.cajon.hasAssertThat
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceIfByAssumeThatQuickFix
class AssumeThatInsteadOfReturnInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Replace conditional test exits by assumeThat() statements with same actual expression"
private const val REPLACE_RETURN_BY_ASSUME_THAT_DESCRIPTION = "Conditional return should probably be an assumeThat() statement instead"
private const val MAX_RECURSION_DEPTH = 5
private const val MAX_STATEMENTS_COUNT = 50
private val TEST_ANNOTATIONS = setOf(
"org.junit.Test",
"org.junit.jupiter.api.Test",
"org.junit.jupiter.api.TestTemplate",
"org.junit.jupiter.api.params.ParameterizedTest"
)
private fun hasEmptyReturn(statement: PsiStatement): Boolean {
return when (statement) {
is PsiBlockStatement -> {
val psiReturnStatement = (statement.firstChild as? PsiCodeBlock)?.statements?.singleOrNull() as? PsiReturnStatement
(psiReturnStatement != null) && (psiReturnStatement.returnValue == null)
}
is PsiReturnStatement -> statement.returnValue == null
else -> false
}
}
private fun registerProblem(holder: ProblemsHolder, isOnTheFly: Boolean, statement: PsiStatement, removeElse: Boolean) {
val problemDescriptor = holder.manager.createProblemDescriptor(
statement,
statement,
REPLACE_RETURN_BY_ASSUME_THAT_DESCRIPTION,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly,
ReplaceIfByAssumeThatQuickFix(removeElse)
)
holder.registerProblem(problemDescriptor)
}
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitMethod(method: PsiMethod) {
super.visitMethod(method)
// Note: replace with if(TEST_ANNOTATIONS.none(method::hasAnnotation)) for IDEA >= 2018.2
val annotations = method.annotations.mapNotNull { it.qualifiedName }
if (annotations.none(TEST_ANNOTATIONS::contains)) return
val containingClass = method.containingClass ?: return
val visitor: PsiElementVisitor = TestMethodVisitor(holder, isOnTheFly, containingClass)
method.accept(visitor)
}
}
}
class TestMethodVisitor(
private val holder: ProblemsHolder,
private val isOnTheFly: Boolean,
private val containingClass: PsiClass
) : JavaRecursiveElementWalkingVisitor() {
private var contSearch = true
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
if (contSearch) {
val methodCallExpression = statement.expression as? PsiMethodCallExpression
if (methodCallExpression != null) {
if (methodCallExpression.hasAssertThat()) {
contSearch = false
} else {
val method = methodCallExpression.resolveMethod()
if (method?.containingClass == containingClass) {
val recursionVisitor = CheckForAssertThatCallsVisitor(containingClass, 1)
method.accept(recursionVisitor)
if (recursionVisitor.aborted || recursionVisitor.foundAssertThat) {
contSearch = false
}
}
}
}
}
if (contSearch) {
super.visitExpressionStatement(statement)
}
}
override fun visitIfStatement(statement: PsiIfStatement) {
if (contSearch) {
checkBranch(statement, statement.thenBranch, false)
checkBranch(statement, statement.elseBranch, true)
}
}
private fun checkBranch(statement: PsiIfStatement, branch: PsiStatement?, removeElse: Boolean) {
if (branch != null) {
if (hasEmptyReturn(branch)) {
registerProblem(holder, isOnTheFly, statement, removeElse)
} else {
branch.accept(TestMethodVisitor(holder, isOnTheFly, containingClass))
}
}
}
}
class CheckForAssertThatCallsVisitor(private val containingClass: PsiClass, private var depth: Int) : JavaRecursiveElementWalkingVisitor() {
var foundAssertThat = false
private var statementCount = 0
var aborted = false
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
if (foundAssertThat || aborted) return
if (++statementCount > MAX_STATEMENTS_COUNT) {
aborted = true
return
}
super.visitExpressionStatement(statement)
val methodCallExpression = statement.expression as? PsiMethodCallExpression
if (methodCallExpression != null) {
foundAssertThat = methodCallExpression.hasAssertThat()
val method = methodCallExpression.resolveMethod()
if (method?.containingClass == containingClass) {
if (depth < MAX_RECURSION_DEPTH) {
val recursionVisitor = CheckForAssertThatCallsVisitor(containingClass, depth + 1)
method.accept(recursionVisitor)
foundAssertThat = recursionVisitor.foundAssertThat
statementCount += recursionVisitor.statementCount
aborted = recursionVisitor.aborted
} else {
aborted = true
}
}
}
}
}
}

View File

@ -0,0 +1,177 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.siyeh.ig.callMatcher.CallMatcher
import com.siyeh.ig.psiutils.EquivalenceChecker
import de.platon42.intellij.plugins.cajon.*
class BogusAssertionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Bogus assertion due to same actual and expected expressions"
private const val ACTUAL_IS_EQUAL_TO_EXPECTED_MESSAGE = "Actual expression in assertThat() is the same as expected"
private const val WEAK_ACTUAL_IS_EQUAL_TO_EXPECTED_MESSAGE = "Same actual and expected expression, but may be testing equals() or hashCode()"
private val SAME_OBJECT =
CallMatcher.instanceCall(
AssertJClassNames.ASSERT_INTERFACE,
MethodNames.IS_EQUAL_TO,
MethodNames.IS_SAME_AS,
"hasSameClassAs",
"hasSameHashCodeAs"
).parameterCount(1)
private val ARRAY_METHODS = arrayOf(
MethodNames.HAS_SAME_SIZE_AS,
MethodNames.CONTAINS,
"containsAnyOf",
MethodNames.CONTAINS_EXACTLY,
"containsExactlyInAnyOrder",
"containsOnly",
"containsSequence",
"containsSubsequence",
MethodNames.STARTS_WITH,
MethodNames.ENDS_WITH
)
private val SAME_BOOLEAN_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_BOOLEAN_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_BYTE_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_BYTE_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_SHORT_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_SHORT_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_INT_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_INT_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_LONG_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_LONG_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_FLOAT_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_FLOAT_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_DOUBLE_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_DOUBLE_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_CHAR_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_CHAR_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val SAME_OBJECT_ARRAY_CONTENTS =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ARRAY_ASSERT_CLASSNAME, *ARRAY_METHODS).parameterCount(1)
private val HASHCODE_OR_IS_EQUAL_TO =
CallMatcher.instanceCall(
AssertJClassNames.ASSERT_INTERFACE,
MethodNames.IS_EQUAL_TO, "hasSameHashCodeAs"
).parameterCount(1)
private val SAME_ENUMERABLE_CONTENTS =
CallMatcher.instanceCall(
AssertJClassNames.ENUMERABLE_ASSERT_INTERFACE,
MethodNames.HAS_SAME_SIZE_AS
).parameterCount(1)
private val SAME_ITERABLE_CONTENTS =
CallMatcher.instanceCall(
AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME,
"hasSameElementsAs",
MethodNames.CONTAINS_ALL,
"containsAnyElementsOf",
"containsOnlyElementsOf",
"containsExactlyElementsOf",
"containsSequence",
"containsSubsequence"
).parameterCount(1)
private val SAME_MAP_CONTENTS =
CallMatcher.instanceCall(
AssertJClassNames.ABSTRACT_MAP_ASSERT_CLASSNAME,
"containsAllEntriesOf",
"containsExactlyEntriesOf",
"containsExactlyInAnyOrderEntriesOf",
MethodNames.HAS_SAME_SIZE_AS
).parameterCount(1)
private val SAME_CHAR_SEQUENCE_CONTENTS =
CallMatcher.instanceCall(
AssertJClassNames.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME,
MethodNames.IS_EQUAL_TO,
MethodNames.IS_EQUAL_TO_IC,
MethodNames.STARTS_WITH,
MethodNames.ENDS_WITH,
"containsSequence",
"containsSubsequence"
).parameterCount(1)
private val SAME_ACTUAL_AND_EXPECTED_MATCHERS = CallMatcher.anyOf(
SAME_OBJECT,
SAME_ENUMERABLE_CONTENTS,
SAME_ITERABLE_CONTENTS,
SAME_MAP_CONTENTS,
SAME_CHAR_SEQUENCE_CONTENTS,
SAME_BOOLEAN_ARRAY_CONTENTS,
SAME_BYTE_ARRAY_CONTENTS,
SAME_SHORT_ARRAY_CONTENTS,
SAME_INT_ARRAY_CONTENTS,
SAME_LONG_ARRAY_CONTENTS,
SAME_FLOAT_ARRAY_CONTENTS,
SAME_DOUBLE_ARRAY_CONTENTS,
SAME_CHAR_ARRAY_CONTENTS,
SAME_OBJECT_ARRAY_CONTENTS
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) } ?: return
val actualExpression = assertThatCall.firstArg
val allCalls = assertThatCall.collectMethodCallsUpToStatement().toList()
// Note: replace with TrackingEquivalenceChecker() for IDEA >= 2019.1
val equivalenceChecker = EquivalenceChecker.getCanonicalPsiEquivalence()!!
val isSameExpression = allCalls
.filter { it.argumentList.expressions.size == 1 }
.filter(SAME_ACTUAL_AND_EXPECTED_MATCHERS::test)
.any { equivalenceChecker.expressionsAreEquivalent(actualExpression, it.firstArg) }
if (isSameExpression) {
if (!hasExpressionWithSideEffects(actualExpression)) {
if (allCalls.any(HASHCODE_OR_IS_EQUAL_TO::test)) {
val method = PsiTreeUtil.getParentOfType(statement, PsiMethod::class.java, true)
val methodName = method?.name
if ((methodName != null)
&& ((methodName.contains("equal", ignoreCase = true) || methodName.contains("hashcode", ignoreCase = true)))
) {
if (isOnTheFly) {
holder.registerProblem(statement, WEAK_ACTUAL_IS_EQUAL_TO_EXPECTED_MESSAGE, ProblemHighlightType.INFORMATION)
}
return
}
}
holder.registerProblem(statement, ACTUAL_IS_EQUAL_TO_EXPECTED_MESSAGE)
}
}
}
private fun hasExpressionWithSideEffects(actualExpression: PsiExpression): Boolean {
var result = false
PsiTreeUtil.processElements(actualExpression) { element ->
val matched = when (element) {
is PsiUnaryExpression -> (element.operationTokenType == JavaTokenType.PLUSPLUS)
|| (element.operationTokenType == JavaTokenType.MINUSMINUS)
is PsiMethodCallExpression -> true
else -> false
}
if (matched) {
result = true
false
} else {
true
}
}
return result
}
}
}
}

View File

@ -0,0 +1,129 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.DeleteMethodCallQuickFix
class ImplicitAssertionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting implicitly covered conditions"
private const val DELETE_IMPLICIT_DESCRIPTION_TEMPLATE = "Delete implicit %s() covered by %s()"
private const val SURPLUS_ASSERTION_MESSAGE = "Implicit %s() assertion is covered by %s()"
private val IS_PRESENT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME, MethodNames.IS_PRESENT, MethodNames.IS_NOT_EMPTY)
.parameterCount(0)!!
private val IS_NOT_PRESENT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME, MethodNames.IS_NOT_PRESENT, MethodNames.IS_EMPTY)
.parameterCount(0)!!
private val OPTIONAL_CONTAINS =
CallMatcher.instanceCall(
AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME,
MethodNames.CONTAINS, MethodNames.CONTAINS_SAME,
"hasValue", "hasValueSatisfying", "containsInstanceOf"
).parameterCount(1)!!
private val OBJECT_ENUMERABLE_ANY_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
MethodNames.CONTAINS, "containsOnly", "containsOnlyNulls", MethodNames.CONTAINS_ONLY_ONCE,
MethodNames.CONTAINS_EXACTLY, "containsExactlyInAnyOrder", "containsExactlyInAnyOrderElementsOf",
MethodNames.CONTAINS_ALL, "containsAnyOf",
"containsAnyElementsOf", "containsExactlyElementsOf", "containsOnlyElementsOf",
"isSubsetOf", "containsSequence", "containsSubsequence",
"doesNotContainSequence", "doesNotContainSubsequence", MethodNames.DOES_NOT_CONTAIN,
"doesNotContainAnyElementsOf", "doesNotHaveDuplicates",
MethodNames.STARTS_WITH, MethodNames.ENDS_WITH, "containsNull", "doesNotContainNull",
"are", "areNot", "have", "doNotHave", "areAtLeastOne", "areAtLeast", "areAtMost", "areExactly",
"haveAtLeastOne", "haveAtLeast", "haveAtMost", "haveExactly",
"hasAtLeastOneElementOfType", "hasOnlyElementsOfType", "hasOnlyElementsOfTypes",
"doesNotHaveAnyElementsOfTypes",
"has", "doesNotHave",
"singleElement", "hasOnlyOneElementSatisfying", "hasSameElementsAs",
"allMatch", "allSatisfy", "anyMatch", "anySatisfy", "noneMatch", "noneSatisfy"
)!!
private val OBJECT_ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
"containsOnlyNulls",
MethodNames.STARTS_WITH, MethodNames.ENDS_WITH, "containsNull",
"areAtLeastOne",
"haveAtLeastOne",
"hasAtLeastOneElementOfType",
"anyMatch", "anySatisfy"
)!!
private val ENUMERABLE_NON_NULL_ASSERTIONS = CallMatcher.instanceCall(
AssertJClassNames.ENUMERABLE_ASSERT_INTERFACE,
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY,
MethodNames.HAS_SIZE, MethodNames.HAS_SIZE_GREATER_THAN, MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO,
MethodNames.HAS_SIZE_LESS_THAN, MethodNames.HAS_SIZE_LESS_THAN_OR_EQUAL_TO,
"hasSizeBetween", MethodNames.HAS_SAME_SIZE_AS
)!!
private val ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
AssertJClassNames.ENUMERABLE_ASSERT_INTERFACE,
MethodNames.HAS_SIZE, MethodNames.HAS_SIZE_GREATER_THAN,
MethodNames.HAS_SAME_SIZE_AS
)!!
private val NON_NULL_CORE_ASSERTIONS = CallMatcher.instanceCall(
AssertJClassNames.ASSERT_INTERFACE,
MethodNames.IS_INSTANCE_OF, "isInstanceOfSatisfying", "isInstanceOfAny", "isExactlyInstanceOf", "isOfAnyClassIn",
MethodNames.IS_NOT_INSTANCE_OF, "isNotInstanceOfAny", "isNotExactlyInstanceOf", "isNotOfAnyClassIn",
"hasSameClassAs", "doesNotHaveSameClassAs",
MethodNames.HAS_TO_STRING, "hasSameHashCodeAs"
)!!
private val GUAVA_IS_PRESENT = CallMatcher.instanceCall(AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME, MethodNames.IS_PRESENT)
.parameterCount(0)!!
private val GUAVA_IS_ABSENT = CallMatcher.instanceCall(AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME, MethodNames.IS_ABSENT)
.parameterCount(0)!!
private val GUAVA_OPTIONAL_CONTAINS = CallMatcher.instanceCall(
AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME,
MethodNames.CONTAINS, "extractingValue", "extractingCharSequence"
)!!
private val MAPPINGS = listOf(
IS_NOT_NULL to CallMatcher.anyOf(
NON_NULL_CORE_ASSERTIONS,
ENUMERABLE_NON_NULL_ASSERTIONS,
OBJECT_ENUMERABLE_ANY_CONTENT_ASSERTIONS,
IS_PRESENT, IS_NOT_PRESENT, OPTIONAL_CONTAINS,
GUAVA_IS_PRESENT, GUAVA_IS_ABSENT, GUAVA_OPTIONAL_CONTAINS
)!!,
IS_NOT_EMPTY to CallMatcher.anyOf(
ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS,
OBJECT_ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS
)!!,
IS_PRESENT to OPTIONAL_CONTAINS,
GUAVA_IS_PRESENT to GUAVA_OPTIONAL_CONTAINS
)
}
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 (!expression.hasAssertThat()) return
val mapping = MAPPINGS.firstOrNull { it.first.test(expression) } ?: return
val followupExpression = expression.findFluentCallTo(mapping.second) ?: return
val redundantName = getOriginalMethodName(expression) ?: return
val followupName = getOriginalMethodName(followupExpression) ?: return
val description = DELETE_IMPLICIT_DESCRIPTION_TEMPLATE.format(redundantName, followupName)
val quickFix = DeleteMethodCallQuickFix(description)
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
holder.registerProblem(expression, textRange, SURPLUS_ASSERTION_MESSAGE.format(redundantName, followupName), quickFix)
}
}
}
}

View File

@ -1,110 +1,143 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.CommonClassNames
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.siyeh.ig.callMatcher.CallMatcher
import com.siyeh.ig.callMatcher.CallMatcher.anyOf
import com.siyeh.ig.callMatcher.CallMatcher.staticCall
import de.platon42.intellij.plugins.cajon.AssertJClassNames
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitAssertMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitAssumeMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitDeltaAssertMethodCallQuickFix
class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
companion object {
private const val DISPLAY_NAME = "Convert JUnit assertions to AssertJ"
private const val DISPLAY_NAME = "Convert JUnit assertions/assumptions to AssertJ"
private const val CONVERT_MESSAGE_TEMPLATE = "%s can be converted to AssertJ style"
private const val CONVERT_DESCRIPTION_TEMPLATE = "Convert %s() to %s().%s()"
private val MAPPINGS = listOf(
private val ASSERT_MAPPINGS = listOf(
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_TRUE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_TRUE_METHOD).parameterTypes("boolean")
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_TRUE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_TRUE_METHOD).parameterTypes("boolean")
),
"isTrue()", false
MethodNames.IS_TRUE, hasExpected = false
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_FALSE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_FALSE_METHOD).parameterTypes("boolean")
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_FALSE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_FALSE_METHOD).parameterTypes("boolean")
),
"isFalse()", false
MethodNames.IS_FALSE, hasExpected = false
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, CommonClassNames.JAVA_LANG_OBJECT),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, CommonClassNames.JAVA_LANG_OBJECT),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)
),
"isNull()", false
MethodNames.IS_NULL, hasExpected = false
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, CommonClassNames.JAVA_LANG_OBJECT),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, CommonClassNames.JAVA_LANG_OBJECT),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_NULL_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)
),
"isNotNull()", false
MethodNames.IS_NOT_NULL, hasExpected = false
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes("double", "double", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float", "float", "float"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes("float", "float", "float")
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes("double", "double", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float", "float", "float"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes("float", "float", "float")
),
"isCloseTo()", hasDelta = true
MethodNames.IS_CLOSE_TO, hasDelta = true
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterCount(3),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterCount(2)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterCount(3),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterCount(2)
),
"isEqualTo()"
MethodNames.IS_EQUAL_TO
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes("double", "double", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float", "float", "float"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes("float", "float", "float")
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes("double", "double", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float", "float", "float"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes("float", "float", "float")
),
"isNotCloseTo()", hasDelta = true
MethodNames.IS_NOT_CLOSE_TO, hasDelta = true
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterCount(3),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterCount(2)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterCount(3),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterCount(2)
),
"isNotEqualTo()"
MethodNames.IS_NOT_EQUAL_TO
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_SAME_METHOD).parameterCount(3),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_SAME_METHOD).parameterCount(2)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_SAME_METHOD).parameterCount(3),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_SAME_METHOD).parameterCount(2)
),
"isSameAs()"
MethodNames.IS_SAME_AS
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_SAME_METHOD).parameterCount(3),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_SAME_METHOD).parameterCount(2)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_SAME_METHOD).parameterCount(3),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_SAME_METHOD).parameterCount(2)
),
"isNotSameAs()"
MethodNames.IS_NOT_SAME_AS
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double[]", "double[]", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes("double[]", "double[]", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float[]", "float[]", "float"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes("float[]", "float[]", "float")
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double[]", "double[]", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes("double[]", "double[]", "double"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "float[]", "float[]", "float"),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes("float[]", "float[]", "float")
),
"containsExactly()", hasDelta = true
MethodNames.CONTAINS_EXACTLY, hasDelta = true
),
Mapping(
anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterCount(2),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterCount(3)
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterCount(2),
staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterCount(3)
),
"containsExactly()"
MethodNames.CONTAINS_EXACTLY
)
)
private val ASSUME_MAPPINGS = listOf(
Mapping(
anyOf(
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_TRUE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_TRUE_METHOD).parameterTypes("boolean")
),
MethodNames.IS_TRUE, hasExpected = false
),
Mapping(
anyOf(
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_FALSE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"),
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_FALSE_METHOD).parameterTypes("boolean")
),
MethodNames.IS_FALSE, hasExpected = false
),
Mapping(
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_NOT_NULL_METHOD).parameterCount(1),
MethodNames.IS_NOT_NULL, hasExpected = false, singleArgument = true
),
Mapping(
anyOf(
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_NO_EXCEPTION).parameterTypes(CommonClassNames.JAVA_LANG_STRING, CommonClassNames.JAVA_LANG_THROWABLE),
staticCall(JUNIT_ASSUME_CLASSNAME, ASSUME_NO_EXCEPTION).parameterTypes(CommonClassNames.JAVA_LANG_THROWABLE)
),
"doesNotThrowAnyException", hasExpected = false
)
)
}
@ -115,59 +148,53 @@ class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
val isJUnitAssertCall = expression.resolveMethod()?.containingClass?.qualifiedName == JUNIT_ASSERT_CLASSNAME
if (!isJUnitAssertCall) {
return // early exit
}
for (mapping in MAPPINGS) {
if (mapping.callMatcher.test(expression)) {
when (expression.resolveMethod()?.containingClass?.qualifiedName) {
JUNIT_ASSERT_CLASSNAME -> {
JavaPsiFacade.getInstance(expression.project)
.findClass(AssertJClassNames.ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return
val mapping = ASSERT_MAPPINGS.firstOrNull { it.callMatcher.test(expression) } ?: return
if (mapping.hasDelta) {
registerDeltaReplacementMethod(holder, expression, mapping.replacement)
registerConvertMethod(holder, expression, mapping.replacement, MethodNames.ASSERT_THAT, ::ReplaceJUnitDeltaAssertMethodCallQuickFix)
} else {
registerSimpleReplacementMethod(holder, expression, mapping.hasExpected, mapping.replacement)
registerConvertMethod(holder, expression, mapping.replacement, MethodNames.ASSERT_THAT) { desc, method ->
ReplaceJUnitAssertMethodCallQuickFix(desc, method, !mapping.hasExpected)
}
}
}
JUNIT_ASSUME_CLASSNAME -> {
JavaPsiFacade.getInstance(expression.project)
.findClass(AssertJClassNames.ASSUMPTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return
val mapping = ASSUME_MAPPINGS.firstOrNull { it.callMatcher.test(expression) } ?: return
if (!mapping.singleArgument || expression.argumentList.expressions.size == 1) {
registerConvertMethod(holder, expression, mapping.replacement, MethodNames.ASSUME_THAT) { desc, method ->
ReplaceJUnitAssumeMethodCallQuickFix(desc, method)
}
}
return
}
}
}
}
}
private fun registerSimpleReplacementMethod(
private fun registerConvertMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
hasExpected: Boolean,
replacementMethod: String
replacementMethod: String,
staticMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) {
val originalMethod = getOriginalMethodName(expression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val description = CONVERT_DESCRIPTION_TEMPLATE.format(originalMethod, staticMethod, replacementMethod)
val message = CONVERT_MESSAGE_TEMPLATE.format(originalMethod)
holder.registerProblem(
expression,
message,
ReplaceJUnitAssertMethodCallQuickFix(description, hasExpected, replacementMethod)
)
}
private fun registerDeltaReplacementMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String
) {
val originalMethod = getOriginalMethodName(expression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = CONVERT_MESSAGE_TEMPLATE.format(originalMethod)
holder.registerProblem(
expression,
message,
ReplaceJUnitDeltaAssertMethodCallQuickFix(description, replacementMethod)
)
val quickfix = quickFixSupplier(description, replacementMethod)
holder.registerProblem(expression, message, quickfix)
}
private class Mapping(
val callMatcher: CallMatcher,
val replacement: String,
val hasExpected: Boolean = true,
val hasDelta: Boolean = false
val hasDelta: Boolean = false,
val singleArgument: Boolean = false
)
}

View File

@ -0,0 +1,117 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.ui.SingleIntegerFieldOptionsPanel
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.siyeh.ig.psiutils.EquivalenceChecker
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.JoinStatementsQuickFix
import javax.swing.JComponent
class JoinAssertThatStatementsInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Join multiple assertThat() statements with same actual expression"
private const val CAN_BE_JOINED_DESCRIPTION = "Multiple assertThat() statements can be joined together"
private const val DEFAULT_SEPARATE_LINE_LIMIT = 1
}
@JvmField
var separateLineLimit: Int = DEFAULT_SEPARATE_LINE_LIMIT
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitCodeBlock(block: PsiCodeBlock) {
super.visitCodeBlock(block)
var lastActualExpression: PsiExpression? = null
var sameCount = 0
var firstStatement: PsiStatement? = null
var lastStatement: PsiStatement? = null
// Note: replace with TrackingEquivalenceChecker() for IDEA >= 2019.1
val equivalenceChecker = EquivalenceChecker.getCanonicalPsiEquivalence()!!
for (statement in block.statements) {
val assertThatCall = isLegitAssertThatCall(statement)
var reset = true
var actualExpression: PsiExpression? = null
if (assertThatCall != null) {
reset = (lastActualExpression == null)
actualExpression = assertThatCall.firstArg
if (!reset) {
val isSame = equivalenceChecker.expressionsAreEquivalent(actualExpression, lastActualExpression)
&& !hasExpressionWithSideEffects(actualExpression)
if (isSame) {
sameCount++
lastStatement = statement
} else {
reset = true
}
}
}
if (reset) {
if (sameCount > 1) {
registerProblem(holder, isOnTheFly, firstStatement!!, lastStatement!!)
}
firstStatement = statement
lastStatement = null
lastActualExpression = actualExpression
sameCount = 1
}
}
if (sameCount > 1) {
registerProblem(holder, isOnTheFly, firstStatement!!, lastStatement!!)
}
}
private fun isLegitAssertThatCall(statement: PsiStatement?): PsiMethodCallExpression? {
if ((statement is PsiExpressionStatement) && (statement.expression is PsiMethodCallExpression)) {
if (!statement.hasAssertThat()) return null
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) }
return assertThatCall?.takeIf { it.findFluentCallTo(COMPLEX_STUFF_THAT_MAKES_JOINING_IMPOSSIBLE) == null }
}
return null
}
private fun hasExpressionWithSideEffects(actualExpression: PsiExpression): Boolean {
var result = false
PsiTreeUtil.processElements(actualExpression) { element ->
val matched = when (element) {
is PsiUnaryExpression -> (element.operationTokenType == JavaTokenType.PLUSPLUS)
|| (element.operationTokenType == JavaTokenType.MINUSMINUS)
is PsiMethodCallExpression -> KNOWN_METHODS_WITH_SIDE_EFFECTS.test(element)
else -> false
}
if (matched) {
result = true
false
} else {
true
}
}
return result
}
}
}
override fun createOptionsPanel(): JComponent {
return SingleIntegerFieldOptionsPanel("Limit for joins before adding line breaks:", this, "separateLineLimit")
}
private fun registerProblem(holder: ProblemsHolder, isOnTheFly: Boolean, firstStatement: PsiStatement, lastStatement: PsiStatement) {
val problemDescriptor = holder.manager.createProblemDescriptor(
firstStatement,
lastStatement,
CAN_BE_JOINED_DESCRIPTION,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly,
JoinStatementsQuickFix(separateLineLimit)
)
holder.registerProblem(problemDescriptor)
}
}

View File

@ -0,0 +1,53 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiExpressionStatement
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.PsiTreeUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.JoinVarArgsContainsQuickFix
class JoinVarArgsContainsInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Join arguments to variadic for contains()/containsOnlyOnce()/doesNotContain()"
private const val JOIN_VARARGS_MESSAGE = "Calls to same methods may be joined to variadic version"
private val MATCHERS = listOf(MethodNames.CONTAINS, MethodNames.CONTAINS_ONLY_ONCE, MethodNames.DOES_NOT_CONTAIN)
.map { CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, it) }
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) } ?: return
val allCalls = assertThatCall.collectMethodCallsUpToStatement().toList()
if (allCalls.find(COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY::test) != null) return
val onlyAssertionCalls = allCalls
.filterNot(NOT_ACTUAL_ASSERTIONS::test)
.toList()
for (methodMatcher in MATCHERS) {
if (onlyAssertionCalls.count(methodMatcher::test) > 1) {
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
val quickFix = JoinVarArgsContainsQuickFix(MATCHERS)
val textRange = TextRange(assertThatCall.textLength, outmostMethodCall.textLength)
holder.registerProblem(outmostMethodCall, textRange, JOIN_VARARGS_MESSAGE, quickFix)
return
}
}
}
}
}
}

View File

@ -0,0 +1,108 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.SwapActualAndExpectedExpressionMethodCallQuickFix
class TwistedAssertionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Twisted or suspicious actual and expected expressions"
private const val TWISTED_ACTUAL_AND_EXPECTED_MESSAGE = "Twisted actual and expected expressions in assertion"
private const val SWAP_ACTUAL_AND_EXPECTED_DESCRIPTION = "Swap actual and expected expressions in assertion"
private const val SWAP_ACTUAL_AND_EXPECTED_AND_REPLACE_DESCRIPTION_TEMPLATE = "Replace %s() by %s() and swap actual and expected expressions"
private const val ACTUAL_IS_A_CONSTANT_MESSAGE = "Actual expression in assertThat() is a constant"
private val GENERIC_IS_EQUAL_TO = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, MethodNames.IS_EQUAL_TO).parameterCount(1)
private val GENERIC_IS_NOT_EQUAL_TO = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, MethodNames.IS_NOT_EQUAL_TO).parameterCount(1)
private val GENERIC_IS_SAME_AS = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, MethodNames.IS_SAME_AS).parameterCount(1)
private val GENERIC_IS_NOT_SAME_AS = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, MethodNames.IS_NOT_SAME_AS).parameterCount(1)
private val GENERIC_IS_GREATER_THAN = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN).parameterCount(1)
private val GENERIC_IS_GREATER_THAN_OR_EQUAL_TO =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN_OR_EQUAL_TO).parameterCount(1)
private val GENERIC_IS_LESS_THAN = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_LESS_THAN).parameterCount(1)
private val GENERIC_IS_LESS_THAN_OR_EQUAL_TO =
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_LESS_THAN_OR_EQUAL_TO).parameterCount(1)
private val STRING_IS_EQUAL_TO_IC = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO_IC).parameterCount(1)
private val STRING_REGEX_MATCHING = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME, "matches", "doesNotMatch").parameterCount(1)
private val CALL_MATCHER_TO_REPLACEMENT_MAP = mapOf(
GENERIC_IS_EQUAL_TO to MethodNames.IS_EQUAL_TO,
GENERIC_IS_NOT_EQUAL_TO to MethodNames.IS_NOT_EQUAL_TO,
GENERIC_IS_SAME_AS to MethodNames.IS_SAME_AS,
GENERIC_IS_NOT_SAME_AS to MethodNames.IS_NOT_SAME_AS,
GENERIC_IS_GREATER_THAN to MethodNames.IS_LESS_THAN_OR_EQUAL_TO,
GENERIC_IS_GREATER_THAN_OR_EQUAL_TO to MethodNames.IS_LESS_THAN,
GENERIC_IS_LESS_THAN to MethodNames.IS_GREATER_THAN_OR_EQUAL_TO,
GENERIC_IS_LESS_THAN_OR_EQUAL_TO to MethodNames.IS_GREATER_THAN,
STRING_IS_EQUAL_TO_IC to MethodNames.IS_EQUAL_TO_IC,
CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO_IC).parameterCount(1)
to MethodNames.IS_NOT_EQUAL_TO_IC
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) } ?: return
val actualExpression = assertThatCall.firstArg
actualExpression.calculateConstantValue() ?: return
val allCalls = assertThatCall.collectMethodCallsUpToStatement().toList()
val tooComplex = allCalls.find(USING_COMPARATOR::test) != null
var severity = ProblemHighlightType.GENERIC_ERROR_OR_WARNING
if (actualExpression.type is PsiClassType) {
val psiManager = PsiManager.getInstance(statement.project)
val javaLangClass = PsiType.getJavaLangClass(psiManager, GlobalSearchScope.allScope(statement.project))
if (actualExpression.type!!.isAssignableFrom(javaLangClass)) {
return
}
}
if (!tooComplex) {
val onlyAssertionCalls = allCalls
.filterNot(NOT_ACTUAL_ASSERTIONS::test)
.toList()
if (onlyAssertionCalls.size == 1) {
val expectedMethodCall = onlyAssertionCalls.first()
if (STRING_REGEX_MATCHING.test(expectedMethodCall)) {
return
}
if (expectedMethodCall.getArgOrNull(0)?.calculateConstantValue() == null) {
val matchedMethod = CALL_MATCHER_TO_REPLACEMENT_MAP.asSequence().firstOrNull { it.key.test(expectedMethodCall) }
if (matchedMethod != null) {
val originalMethodName = getOriginalMethodName(expectedMethodCall)
val replacementMethod = matchedMethod.value
val description = if (originalMethodName == replacementMethod) {
SWAP_ACTUAL_AND_EXPECTED_DESCRIPTION
} else {
SWAP_ACTUAL_AND_EXPECTED_AND_REPLACE_DESCRIPTION_TEMPLATE.format(originalMethodName, replacementMethod)
}
holder.registerProblem(
statement,
TWISTED_ACTUAL_AND_EXPECTED_MESSAGE,
SwapActualAndExpectedExpressionMethodCallQuickFix(description, replacementMethod)
)
return
}
} else {
severity = ProblemHighlightType.WEAK_WARNING
}
}
}
holder.registerProblem(statement, ACTUAL_IS_A_CONSTANT_MESSAGE, severity)
}
}
}
}

View File

@ -1,32 +1,10 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import org.jetbrains.annotations.NonNls
abstract class AbstractCommonQuickFix(private val description: String) : LocalQuickFix {
override fun getName() = description
override fun getFamilyName() = description
companion object {
@NonNls
const val GUAVA_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.Assertions"
}
protected fun addStaticImport(method: PsiMethod, element: PsiMethodCallExpression, factory: PsiElementFactory, vararg allowedClashes: String) {
val methodName = method.name
val containingClass = method.containingClass ?: return
val importList = (element.containingFile as PsiJavaFile).importList ?: return
val notImportedStatically = importList.importStaticStatements.none {
val targetClass = it.resolveTargetClass() ?: return@none false
((it.referenceName == methodName) && !allowedClashes.contains(targetClass.qualifiedName))
|| (it.isOnDemand && (targetClass == method.containingClass))
}
if (notImportedStatically) {
importList.add(factory.createImportStaticStatement(containingClass, methodName))
}
}
}

View File

@ -0,0 +1,23 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.qualifierExpression
class DeleteMethodCallQuickFix(description: String) : AbstractCommonQuickFix(description) {
companion object {
private const val DELETE_METHOD_DESCRIPTION = "Delete unnecessary method calls"
}
override fun getFamilyName(): String {
return DELETE_METHOD_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val methodCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
methodCallExpression.replace(methodCallExpression.qualifierExpression)
}
}

View File

@ -0,0 +1,26 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.*
class ForGuavaPostFix {
companion object {
val REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT: (Project, ProblemDescriptor) -> Unit = exit@
{ _, descriptor ->
val element = descriptor.startElement
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return@exit
val assertThatCall = statement.findStaticMethodCall() ?: return@exit
val newMethodCall = createGuavaAssertThat(element, assertThatCall.firstArg)
newMethodCall.resolveMethod()?.addAsStaticImport(element, AssertJClassNames.ASSERTIONS_CLASSNAME)
val parentCall = PsiTreeUtil.getParentOfType(assertThatCall, PsiMethodCallExpression::class.java) ?: return@exit
parentCall.replaceQualifier(newMethodCall)
parentCall.shortenAndReformat()
}
}
}

View File

@ -0,0 +1,37 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.*
class HasHashCodeQuickFix :
AbstractCommonQuickFix(HASHCODE_DESCRIPTION) {
companion object {
private const val HASHCODE_DESCRIPTION = "Replace calls to hashCode() with hasSameHashCodeAs()"
}
override fun getFamilyName(): String {
return HASHCODE_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg as? PsiMethodCallExpression ?: return
val methodsToFix = assertThatMethodCall.gatherAssertionCalls()
assertExpression.replace(assertExpression.qualifierExpression)
methodsToFix
.forEach {
val innerHashCodeObject = (it.firstArg as PsiMethodCallExpression).qualifierExpression
val expectedExpression = createExpectedMethodCall(it, "hasSameHashCodeAs", innerHashCodeObject)
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
}

View File

@ -0,0 +1,31 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiUnaryExpression
import com.intellij.psi.util.PsiUtil
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class InvertUnaryExpressionQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
companion object {
private const val INVERT_CONDITION_DESCRIPTION = "Invert condition in isEqualTo()/isNotEqualTo() expressions"
}
override fun getFamilyName(): String {
return INVERT_CONDITION_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val methodCall = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertExpression = methodCall.firstArg as? PsiUnaryExpression ?: return
val operand = PsiUtil.skipParenthesizedExprDown(assertExpression.operand) ?: return
val expectedExpression = createExpectedMethodCall(assertExpression, replacementMethod, operand)
expectedExpression.replaceQualifierFromMethodCall(methodCall)
methodCall.replace(expectedExpression)
}
}

View File

@ -0,0 +1,37 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiUnaryExpression
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import de.platon42.intellij.plugins.cajon.*
class InvertUnaryStatementQuickFix : AbstractCommonQuickFix(INVERT_CONDITION_DESCRIPTION) {
companion object {
private const val INVERT_CONDITION_DESCRIPTION = "Invert condition in assertThat()"
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg as? PsiUnaryExpression ?: return
val operand = PsiUtil.skipParenthesizedExprDown(assertExpression.operand) ?: return
assertExpression.replace(operand)
var methodCall: PsiMethodCallExpression? = assertThatMethodCall
while (methodCall != null) {
val expectedResult = methodCall.getExpectedBooleanResult()
val nextMethodCall = PsiTreeUtil.getParentOfType(methodCall, PsiMethodCallExpression::class.java)
if (expectedResult != null) {
val replacementMethod = expectedResult.map(MethodNames.IS_FALSE, MethodNames.IS_TRUE)
val expectedExpression = createExpectedMethodCall(methodCall, replacementMethod)
expectedExpression.replaceQualifierFromMethodCall(methodCall)
methodCall.replace(expectedExpression)
}
methodCall = nextMethodCall
}
}
}

View File

@ -0,0 +1,79 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.findStaticMethodCall
import de.platon42.intellij.plugins.cajon.shortenAndReformat
class JoinStatementsQuickFix(private val separateLineLimit: Int) : AbstractCommonQuickFix(JOIN_STATEMENTS_MESSAGE) {
companion object {
private const val JOIN_STATEMENTS_MESSAGE = "Join assertThat() statements"
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val firstStatement = descriptor.startElement as PsiExpressionStatement
val lastStatement = descriptor.endElement as PsiExpressionStatement
val expressionCount = countExpressions(firstStatement, lastStatement)
val addLineBreaks = (expressionCount > separateLineLimit)
do {
val commentsToKeep = ArrayList<PsiComment>()
val stuffToDelete = ArrayList<PsiElement>()
var previousStatement = lastStatement.prevSibling!!
while (previousStatement !is PsiExpressionStatement) {
if (previousStatement is PsiComment) {
commentsToKeep.add(previousStatement.copy() as PsiComment)
}
stuffToDelete.add(previousStatement)
previousStatement = previousStatement.prevSibling!!
}
stuffToDelete.forEach { if (it.isValid) it.delete() }
val statementComments = PsiTreeUtil.getChildrenOfAnyType(previousStatement, PsiComment::class.java)
commentsToKeep.addAll(statementComments)
val assertThatCallOfCursorStatement = lastStatement.findStaticMethodCall()!!
val lastElementBeforeConcat = assertThatCallOfCursorStatement.parent
commentsToKeep.forEach {
lastElementBeforeConcat.addAfter(it, lastElementBeforeConcat.firstChild)
addLineBreak(project, lastElementBeforeConcat)
}
if (commentsToKeep.isEmpty() && addLineBreaks) {
addLineBreak(project, lastElementBeforeConcat)
}
val newLeaf = previousStatement.firstChild
assertThatCallOfCursorStatement.replace(newLeaf)
previousStatement.delete()
} while (previousStatement !== firstStatement)
val codeBlock = PsiTreeUtil.getParentOfType(lastStatement, PsiCodeBlock::class.java) ?: return
codeBlock.shortenAndReformat()
}
private fun addLineBreak(project: Project, lastElementBeforeConcat: PsiElement) {
// was PsiParserFacade.getInstance(project).createWhiteSpaceFromText("\n\t"), changed due to breaking API changes
val newLineNode = project.getService(PsiParserFacade::class.java).createWhiteSpaceFromText("\n\t")
lastElementBeforeConcat.addAfter(newLineNode, lastElementBeforeConcat.firstChild)
}
private fun countExpressions(firstStatement: PsiElement, lastStatement: PsiElement): Int {
var count = 0
var currentStatement = firstStatement
do {
while (currentStatement !is PsiExpressionStatement) {
currentStatement = currentStatement.nextSibling!!
}
count++
if (currentStatement === lastStatement) {
break
}
currentStatement = currentStatement.nextSibling!!
} while (true)
return count
}
}

View File

@ -0,0 +1,38 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
class JoinVarArgsContainsQuickFix(private val matchers: Iterable<CallMatcher>) : AbstractCommonQuickFix(JOIN_VARARGS_DESCRIPTION) {
companion object {
private const val JOIN_VARARGS_DESCRIPTION = "Join multiple arguments to variadic argument method calls"
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
var outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
for (matcher in matchers) {
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val methodsToFix = assertThatMethodCall.gatherAssertionCalls()
val matchedCalls = methodsToFix.filter(matcher::test)
if (matchedCalls.size > 1) {
val mainCall = matchedCalls.first()
val args = mutableListOf(*mainCall.argumentList.expressions)
for (secondaryCall in matchedCalls.asSequence().drop(1)) {
args.addAll(secondaryCall.argumentList.expressions)
}
val newMainCall = createExpectedMethodCall(mainCall, mainCall.methodExpression.qualifiedName, *args.toTypedArray())
newMainCall.replaceQualifierFromMethodCall(mainCall)
mainCall.replace(newMainCall)
for (secondaryCall in matchedCalls.asSequence().drop(1)) {
val newQualifier = secondaryCall.qualifierExpression
outmostCallExpression = secondaryCall.replace(newQualifier).findOutmostMethodCall() ?: return
}
}
}
}
}

View File

@ -0,0 +1,44 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiInstanceOfExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.PsiUtil
import de.platon42.intellij.plugins.cajon.*
class MoveOutInstanceOfExpressionQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
companion object {
private const val REMOVE_INSTANCEOF_DESCRIPTION = "Move instanceof in actual expressions out of assertThat()"
}
override fun getFamilyName(): String {
return REMOVE_INSTANCEOF_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg as? PsiInstanceOfExpression ?: return
val expectedClass = assertExpression.checkType ?: return
val methodsToFix = assertThatMethodCall.collectMethodCallsUpToStatement()
.filter { it.getExpectedBooleanResult() != null }
.toList()
val factory = JavaPsiFacade.getElementFactory(project)
val classObjectAccess = factory.createExpressionFromText("${expectedClass.type.canonicalText}.class", null)
val operand = PsiUtil.deparenthesizeExpression(assertExpression.operand) ?: return
assertExpression.replace(operand)
methodsToFix
.forEach {
val expectedExpression = createExpectedMethodCall(it, replacementMethod, classObjectAccess)
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
}

View File

@ -0,0 +1,82 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethodCallExpression
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
class MoveOutMethodCallExpressionQuickFix(
description: String,
private val replacementMethod: String,
private val useNullNonNull: Boolean = false,
private val noExpectedExpression: Boolean = false,
private val keepExpectedAsSecondArgument: Boolean = false,
private val replaceOnlyThisMethod: CallMatcher? = null,
private val replaceFromOriginalMethod: Boolean = false
) :
AbstractCommonQuickFix(description) {
companion object {
private const val REMOVE_ACTUAL_EXPRESSION_DESCRIPTION = "Move method calls in actual expressions out of assertThat()"
}
override fun getFamilyName(): String {
return REMOVE_ACTUAL_EXPRESSION_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg as? PsiMethodCallExpression ?: return
val assertExpressionArg = if (noExpectedExpression) null else assertExpression.getArgOrNull(0)?.copy() as PsiExpression?
when {
replaceOnlyThisMethod != null -> {
val methodsToFix = assertThatMethodCall.collectMethodCallsUpToStatement()
.filter(replaceOnlyThisMethod::test)
.toList()
assertExpression.replace(assertExpression.qualifierExpression)
methodsToFix
.forEach {
val expectedExpression = createExpectedMethodCall(
it,
replacementMethod,
*if (replaceFromOriginalMethod || noExpectedExpression) listOfNotNull(assertExpressionArg).toTypedArray() else it.argumentList.expressions
)
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
keepExpectedAsSecondArgument -> {
assertExpressionArg ?: return
val secondArg =
if (useNullNonNull) JavaPsiFacade.getElementFactory(project).createExpressionFromText("null", null) else outmostCallExpression.getArgOrNull(0)?.copy() ?: return
assertExpression.replace(assertExpression.qualifierExpression)
val expectedExpression = createExpectedMethodCall(outmostCallExpression, replacementMethod, assertExpressionArg, secondArg)
expectedExpression.replaceQualifierFromMethodCall(outmostCallExpression)
outmostCallExpression.replace(expectedExpression)
}
else -> {
val methodsToFix = assertThatMethodCall.collectMethodCallsUpToStatement()
.filter { (if (useNullNonNull) it.getExpectedNullNonNullResult() else it.getExpectedBooleanResult()) != null }
.toList()
assertExpression.replace(assertExpression.qualifierExpression)
methodsToFix
.forEach {
val expectedExpression = createExpectedMethodCall(it, replacementMethod, *listOfNotNull(assertExpressionArg).toTypedArray())
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
}
}
}

View File

@ -0,0 +1,20 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
class QuickFixWithPostfixDelegate(
private val mainFix: LocalQuickFix,
private val postfix: (Project, ProblemDescriptor) -> Unit
) : LocalQuickFix by mainFix {
override fun getName(): String {
return mainFix.name
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
mainFix.applyFix(project, descriptor)
postfix(project, descriptor)
}
}

View File

@ -0,0 +1,39 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.*
class RemoveActualOutmostMethodCallQuickFix(
description: String,
private val replacementMethod: String,
private val noExpectedExpression: Boolean = false
) : AbstractCommonQuickFix(description) {
companion object {
private const val REMOVE_ACTUAL_EXPRESSION_DESCRIPTION = "Remove method calls in actual expressions and use better assertion"
}
override fun getFamilyName(): String {
return REMOVE_ACTUAL_EXPRESSION_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg as? PsiMethodCallExpression ?: return
val methodsToFix = assertThatMethodCall.gatherAssertionCalls()
assertExpression.replace(assertExpression.qualifierExpression)
methodsToFix
.forEach {
val args = if (noExpectedExpression) emptyArray() else it.argumentList.expressions
val expectedExpression = createExpectedMethodCall(it, replacementMethod, *args)
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
}

View File

@ -0,0 +1,35 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiReferenceExpression
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class ReplaceHasSizeMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val methodCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
replaceCollectionAndMapSizeOrArrayLength(methodCallExpression.firstArg)
val expectedExpression = createExpectedMethodCall(methodCallExpression, replacementMethod, methodCallExpression.firstArg)
expectedExpression.replaceQualifierFromMethodCall(methodCallExpression)
methodCallExpression.replace(expectedExpression)
}
private fun replaceCollectionAndMapSizeOrArrayLength(assertExpression: PsiExpression) {
assertExpression.replace(
when (assertExpression) {
is PsiReferenceExpression -> assertExpression.qualifierExpression!!
is PsiMethodCallExpression -> assertExpression.qualifierExpression
else -> return
}
)
}
}

View File

@ -0,0 +1,51 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiBlockStatement
import com.intellij.psi.PsiDeclarationStatement
import com.intellij.psi.PsiIfStatement
import de.platon42.intellij.plugins.cajon.*
class ReplaceIfByAssumeThatQuickFix(private val removeElse: Boolean) : AbstractCommonQuickFix(REPLACE_IF_MESSAGE) {
companion object {
private const val REPLACE_IF_MESSAGE = "Replace if statement by assumeTrue()"
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val ifStatement = descriptor.startElement as PsiIfStatement
val condition = ifStatement.condition ?: return
val factory = JavaPsiFacade.getElementFactory(ifStatement.project)
val assumptionExpression = if (removeElse) MethodNames.IS_TRUE else MethodNames.IS_FALSE
val assumeThatStatement = factory.createStatementFromText(
"${AssertJClassNames.ASSUMPTIONS_CLASSNAME}.${MethodNames.ASSUME_THAT}(true).$assumptionExpression();",
ifStatement
)
val assumeThatMethodCall = assumeThatStatement.findStaticMethodCall() ?: return
assumeThatMethodCall.firstArg.replace(condition)
assumeThatMethodCall.resolveMethod()?.addAsStaticImport(ifStatement)
val branchToKeep = (if (removeElse) ifStatement.thenBranch else ifStatement.elseBranch)?.copy()
val parentBlock = ifStatement.parent
if (branchToKeep != null) {
val anchorElement = ifStatement.nextSibling
if (branchToKeep is PsiBlockStatement) {
val codeBlock = branchToKeep.codeBlock
if (codeBlock.statements.isNotEmpty()) {
val hasDeclarations = codeBlock.statements.any { it is PsiDeclarationStatement }
if (hasDeclarations) {
parentBlock.addAfter(branchToKeep, anchorElement)
} else {
parentBlock.addRangeAfter(codeBlock.firstBodyElement, codeBlock.lastBodyElement, anchorElement)
}
}
} else {
parentBlock.addAfter(branchToKeep, anchorElement)
}
}
ifStatement.replace(assumeThatStatement).shortenAndReformat()
}
}

View File

@ -2,57 +2,56 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
class ReplaceJUnitAssertMethodCallQuickFix(description: String, private val hasExpected: Boolean, private val replacementMethod: String) :
class ReplaceJUnitAssertMethodCallQuickFix(description: String, private val replacementMethod: String, private val noExpectedExpression: Boolean) :
AbstractCommonQuickFix(description) {
companion object {
private const val CONVERT_DESCRIPTION = "Convert JUnit assertions to assertJ"
}
override fun getFamilyName(): String {
return CONVERT_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 args = methodCallExpression.argumentList
val count = args.expressionCount
val count = args.expressions.size
val actualExpression = args.expressions[count - 1] ?: return
val (expectedExpression, messageExpression) = if (hasExpected) {
val expected = args.expressions[count - 2] ?: return
val message = if (count > 2) args.expressions[0] else null
Pair(expected, message)
val (expectedExpressions, messageExpression) = if (noExpectedExpression) {
val message = args.expressions.getOrNull(count - 2)
emptyArray<PsiExpression>() to message
} else {
val message = if (count > 1) args.expressions[0] else null
Pair(null, message)
val expected = args.expressions[count - 2] ?: return
val message = args.expressions.getOrNull(count - 3)
arrayOf(expected) to message
}
val expectedMethodCall = factory.createExpressionFromText(
"a.${if (hasExpected) replacementMethod.replace("()", "(e)") else replacementMethod}", element
) as PsiMethodCallExpression
if (hasExpected) {
expectedMethodCall.argumentList.expressions[0].replace(expectedExpression!!)
val swapActualAndExpected = ((expectedExpressions.getOrNull(0)?.calculateConstantValue() == null)
&& (actualExpression.calculateConstantValue() != null))
val (expectedMethodCall, newMethodCall) = if (swapActualAndExpected) {
createExpectedMethodCall(element, replacementMethod, actualExpression) to
createAssertThat(element, expectedExpressions.single())
} else {
createExpectedMethodCall(element, replacementMethod, *expectedExpressions) to
createAssertThat(element, actualExpression)
}
val newMethodCall = factory.createExpressionFromText(
"org.assertj.core.api.Assertions.assertThat(a)", element
) as PsiMethodCallExpression
newMethodCall.argumentList.expressions[0].replace(actualExpression)
if (messageExpression != null) {
val asExpression = factory.createExpressionFromText("a.as(desc)", element) as PsiMethodCallExpression
asExpression.argumentList.expressions[0].replace(messageExpression)
asExpression.methodExpression.qualifierExpression!!.replace(newMethodCall)
expectedMethodCall.methodExpression.qualifierExpression!!.replace(asExpression)
val asExpression = createExpectedMethodCall(element, MethodNames.AS, messageExpression)
asExpression.replaceQualifier(newMethodCall)
expectedMethodCall.replaceQualifier(asExpression)
} else {
expectedMethodCall.methodExpression.qualifierExpression!!.replace(newMethodCall)
expectedMethodCall.replaceQualifier(newMethodCall)
}
val assertThatMethod = newMethodCall.resolveMethod() ?: return
addStaticImport(assertThatMethod, element, factory, GUAVA_ASSERTIONS_CLASSNAME)
val codeStyleManager = JavaCodeStyleManager.getInstance(element.project)
val newElement = element.replace(expectedMethodCall)
val shortened = codeStyleManager.shortenClassReferences(newElement)
CodeStyleManager.getInstance(element.project).reformat(shortened)
newMethodCall.resolveMethod()?.addAsStaticImport(element, GUAVA_ASSERTIONS_CLASSNAME)
element.replace(expectedMethodCall).shortenAndReformat()
}
}

View File

@ -0,0 +1,41 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.*
class ReplaceJUnitAssumeMethodCallQuickFix(description: String, private val replacementMethod: String) :
AbstractCommonQuickFix(description) {
companion object {
private const val CONVERT_DESCRIPTION = "Convert JUnit assumptions to assertJ"
}
override fun getFamilyName(): String {
return CONVERT_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val args = methodCallExpression.argumentList
val count = args.expressions.size
val actualExpression = args.expressions[count - 1] ?: return
val messageExpression = args.expressions.getOrNull(count - 2)
val expectedMethodCall = createExpectedMethodCall(element, replacementMethod)
val newMethodCall = createAssumeThat(element, actualExpression)
if (messageExpression != null) {
val asExpression = createExpectedMethodCall(element, MethodNames.AS, messageExpression)
asExpression.replaceQualifier(newMethodCall)
expectedMethodCall.replaceQualifier(asExpression)
} else {
expectedMethodCall.replaceQualifier(newMethodCall)
}
newMethodCall.resolveMethod()?.addAsStaticImport(element)
element.replace(expectedMethodCall).shortenAndReformat()
}
}

View File

@ -2,59 +2,52 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
class ReplaceJUnitDeltaAssertMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
companion object {
private const val CONVERT_DESCRIPTION = "Convert JUnit assertions with delta to assertJ"
}
override fun getFamilyName(): String {
return CONVERT_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 args = methodCallExpression.argumentList
val count = args.expressionCount
val actualExpression = args.expressions[count - 2] ?: return
val messageExpression = if (count > 3) args.expressions[0] else null
val count = args.expressions.size
val messageExpression = args.expressions.getOrNull(count - 4)
val expectedExpression = args.expressions[count - 3] ?: return
val actualExpression = args.expressions[count - 2] ?: return
val deltaExpression = args.expressions[count - 1] ?: return
val offsetMethodCall = factory.createExpressionFromText(
"org.assertj.core.data.Offset.offset(c)", element
) as PsiMethodCallExpression
val offsetMethodCall = createMethodCall(element, "org.assertj.core.data.Offset.offset", deltaExpression)
offsetMethodCall.argumentList.expressions[0].replace(deltaExpression)
val expectedMethodCall = factory.createExpressionFromText(
"a.${replacementMethod.removeSuffix("()")}(e, offs)", element
) as PsiMethodCallExpression
expectedMethodCall.argumentList.expressions[0].replace(expectedExpression)
expectedMethodCall.argumentList.expressions[1].replace(offsetMethodCall)
val newMethodCall = factory.createExpressionFromText(
"org.assertj.core.api.Assertions.assertThat(a)", element
) as PsiMethodCallExpression
newMethodCall.argumentList.expressions[0].replace(actualExpression)
if (messageExpression != null) {
val asExpression = factory.createExpressionFromText("a.as(desc)", element) as PsiMethodCallExpression
asExpression.argumentList.expressions[0].replace(messageExpression)
asExpression.methodExpression.qualifierExpression!!.replace(newMethodCall)
expectedMethodCall.methodExpression.qualifierExpression!!.replace(asExpression)
val swapActualAndExpected = ((expectedExpression.calculateConstantValue() == null)
&& (actualExpression.calculateConstantValue() != null))
val (expectedMethodCall, newMethodCall) = if (swapActualAndExpected) {
createExpectedMethodCall(element, replacementMethod, actualExpression, offsetMethodCall) to
createAssertThat(element, expectedExpression)
} else {
expectedMethodCall.methodExpression.qualifierExpression!!.replace(newMethodCall)
createExpectedMethodCall(element, replacementMethod, expectedExpression, offsetMethodCall) to
createAssertThat(element, actualExpression)
}
val assertThatMethod = newMethodCall.resolveMethod() ?: return
addStaticImport(assertThatMethod, element, factory, GUAVA_ASSERTIONS_CLASSNAME)
val offsetMethod = offsetMethodCall.resolveMethod() ?: return
addStaticImport(offsetMethod, element, factory)
if (messageExpression != null) {
val asExpression = createExpectedMethodCall(element, MethodNames.AS, messageExpression)
asExpression.replaceQualifier(newMethodCall)
expectedMethodCall.replaceQualifier(asExpression)
} else {
expectedMethodCall.replaceQualifier(newMethodCall)
}
val codeStyleManager = JavaCodeStyleManager.getInstance(element.project)
val newElement = element.replace(expectedMethodCall)
val shortened = codeStyleManager.shortenClassReferences(newElement)
CodeStyleManager.getInstance(element.project).reformat(shortened)
newMethodCall.resolveMethod()?.addAsStaticImport(element, GUAVA_ASSERTIONS_CLASSNAME)
offsetMethodCall.resolveMethod()?.addAsStaticImport(element)
element.replace(expectedMethodCall).shortenAndReformat()
}
}

View File

@ -2,19 +2,25 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class ReplaceSimpleMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
companion object {
private const val REPLACE_DESCRIPTION = "Replace methods by better ones"
}
override fun getFamilyName(): String {
return REPLACE_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 expectedExpression =
factory.createExpressionFromText("a.$replacementMethod", element) as PsiMethodCallExpression
expectedExpression.methodExpression.qualifierExpression!!.replace(oldQualifier)
val expectedExpression = createExpectedMethodCall(element, replacementMethod)
expectedExpression.replaceQualifierFromMethodCall(methodCallExpression)
element.replace(expectedExpression)
}
}

View File

@ -2,41 +2,48 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiReferenceExpression
import de.platon42.intellij.plugins.cajon.*
class ReplaceSizeMethodCallQuickFix(
description: String,
private val replacementMethod: String,
private val noExpectedExpression: Boolean,
private val expectedIsCollection: Boolean
private val noExpectedExpression: Boolean = false,
private val expectedIsCollection: Boolean = false
) : AbstractCommonQuickFix(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 assertExpression = methodCallExpression.argumentList.expressions[0] ?: return
replaceCollectionSizeOrArrayLength(assertExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val expectedExpression =
factory.createExpressionFromText("a.${if (noExpectedExpression) replacementMethod else replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
if (!noExpectedExpression) {
if (expectedIsCollection) {
replaceCollectionSizeOrArrayLength(oldExpectedExpression.argumentList.expressions[0])
}
expectedExpression.argumentList.expressions[0].replace(oldExpectedExpression.argumentList.expressions[0])
}
expectedExpression.methodExpression.qualifierExpression!!.replace(oldExpectedExpression.methodExpression.qualifierExpression!!)
oldExpectedExpression.replace(expectedExpression)
companion object {
private const val REPLACE_DESCRIPTION = "Replace methods by better ones"
}
private fun replaceCollectionSizeOrArrayLength(assertExpression: PsiExpression) {
override fun getFamilyName(): String {
return REPLACE_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = assertThatMethodCall.firstArg
replaceCollectionAndMapSizeOrArrayLength(assertExpression)
if (expectedIsCollection) {
replaceCollectionAndMapSizeOrArrayLength(outmostCallExpression.firstArg)
}
val args = if (noExpectedExpression) emptyArray() else arrayOf(outmostCallExpression.firstArg)
val expectedExpression = createExpectedMethodCall(outmostCallExpression, replacementMethod, *args)
expectedExpression.replaceQualifierFromMethodCall(outmostCallExpression)
outmostCallExpression.replace(expectedExpression)
}
private fun replaceCollectionAndMapSizeOrArrayLength(assertExpression: PsiExpression) {
assertExpression.replace(
when (assertExpression) {
is PsiReferenceExpression -> assertExpression.qualifierExpression!!
is PsiMethodCallExpression -> assertExpression.methodExpression.qualifierExpression!!
is PsiMethodCallExpression -> assertExpression.qualifierExpression
else -> return
}
)

View File

@ -2,35 +2,44 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.*
class SplitBinaryExpressionMethodCallQuickFix(
description: String,
private val replacementMethod: String,
private val pickRightOperand: Boolean,
private val noExpectedExpression: Boolean
private val pickRightOperand: Boolean = false,
private val noExpectedExpression: Boolean = false
) : AbstractCommonQuickFix(description) {
companion object {
private const val SPLIT_EXPRESSION_DESCRIPTION = "Split binary expressions out of assertThat()"
}
override fun getFamilyName(): String {
return SPLIT_EXPRESSION_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 binaryExpression = methodCallExpression.argumentList.expressions[0] as? PsiBinaryExpression ?: return
val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val methodsToFix = assertThatMethodCall.collectMethodCallsUpToStatement()
.filter { it.getExpectedBooleanResult() != null }
.toList()
val binaryExpression = assertThatMethodCall.firstArg as? PsiBinaryExpression ?: return
val expectedArgument = (if (pickRightOperand) binaryExpression.lOperand else binaryExpression.rOperand)?.copy() ?: return
binaryExpression.replace(if (pickRightOperand) binaryExpression.rOperand!! else binaryExpression.lOperand)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val expectedExpression =
factory.createExpressionFromText("a.${if (noExpectedExpression) replacementMethod else replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
if (!noExpectedExpression) {
expectedExpression.argumentList.expressions[0].replace(expectedArgument)
}
expectedExpression.methodExpression.qualifierExpression!!.replace(oldExpectedExpression.methodExpression.qualifierExpression!!)
oldExpectedExpression.replace(expectedExpression)
val args = if (noExpectedExpression) emptyArray() else arrayOf(expectedArgument)
methodsToFix
.forEach {
val expectedExpression = createExpectedMethodCall(it, replacementMethod, *args)
expectedExpression.replaceQualifierFromMethodCall(it)
it.replace(expectedExpression)
}
}
}

View File

@ -1,28 +0,0 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
class SplitEqualsExpressionMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(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 equalsMethodCall = methodCallExpression.argumentList.expressions[0] as? PsiMethodCallExpression ?: return
val expectedArgument = equalsMethodCall.argumentList.expressions[0].copy()
equalsMethodCall.replace(equalsMethodCall.methodExpression.qualifierExpression!!)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val expectedExpression =
factory.createExpressionFromText("a.${replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
expectedExpression.argumentList.expressions[0].replace(expectedArgument)
expectedExpression.methodExpression.qualifierExpression!!.replace(oldExpectedExpression.methodExpression.qualifierExpression!!)
oldExpectedExpression.replace(expectedExpression)
}
}

View File

@ -0,0 +1,34 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import de.platon42.intellij.plugins.cajon.*
class SwapActualAndExpectedExpressionMethodCallQuickFix(
description: String,
private val replacementMethod: String
) : AbstractCommonQuickFix(description) {
companion object {
private const val SPLIT_EXPRESSION_DESCRIPTION = "Swap actual and expected expressions of assertions"
}
override fun getFamilyName(): String {
return SPLIT_EXPRESSION_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val assertThatMethodCall = descriptor.startElement.findStaticMethodCall() ?: return
val methodToFix = assertThatMethodCall.collectMethodCallsUpToStatement()
.filterNot(NOT_ACTUAL_ASSERTIONS::test)
.first()
val oldActualExpression = assertThatMethodCall.firstArg.copy()!!
assertThatMethodCall.firstArg.replace(methodToFix.firstArg)
val expectedExpression = createExpectedMethodCall(methodToFix, replacementMethod, oldActualExpression)
expectedExpression.replaceQualifierFromMethodCall(methodToFix)
methodToFix.replace(expectedExpression)
}
}

View File

@ -0,0 +1,29 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class UnwrapExpectedStaticMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
companion object {
private const val REMOVE_EXPECTED_OUTMOST_DESCRIPTION = "Unwrap expected expressions and use better assertion"
}
override fun getFamilyName(): String {
return REMOVE_EXPECTED_OUTMOST_DESCRIPTION
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val oldExpectedExpression = element.findOutmostMethodCall() ?: return
val expectedMethodCallExpression = oldExpectedExpression.firstArg as? PsiMethodCallExpression ?: return
val expectedExpression = createExpectedMethodCall(element, replacementMethod, expectedMethodCallExpression.firstArg)
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
}
}

View File

@ -0,0 +1,149 @@
package de.platon42.intellij.plugins.cajon.references
import com.intellij.openapi.util.TextRange
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.*
import com.intellij.psi.util.PropertyUtilBase
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.util.ArrayUtil
import com.intellij.util.ProcessingContext
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
class ExtractorReferenceContributor : PsiReferenceContributor() {
companion object {
private val BY_NAME = CallMatcher.staticCall(AssertJClassNames.EXTRACTORS_CLASSNAME, "byName")
private val RESULT_OF = CallMatcher.staticCall(AssertJClassNames.EXTRACTORS_CLASSNAME, "resultOf")
.parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!
private val propertyOrFieldReferenceProvider = PropertyOrFieldReferenceProvider()
private val iterablePropertyOrFieldReferenceProvider = IterablePropertyOrFieldReferenceProvider()
private val iterableResultOfReferenceProvider = IterableResultOfReferenceProvider()
private fun lookupFieldOrProperty(containingClass: PsiClass, path: String, startOffset: Int): List<Pair<TextRange, List<PsiElement>>> {
val partName = path.substring(startOffset).substringBefore(".")
val nextOffset = startOffset + partName.length + 1
val matchedGetter = PropertyUtilBase.findPropertyGetter(containingClass, partName, false, true)
val fieldResult = PropertyUtilBase.findPropertyField(containingClass, partName, false)
val textRange = TextRange(startOffset + 1, nextOffset)
val matchedBareMethod = containingClass.allMethods.find { (it.name == partName) && !it.hasModifierProperty(PsiModifier.STATIC) }
val targets = listOfNotNull<PsiElement>(fieldResult, matchedGetter, matchedBareMethod)
if (targets.isNotEmpty()) {
val results = listOf(textRange to targets)
if (nextOffset >= path.length) {
return results
}
val nextClass = PsiTypesUtil.getPsiClass(matchedGetter?.returnType ?: fieldResult?.type) ?: return results
return listOf(results, lookupFieldOrProperty(nextClass, path, nextOffset)).flatten()
}
return emptyList()
}
private fun lookupMethod(containingClass: PsiClass, methodName: String): List<Pair<TextRange, List<PsiElement>>>? {
val matchedMethod = containingClass.allMethods.find { (it.name == methodName) && !it.hasModifierProperty(PsiModifier.STATIC) } ?: return null
val textRange = TextRange(1, methodName.length + 1)
return listOf(textRange to listOf(matchedMethod))
}
private fun findActualType(element: PsiElement): PsiClassType? {
val assertThatCall = element.findStaticMethodCall()
return assertThatCall?.firstArg?.type as? PsiClassType
}
private fun findAndCreateReferences(element: PsiElement, finder: (PsiLiteralExpression) -> List<Pair<TextRange, List<PsiElement>>>?): Array<PsiReference> {
val literal = element as PsiLiteralExpression
val results = finder(literal) ?: return PsiReference.EMPTY_ARRAY
return results.map { ExtractorReference(literal, it.first, it.second) }.toTypedArray()
}
}
override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression::class.java), propertyOrFieldReferenceProvider)
registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression::class.java), iterablePropertyOrFieldReferenceProvider)
registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression::class.java), iterableResultOfReferenceProvider)
}
class ExtractorReference(literal: PsiLiteralExpression, range: TextRange, private val targets: List<PsiElement>) :
PsiPolyVariantReferenceBase<PsiLiteralExpression>(literal, range, true) {
// Do not remove due to compatibility issue with IDEA <= 2018.2
override fun getVariants(): Array<Any> {
return ArrayUtil.EMPTY_OBJECT_ARRAY
}
override fun resolve(): PsiElement? {
return multiResolve(false).map(ResolveResult::getElement).firstOrNull()
}
override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
return PsiElementResolveResult.createResults(targets)
}
}
class PropertyOrFieldReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> = findAndCreateReferences(element, ::findReferences)
fun findReferences(element: PsiLiteralExpression): List<Pair<TextRange, List<PsiElement>>>? {
val literal = element.value as? String ?: return null
var methodCallExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression::class.java) ?: return null
var isResultOf = false
if (BY_NAME.test(methodCallExpression)) {
methodCallExpression = PsiTreeUtil.getParentOfType(methodCallExpression, PsiMethodCallExpression::class.java) ?: return null
} else if (RESULT_OF.test(methodCallExpression)) {
methodCallExpression = PsiTreeUtil.getParentOfType(methodCallExpression, PsiMethodCallExpression::class.java) ?: return null
isResultOf = true
}
if (!EXTRACTING_FROM_OBJECT.test(methodCallExpression)) {
return emptyList()
}
val containingClass = PsiTypesUtil.getPsiClass(findActualType(methodCallExpression)) ?: return null
return if (isResultOf) lookupMethod(containingClass, literal) else lookupFieldOrProperty(containingClass, literal, 0)
}
}
class IterablePropertyOrFieldReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> = findAndCreateReferences(element, ::findReferences)
fun findReferences(element: PsiLiteralExpression): List<Pair<TextRange, List<PsiElement>>>? {
val literal = element.value as? String ?: return null
var methodCallExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression::class.java) ?: return null
var isResultOf = false
if (BY_NAME.test(methodCallExpression)) {
methodCallExpression = PsiTreeUtil.getParentOfType(methodCallExpression, PsiMethodCallExpression::class.java) ?: return null
} else if (RESULT_OF.test(methodCallExpression)) {
methodCallExpression = PsiTreeUtil.getParentOfType(methodCallExpression, PsiMethodCallExpression::class.java) ?: return null
isResultOf = true
}
if (!CallMatcher.anyOf(EXTRACTING_FROM_ITERABLE, FLAT_EXTRACTING_FROM_ITERABLE).test(methodCallExpression)) return null
val iterableType = findActualType(methodCallExpression) ?: return null
if (iterableType.parameters.isEmpty()) return null
val innerType = iterableType.resolveGenerics().substitutor.substitute(iterableType.parameters[0])
val containingClass = PsiTypesUtil.getPsiClass(innerType) ?: return null
return if (isResultOf) lookupMethod(containingClass, literal) else lookupFieldOrProperty(containingClass, literal, 0)
}
}
class IterableResultOfReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> = findAndCreateReferences(element, ::findReferences)
fun findReferences(element: PsiLiteralExpression): List<Pair<TextRange, List<PsiElement>>>? {
val literal = element.value as? String ?: return null
val methodCallExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression::class.java) ?: return null
if (!EXTRACTING_RESULT_OF_FROM_ITERABLE.test(methodCallExpression)) return null
val iterableType = findActualType(methodCallExpression) ?: return null
val innerType = iterableType.resolveGenerics().substitutor.substitute(iterableType.parameters[0])
val containingClass = PsiTypesUtil.getPsiClass(innerType) ?: return null
return lookupMethod(containingClass, literal)
}
}
}

View File

@ -1,28 +1,35 @@
<idea-plugin>
<id>de.platon42.cajon</id>
<name>Concise AssertJ Optimizing Nitpicker (Cajon)</name>
<vendor email="chrisly@platon42.de" url="https://github.com/chrisly42/cajon-plugin">Platon42</vendor>
<vendor email="chrisly@platon42.de" url="https://git.platon42.de/chrisly42/cajon-plugin">Chris 'platon42' Hodges</vendor>
<description><![CDATA[
Cajon is an IntelliJ IDEA Plugin for shortening and optimizing AssertJ assertions.
It adds inspections and quick fixes to fully make use of the AssertJ methods
to make the intention clear and concise. It can also convert JUnit 4 assertions to AssertJ.
It adds several <b>inspections and quick fixes</b> to fully use the fluent assertion methods
and thus makes the intention clear and concise, also generating better messages on test failures.
It can also be used to <b>convert JUnit 4 assertions and assumptions to AssertJ</b>.
It supports <b>referencing inside extracting</b>()-methods with strings, adding refactoring safety.
<b>Bogus or twisted assertions</b> are also reported.
<p>
<a href="https://github.com/chrisly42/cajon-plugin/blob/master/README.md">Full documentation here...</a>
]]></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">
<psi.referenceContributor implementation="de.platon42.intellij.plugins.cajon.references.ExtractorReferenceContributor"/>
<localInspection groupPath="Java" shortName="AssertThatObjectIsNullOrNotNull" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectIsNullOrNotNullInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBooleanIsTrueOrFalse" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBooleanIsTrueOrFalseInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBooleanCondition" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBooleanConditionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatInvertedBooleanCondition" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatInvertedBooleanConditionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatIsZeroOne" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatIsZeroOneInspection"/>
<localInspection groupPath="Java" shortName="AssertThatInstanceOf" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatInstanceOfInspection"/>
<localInspection groupPath="Java" shortName="AssertThatStringIsEmpty" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatStringIsEmptyInspection"/>
<localInspection groupPath="Java" shortName="AssertThatEnumerableIsEmpty" enabledByDefault="true" level="WARNING"
@ -31,8 +38,40 @@
<localInspection groupPath="Java" shortName="AssertThatSize" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatSizeInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBinaryExpressionIsTrueOrFalse" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBinaryExpressionIsTrueOrFalseInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBinaryExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBinaryExpressionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatObjectExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectExpressionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatComparable" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatComparableInspection"/>
<localInspection groupPath="Java" shortName="AssertThatStringExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatStringExpressionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatCollectionOrMapExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatCollectionOrMapExpressionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatFileExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatFileExpressionInspection"/>
<localInspection groupPath="Java" shortName="AssertThatPathExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatPathExpressionInspection"/>
<localInspection groupPath="Java" shortName="JoinAssertThatStatements" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JoinAssertThatStatementsInspection"/>
<localInspection groupPath="Java" shortName="JoinVarArgsContains" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JoinVarArgsContainsInspection"/>
<localInspection groupPath="Java" shortName="AssumeThatInsteadOfReturn" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssumeThatInsteadOfReturnInspection"/>
<localInspection groupPath="Java" shortName="AssertThatJava8Optional" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatJava8OptionalInspection"/>
<localInspection groupPath="Java" shortName="AssertThatGuavaOptional" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatGuavaOptionalInspection"/>
<localInspection groupPath="Java" shortName="ImplicitAssertion" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.ImplicitAssertionInspection"/>
<localInspection groupPath="Java" shortName="TwistedAssertion" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.TwistedAssertionInspection"/>
<localInspection groupPath="Java" shortName="BogusAssertion" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.BogusAssertionInspection"/>
<localInspection groupPath="Java" shortName="JUnitAssertToAssertJ" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JUnitAssertToAssertJInspection"/>
@ -40,4 +79,4 @@
<actions>
</actions>
</idea-plugin>
</idea-plugin>

View File

@ -4,7 +4,6 @@ Turns a binary expression in the kind of assertThat(actual &lt;operator&gt; expe
into assertThat(actual).is&lt;operator&gt;(expected).
<!-- tooltip end -->
There are over 150 combinations that are found with this inspections.
It also detects assertThat(actual.equals(expected)) assertions.
Also works with constant expressions on the expected side.
Swaps actual and expected when actual is a constant expression (correctly transforming the used operator).
</body>

View File

@ -2,6 +2,6 @@
<body>
Turns assertThat(condition).isEqualTo(true/false) into assertThat(condition).isTrue()/isFalse().
<!-- tooltip end -->
Also works with constant expressions and Boolean.TRUE/FALSE.
<br>Also works with constant expressions and Boolean.TRUE/FALSE.
</body>
</html>

View File

@ -0,0 +1,18 @@
<html>
<body>
Turns assertThat(collectionOrMap.someMethod(arg)).isTrue/isFalse() into assertThat(collectionOrMap).someMethod(arg) and
assertThat(map.get(key)).isEqualTo/isNotEqualTo(value) into assertThat(map).containsEntry(key, value).
<!-- tooltip end -->
<br>someMethod() can be isEmpty(), contains(), and containsAll() for collections and
isEmpty(), containsKey(), and containsValue() for maps.
get() may be transformed into containsKey(), doesNotContainKey(), containsEntry() or doesNotContainEntry().
<br>
If you are using degenerated maps in your project that may contain null values (i.e.
map.contains(key) == true AND map.get(key) == null
is valid for some entries in your map), the default behavior of the quickfix
assertThat(map.get(key)).isNull() turning into assertThat(map).doesNotContainKey(key)
is not an equivalent transformation. The settings below can change this behavior to instead transform it into
assertThat(map).containsEntry(key, null) for those cases, create both quickfix choices or simply ignore this
case altogether (if in doubt).
</body>
</html>

View File

@ -0,0 +1,5 @@
<html>
<body>
Turns assertThat(obj1.compareTo(obj2)) into assertThat(obj1).someMethod(obj2).
</body>
</html>

View File

@ -1,6 +1,6 @@
<html>
<body>
Turns assertThat(enumerable).hasSize(0) into assertThat(enumerable).isEmpty().
Turns assertThat(enumerable).hasSize(0) and similar into assertThat(enumerable).isEmpty() or .isNotEmpty().
<!-- tooltip end -->
<br>Works with anything that is enumerable such as arrays, iterables, collections, etc.
</body>

View File

@ -0,0 +1,8 @@
<html>
<body>
Operates on assertions on objects of type File. Turns assertThat(file.someMethod(arg)).someAssertion() into assertThat(file).someMethod(arg).
<!-- tooltip end -->
<br>someMethod() can be canRead(), canWrite(), exists(), isAbsolute(), isDirectory(), isFile(),
getName(), getParent(), getParentFile(), list() and listFiles().
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Looks at expected and actual expression being of Guava Optional type and whether the statement effectively tries to assert the
presence, absence or content and then replaces the statement by isPresent(), isAbsent(), or contains().
<!-- tooltip end -->
<br>Requires AssertJ-Guava to be in classpath.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Turns assertThat(object instanceof classname).isEqualTo(true/false) into assertThat(object).is(Not)InstanceOf(classname.class).
<!-- tooltip end -->
<br>Also works with constant expressions and Boolean.TRUE/FALSE.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Turns assertThat(!condition).isEqualTo(true/false) into assertThat(condition).isFalse()/isTrue() and negates .is(Not)EqualTo(!var).
<!-- tooltip end -->
<br>Also works with constant expressions and Boolean.TRUE/FALSE.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Turns assertThat(numeric).isEqualTo(0/1) into assertThat(numeric).isZero()/isOne()
or assertThat(numeric).isNotEqualTo(0) into assertThat(numeric).isNotZero().
<!-- tooltip end -->
<br>Also works with constant expressions.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Looks at expected and actual expression being of Java 8 Optional type and whether the statement effectively tries to assert the
presence, absence or content and then replaces the statement by isPresent(), isNotPresent(), contains(), or containsSame().
<!-- tooltip end -->
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<body>
Tries to simplify Object method calls such as toString, hashCode() and equals().
<!-- tooltip end -->
<br>Turns assertThat(object.toString()).isEqualTo(expression) into assertThat(object).hasToString(expression).
<br>Turns assertThat(object.hashCode()).isEqualTo(otherObject.hashCode()) into assertThat(object).hasSameHashCodeAs(otherObject).
<br>Turns assertThat(object.equals(otherObject)).isEqualTo(true) into assertThat(object).isEqualTo(otherObject) (and variations).
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Operates on assertions on objects of type Path. Turns assertThat(file.someMethod(arg)).someAssertion() into assertThat(path).someMethod(arg).
<!-- tooltip end -->
<br>someMethod() can be isAbsolute(), getParent(), startsWith() and endsWith().
</body>
</html>

View File

@ -1,6 +1,7 @@
<html>
<body>
Makes assertions on sizes of arrays or collections more concise by replacing them with isEmpty(), isNotEmpty(), hasSize(), or hasSameSizeAs().
Makes assertions on sizes of arrays, collections, maps, strings, or CharSequences more concise by replacing them with isEmpty(), isNotEmpty(), hasSize(), or hasSameSizeAs().
<!-- tooltip end -->
<br>Several more conversions are available with AssertJ 3.12.0 or later.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Turns assertThat(string.someMethod(arg)).isTrue/isFalse() into assertThat(string).someMethod(arg).
<!-- tooltip end -->
<br>someMethod() can be isEmpty(), equals(), equalsIgnoreCase(), contentEquals(), contains(), startsWith(), endsWith(),
matches(), compareToIgnoreCase(), indexOf(), and trim().
</body>
</html>

View File

@ -0,0 +1,18 @@
<html>
<body>
Tries to detect bogus uses of return statements in test methods and replaces them by assumeThat() calls.
<!-- tooltip end -->
<br>
Novices will use these to skip test execution by bailing out early on some preconditions not met.
However, this suggests that the test has actually been run and passed instead of showing the test
as being skipped.
<p>Return statements in if statements in main test methods
(must be annotated with JUnit 4 or Jupiter @Test annotations)
will be verified to have at least one assertThat() statement in the code flow.
Method calls within the same class will be examined for assertThat() statements, too.
However, at most 50 statements and down to five recursions will be tolerated before giving up.
</p>
<p>Currently, the quickfix may lose some comments during operation. The other branch of the if statement
will be inlined (blocks with declarations will remain a code block due to variable scope).</p>
</body>
</html>

View File

@ -0,0 +1,6 @@
<html>
<body>
Finds typical copy and paste errors where the assertion will never fail, such as assertThat(foo).isEqualTo(foo), because actual
and expected expressions are the same.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Finds assertion method calls that are already implicitly covered by prior assertions.
<!-- tooltip end -->
This works for isNotNull(), isNotEmpty() and isPresent(). It does not cover all edge cases, such as .isNotNull().isNullOrEmpty()
or comparing against non-null literals and is conservative to not eliminate legitimate assertions.
</body>
</html>

View File

@ -1,10 +1,12 @@
<html>
<body>
Tries to convert most of the JUnit 4 assertions to AssertJ-Format.
Tries to convert most of the JUnit 4 assertions and assumptions to AssertJ format.
<!-- tooltip end -->
<br>Works for assertTrue(), assertFalse(), assertNull(), assertNotNull(), assertEquals(), assertNotEquals(), assertSame() assertNotSame(), assertArrayEquals().
Copes with variants with message and without, handles special versions for double and float types (including arrays).
<p></p>
Does not support Hamcrest-Matchers. If you need that kind of conversion, you might want to check out the Assertions2AssertJ plugin by Ric Emery.
Also converts assumeTrue(), assumeFalse(), assumeNotNull(), assumeNoException() to assumeThat().
<p>
Does not support Hamcrest-Matchers. If you need that kind of conversion, you might want to check out the Assertions2AssertJ plugin by Ric Emery.
</p>
</body>
</html>

View File

@ -0,0 +1,10 @@
<html>
<body>
Joins consecutive assertThat() statements with the same actual expression together.
<!-- tooltip end -->
<br>If the AssertThat()-Statement contains .extracting() methods, they will not be joined.
<br>During joining multiple statements, line breaks may be added to avoid too long lines. The number of statements to join without
adding line breaks can be configured.
<br>Also retains comments during operation and forces a line break for these cases.
</body>
</html>

View File

@ -0,0 +1,11 @@
<html>
<body>
Finds assertions where multiple .contains(), .containsOnlyOnce() or .doesNotContain() are
used in a single statement that could be joined together.
<!-- tooltip end -->
Only works when variadic arguments are possible and will not be performed on more complex
statements with .extracting() or .as() to avoid changing semantics.
<br>
Note that the quickfix does not handle comments very well and might remove them during the operation.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Finds assertion method calls that have the expected and actual expressions twisted, such as assertThat(5).isEqualTo(foo).
<!-- tooltip end -->
For some obvious cases, a quickfix to swap the actual and expected expressions is provided.
</body>
</html>

View File

@ -0,0 +1,151 @@
package de.platon42.intellij.jupiter
import com.intellij.jarRepository.JarRepositoryManager
import com.intellij.jarRepository.RemoteRepositoryDescription
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
import com.intellij.testFramework.rules.TestNameExtension
import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.RegisterExtension
import java.nio.file.Paths
import java.util.*
import java.util.function.Consumer
import java.util.stream.Collectors
import java.util.stream.Stream
abstract class AbstractJUnit5TestCase {
@RegisterExtension
protected val testNameRule = TestNameExtension()
@RegisterExtension
protected val edtInterceptorExtension = EdtInterceptorExtension()
protected fun getTestName(lowercaseFirstLetter: Boolean): String {
return PlatformTestUtil.getTestName(testNameRule.methodName, lowercaseFirstLetter)
}
@RegisterExtension
private val testCase = object : LightJavaCodeInsightFixtureTestCase(), BeforeEachCallback, AfterEachCallback {
lateinit var extensionContext: ExtensionContext
override fun getProjectDescriptor(): LightProjectDescriptor {
val testJdk = getMethodOrClassAnnotation(TestJdk::class.java) ?: return super.getProjectDescriptor()
val projectDescriptor: DefaultLightProjectDescriptor = object : DefaultLightProjectDescriptor({ IdeaTestUtil.getMockJdk(testJdk.value.toJavaVersion()) }) {
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
super.configureModule(module, model, contentEntry)
val localJars = getMethodOrClassAnnotation(AddLocalJarToModule::class.java)
if (localJars != null) {
localJars.value.forEach {
addJarContaining(
model,
it.java
)
}
}
val mavenDependencies = getMethodOrClassAnnotations(AddMavenDependencyToModule::class.java)
mavenDependencies.forEach(Consumer { it: AddMavenDependencyToModule ->
addFromMaven(
model,
it.value,
it.includeTransitiveDependencies,
it.scope
)
})
}
}
return projectDescriptor
}
fun addJarContaining(model: ModifiableRootModel?, clazz: Class<*>) {
val filename = clazz.getResource(clazz.simpleName + ".class").file
val jarName = filename.substring(0, filename.indexOf(".jar") + 4).removePrefix("file:")
val jarPath = Paths.get(jarName)
val jarFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(jarName)
myFixture.allowTreeAccessForFile(jarFile!!)
PsiTestUtil.addLibrary(
model!!,
jarPath.fileName.toString().replace(".jar", ""),
jarPath.parent.toString(),
jarPath.fileName.toString()
)
}
fun addFromMaven(
model: ModifiableRootModel, mavenCoordinates: String,
includeTransitiveDependencies: Boolean, dependencyScope: DependencyScope?
) {
val remoteRepositoryDescriptions = RemoteRepositoryDescription.DEFAULT_REPOSITORIES
val libraryProperties = RepositoryLibraryProperties(mavenCoordinates, includeTransitiveDependencies)
val roots =
JarRepositoryManager.loadDependenciesModal(model.project, libraryProperties, false, false, null, remoteRepositoryDescriptions)
val tableModel = model.moduleLibraryTable.modifiableModel
val library = tableModel.createLibrary(mavenCoordinates)
val libraryModel = library.modifiableModel
check(!roots.isEmpty()) { String.format("No roots for '%s'", mavenCoordinates) }
for (root in roots) {
libraryModel.addRoot(root.file, root.type)
}
val libraryOrderEntry = model.findLibraryOrderEntry(library) ?: throw java.lang.IllegalStateException("Unable to find registered library $mavenCoordinates")
libraryOrderEntry.scope = dependencyScope!!
libraryModel.commit()
tableModel.commit()
}
override fun getTestDataPath(): String {
val testDataPath = getMethodOrClassAnnotation(TestDataPath::class.java) ?: return super.getTestDataPath()
val testDataSubPath = getMethodOrClassAnnotation(TestDataSubPath::class.java) ?: return testDataPath.value
return Paths.get(testDataPath.value, testDataSubPath.value).toString()
}
fun getMyFixture(): JavaCodeInsightTestFixture = myFixture
private fun <T : Annotation?> getMethodOrClassAnnotation(clazz: Class<T>): T? {
var annotation = extensionContext.requiredTestMethod.getAnnotation(clazz)
if (annotation == null) {
annotation = extensionContext.requiredTestClass.getAnnotation(clazz)
}
return annotation
}
private fun <T : Annotation?> getMethodOrClassAnnotations(clazz: Class<T>): List<T> {
return Stream.of(
extensionContext.requiredTestMethod.getAnnotationsByType(clazz),
extensionContext.requiredTestClass.getAnnotationsByType(clazz)
)
.flatMap { array: Array<T>? -> Arrays.stream(array) }
.collect(Collectors.toList())
}
override fun beforeEach(context: ExtensionContext) {
extensionContext = context
setUp()
}
override fun afterEach(context: ExtensionContext) {
tearDown()
}
}
protected val fixture: JavaCodeInsightTestFixture get() = testCase.getMyFixture()
}

View File

@ -1,10 +0,0 @@
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,9 @@
package de.platon42.intellij.jupiter
import java.lang.annotation.Inherited
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
annotation class AddLocalJarToModule(vararg val value: KClass<*>)

View File

@ -0,0 +1,17 @@
package de.platon42.intellij.jupiter
import com.intellij.openapi.roots.DependencyScope
import java.lang.annotation.Inherited
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
@JvmRepeatable(
AddMavenDependencyToModule.List::class
)
annotation class AddMavenDependencyToModule(val value: String, val includeTransitiveDependencies: Boolean = false, val scope: DependencyScope = DependencyScope.COMPILE) {
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
annotation class List(vararg val value: AddMavenDependencyToModule)
}

View File

@ -0,0 +1,50 @@
package de.platon42.intellij.jupiter
import com.intellij.testFramework.TestLoggerFactory
import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy
import com.intellij.testFramework.runInEdtAndWait
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.InvocationInterceptor
import org.junit.jupiter.api.extension.ReflectiveInvocationContext
import java.lang.reflect.Method
class EdtInterceptorExtension : InvocationInterceptor {
override fun interceptTestMethod(
invocation: InvocationInterceptor.Invocation<Void>,
invocationContext: ReflectiveInvocationContext<Method>,
extensionContext: ExtensionContext
) {
val throwables = arrayOfNulls<Throwable>(1)
val runnable = Runnable {
try {
TestLoggerFactory.onTestStarted()
invocation.proceed()
TestLoggerFactory.onTestFinished(true, extensionContext.displayName)
} catch (e: Throwable) {
TestLoggerFactory.onTestFinished(false, extensionContext.displayName)
throwables[0] = e
}
}
invokeTestRunnable(runnable)
if (throwables[0] != null) {
throw throwables[0]!!
}
}
companion object {
private fun invokeTestRunnable(runnable: Runnable) {
val policy = IdeaTestExecutionPolicy.current()
if (policy != null && !policy.runInDispatchThread()) {
runnable.run()
} else {
runInEdtAndWait {
runnable.run()
}
}
}
}
}

View File

@ -1,169 +0,0 @@
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(
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

@ -1,11 +0,0 @@
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

@ -1,11 +0,0 @@
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 {
}

Some files were not shown because too many files have changed in this diff Show More