Compare commits

...
43 Commits
Author SHA1 Message Date
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 341d1877df Prepared for 1.4 release. 2019-08-25 18:40:58 +02:00
chrisly42 05dc4905ca Extended AssertThatCollectionOrMap inspection for several assertThat(map.get()) cases as suggested by Stefan H. 2019-08-23 19:58:11 +02:00
chrisly42 315b93d90d Extended AssertThatSize inspection to Maps, too. 2019-08-23 18:21:08 +02:00
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 5048c898ec Fixed broken code in test classes (missing imports, undefined variables). 2019-07-30 19:39:16 +02:00
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 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
chrisly42 e797dc2515 Extended documentation about assumeThat() preconditions. 2019-05-16 20:06:14 +02:00
chrisly42 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
chrisly42 94c695617a Improved JoinAssertThatStatements detection of expressions with side-effects and added pre/post-increment/decrement detection. 2019-05-11 13:40:37 +02:00
chrisly42 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
chrisly42 0e2007641c Compatibility fixes for IDEA <= 2019.1 :-/ 2019-05-05 20:06:12 +02:00
chrisly42 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
chrisly42 66f1467b23 Added coveralls. 2019-05-04 16:40:05 +02:00
chrisly42 6a36294a2b Added travis Build Status to readme. 2019-05-04 16:15:36 +02:00
chrisly42 0b79a6d7dc Added early bail-outs via simple text matchings to optimize speed of false positive detection. 2019-05-04 15:54:27 +02:00
chrisly42 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
chrisly42 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
chrisly42 e55acf9c74 Extended test cases for AssertThatBooleanCondition and AssertThatObjectIsNullOrNotNull. 2019-04-29 20:10:55 +02:00
chrisly42 3ece81b024 Minor fixes to descriptions. 2019-04-29 19:58:05 +02:00
chrisly42 0b2ce470db Fixed missing description for JoinAssertThatStatements and detection of equivalent expressions. 2019-04-29 19:58:00 +02:00
chrisly42 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
chrisly42 666e373405 Extended AssertThatSize inspection to transform hasSize() into hasSameSizeAs(), if possible. 2019-04-23 20:16:43 +02:00
chrisly42 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
chrisly42 66508ceb2c New AssertThatInstanceOf inspection that moves instanceof expressions out of assertThat(). Fixes to documentation. 2019-04-22 19:03:59 +02:00
chrisly42 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
chrisly42 a707eee9ad Extended AssertThatSize intention to take strings and CharSequences into account, too. 2019-04-20 09:04:38 +02:00
chrisly42 533c20906a Added a note on referencing. Moved AssertThatStringExpression to a more appropriate place in documentation. 2019-04-19 19:44:40 +02:00
chrisly42 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
chrisly42 faeb509797 Fixed default method not available in IDEA <2018.3. Sigh :-( 2019-04-18 22:29:30 +02:00
chrisly42 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
chrisly42 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
150 changed files with 6405 additions and 1503 deletions
+10
View File
@@ -0,0 +1,10 @@
language: java
jdk:
- openjdk8
before_script:
- chmod +x gradlew
- chmod +x gradle/wrapper/gradle-wrapper.jar
script:
- ./gradlew clean test jacocoTestReport coveralls
+465 -53
View File
@@ -1,15 +1,17 @@
# Cajon - Concise AssertJ Optimizing Nitpicker # Cajon - Concise AssertJ Optimizing Nitpicker [![Build Status](https://travis-ci.org/chrisly42/cajon-plugin.svg?branch=master)](https://travis-ci.org/chrisly42/cajon-plugin) [![Coverage Status](https://coveralls.io/repos/github/chrisly42/cajon-plugin/badge.svg?branch=master)](https://coveralls.io/github/chrisly42/cajon-plugin?branch=master)
Cajon is an IntelliJ IDEA Plugin for shortening and optimizing AssertJ assertions. Cajon is an IntelliJ IDEA Plugin for shortening and optimizing [AssertJ](https://assertj.github.io/doc/) assertions.
## Why? ## Purpose
First, code is easier to read, when it is concise and reflects the intention clearly. First, code is easier to read, when it is concise and reflects the intention clearly.
AssertJ has plenty of different convenience methods that describing various intentions precisely. AssertJ has plenty of different convenience methods that describing various intentions precisely.
Why write longer, more complex code that can be expressed in brevity? Why write longer, more complex code that can be expressed in brevity?
Second, AssertJ is able to output more meaningful descriptions when an assertion fails. Second, when using the available special assertion methods of AssertJ, a failure of a condition
can be expressed in better detail and with more meaningful descriptions.
This makes finding bugs and fixing failed tests more efficient. This makes finding bugs and fixing failed tests more efficient.
Nobody likes to read failures of the kind "failed because true is not false".
For example: For example:
@@ -17,7 +19,7 @@ For example:
assertThat(collection.size()).isEqualTo(5); assertThat(collection.size()).isEqualTo(5);
``` ```
If the collection has more or less than 5 elements, the assertion will fail, but will not If the collection has more or less than five elements, the assertion will fail, but will not
tell you about the contents, making it hard to guess what went wrong. tell you about the contents, making it hard to guess what went wrong.
Instead, if you wrote the same assertion the following way: Instead, if you wrote the same assertion the following way:
@@ -26,24 +28,91 @@ Instead, if you wrote the same assertion the following way:
assertThat(collection).hasSize(5); assertThat(collection).hasSize(5);
``` ```
Then AssertJ would tell you the contents of the collection on failure. Then AssertJ would tell you the _actual contents_ of the collection on failure.
## Conversion of JUnit assertions to AssertJ ## Conversion of JUnit assertions to AssertJ
The plugin also supports the conversion of the most common JUnit 4 assertions to AssertJ. The plugin also supports the conversion of the most common JUnit 4 assertions to AssertJ.
## Lookup and refactoring of string-based extracting()
AssertJ allows [extracting POJO fields/properties on iterables/arrays](http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#extracted-properties-assertion).
Using strings is not safe for refactoring (and before Java 8 Lambdas were available,
creating extractor functions just for testing purpose was a bit too tedious).
This plugin adds support for referencing these fields (so you can ctrl(/cmd)-click on the
string to go to the definition) and also allows safe refactoring on the
fields (refactoring a getter method without a corresponding field will not work
correctly right now).
## Usage ## Usage
The plugin will report inspections in your opened editor file as warnings. The plugin will report inspections in your opened editor file as warnings.
You can then quick-fix these with your quick-fix hotkey (usually Alt-Return or Opt-Return). You can then quick-fix these with your quick-fix hotkey (usually Alt-Return or Opt-Return).
Or, you can use the "Run Inspection by Name..." action to run one inspection on a bigger scope (e.g. the whole project). Or, you can use the "Run Inspection by Name..." action to run one inspection on a bigger scope (e.g. the whole project).
Applying a quick fix might result in further optimization possibilities, so
you might need to perform a couple of fixes before you get to the final result.
Check out this example where every line represents the result after a Cajon quickfix:
```
assertFalse(!(array.length == collection.size()));
assertThat(!(array.length == collection.size())).isFalse();
assertThat(array.length == collection.size()).isTrue();
assertThat(array.length).isEqualTo(collection.size());
assertThat(array).hasSameSizeAs(collection);
```
You can toggle the various inspections in the Settings/Editor/Inspections in the AssertJ group. You can toggle the various inspections in the Settings/Editor/Inspections in the AssertJ group.
## Implemented inspections ## Implemented inspections and quickfixes
- JoinAssertThatStatements
Joins multiple ```assertThat()``` statements with same actual expression together.
```
from: assertThat(expected).someCondition();
assertThat(expected).anotherCondition();
to: assertThat(expected).someCondition()
.anotherCondition();
```
Joining will work on actual expressions inside ```assertThat()``` that are equivalent expressions,
except for method calls with known side-effect methods such as ```Iterator.next()``` and
pre/post-increment/decrement operations -- please notify me about others.
The comments of the statements will be preserved. When using ```extracting()``` or similar,
the statements will not be merged.
The behavior regarding the insertion of line breaks between the expressions can be configured in the
inspection settings.
- JoinVarArgsContains
Looks for ```.contains()```, ```.doesNotContain()```, and .```containsOnlyOnce()``` calls for iterables
within the same statement. The available quickfix can join the arguments to variadic version of the call
and remove the surplus one.
```
from: assertThat(expected).contains("foo").doesNotContain("bar").contains("etc").doesNotContain("huh");
to: assertThat(expected).contains("foo", "etc").doesNotContain("bar", "huh");
```
Will not be performed on more complex statements with ```.extracting()``` or ```.as()``` to avoid
changing semantics or losing descriptions.
Note that the quickfix does not handle comments very well and might remove them during the operation.
You may need to perform some manual reformatting, if the line gets too long after applying the fix.
- AssertThatObjectIsNullOrNotNull - AssertThatObjectIsNullOrNotNull
Uses ```isNull()``` and ```isNotNull()``` instead.
``` ```
from: assertThat(object).isEqualTo(null); from: assertThat(object).isEqualTo(null);
to: assertThat(object).isNull(); to: assertThat(object).isNull();
@@ -52,26 +121,157 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
to: assertThat(object).isNotNull(); to: assertThat(object).isNotNull();
``` ```
- AssertThatBooleanIsTrueOrFalse - AssertThatBooleanCondition
Uses ```isTrue()``` and ```isFalse()``` instead.
``` ```
from: assertThat(booleanValue).isEqualTo(true/false/Boolean.TRUE/Boolean.FALSE); from: assertThat(booleanValue).isEqualTo(true/false/Boolean.TRUE/Boolean.FALSE);
to: assertThat(booleanValue).isTrue()/isFalse(); to: assertThat(booleanValue).isTrue()/isFalse();
``` ```
- AssertThatInvertedBooleanCondition
Inverts the boolean condition to make it more readable.
```
from: assertThat(!booleanValue).isEqualTo(true/false/Boolean.TRUE/Boolean.FALSE);
from: assertThat(!booleanValue).isTrue()/isFalse();
to: assertThat(booleanValue).isFalse()/isTrue();
```
- AssertThatInstanceOf
Moves ```instanceof``` expressions out of ```assertThat()```.
```
from: assertThat(object instanceof classname).isEqualTo(true);
from: assertThat(object instanceof classname).isTrue();
to: assertThat(object).isInstanceOf(classname.class);
from: assertThat(object instanceof classname).isEqualTo(false);
from: assertThat(object instanceof classname).isFalse();
to: assertThat(object).isNotInstanceOf(classname.class);
```
- AssertThatStringIsEmpty - AssertThatStringIsEmpty
Uses ```isEmpty()``` for empty string assertions.
``` ```
from: assertThat(charSequence/string).isEqualTo(""); from: assertThat(charSequence/string).isEqualTo("");
from: assertThat(charSequence/string).hasSize(0); from: assertThat(charSequence/string).hasSize(0);
to: assertThat(charSequence/string).isEmpty(); to: assertThat(charSequence/string).isEmpty();
``` ```
The ```assertThat(string.length()).isEqualTo(0);``` case is handled in the AssertThatSize inspection.
- AssertThatStringExpression
Moves string operations inside ```assertThat()``` out.
```
from: assertThat(stringActual.isEmpty()).isTrue();
to: assertThat(stringActual).isEmpty();
from: assertThat(stringActual.equals(stringExpected)).isTrue();
from: assertThat(stringActual.contentEquals(charSeqExpected)).isTrue();
to: assertThat(stringActual).isEqualTo(stringExpected);
from: assertThat(stringActual.equalsIgnoreCase(stringExpected)).isTrue();
to: assertThat(stringActual).isEqualToIgnoringCase(stringExpected);
from: assertThat(stringActual.contains(stringExpected)).isTrue();
to: assertThat(stringActual).contains(stringExpected);
from: assertThat(stringActual.startsWith(stringExpected)).isTrue();
to: assertThat(stringActual).startsWith(stringExpected);
from: assertThat(stringActual.endsWith(stringExpected)).isTrue();
to: assertThat(stringActual).endsWith(stringExpected);
```
Analogously with ```isFalse()```.
- AssertThatObjectExpression
Handles equals(), toString() and hashCode() inside an expected expression.
```
from: assertThat(objActual.equals(objExpected)).isTrue();
to: assertThat(objActual).isEqualTo(objExpected);
from: assertThat(objActual.toString()).isEqualTo(stringExpected);
to: assertThat(objActual).hasToString(stringExpected);
from: assertThat(objActual.hashCode()).isEqualTo(objExpected.hashCode());
to: assertThat(objActual).hasSameHashCodeAs(objExpected);
```
- AssertThatCollectionOrMapExpression
Moves collection and map operations inside ```assertThat()``` out.
```
from: assertThat(collection.isEmpty()).isTrue();
to: assertThat(collection).isEmpty();
from: assertThat(collection.contains("foobar")).isTrue();
to: assertThat(collection).contains("foobar");
from: assertThat(collection.containsAll(otherCollection)).isTrue();
to: assertThat(collection).containsAll(otherCollection);
from: assertThat(map.isEmpty()).isTrue();
to: assertThat(map).isEmpty();
from: assertThat(map.containsKey(key)).isTrue();
to: assertThat(map).containsKey(key);
from: assertThat(map.containsValue(value)).isTrue();
to: assertThat(map).containsValue(value);
```
Analogously with ```isFalse()``` (except for ```containsAll()```).
Additional transformations for maps:
```
from: assertThat(map.get(key)).isEqualTo(value);
to: assertThat(map).containsEntry(key, value);
from: assertThat(map.get(key)).isNotEqualTo(value);
to: assertThat(map).doesNotContainEntry(key, value);
from: assertThat(map.get(key)).isNotNull();
to: assertThat(map).containsKey(key);
from: assertThat(map.get(key)).isNull();
to: assertThat(map).doesNotContainKey(key);
```
The last transformation is the default, but may not be 100% equivalent depending whether the map
is a degenerated case with ```null``` values, where ```map.get(key)``` returns ```null```,
but ```containsKey(key)``` is ```true```.
For that special case (which usually is the result of a bad design decision!)
the quickfix should rather generate ```assertThat(map).containsEntry(key, null)```.
Therefore, the behavior can be configured in the settings for this inspection to either
create the default case (```doesNotContainKey```), the degenerated case (```containsEntry```),
choosing between both fixes (does not work well for batch processing), or ignore this edge case
altogether (just to be sure to not break any code).
- AssertThatEnumerableIsEmpty - AssertThatEnumerableIsEmpty
Uses ```isEmpty()``` for ```hasSize(0)``` iterable assertions instead.
``` ```
from: assertThat(enumerable).hasSize(0); from: assertThat(enumerable).hasSize(0);
to: assertThat(enumerable).isEmpty(); to: assertThat(enumerable).isEmpty();
``` ```
- AssertThatSize - AssertThatSize
Makes assertions on sizes of arrays, collections, maps, strings,
or ```CharSequence```s more concise.
``` ```
from: assertThat(array.length).isEqualTo(0); from: assertThat(array.length).isEqualTo(0);
from: assertThat(array.length).isLessThanOrEqualTo(0); from: assertThat(array.length).isLessThanOrEqualTo(0);
@@ -86,9 +286,12 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
from: assertThat(array.length).isEqualTo(anotherArray.length); from: assertThat(array.length).isEqualTo(anotherArray.length);
to: assertThat(array).hasSameSizeAs(anotherArray); to: assertThat(array).hasSameSizeAs(anotherArray);
from: assertThat(array).hasSize(anotherArray.length);
to: assertThat(array).hasSameSizeAs(anotherArray);
``` ```
with AssertJ 13.2.0 or higher and additionally with AssertJ 3.12.0 or later
``` ```
from: assertThat(array.length).isLessThanOrEqualTo(expression); from: assertThat(array.length).isLessThanOrEqualTo(expression);
@@ -103,16 +306,29 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
from: assertThat(array.length).isGreaterThanOrEqualTo(expression); from: assertThat(array.length).isGreaterThanOrEqualTo(expression);
to: assertThat(array).hasSizeGreaterThanOrEqualTo(expression); to: assertThat(array).hasSizeGreaterThanOrEqualTo(expression);
``` ```
and analogously for collections, maps, strings and CharSequences, e.g:
and analogously for collections... ```
from: assertThat("string".length()).isLessThan(1);
to: assertThat("string").isEmpty();
from: assertThat(map.size()).isEqualTo(anotherMap.size())
to: assertThat(map).hasSameSizeAs(anotherMap);
from: assertThat("string".length()).hasSize("strong".length())
to: assertThat("string").hasSameSizeAs("strong");
```
- AssertThatBinaryExpression
Splits a boolean condition represented by binary expression out of ```assertThat()```.
- AssertThatBinaryExpressionIsTrueOrFalse
``` ```
from: assertThat(primActual == primExpected).isTrue(); from: assertThat(primActual == primExpected).isTrue();
to: assertThat(primActual).isEqualTo(primExpected); to: assertThat(primActual).isEqualTo(primExpected);
from: assertThat(10 < primActual).isNotEqualTo(false); from: assertThat(10 < primActual).isNotEqualTo(false);
to: assertThat(primActual).isGreaterThan(primExpected); to: assertThat(primActual).isGreaterThan(10);
from: assertThat(objActual != objExpected).isEqualTo(true); from: assertThat(objActual != objExpected).isEqualTo(true);
to: assertThat(objActual).isNotSameAs(objExpected); to: assertThat(objActual).isNotSameAs(objExpected);
@@ -120,9 +336,32 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
from: assertThat(null == objActual).isFalse(); from: assertThat(null == objActual).isFalse();
to: assertThat(objActual).isNotNull(); to: assertThat(objActual).isNotNull();
``` ```
and many, many more combinations (more than 150). ...and many, many more combinations (more than 150).
- ImplicitAssertion
Detects and removes implicit use of ```isNotNull()```, ```isNotEmpty()``` and
```isPresent()``` when followed by an assertion that will implicitly cover this
check.
```
from: assertThat(string).isNotNull().startsWith("foo");
to: assertThat(string).startsWith("foo");
from: assertThat(list).isNotEmpty().hasSize(10);
to: assertThat(list).hasSize(10);
from: assertThat(optional).isPresent().contains("foo");
to: assertThat(optional).contains("foo");
```
...and many more combinations (more than 100).
- AssertThatJava8Optional - AssertThatJava8Optional
Examines the statement for Java 8 ```Optional``` type and whether the statement
effectively tries to assert the presence, absence or content and then
replaces the statement by better assertions.
``` ```
from: assertThat(opt.isPresent()).isEqualTo(true); from: assertThat(opt.isPresent()).isEqualTo(true);
from: assertThat(opt.isPresent()).isNotEqualTo(false); from: assertThat(opt.isPresent()).isNotEqualTo(false);
@@ -140,8 +379,14 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
from: assertThat(opt.get()).isSameAs("foo"); from: assertThat(opt.get()).isSameAs("foo");
to: assertThat(opt).containsSame("foo"); to: assertThat(opt).containsSame("foo");
from: assertThat(opt.orElse(null)).isEqualTo(null);
to: assertThat(opt).isNotPresent();
from: assertThat(opt.orElse(null)).isNotEqualTo(null);
to: assertThat(opt).isPresent();
from: assertThat(opt).isEqualTo(Optional.of("foo")); from: assertThat(opt).isEqualTo(Optional.of("foo"));
from: assertThat(opt).isEqualTo(Optional.ofNullable("foo")); from: assertThat(opt).isEqualTo(Optional.ofNullable("foo")); // only for constant "foo"
to: assertThat(opt).contains("foo"); to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.empty()); from: assertThat(opt).isEqualTo(Optional.empty());
@@ -151,7 +396,99 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
to: assertThat(opt).isPresent(); to: assertThat(opt).isPresent();
``` ```
- AssertThatGuavaOptional
Examines the statement for Google Guava ```Optional``` type and whether the statement
effectively tries to assert the presence, absence or content and then
replaces the statement by better assertions.
```
from: assertThat(opt.isPresent()).isEqualTo(true);
from: assertThat(opt.isPresent()).isNotEqualTo(false);
from: assertThat(opt.isPresent()).isTrue();
to: assertThat(opt).isPresent();
from: assertThat(opt.isPresent()).isEqualTo(false);
from: assertThat(opt.isPresent()).isNotEqualTo(true);
from: assertThat(opt.isPresent()).isFalse();
to: assertThat(opt).isAbsent();
from: assertThat(opt.get()).isEqualTo("foo");
to: assertThat(opt).contains("foo");
from: assertThat(opt.orNull()).isEqualTo(null);
to: assertThat(opt).isAbsent();
from: assertThat(opt.orNull()).isNotEqualTo(null);
to: assertThat(opt).isPresent();
from: assertThat(opt).isEqualTo(Optional.of("foo"));
from: assertThat(opt).isEqualTo(Optional.fromNullable("foo")); // only for constant "foo"
to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.absent());
to: assertThat(opt).isAbsent();
from: assertThat(opt).isNotEqualTo(Optional.absent());
to: assertThat(opt).isPresent();
```
AssertJ for Guava needs to be available in the classpath.
- AssumeThatInsteadOfReturn
Tries to detect bogus uses of return statements in test methods and replaces them by ```assumeThat()``` calls.
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.
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.
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).
The quickfix will only work with AssertJ >= 2.9.0 (for 2.x releases) or >= 3.9.0 (for 3.x releases).
The generated ```assumeThat()``` statement could be optimized further (similar to ```assertThat()```), but
there is currently no support in Cajon for this (you could rename the method to ```assertThat()``` optimize it
and turn it back into ```assumeThat()``` in the end).
Example:
```
@Test
public void check_fuel_emission() {
if (System.getProperty("manufacturer").equals("Volkswagen")) {
return;
}
double nitroxppm = doWltpDrivingCycle();
assertThat(nitroxppm).isLessThan(500.0);
}
```
will be transformed to
```
@Test
public void check_fuel_emission() {
assumeThat(System.getProperty("manufacturer").equals("Volkswagen")).isFalse();
double nitroxppm = doWltpDrivingCycle();
assertThat(nitroxppm).isLessThan(500.0);
}
```
- JUnitAssertToAssertJ - JUnitAssertToAssertJ
Tries to convert most of the JUnit 4 assertions and assumptions to AssertJ format.
Sometimes the expected and actual expressions are specified in wrong order --
Cajon tries to swap these when it detects the supposed actual expression to be a
constant while the expected one is not.
Does not support Hamcrest-Matchers.
If you need that kind of conversion, you might want to check out the
[Assertions2AssertJ plugin](https://plugins.jetbrains.com/plugin/10345-assertions2assertj) by Ric Emery.
``` ```
assertTrue(condition); assertTrue(condition);
assertTrue(message, condition); assertTrue(message, condition);
@@ -159,8 +496,8 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
assertFalse(message, condition); assertFalse(message, condition);
assertNull(object); assertNull(object);
assertNull(message, object); assertNull(message, object);
assertNonNull(object); assertNotNull(object);
assertNonNull(message, object); assertNotNull(message, object);
assertEquals(expected, actual); assertEquals(expected, actual);
assertEquals(message, expected, actual); assertEquals(message, expected, actual);
assertEquals(expectedDoubleOrFloat, actualDoubleOrFloat, delta); assertEquals(expectedDoubleOrFloat, actualDoubleOrFloat, delta);
@@ -177,67 +514,142 @@ You can toggle the various inspections in the Settings/Editor/Inspections in the
assertArrayEquals(message, expectedArray, actualArray); assertArrayEquals(message, expectedArray, actualArray);
assertArrayEquals(expectedDoubleOrFloatArray, actualDoubleOrFloatArray, delta); assertArrayEquals(expectedDoubleOrFloatArray, actualDoubleOrFloatArray, delta);
assertArrayEquals(message, expectedDoubleOrFloatArray, actualDoubleOrFloatArray, delta); assertArrayEquals(message, expectedDoubleOrFloatArray, actualDoubleOrFloatArray, delta);
assumeTrue(condition);
assumeTrue(message, condition);
assumeFalse(condition);
assumeFalse(message, condition);
assumeNotNull(object); // single argument only!
assumeNoException(throwable);
assumeNoException(message, throwable);
``` ```
- AssertThatGuavaOptional ### Implemented referencing
``` ```
from: assertThat(opt.isPresent()).isEqualTo(true); .extracting("field")
from: assertThat(opt.isPresent()).isNotEqualTo(false); .extracting("outerField.fieldInsideObjectTypeOfOuterField.andSoOn")
from: assertThat(opt.isPresent()).isTrue(); .extracting("property") // where the class has a getProperty() (or isProperty() for boolean) method
to: assertThat(opt).isPresent(); .extracting("bareMethod") // supported with AssertJ 13.12.0
.extracting(Extractors.byName("fieldOrPropertyOrBareMethod")
from: assertThat(opt.isPresent()).isEqualTo(false); .extracting(Extractors.byName("fieldOrPropertyOrBareMethod.orAPathLikeAbove")
from: assertThat(opt.isPresent()).isNotEqualTo(true); .extracting(Extractors.resultOf("bareMethod")
from: assertThat(opt.isPresent()).isFalse(); .extractingResultOf("bareMethod")
to: assertThat(opt).isAbsent(); .flatExtracting("fieldOrPropertyOrBareMethod.orAPathLikeAbove")
.flatExtracting(Extractors.byName("fieldOrPropertyOrBareMethod.orAPathLikeAbove")
from: assertThat(opt.get()).isEqualTo("foo"); .flatExtracting(Extractors.resultOf("bareMethod")
to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.of("foo"));
from: assertThat(opt).isEqualTo(Optional.fromNullable("foo"));
to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.absent());
to: assertThat(opt).isAbsent();
from: assertThat(opt).isNotEqualTo(Optional.absent());
to: assertThat(opt).isPresent();
``` ```
Works on both POJOs and ```Iterable```s/```Array```s.
AssertJ for Guava needs to be available in the classpath. Implementation is very basic though and does not work with fancy cascaded ```.extracting()``` sequences.
If there's demand, I could add it.
## Development notice ## Development notice
Cajon is written in Kotlin 1.3. Cajon is written in Kotlin 1.3.
Cajon is probably the only plugin that uses JUnit 5 Jupiter for unit testing so far (or at least the only one that I'm aware of ;) ). Cajon is probably the only plugin that uses JUnit 5 Jupiter for unit testing so far (or at least the only one that I'm aware of ;) ).
The IntelliJ framework actually uses the JUnit 3 TestCase for plugin testing and I took me quite a while to make it work with JUnit 5. The IntelliJ framework actually uses the JUnit 3 TestCase for plugin testing and it took me quite a while to make it work with JUnit 5.
Feel free to use the code (in package de.platon42.intellij.jupiter) for your projects (with attribution). Feel free to use the code (in package ```de.platon42.intellij.jupiter```) for your projects (with attribution).
## TODO ## Planned features
- AssertThatNegatedBooleanExpression - More Optional fixes such as opt1.get() == opt2.get() etc.
- AssertThatInstanceOf - More moving out of methods for File, Path, LocalDate/Time etc.
- Referencing string properties inside extracting() - Converting ```foo.compareTo(bar) == 0``` to ```isEqualTo()``` (yes, I've *really* seen code like that)
- Extraction with property names to lambda with Java 8 - Extraction with property names to lambda with Java 8
``` ```
from: assertThat(object).extracting("propOne", "propNoGetter", "propTwo.innerProp")... from: assertThat(object).extracting("propOne", "propNoGetter", "propTwo.innerProp")...
to: assertThat(object).extracting(type::getPropOne, it -> it.propNoGetter, it -> it.getPropTwo().getInnerProp())... to: assertThat(object).extracting(type::getPropOne, it -> it.propNoGetter, it -> it.getPropTwo().getInnerProp())...
``` ```
- Kotlin support
## Changelog ## Changelog
#### V1.5 (24-Sep-19)
- Fix for AssertThatCollectionOrMap inspection sometimes causing an index out of bounds exception.
- AssertThatGuavaOptional inspections will now avoid conversions from ```.get()``` to ```.contains()```
for array types (currently not correctly supported by ```contains()``` in AssertJ-Guava).
- Added an settings option for AssertThatCollectionOrMap inspection respecting the degenerated case of maps with ```null``` values.
It is now possible to change the behavior for ```map.get(key) == null```, so it can offer either ```.doesNotContainKey()``` (default)
or ```.containsEntry(key, null)```, or even both.
- Fixes to AssertThatSize inspection after extending it for Maps in previous release as not all
combinations for ```.hasSameSizeAs()``` are supported.
#### V1.4 (25-Aug-19)
- Minor fix for highlighting of JoinVarArgsContains inspection.
- Extended AssertThatSize inspection to Maps, too.
- Extended AssertThatCollectionOrMap inspection for several ```assertThat(map.get())``` cases as suggested by Georgij G.
#### V1.3 (03-Aug-19)
- New JoinVarArgsContains inspection that will detect multiple ```.contains()```, ```.doesNotContain()```,
and ```.containsOnlyOnce()``` calls within the same statement that could be joined together using variadic arguments.
- AssertJ 3.13.0 broke some inspections due to new ```AbstractStringAssert::isEqualTo()``` method.
- 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.
#### V1.2 (23-Jun-19)
- Due to popular demand the JoinAssertThatStatements inspection will now add line breaks on joining statements.
The amount of statements joined without causing line breaks can be configured but defaults to 1 (always).
#### V1.1 (09-Jun-19)
- Improved JoinAssertThatStatements detection of expressions with side-effects and added pre/post-increment/decrement detection.
- Added Guava Optional ```opt.orNull() == null``` case. You know, I'm not making this stuff up, people actually write this kind of code.
- Added Java 8 Optional ```opt.orElse(null) == null``` case, too.
- 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.
- New ImplicitAssertion inspection for implicit ```isNotNull()```, ```isNotEmpty()``` and ```isPresent()``` assertions that will be covered by chained assertions.
- 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.
#### V1.0 (06-May-19)
- First release to be considered stable enough for production use.
- 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.
#### V0.8 (05-May-19)
- Fixed missing description for JoinAssertThatStatements and detection of equivalent expressions (sorry, released it too hastily).
- 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.
- Heavily reworked inspections for edge cases, such as multiple ```isEqualTo()``` calls inside a single statement.
- Some inspections could generate bogus code for weird situations, this has been made more fool-proof.
- Corrected highlighting for many inspections.
- Fixed family names for inspections in batch mode.
- Reworded many inspection messages for better understanding.
- 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.
#### V0.7 (28-Apr-19)
- Another fix for AssertThatGuavaOptional inspection regarding using the same family name for slightly different quick fix executions
(really, Jetbrains, this sucks for no reason).
- Extended AssertThatSize inspection to transform ```hasSize()``` into ```hasSameSizeAs()```, if possible.
- Implemented first version of JoinAssertThatStatements inspection that will try to merge ```assertThat()``` statements with the same
actual object together, preserving comments.
#### V0.6 (22-Apr-19)
- New AssertThatStringExpression inspection that will move ```isEmpty()```, ```equals()```, ```equalsIgnoreCase()```, ```contains()```,
```startsWith()```, and ```endsWith()``` out of actual expression.
- Extended AssertThatSize inspection to take ```String```s and ```CharSequences``` into account, too.
- New AssertThatInvertedBooleanCondition inspection that will remove inverted boolean expressions inside ```assertThat()```.
- Renamed a few inspections to better/shorter names.
- New AssertThatInstanceOf inspection that moves instanceof expressions out of ```assertThat()```.
#### V0.5 (18-Apr-19)
- Fixed incompatibility with IDEA versions < 2018.2 (affected AssertThatSizeInspection). Minimal version is now 2017.3.
- Fixed missing Guava imports (if not already present) for AssertThatGuavaInspection. This was a major PITA to get right.
- 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.
#### V0.4 (11-Apr-19) #### V0.4 (11-Apr-19)
- Reduced minimal supported IDEA version from 2018.2 to 2017.2. - Reduced minimal supported IDEA version from 2018.2 to 2017.2.
- New inspection AssertThatJava8Optional that operates on Java 8 Optional objects and tries to use contains(), containsSame(), isPresent(), and isNotPresent() instead. - New inspection AssertThatJava8Optional that operates on Java 8 ```Optional``` objects and tries to use ```contains()```, ```containsSame()```, ```isPresent()```, and ```isNotPresent()``` instead.
- New inspection AssertThatGuavaOptional that operates on Guava Optional objects and tries to use contains(), isPresent(), and isAbsent() instead. - New inspection AssertThatGuavaOptional that operates on Guava ```Optional``` objects and tries to use ```contains()```, ```isPresent()```, and ```isAbsent()``` instead.
- Added support in AssertThatBinaryExpressionIsTrueOrFalse for is(Not)EqualTo(Boolean.TRUE/FALSE). - Added support in AssertThatBinaryExpressionIsTrueOrFalse for ```is(Not)EqualTo(Boolean.TRUE/FALSE)```.
#### V0.3 (07-Apr-19) #### V0.3 (07-Apr-19)
- New inspection AssertThatBinaryExpressionIsTrueOrFalse that will find and fix common binary expressions and equals() statements (more than 150 combinations) inside assertThat(). - New inspection AssertThatBinaryExpressionIsTrueOrFalse that will find and fix common binary expressions and ```equals()``` statements (more than 150 combinations) inside ```assertThat()```.
- Merged AssertThatObjectIsNull and AssertThatObjectIsNotNull to AssertThatObjectIsNullOrNotNull. - Merged AssertThatObjectIsNull and AssertThatObjectIsNotNull to AssertThatObjectIsNullOrNotNull.
- Support for hasSizeLessThan(), hasSizeLessThanOrEqualTo(), hasSizeGreaterThanOrEqualTo(), and hasSizeGreaterThan() for AssertThatSizeInspection (with AssertJ >=13.2.0). - Support for ```hasSizeLessThan()```, ```hasSizeLessThanOrEqualTo()```, ```hasSizeGreaterThanOrEqualTo()```, and ```hasSizeGreaterThan()``` for AssertThatSizeInspection (with AssertJ >=3.12.0).
- Really fixed highlighting for JUnit conversion. Sorry. - Really fixed highlighting for JUnit conversion. Sorry.
#### V0.2 (01-Apr-19) #### V0.2 (01-Apr-19)
+35 -24
View File
@@ -1,11 +1,13 @@
plugins { plugins {
id 'java' id 'java'
id 'org.jetbrains.intellij' version '0.4.3' id 'org.jetbrains.intellij' version '0.4.10'
id 'org.jetbrains.kotlin.jvm' version '1.3.30' id 'org.jetbrains.kotlin.jvm' version '1.3.50'
id 'jacoco'
id 'com.github.kt3k.coveralls' version '2.8.4'
} }
group 'de.platon42' group 'de.platon42'
version '0.4' version '1.5'
repositories { repositories {
mavenCentral() mavenCentral()
@@ -18,13 +20,12 @@ repositories {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile "org.assertj:assertj-core:3.12.2" testCompile "org.assertj:assertj-core:3.13.2"
testCompile "org.assertj:assertj-guava:3.2.1" testCompile "org.assertj:assertj-guava:3.2.1"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.4.0'
testImplementation "org.jetbrains.kotlin:kotlin-test" testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit" // testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
} }
compileKotlin { compileKotlin {
@@ -34,35 +35,45 @@ compileTestKotlin {
kotlinOptions.jvmTarget = "1.8" kotlinOptions.jvmTarget = "1.8"
} }
intellij { intellij {
version '2019.1' version '2019.2.3'
// pluginName 'Concise AssertJ Optimizing Nitpicker (Cajon)' // pluginName 'Concise AssertJ Optimizing Nitpicker (Cajon)'
updateSinceUntilBuild false updateSinceUntilBuild false
plugins = ['java']
} }
patchPluginXml { patchPluginXml {
changeNotes """ changeNotes """
<h4>V0.4 (11-Apr-19)</h4> <h4>V1.5 (24-Sep-19)</h4>
<ul> <ul>
<li>Reduced minimal supported IDEA version from 2018.2 to 2017.2. <li>Fix for AssertThatCollectionOrMap inspection sometimes causing an index out of bounds exception.
<li>New inspection AssertThatJava8Optional that operates on Java 8 Optional objects and tries to use contains(), containsSame(), isPresent(), and isNotPresent() instead. <li>AssertThatGuavaOptional inspections will now avoid conversions from .get() to .contains()
<li>New inspection AssertThatGuavaOptional that operates on Guava Optional objects and tries to use contains(), isPresent(), and isAbsent() instead. for array types (currently not correctly supported by AssertJ-Guava).
<li>Added support in AssertThatBinaryExpressionIsTrueOrFalse for is(Not)EqualTo(Boolean.TRUE/FALSE). <li>Added an settings option for AssertThatCollectionOrMap inspection respecting the degenerated case of maps with null values.
</ul> It is now possible to change the behavior for map.get(key) == null, so it can offer either .doesNotContainKey() (default)
<h4>V0.3 (07-Apr-19)</h4> or .containsEntry(key, null), or even both.
<ul> <li>Fixes to AssertThatSize inspection after extending it for Maps in previous release as not all
<li>New inspection AssertThatBinaryExpressionIsTrueOrFalse that will find and fix common binary expressions and equals() statements (more than 150 combinations) inside assertThat(). combinations for .hasSameSizeAs() are supported.
<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> </ul>
<p>Full changelog available at <a href="https://github.com/chrisly42/cajon-plugin#changelog">Github project site</a>.</p>
""" """
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
// testLogging { testLogging {
// events "passed", "skipped", "failed" events "passed", "skipped", "failed"
// } }
}
jacoco {
toolVersion = '0.8.4'
}
jacocoTestReport {
reports {
xml.enabled true
csv.enabled false
}
} }
publishPlugin { publishPlugin {
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
@@ -7,28 +7,54 @@ class AssertJClassNames {
@NonNls @NonNls
const val ASSERTIONS_CLASSNAME = "org.assertj.core.api.Assertions" 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 @NonNls
const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert" const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert"
@NonNls @NonNls
const val ABSTRACT_OBJECT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractObjectAssert"
@NonNls
const val ABSTRACT_BOOLEAN_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanAssert" const val ABSTRACT_BOOLEAN_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanAssert"
@NonNls @NonNls
const val ABSTRACT_INTEGER_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntegerAssert" const val ABSTRACT_INTEGER_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntegerAssert"
@NonNls @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" const val ABSTRACT_COMPARABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractComparableAssert"
@NonNls @NonNls
const val ABSTRACT_STRING_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractStringAssert" const val ABSTRACT_STRING_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractStringAssert"
@NonNls @NonNls
const val ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharSequenceAssert" const val ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharSequenceAssert"
@NonNls @NonNls
const val ABSTRACT_MAP_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractMapAssert"
@NonNls
const val ABSTRACT_ITERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIterableAssert" const val ABSTRACT_ITERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIterableAssert"
@NonNls @NonNls
const val ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.EnumerableAssert" const val ABSTRACT_OPTIONAL_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractOptionalAssert"
@NonNls
const val EXTRACTORS_CLASSNAME = "org.assertj.core.extractor.Extractors"
@NonNls @NonNls
const val GUAVA_OPTIONAL_CLASSNAME = "com.google.common.base.Optional" const val GUAVA_OPTIONAL_CLASSNAME = "com.google.common.base.Optional"
@NonNls @NonNls
const val GUAVA_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.Assertions" const val GUAVA_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.Assertions"
@NonNls @NonNls
const val GUAVA_OPTIONAL_ASSERT_CLASSNAME = "org.assertj.guava.api.OptionalAssert" const val GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.OptionalAssert"
} }
} }
@@ -0,0 +1,53 @@
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.instanceCall(AssertJClassNames.ASSERT_INTERFACE, "usingComparator", "usingDefaultComparator")!!
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",
"hasOnlyOneElementSatisfying", "anyMatch", "noneMatch", "anySatisfy", "noneSatisfy"
)!!
val COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY = CallMatcher.anyOf(
DESCRIBED_AS,
WITH_REPRESENTATION_AND_SUCH,
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")
)!!
@@ -1,19 +1,185 @@
package de.platon42.intellij.plugins.cajon package de.platon42.intellij.plugins.cajon
import com.intellij.psi.PsiExpression import com.intellij.lang.jvm.JvmModifier
import com.intellij.psi.PsiMethodCallExpression 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() = this.methodExpression.qualifierExpression!! val PsiMethodCallExpression.qualifierExpression: PsiExpression get() = methodExpression.qualifierExpression!!
val PsiMethodCallExpression.firstArg: PsiExpression get() = this.argumentList.expressions[0]!! val PsiMethodCallExpression.firstArg: PsiExpression get() = getArg(0)
fun PsiMethodCallExpression.replaceQualifier(qualifier: PsiExpression) { fun PsiElement.hasAssertThat(): Boolean {
this.qualifierExpression.replace(qualifier) 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) { fun PsiMethodCallExpression.replaceQualifierFromMethodCall(oldMethodCall: PsiMethodCallExpression) {
this.qualifierExpression.replace(oldMethodCall.qualifierExpression) qualifierExpression.replace(oldMethodCall.qualifierExpression)
} }
fun PsiMethodCallExpression.getArg(n: Int): PsiExpression = this.argumentList.expressions[n] 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()?.hasModifier(JvmModifier.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(it) }
.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 <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? {
if (argIndex >= argumentList.expressions.size) return null
return getArg(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
}
@@ -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 }
}
@@ -11,8 +11,19 @@ class MethodNames {
@NonNls @NonNls
const val ASSERT_THAT = "assertThat" const val ASSERT_THAT = "assertThat"
@NonNls
const val ASSUME_THAT = "assumeThat"
@NonNls @NonNls
const val AS = "as" const val AS = "as"
@NonNls
const val DESCRIBED_AS = "describedAs"
@NonNls
const val IN_HEXADECIMAL = "inHexadecimal"
@NonNls
const val IN_BINARY = "inBinary"
@NonNls @NonNls
const val IS_EQUAL_TO = "isEqualTo" const val IS_EQUAL_TO = "isEqualTo"
@NonNls @NonNls
@@ -21,6 +32,10 @@ class MethodNames {
const val IS_SAME_AS = "isSameAs" const val IS_SAME_AS = "isSameAs"
@NonNls @NonNls
const val IS_NOT_SAME_AS = "isNotSameAs" const val IS_NOT_SAME_AS = "isNotSameAs"
@NonNls
const val HAS_TO_STRING = "hasToString"
@NonNls @NonNls
const val IS_GREATER_THAN = "isGreaterThan" const val IS_GREATER_THAN = "isGreaterThan"
@NonNls @NonNls
@@ -38,16 +53,22 @@ class MethodNames {
@NonNls @NonNls
const val IS_FALSE = "isFalse" const val IS_FALSE = "isFalse"
@NonNls @NonNls
const val IS_NULL = "isNull" const val IS_NULL = "isNull" // terminal, returns void
@NonNls @NonNls
const val IS_NOT_NULL = "isNotNull" const val IS_NOT_NULL = "isNotNull"
@NonNls @NonNls
const val IS_CLOSE_TO = "isCloseTo" const val IS_CLOSE_TO = "isCloseTo"
@NonNls @NonNls
const val IS_NOT_CLOSE_TO = "isNotCloseTo" const val IS_NOT_CLOSE_TO = "isNotCloseTo"
@NonNls
const val IS_INSTANCE_OF = "isInstanceOf"
@NonNls
const val IS_NOT_INSTANCE_OF = "isNotInstanceOf"
@NonNls @NonNls
const val IS_EMPTY = "isEmpty" const val IS_NULL_OR_EMPTY = "isNullOrEmpty" // terminal, returns void
@NonNls
const val IS_EMPTY = "isEmpty" // terminal, returns void
@NonNls @NonNls
const val IS_NOT_EMPTY = "isNotEmpty" const val IS_NOT_EMPTY = "isNotEmpty"
@NonNls @NonNls
@@ -65,8 +86,38 @@ class MethodNames {
@NonNls @NonNls
const val CONTAINS = "contains" const val CONTAINS = "contains"
@NonNls @NonNls
const val CONTAINS_ONLY_ONCE = "containsOnlyOnce"
@NonNls
const val DOES_NOT_CONTAIN = "doesNotContain"
@NonNls
const val CONTAINS_EXACTLY = "containsExactly" const val CONTAINS_EXACTLY = "containsExactly"
@NonNls @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" const val CONTAINS_SAME = "containsSame"
@NonNls @NonNls
const val IS_PRESENT = "isPresent" const val IS_PRESENT = "isPresent"
@@ -1,25 +1,28 @@
package de.platon42.intellij.plugins.cajon.inspections package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.PsiTypesUtil
import com.siyeh.ig.callMatcher.CallMatcher import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME 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_COMPARABLE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ENUMERABLE_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_INTEGER_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_LONG_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.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_ASSERTIONS_CLASSNAME
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_OPTIONAL_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.MethodNames
import de.platon42.intellij.plugins.cajon.getArg
import de.platon42.intellij.plugins.cajon.qualifierExpression import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveExpectedOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSimpleMethodCallQuickFix import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSimpleMethodCallQuickFix
open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() { open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
@@ -29,6 +32,9 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s() would be more concise than %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"
val TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP = mapOf<IElementType, String>( val TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP = mapOf<IElementType, String>(
JavaTokenType.EQEQ to MethodNames.IS_EQUAL_TO, JavaTokenType.EQEQ to MethodNames.IS_EQUAL_TO,
@@ -73,28 +79,52 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
val ASSERT_THAT_JAVA8_OPTIONAL = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT) val ASSERT_THAT_JAVA8_OPTIONAL = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes(CommonClassNames.JAVA_UTIL_OPTIONAL)!! .parameterTypes(CommonClassNames.JAVA_UTIL_OPTIONAL)!!
val ASSERT_THAT_GUAVA_OPTIONAL = CallMatcher.staticCall(GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT) val GUAVA_ASSERT_THAT_ANY = CallMatcher.staticCall(GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
.parameterTypes(GUAVA_OPTIONAL_CLASSNAME)!! .parameterCount(1)!!
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO) val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!! .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_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) val IS_EQUAL_TO_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("boolean")!! .parameterTypes("boolean")!!
val IS_NOT_EQUAL_TO_BOOLEAN =
CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO) 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_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
.parameterTypes("boolean")!! .parameterTypes("boolean")!!
val IS_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_SAME_AS) 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_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_SAME_AS)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!! .parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_NOT_SAME_AS) val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_SAME_AS)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!! .parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE) val IS_NULL = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NULL)
.parameterCount(0)!!
val IS_NOT_NULL = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_NULL)
.parameterCount(0)!!
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")!! .parameterTypes("int")!!
val IS_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
.parameterTypes("int")!!
val IS_GREATER_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN) val IS_GREATER_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN)
.parameterTypes("int")!! .parameterTypes("int")!!
val IS_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN_OR_EQUAL_TO) val IS_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, MethodNames.IS_GREATER_THAN_OR_EQUAL_TO)
@@ -117,6 +147,10 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
val COLLECTION_SIZE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "size") val COLLECTION_SIZE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "size")
.parameterCount(0)!! .parameterCount(0)!!
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, "equals") val OBJECT_EQUALS = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "equals")
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!! .parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
@@ -124,6 +158,8 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
.parameterCount(0)!! .parameterCount(0)!!
val OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "isPresent") val OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "isPresent")
.parameterCount(0)!! .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") val OPTIONAL_OF = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "of")
.parameterCount(1)!! .parameterCount(1)!!
@@ -136,6 +172,8 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
.parameterCount(0)!! .parameterCount(0)!!
val GUAVA_OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(GUAVA_OPTIONAL_CLASSNAME, "isPresent") val GUAVA_OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(GUAVA_OPTIONAL_CLASSNAME, "isPresent")
.parameterCount(0)!! .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") val GUAVA_OPTIONAL_OF = CallMatcher.staticCall(GUAVA_OPTIONAL_CLASSNAME, "of")
.parameterCount(1)!! .parameterCount(1)!!
@@ -171,74 +209,71 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod) val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = SIMPLIFY_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod) val message = SIMPLIFY_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod)
val quickFix = ReplaceSimpleMethodCallQuickFix(description, replacementMethod) val quickFix = ReplaceSimpleMethodCallQuickFix(description, replacementMethod)
holder.registerProblem(expression, message, quickFix) val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
holder.registerProblem(expression, textRange, message, quickFix)
} }
protected fun registerRemoveActualOutmostMethod( protected fun registerMoveOutMethod(
holder: ProblemsHolder, holder: ProblemsHolder,
expression: PsiMethodCallExpression, expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression, oldActualExpression: PsiMethodCallExpression,
replacementMethod: String, replacementMethod: String,
noExpectedExpression: Boolean = false quickFixSupplier: (String, String) -> LocalQuickFix
) { ) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return val originalMethod = getOriginalMethodName(oldActualExpression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod) val description = MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod) val message = MOVING_OUT_MESSAGE_TEMPLATE.format(originalMethod)
val quickfix = RemoveActualOutmostMethodCallQuickFix(description, replacementMethod, noExpectedExpression) val quickfix = quickFixSupplier(description, replacementMethod)
holder.registerProblem(expression, message, quickfix) holder.registerProblem(expression, message, quickfix)
} }
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( protected fun registerRemoveExpectedOutmostMethod(
holder: ProblemsHolder, holder: ProblemsHolder,
expression: PsiMethodCallExpression, expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression, oldExpectedCallExpression: PsiMethodCallExpression,
replacementMethod: String replacementMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) { ) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return registerConciseMethod(REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod, quickFixSupplier)
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
val quickfix = RemoveExpectedOutmostMethodCallQuickFix(description, replacementMethod)
holder.registerProblem(expression, message, quickfix)
}
protected fun calculateConstantParameterValue(expression: PsiMethodCallExpression, argIndex: Int): Any? {
if (argIndex >= expression.argumentList.expressions.size) return null
val valueExpression = expression.getArg(argIndex)
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
val value = constantEvaluationHelper.computeConstantExpression(valueExpression)
if (value == null) {
val field = (valueExpression 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
}
protected fun getExpectedBooleanResult(expectedCallExpression: PsiMethodCallExpression): Boolean? {
val isTrue = IS_TRUE.test(expectedCallExpression)
val isFalse = IS_FALSE.test(expectedCallExpression)
if (isTrue || isFalse) {
return isTrue
} else {
val isEqualTo = IS_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_EQUAL_TO_OBJECT.test(expectedCallExpression)
val isNotEqualTo = IS_NOT_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)
if (isEqualTo || isNotEqualTo) {
val constValue = calculateConstantParameterValue(expectedCallExpression, 0) as? Boolean ?: return null
return isNotEqualTo xor constValue
}
}
return null
}
protected 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.findMethodsByName(methodname).isNotEmpty()
} }
} }
@@ -7,12 +7,10 @@ import org.jetbrains.annotations.NonNls
open class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool() { open class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool() {
companion object { 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 @NonNls
const val JUNIT_ASSERT_CLASSNAME = "org.junit.Assert" const val JUNIT_ASSERT_CLASSNAME = "org.junit.Assert"
@NonNls
const val JUNIT_ASSUME_CLASSNAME = "org.junit.Assume"
@NonNls @NonNls
const val ASSERT_TRUE_METHOD = "assertTrue" const val ASSERT_TRUE_METHOD = "assertTrue"
@@ -32,6 +30,16 @@ open class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool()
const val ASSERT_NOT_SAME_METHOD = "assertNotSame" const val ASSERT_NOT_SAME_METHOD = "assertNotSame"
@NonNls @NonNls
const val ASSERT_ARRAY_EQUALS_METHOD = "assertArrayEquals" 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 { override fun getGroupDisplayName(): String {
@@ -0,0 +1,80 @@
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 replacementMethod = expectedResult.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)
}
}
@@ -1,97 +0,0 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.MethodNames.Companion.IS_NOT_NULL
import de.platon42.intellij.plugins.cajon.MethodNames.Companion.IS_NULL
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
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_EXPRESSION_DESCRIPTION_TEMPLATE = "Split %s expression out of assertThat()"
private const val MORE_MEANINGFUL_MESSAGE_TEMPLATE = "Moving %s 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 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 expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
val assertThatArgument = expression.firstArg
if (assertThatArgument is PsiMethodCallExpression && OBJECT_EQUALS.test(assertThatArgument)) {
val replacementMethod = if (expectedResult) MethodNames.IS_EQUAL_TO else MethodNames.IS_NOT_EQUAL_TO
val type = "${MethodNames.EQUALS}()"
val description = SPLIT_EXPRESSION_DESCRIPTION_TEMPLATE.format(type)
val message = MORE_MEANINGFUL_MESSAGE_TEMPLATE.format(type)
val quickFix = SplitEqualsExpressionMethodCallQuickFix(description, replacementMethod)
holder.registerProblem(expression, message, quickFix)
return
}
val binaryExpression = assertThatArgument 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
} else if (isLeftNull || isRightNull) {
val replacementMethod = if (expectedResult) IS_NULL else IS_NOT_NULL
registerSplitBinaryExpressionMethod(holder, expression, replacementMethod, pickRightOperand = isLeftNull, noExpectedExpression = true)
return
}
val isPrimitive = bothTypes.all(TypeConversionUtil::isPrimitiveAndNotNull)
val isNumericType = bothTypes.all(TypeConversionUtil::isNumericType)
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
val swapExpectedAndActual = constantEvaluationHelper.computeConstantExpression(binaryExpression.lOperand) != null
val tokenType = binaryExpression.operationSign.tokenType
.let {
if (swapExpectedAndActual) SWAP_SIDE_OF_BINARY_OPERATOR.getOrDefault(it, it) else it
}
.let {
if (expectedResult) it else INVERT_BINARY_OPERATOR.getOrDefault(it, it)
} ?: return
val mappingToUse =
(isPrimitive || isNumericType).map(TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP, TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS)
val replacementMethod = mappingToUse[tokenType] ?: return
registerSplitBinaryExpressionMethod(holder, expression, replacementMethod, pickRightOperand = swapExpectedAndActual)
}
private fun registerSplitBinaryExpressionMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String,
pickRightOperand: Boolean = false,
noExpectedExpression: Boolean = false
) {
val type = "binary"
val description = SPLIT_EXPRESSION_DESCRIPTION_TEMPLATE.format(type)
val message = MORE_MEANINGFUL_MESSAGE_TEMPLATE.format(type)
val quickFix = SplitBinaryExpressionMethodCallQuickFix(description, replacementMethod, pickRightOperand, noExpectedExpression)
holder.registerProblem(expression, message, quickFix)
}
}
}
}
@@ -5,15 +5,13 @@ import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.TypeConversionUtil import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
class AssertThatBooleanIsTrueOrFalseInspection : AbstractAssertJInspection() { class AssertThatBooleanConditionInspection : AbstractAssertJInspection() {
companion object { companion object {
private const val DISPLAY_NAME = "Asserting true or false" private const val DISPLAY_NAME = "Asserting a boolean condition"
} }
override fun getDisplayName() = DISPLAY_NAME override fun getDisplayName() = DISPLAY_NAME
@@ -22,22 +20,18 @@ class AssertThatBooleanIsTrueOrFalseInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
val matchingCalls = listOf( val matchingCalls = listOf(
IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_BOOLEAN, IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_BOOLEAN,
IS_NOT_EQUAL_TO_OBJECT, IS_NOT_EQUAL_TO_BOOLEAN IS_NOT_EQUAL_TO_OBJECT, IS_NOT_EQUAL_TO_BOOLEAN
).map { it.test(expression) } ).map { it.test(expression) }
if (matchingCalls.none { it }) { if (matchingCalls.none { it }) return
return if (!checkAssertedType(expression, ABSTRACT_BOOLEAN_ASSERT_CLASSNAME)) return
}
if (!checkAssertedType(expression, ABSTRACT_BOOLEAN_ASSERT_CLASSNAME)) {
return
}
val expectedExpression = expression.firstArg val expectedExpression = expression.firstArg
if (!TypeConversionUtil.isBooleanType(expectedExpression.type)) { if (!TypeConversionUtil.isBooleanType(expectedExpression.type)) return
return
} val expectedResult = expression.calculateConstantParameterValue(0) as? Boolean ?: return
val expectedResult = calculateConstantParameterValue(expression, 0) as? Boolean ?: return
val flippedBooleanTest = matchingCalls.drop(2).any { it } val flippedBooleanTest = matchingCalls.drop(2).any { it }
val replacementMethod = (expectedResult xor flippedBooleanTest).map(MethodNames.IS_TRUE, MethodNames.IS_FALSE) val replacementMethod = (expectedResult xor flippedBooleanTest).map(MethodNames.IS_TRUE, MethodNames.IS_FALSE)
@@ -0,0 +1,172 @@
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 : AbstractAssertJInspection() {
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(
Mapping(
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
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, MethodNames.CONTAINS).parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, MethodNames.CONTAINS_ALL).parameterCount(1),
MethodNames.CONTAINS_ALL, null
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, MethodNames.CONTAINS_KEY).parameterCount(1),
MethodNames.CONTAINS_KEY, MethodNames.DOES_NOT_CONTAIN_KEY
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, MethodNames.CONTAINS_VALUE).parameterCount(1),
MethodNames.CONTAINS_VALUE, MethodNames.DOES_NOT_CONTAIN_VALUE
)
)
}
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
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
if (MAP_GET_MATCHER.test(assertThatArgument)) {
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
) { desc ->
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 {
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) return
val mapping = MAPPINGS.firstOrNull { it.callMatcher.test(assertThatArgument) } ?: return
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = if (expectedResult) mapping.replacementForTrue else mapping.replacementForFalse ?: return
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method)
}
}
}
}
}
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
}
private class Mapping(
val callMatcher: CallMatcher,
val replacementForTrue: String,
val replacementForFalse: String?
)
}
@@ -4,7 +4,10 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
import de.platon42.intellij.plugins.cajon.hasAssertThat
class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() { class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
@@ -18,11 +21,11 @@ class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) super.visitMethodCallExpression(expression)
if (!HAS_SIZE.test(expression)) { if (!expression.hasAssertThat()) return
return val isLastExpression = expression.parent is PsiStatement
} if (!(HAS_SIZE.test(expression) && isLastExpression)) return
val value = calculateConstantParameterValue(expression, 0) ?: return val value = expression.calculateConstantParameterValue(0) ?: return
if (value == 0) { if (value == 0) {
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY) registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
} }
@@ -1,61 +1,118 @@
package de.platon42.intellij.plugins.cajon.inspections package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.AssertJClassNames import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.quickfixes.*
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
class AssertThatGuavaOptionalInspection : AbstractAssertJInspection() { class AssertThatGuavaOptionalInspection : AbstractAssertJInspection() {
companion object { companion object {
private const val DISPLAY_NAME = "Asserting an Optional (Guava)" 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 getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() { 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.resolveMethod()?.returnType 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) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) super.visitMethodCallExpression(expression)
JavaPsiFacade.getInstance(expression.project) if (!expression.hasAssertThat()) return
.findClass(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return val staticMethodCall = expression.findStaticMethodCall() ?: return
val assertThatGuava = ASSERT_THAT_GUAVA_OPTIONAL.test(expression) if (!checkPreconditions(staticMethodCall)) return
if (!(ASSERT_THAT_ANY.test(expression) || assertThatGuava)) {
return
}
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
if (assertThatGuava) { // We're not calling an assertThat() from Guava, but a core-AssertJ one!
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) { // We need to replace that by the Guava one, if we want to apply a formally correct fix.
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return if (IS_EQUAL_TO_OBJECT.test(expression)) {
if (GUAVA_OPTIONAL_OF.test(innerExpectedCall) || GUAVA_OPTIONAL_FROM_NULLABLE.test(innerExpectedCall)) { val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
registerRemoveExpectedOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS) 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)) { } else if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_ABSENT) registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_ABSENT)
} }
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)) { } else if (IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) { if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_PRESENT) registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_PRESENT)
}
} }
} }
} else {
val actualExpression = expression.firstArg as? PsiMethodCallExpression ?: return
if (GUAVA_OPTIONAL_GET.test(actualExpression) && IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) { private fun checkPreconditions(staticMethodCall: PsiMethodCallExpression): Boolean {
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS) val assertThatGuava = GUAVA_ASSERT_THAT_ANY.test(staticMethodCall)
} else if (GUAVA_OPTIONAL_IS_PRESENT.test(actualExpression)) {
val expectedPresence = getExpectedBooleanResult(expectedCallExpression) ?: return if (ASSERT_THAT_ANY.test(staticMethodCall) || assertThatGuava) {
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_ABSENT) JavaPsiFacade.getInstance(staticMethodCall.project)
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, replacementMethod, noExpectedExpression = true) .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)
} }
} }
@@ -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)
}
}
@@ -0,0 +1,34 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import de.platon42.intellij.plugins.cajon.*
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"
}
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 (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) return
expression.getExpectedBooleanResult() ?: 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())
}
}
}
}
}
@@ -1,14 +1,12 @@
package de.platon42.intellij.plugins.cajon.inspections package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor import com.intellij.psi.*
import com.intellij.psi.PsiElementVisitor import com.siyeh.ig.callMatcher.CallMatcher
import com.intellij.psi.PsiMethodCallExpression import de.platon42.intellij.plugins.cajon.*
import com.intellij.psi.PsiStatement import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
import com.intellij.psi.util.PsiTreeUtil import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.quickfixes.UnwrapExpectedStaticMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
class AssertThatJava8OptionalInspection : AbstractAssertJInspection() { class AssertThatJava8OptionalInspection : AbstractAssertJInspection() {
@@ -20,41 +18,59 @@ class AssertThatJava8OptionalInspection : AbstractAssertJInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitMethodCallExpression(expression) super.visitExpressionStatement(statement)
if (!ASSERT_THAT_ANY.test(expression)) { if (!statement.hasAssertThat()) return
return val staticMethodCall = statement.findStaticMethodCall() ?: return
} if (!ASSERT_THAT_ANY.test(staticMethodCall)) return
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
if (ASSERT_THAT_JAVA8_OPTIONAL.test(expression)) {
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
if (OPTIONAL_OF.test(innerExpectedCall) || OPTIONAL_OF_NULLABLE.test(innerExpectedCall)) {
registerRemoveExpectedOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS)
} else if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_NOT_PRESENT)
}
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_PRESENT)
}
}
} else {
val actualExpression = expression.firstArg as? PsiMethodCallExpression ?: return
val actualExpression = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
if (OPTIONAL_GET.test(actualExpression)) { if (OPTIONAL_GET.test(actualExpression)) {
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) { val expectedCallExpression = staticMethodCall.gatherAssertionCalls().singleOrNull() ?: return
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS) 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)) { } else if (IS_SAME_AS_OBJECT.test(expectedCallExpression)) {
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS_SAME) registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS_SAME) { desc, method ->
RemoveActualOutmostMethodCallQuickFix(desc, method)
}
} }
} else if (OPTIONAL_IS_PRESENT.test(actualExpression)) { } else if (OPTIONAL_IS_PRESENT.test(actualExpression)) {
val expectedPresence = getExpectedBooleanResult(expectedCallExpression) ?: return val expectedPresence = outmostMethodCall.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT) val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT)
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, replacementMethod, noExpectedExpression = true) 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)
} }
} }
} }
@@ -0,0 +1,57 @@
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
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
class AssertThatObjectExpressionInspection : AbstractAssertJInspection() {
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)
}
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.firstArg as? PsiMethodCallExpression ?: return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
when {
OBJECT_EQUALS.test(assertThatArgument) -> {
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = expectedResult.map(MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO)
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method)
}
}
OBJECT_TO_STRING.test(assertThatArgument) -> {
staticMethodCall.findFluentCallTo(IS_EQUAL_TO_OBJECT) ?: staticMethodCall.findFluentCallTo(IS_EQUAL_TO_STRING) ?: return
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, MethodNames.HAS_TO_STRING) { desc, method ->
RemoveActualOutmostMethodCallQuickFix(desc, method)
}
}
OBJECT_HASHCODE.test(assertThatArgument) -> {
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())
}
}
}
}
}
}
}
@@ -1,12 +1,11 @@
package de.platon42.intellij.plugins.cajon.inspections package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor import com.intellij.psi.*
import com.intellij.psi.PsiElementVisitor import com.siyeh.ig.callMatcher.CallMatcher
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiType
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.firstArg import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.hasAssertThat
import de.platon42.intellij.plugins.cajon.map import de.platon42.intellij.plugins.cajon.map
class AssertThatObjectIsNullOrNotNullInspection : AbstractAssertJInspection() { class AssertThatObjectIsNullOrNotNullInspection : AbstractAssertJInspection() {
@@ -21,11 +20,11 @@ class AssertThatObjectIsNullOrNotNullInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
val isNotEqualTo = IS_NOT_EQUAL_TO_OBJECT.test(expression) val isNotEqualTo = IS_NOT_EQUAL_TO_OBJECT.test(expression)
val isEqualTo = IS_EQUAL_TO_OBJECT.test(expression) val isEqualTo = CallMatcher.anyOf(IS_EQUAL_TO_OBJECT, IS_EQUAL_TO_STRING).test(expression)
if (!(isEqualTo || isNotEqualTo)) { val isLastExpression = expression.parent is PsiStatement
return if (!((isEqualTo && isLastExpression) || isNotEqualTo)) return
}
if (expression.firstArg.type == PsiType.NULL) { if (expression.firstArg.type == PsiType.NULL) {
registerSimplifyMethod(holder, expression, isEqualTo.map(MethodNames.IS_NULL, MethodNames.IS_NOT_NULL)) registerSimplifyMethod(holder, expression, isEqualTo.map(MethodNames.IS_NULL, MethodNames.IS_NOT_NULL))
@@ -2,17 +2,19 @@ package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.* 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_ITERABLE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_MAP_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.firstArg import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceHasSizeMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.map
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSizeMethodCallQuickFix import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSizeMethodCallQuickFix
class AssertThatSizeInspection : AbstractAssertJInspection() { class AssertThatSizeInspection : AbstractAssertJInspection() {
companion object { companion object {
private const val DISPLAY_NAME = "Asserting the size of an collection or array" 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( private val BONUS_EXPRESSIONS_CALL_MATCHER_MAP = listOf(
IS_LESS_THAN_INT to MethodNames.HAS_SIZE_LESS_THAN, IS_LESS_THAN_INT to MethodNames.HAS_SIZE_LESS_THAN,
@@ -20,75 +22,127 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
IS_GREATER_THAN_INT to MethodNames.HAS_SIZE_GREATER_THAN, 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 IS_GREATER_THAN_OR_EQUAL_TO_INT to MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO
) )
}
override fun getDisplayName() = DISPLAY_NAME private fun isCharSequenceLength(expression: PsiExpression) = (expression is PsiMethodCallExpression) && CHAR_SEQUENCE_LENGTH.test(expression)
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.firstArg
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) {
registerReplaceSizeMethod(holder, expression, expectedCallExpression, MethodNames.IS_EMPTY, noExpectedExpression = true)
} else {
val equalToExpression = expectedCallExpression.firstArg
if (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) {
registerReplaceSizeMethod(holder, expression, expectedCallExpression, MethodNames.HAS_SAME_SIZE_AS, expectedIsCollection = true)
} else {
registerReplaceSizeMethod(holder, expression, expectedCallExpression, MethodNames.HAS_SIZE)
}
}
} else {
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_ZERO.test(expectedCallExpression))
val isTestForNotEmpty = ((IS_GREATER_THAN_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_NOT_ZERO.test(expectedCallExpression))
if (isTestForEmpty || isTestForNotEmpty) {
val replacementMethod = isTestForEmpty.map(MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY)
registerReplaceSizeMethod(holder, expression, expectedCallExpression, replacementMethod, noExpectedExpression = true)
} else if (hasAssertJMethod(expression, ABSTRACT_ITERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE_LESS_THAN)) {
// new stuff in AssertJ 13.2.0
val matchedMethod = BONUS_EXPRESSIONS_CALL_MATCHER_MAP.find { it.first.test(expectedCallExpression) }?.second ?: return
registerReplaceSizeMethod(holder, expression, expectedCallExpression, matchedMethod)
}
}
}
}
private fun isCollectionSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && COLLECTION_SIZE.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 { private fun isArrayLength(expression: PsiExpression): Boolean {
val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false
return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType) return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType)
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length")) && ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
} }
private fun registerReplaceSizeMethod( fun getMatch(expression: PsiMethodCallExpression, isForArrayOrCollection: Boolean, isForMap: Boolean, isForString: Boolean): Match? {
holder: ProblemsHolder, val isLastExpression = expression.parent is PsiStatement
expression: PsiMethodCallExpression, val constValue = expression.calculateConstantParameterValue(0)
expectedCallExpression: PsiMethodCallExpression, if (IS_EQUAL_TO_INT.test(expression)) {
replacementMethod: String, return if ((constValue == 0) && isLastExpression) {
noExpectedExpression: Boolean = false, Match(expression, MethodNames.IS_EMPTY, noExpectedExpression = true)
expectedIsCollection: Boolean = false } else {
val equalToExpression = expression.firstArg
val equalsArrayOrCollectionSize = isArrayLength(equalToExpression) ||
isCollectionSize(equalToExpression)
if ((isForArrayOrCollection && equalsArrayOrCollectionSize)
|| (isForMap && (equalsArrayOrCollectionSize || isMapSize(equalToExpression)))
|| (isForString && (equalsArrayOrCollectionSize || isCharSequenceLength(equalToExpression)))
) { ) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return Match(expression, MethodNames.HAS_SAME_SIZE_AS, expectedIsCollection = true)
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod) } else {
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod) Match(expression, MethodNames.HAS_SIZE)
val quickfix = ReplaceSizeMethodCallQuickFix(description, replacementMethod, noExpectedExpression, expectedIsCollection) }
holder.registerProblem(expression, message, quickfix) }
} else {
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expression) && (constValue == 1))
|| IS_ZERO.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.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 visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) return
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_INT.test(staticMethodCall)) 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)
}
} else {
// I could try to create a quickfix for this, too, but it's probably not worth the effort
holder.registerProblem(statement, REMOVE_ALL_MESSAGE)
}
}
}
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (!expression.hasAssertThat()) return
if (!HAS_SIZE.test(expression)) return
val actualExpression = expression.firstArg
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
registerConciseMethod(
REMOVE_SIZE_DESCRIPTION_TEMPLATE,
holder,
expression,
expression,
MethodNames.HAS_SAME_SIZE_AS,
::ReplaceHasSizeMethodCallQuickFix
)
}
}
}
class Match(
val methodCall: PsiMethodCallExpression,
val replacementMethod: String,
val noExpectedExpression: Boolean = false,
val expectedIsCollection: Boolean = false
)
} }
@@ -0,0 +1,74 @@
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
class AssertThatStringExpressionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a string specific expression"
private val MAPPINGS = listOf(
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "isEmpty").parameterCount(0),
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY
),
Mapping(
CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equals").parameterCount(1),
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contentEquals").parameterCount(1)
),
MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equalsIgnoreCase").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.IS_EQUAL_TO_IC, MethodNames.IS_NOT_EQUAL_TO_IC
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contains").parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "startsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.STARTS_WITH, MethodNames.DOES_NOT_START_WITH
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "endsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.ENDS_WITH, MethodNames.DOES_NOT_END_WITH
)
)
}
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 assertThatArgument = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
val mapping = MAPPINGS.firstOrNull { it.callMatcher.test(assertThatArgument) } ?: return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = if (expectedResult) mapping.replacementForTrue else mapping.replacementForFalse
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod) { desc, method ->
MoveOutMethodCallExpressionQuickFix(desc, method)
}
}
}
}
private class Mapping(
val callMatcher: CallMatcher,
val replacementForTrue: String,
val replacementForFalse: String
)
}
@@ -4,8 +4,12 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression 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.AssertJClassNames.Companion.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
import de.platon42.intellij.plugins.cajon.hasAssertThat
class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() { class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
@@ -19,17 +23,15 @@ class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) 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) val hasSize = HAS_SIZE.test(expression)
if (!(isEqual || hasSize)) { val isLastExpression = expression.parent is PsiStatement
return if (!((isEqual || hasSize) && isLastExpression)) return
}
if (!checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)) { if (!checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)) return
return
}
val value = calculateConstantParameterValue(expression, 0) ?: return val value = expression.calculateConstantParameterValue(0) ?: return
if ((isEqual && (value == "")) || (hasSize && (value == 0))) { if ((isEqual && (value == "")) || (hasSize && (value == 0))) {
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY) registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
} }
@@ -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
}
}
}
}
}
}
@@ -0,0 +1,130 @@
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,
"containsExactly", "containsExactlyInAnyOrder", "containsExactlyInAnyOrderElementsOf",
"containsAll", "containsAnyOf",
"containsAnyElementsOf", "containsExactlyElementsOf", "containsOnlyElementsOf",
"isSubsetOf", "containsSequence", "containsSubsequence",
"doesNotContainSequence", "doesNotContainSubsequence", "doesNotContain",
"doesNotContainAnyElementsOf", "doesNotHaveDuplicates",
"startsWith", "endsWith", "containsNull", "doesNotContainNull",
"are", "areNot", "have", "doNotHave", "areAtLeastOne", "areAtLeast", "areAtMost", "areExactly",
"haveAtLeastOne", "haveAtLeast", "haveAtMost", "haveExactly",
"doesNotHave", "doesNotHaveSameClassAs",
"hasAtLeastOneElementOfType", "hasOnlyElementsOfType", "hasOnlyElementsOfTypes",
"doesNotHaveAnyElementsOfTypes",
"has", "doesNotHave",
"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",
"startsWith", "endsWith", "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,
"isInstanceOf", "isInstanceOfSatisfying", "isInstanceOfAny", "isExactlyInstanceOf", "isOfAnyClassIn",
"isNotInstanceOf", "isNotInstanceOfAny", "isNotExactlyInstanceOf", "isNotOfAnyClassIn",
"hasSameClassAs", "doesNotHaveSameClassAs",
"hasToString", "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)
}
}
}
}
@@ -1,112 +1,145 @@
package de.platon42.intellij.plugins.cajon.inspections package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.siyeh.ig.callMatcher.CallMatcher import com.siyeh.ig.callMatcher.CallMatcher
import com.siyeh.ig.callMatcher.CallMatcher.anyOf 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.AssertJClassNames
import de.platon42.intellij.plugins.cajon.MethodNames import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitAssertMethodCallQuickFix import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitAssertMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitAssumeMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitDeltaAssertMethodCallQuickFix import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceJUnitDeltaAssertMethodCallQuickFix
class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() { class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
companion object { 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( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_TRUE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"), 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("boolean")
), ),
MethodNames.IS_TRUE, false MethodNames.IS_TRUE, hasExpected = false
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_FALSE_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "boolean"), 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("boolean")
), ),
MethodNames.IS_FALSE, false MethodNames.IS_FALSE, hasExpected = false
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.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_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_OBJECT)
), ),
MethodNames.IS_NULL, false MethodNames.IS_NULL, hasExpected = false
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.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_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_OBJECT)
), ),
MethodNames.IS_NOT_NULL, false MethodNames.IS_NOT_NULL, hasExpected = false
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterTypes(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"), 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"), 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"), 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("float", "float", "float")
), ),
MethodNames.IS_CLOSE_TO, hasDelta = true MethodNames.IS_CLOSE_TO, hasDelta = true
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_EQUALS_METHOD).parameterCount(3), 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(2)
), ),
MethodNames.IS_EQUAL_TO MethodNames.IS_EQUAL_TO
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.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(CommonClassNames.JAVA_LANG_STRING, "double", "double", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterTypes("double", "double", "double"), 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"), 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("float", "float", "float")
), ),
MethodNames.IS_NOT_CLOSE_TO, hasDelta = true MethodNames.IS_NOT_CLOSE_TO, hasDelta = true
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_EQUALS_METHOD).parameterCount(3), 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(2)
), ),
MethodNames.IS_NOT_EQUAL_TO MethodNames.IS_NOT_EQUAL_TO
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_SAME_METHOD).parameterCount(3), 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(2)
), ),
MethodNames.IS_SAME_AS MethodNames.IS_SAME_AS
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_NOT_SAME_METHOD).parameterCount(3), 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(2)
), ),
MethodNames.IS_NOT_SAME_AS MethodNames.IS_NOT_SAME_AS
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.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(CommonClassNames.JAVA_LANG_STRING, "double[]", "double[]", "double"),
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterTypes("double[]", "double[]", "double"), 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"), 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("float[]", "float[]", "float")
), ),
MethodNames.CONTAINS_EXACTLY, hasDelta = true MethodNames.CONTAINS_EXACTLY, hasDelta = true
), ),
Mapping( Mapping(
anyOf( anyOf(
CallMatcher.staticCall(JUNIT_ASSERT_CLASSNAME, ASSERT_ARRAY_EQUALS_METHOD).parameterCount(2), 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(3)
), ),
MethodNames.CONTAINS_EXACTLY 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
)
)
} }
override fun getDisplayName() = DISPLAY_NAME override fun getDisplayName() = DISPLAY_NAME
@@ -115,55 +148,53 @@ class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
return object : JavaElementVisitor() { return object : JavaElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression) super.visitMethodCallExpression(expression)
val isJUnitAssertCall = expression.resolveMethod()?.containingClass?.qualifiedName == JUNIT_ASSERT_CLASSNAME when (expression.resolveMethod()?.containingClass?.qualifiedName) {
if (!isJUnitAssertCall) { JUNIT_ASSERT_CLASSNAME -> {
return // early exit
}
JavaPsiFacade.getInstance(expression.project) JavaPsiFacade.getInstance(expression.project)
.findClass(AssertJClassNames.ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return .findClass(AssertJClassNames.ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return
for (mapping in MAPPINGS) { val mapping = ASSERT_MAPPINGS.firstOrNull { it.callMatcher.test(expression) } ?: return
if (mapping.callMatcher.test(expression)) {
if (mapping.hasDelta) { if (mapping.hasDelta) {
registerDeltaReplacementMethod(holder, expression, mapping.replacement) registerConvertMethod(holder, expression, mapping.replacement, MethodNames.ASSERT_THAT, ::ReplaceJUnitDeltaAssertMethodCallQuickFix)
} else { } 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, holder: ProblemsHolder,
expression: PsiMethodCallExpression, expression: PsiMethodCallExpression,
hasExpected: Boolean, replacementMethod: String,
replacementMethod: String staticMethod: String,
quickFixSupplier: (String, String) -> LocalQuickFix
) { ) {
val originalMethod = getOriginalMethodName(expression) ?: return 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) val message = CONVERT_MESSAGE_TEMPLATE.format(originalMethod)
val quickFix = ReplaceJUnitAssertMethodCallQuickFix(description, !hasExpected, replacementMethod) val quickfix = quickFixSupplier(description, replacementMethod)
holder.registerProblem(expression, message, quickFix) holder.registerProblem(expression, message, quickfix)
}
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)
val quickFix = ReplaceJUnitDeltaAssertMethodCallQuickFix(description, replacementMethod)
holder.registerProblem(expression, message, quickFix)
} }
private class Mapping( private class Mapping(
val callMatcher: CallMatcher, val callMatcher: CallMatcher,
val replacement: String, val replacement: String,
val hasExpected: Boolean = true, val hasExpected: Boolean = true,
val hasDelta: Boolean = false val hasDelta: Boolean = false,
val singleArgument: Boolean = false
) )
} }
@@ -0,0 +1,116 @@
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(EXTRACTING_CALL_MATCHERS) == 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)
}
}
@@ -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.visitStatement(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(it) }
.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
}
}
}
}
}
}
@@ -1,35 +1,10 @@
package de.platon42.intellij.plugins.cajon.quickfixes package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.LocalQuickFix
import com.intellij.psi.*
import de.platon42.intellij.plugins.cajon.AssertJClassNames
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.firstArg
abstract class AbstractCommonQuickFix(private val description: String) : LocalQuickFix { abstract class AbstractCommonQuickFix(private val description: String) : LocalQuickFix {
override fun getName() = description
override fun getFamilyName() = description override fun getFamilyName() = description
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))
}
}
protected fun createAssertThat(context: PsiElement, actualExpression: PsiExpression): PsiMethodCallExpression {
val factory = JavaPsiFacade.getElementFactory(context.project)
val newMethodCall = factory.createExpressionFromText(
"${AssertJClassNames.ASSERTIONS_CLASSNAME}.${MethodNames.ASSERT_THAT}(a)", context
) as PsiMethodCallExpression
newMethodCall.firstArg.replace(actualExpression)
return newMethodCall
}
} }
@@ -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)
}
}
@@ -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()
}
}
}
@@ -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)
}
}
}
@@ -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
}
}
}
@@ -0,0 +1,80 @@
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) {
val newLineNode =
PsiParserFacade.SERVICE.getInstance(project).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
}
}
@@ -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
}
}
}
}
}
@@ -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)
}
}
}
@@ -0,0 +1,57 @@
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.*
class MoveOutMethodCallExpressionQuickFix(
description: String,
private val replacementMethod: String,
private val useNullNonNull: Boolean = false,
private val noExpectedExpression: Boolean = false,
private val keepExpectedAsSecondArgument: 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()
if (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)
}
}
}
}
@@ -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)
}
}
@@ -2,37 +2,38 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement import de.platon42.intellij.plugins.cajon.*
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class RemoveActualOutmostMethodCallQuickFix( class RemoveActualOutmostMethodCallQuickFix(
description: String, description: String,
private val replacementMethod: String, private val replacementMethod: String,
private val noExpectedExpression: Boolean private val noExpectedExpression: Boolean = false
) : AbstractCommonQuickFix(description) { ) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { companion object {
val element = descriptor.startElement private const val REMOVE_ACTUAL_EXPRESSION_DESCRIPTION = "Remove method calls in actual expressions and use better assertion"
val methodCallExpression = element as? PsiMethodCallExpression ?: return }
val assertExpression = methodCallExpression.firstArg as? PsiMethodCallExpression ?: return
assertExpression.replace(assertExpression.qualifierExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project) override fun getFamilyName(): String {
val expectedExpression = factory.createExpressionFromText( return REMOVE_ACTUAL_EXPRESSION_DESCRIPTION
"a.$replacementMethod${noExpectedExpression.map("()", "(e)")}", element }
) as PsiMethodCallExpression
if (!noExpectedExpression) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
expectedExpression.firstArg.replace(oldExpectedExpression.firstArg) 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)
} }
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
} }
} }
@@ -1,27 +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
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class RemoveExpectedOutmostMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression =
factory.createExpressionFromText("a.$replacementMethod(e)", element) as PsiMethodCallExpression
val expectedMethodCallExpression = oldExpectedExpression.firstArg as? PsiMethodCallExpression ?: return
expectedExpression.firstArg.replace(expectedMethodCallExpression.firstArg)
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
}
}
@@ -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
}
)
}
}
@@ -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()
}
}
@@ -2,59 +2,56 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project 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.PsiMethodCallExpression
import com.intellij.psi.codeStyle.CodeStyleManager import de.platon42.intellij.plugins.cajon.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
import de.platon42.intellij.plugins.cajon.MethodNames
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
import de.platon42.intellij.plugins.cajon.replaceQualifier
class ReplaceJUnitAssertMethodCallQuickFix(description: String, private val noExpectedExpression: Boolean, private val replacementMethod: String) : class ReplaceJUnitAssertMethodCallQuickFix(description: String, private val replacementMethod: String, private val noExpectedExpression: Boolean) :
AbstractCommonQuickFix(description) { 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) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return val methodCallExpression = element as? PsiMethodCallExpression ?: return
val args = methodCallExpression.argumentList val args = methodCallExpression.argumentList
val count = args.expressions.size val count = args.expressions.size
val actualExpression = args.expressions[count - 1] ?: return val actualExpression = args.expressions[count - 1] ?: return
val (expectedExpression, messageExpression) = if (noExpectedExpression) { val (expectedExpressions, messageExpression) = if (noExpectedExpression) {
val message = if (count > 1) args.expressions[0] else null val message = args.expressions.getOrNull(count - 2)
null to message emptyArray<PsiExpression>() to message
} else { } else {
val expected = args.expressions[count - 2] ?: return val expected = args.expressions[count - 2] ?: return
val message = if (count > 2) args.expressions[0] else null val message = args.expressions.getOrNull(count - 3)
expected to message arrayOf(expected) to message
} }
val factory = JavaPsiFacade.getElementFactory(element.project) val swapActualAndExpected = ((expectedExpressions.getOrNull(0)?.calculateConstantValue() == null)
val expectedMethodCall = factory.createExpressionFromText( && (actualExpression.calculateConstantValue() != null))
"a.$replacementMethod${noExpectedExpression.map("()", "(e)")}", element val (expectedMethodCall, newMethodCall) = if (swapActualAndExpected) {
) as PsiMethodCallExpression createExpectedMethodCall(element, replacementMethod, actualExpression) to
if (!noExpectedExpression) { createAssertThat(element, expectedExpressions.single())
expectedMethodCall.firstArg.replace(expectedExpression!!) } else {
createExpectedMethodCall(element, replacementMethod, *expectedExpressions) to
createAssertThat(element, actualExpression)
} }
val newMethodCall = createAssertThat(element, actualExpression)
if (messageExpression != null) { if (messageExpression != null) {
val asExpression = factory.createExpressionFromText("a.${MethodNames.AS}(desc)", element) as PsiMethodCallExpression val asExpression = createExpectedMethodCall(element, MethodNames.AS, messageExpression)
asExpression.firstArg.replace(messageExpression)
asExpression.replaceQualifier(newMethodCall) asExpression.replaceQualifier(newMethodCall)
expectedMethodCall.replaceQualifier(asExpression) expectedMethodCall.replaceQualifier(asExpression)
} else { } else {
expectedMethodCall.replaceQualifier(newMethodCall) expectedMethodCall.replaceQualifier(newMethodCall)
} }
val assertThatMethod = newMethodCall.resolveMethod() ?: return newMethodCall.resolveMethod()?.addAsStaticImport(element, GUAVA_ASSERTIONS_CLASSNAME)
addStaticImport(assertThatMethod, element, factory, GUAVA_ASSERTIONS_CLASSNAME) element.replace(expectedMethodCall).shortenAndReformat()
val codeStyleManager = JavaCodeStyleManager.getInstance(element.project)
val newElement = element.replace(expectedMethodCall)
val shortened = codeStyleManager.shortenClassReferences(newElement)
CodeStyleManager.getInstance(element.project).reformat(shortened)
} }
} }
@@ -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()
}
}
@@ -2,60 +2,52 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.codeStyle.CodeStyleManager import de.platon42.intellij.plugins.cajon.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.getArg
import de.platon42.intellij.plugins.cajon.replaceQualifier
class ReplaceJUnitDeltaAssertMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) { 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) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return val methodCallExpression = element as? PsiMethodCallExpression ?: return
val args = methodCallExpression.argumentList val args = methodCallExpression.argumentList
val count = args.expressions.size val count = args.expressions.size
val actualExpression = args.expressions[count - 2] ?: return val messageExpression = args.expressions.getOrNull(count - 4)
val messageExpression = if (count > 3) args.expressions[0] else null
val expectedExpression = args.expressions[count - 3] ?: return val expectedExpression = args.expressions[count - 3] ?: return
val actualExpression = args.expressions[count - 2] ?: return
val deltaExpression = args.expressions[count - 1] ?: return val deltaExpression = args.expressions[count - 1] ?: return
val factory = JavaPsiFacade.getElementFactory(element.project) val offsetMethodCall = createMethodCall(element, "org.assertj.core.data.Offset.offset", deltaExpression)
val offsetMethodCall = factory.createExpressionFromText(
"org.assertj.core.data.Offset.offset(c)", element
) as PsiMethodCallExpression
offsetMethodCall.firstArg.replace(deltaExpression) val swapActualAndExpected = ((expectedExpression.calculateConstantValue() == null)
&& (actualExpression.calculateConstantValue() != null))
val expectedMethodCall = factory.createExpressionFromText( val (expectedMethodCall, newMethodCall) = if (swapActualAndExpected) {
"a.$replacementMethod(e, offs)", element createExpectedMethodCall(element, replacementMethod, actualExpression, offsetMethodCall) to
) as PsiMethodCallExpression createAssertThat(element, expectedExpression)
} else {
expectedMethodCall.firstArg.replace(expectedExpression) createExpectedMethodCall(element, replacementMethod, expectedExpression, offsetMethodCall) to
expectedMethodCall.getArg(1).replace(offsetMethodCall) createAssertThat(element, actualExpression)
}
val newMethodCall = createAssertThat(element, actualExpression)
if (messageExpression != null) { if (messageExpression != null) {
val asExpression = factory.createExpressionFromText("a.as(desc)", element) as PsiMethodCallExpression val asExpression = createExpectedMethodCall(element, MethodNames.AS, messageExpression)
asExpression.firstArg.replace(messageExpression)
asExpression.replaceQualifier(newMethodCall) asExpression.replaceQualifier(newMethodCall)
expectedMethodCall.replaceQualifier(asExpression) expectedMethodCall.replaceQualifier(asExpression)
} else { } else {
expectedMethodCall.replaceQualifier(newMethodCall) expectedMethodCall.replaceQualifier(newMethodCall)
} }
val assertThatMethod = newMethodCall.resolveMethod() ?: return newMethodCall.resolveMethod()?.addAsStaticImport(element, GUAVA_ASSERTIONS_CLASSNAME)
addStaticImport(assertThatMethod, element, factory, GUAVA_ASSERTIONS_CLASSNAME) offsetMethodCall.resolveMethod()?.addAsStaticImport(element)
val offsetMethod = offsetMethodCall.resolveMethod() ?: return element.replace(expectedMethodCall).shortenAndReformat()
addStaticImport(offsetMethod, element, factory)
val codeStyleManager = JavaCodeStyleManager.getInstance(element.project)
val newElement = element.replace(expectedMethodCall)
val shortened = codeStyleManager.shortenClassReferences(newElement)
CodeStyleManager.getInstance(element.project).reformat(shortened)
} }
} }
@@ -2,19 +2,24 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class ReplaceSimpleMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) { 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) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return val methodCallExpression = element as? PsiMethodCallExpression ?: return
val expectedExpression = createExpectedMethodCall(element, replacementMethod)
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression =
factory.createExpressionFromText("a.$replacementMethod()", element) as PsiMethodCallExpression
expectedExpression.replaceQualifierFromMethodCall(methodCallExpression) expectedExpression.replaceQualifierFromMethodCall(methodCallExpression)
element.replace(expectedExpression) element.replace(expectedExpression)
} }
@@ -2,43 +2,44 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.* import com.intellij.psi.PsiExpression
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.PsiMethodCallExpression
import de.platon42.intellij.plugins.cajon.firstArg import com.intellij.psi.PsiReferenceExpression
import de.platon42.intellij.plugins.cajon.map import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class ReplaceSizeMethodCallQuickFix( class ReplaceSizeMethodCallQuickFix(
description: String, description: String,
private val replacementMethod: String, private val replacementMethod: String,
private val noExpectedExpression: Boolean, private val noExpectedExpression: Boolean = false,
private val expectedIsCollection: Boolean private val expectedIsCollection: Boolean = false
) : AbstractCommonQuickFix(description) { ) : 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) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val methodCallExpression = element as? PsiMethodCallExpression ?: return val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val assertExpression = methodCallExpression.firstArg val assertExpression = assertThatMethodCall.firstArg
replaceCollectionSizeOrArrayLength(assertExpression) replaceCollectionAndMapSizeOrArrayLength(assertExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression = factory.createExpressionFromText(
"a.$replacementMethod${noExpectedExpression.map("()", "(e)")}", element
) as PsiMethodCallExpression
if (!noExpectedExpression) {
if (expectedIsCollection) { if (expectedIsCollection) {
replaceCollectionSizeOrArrayLength(oldExpectedExpression.firstArg) replaceCollectionAndMapSizeOrArrayLength(outmostCallExpression.firstArg)
}
expectedExpression.firstArg.replace(oldExpectedExpression.firstArg)
}
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
} }
private fun replaceCollectionSizeOrArrayLength(assertExpression: PsiExpression) { 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( assertExpression.replace(
when (assertExpression) { when (assertExpression) {
is PsiReferenceExpression -> assertExpression.qualifierExpression!! is PsiReferenceExpression -> assertExpression.qualifierExpression!!
@@ -2,40 +2,44 @@ package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiBinaryExpression import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement import de.platon42.intellij.plugins.cajon.*
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.map
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class SplitBinaryExpressionMethodCallQuickFix( class SplitBinaryExpressionMethodCallQuickFix(
description: String, description: String,
private val replacementMethod: String, private val replacementMethod: String,
private val pickRightOperand: Boolean, private val pickRightOperand: Boolean = false,
private val noExpectedExpression: Boolean private val noExpectedExpression: Boolean = false
) : AbstractCommonQuickFix(description) { ) : 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) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement val outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
val methodCallExpression = element as? PsiMethodCallExpression ?: return val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
val binaryExpression = methodCallExpression.firstArg as? PsiBinaryExpression ?: 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 val expectedArgument = (if (pickRightOperand) binaryExpression.lOperand else binaryExpression.rOperand)?.copy() ?: return
binaryExpression.replace(if (pickRightOperand) binaryExpression.rOperand!! else binaryExpression.lOperand) binaryExpression.replace(if (pickRightOperand) binaryExpression.rOperand!! else binaryExpression.lOperand)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return val args = if (noExpectedExpression) emptyArray() else arrayOf(expectedArgument)
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project) methodsToFix
val expectedExpression = factory.createExpressionFromText( .forEach {
"a.$replacementMethod${noExpectedExpression.map("()", "(e)")}", element val expectedExpression = createExpectedMethodCall(it, replacementMethod, *args)
) as PsiMethodCallExpression expectedExpression.replaceQualifierFromMethodCall(it)
if (!noExpectedExpression) { it.replace(expectedExpression)
expectedExpression.firstArg.replace(expectedArgument) }
}
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
} }
} }
@@ -1,33 +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
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class SplitEqualsExpressionMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val equalsMethodCall = methodCallExpression.firstArg as? PsiMethodCallExpression ?: return
val expectedArgument = equalsMethodCall.firstArg.copy()
equalsMethodCall.replace(equalsMethodCall.qualifierExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression = factory.createExpressionFromText(
"a.$replacementMethod(e)", element
) as PsiMethodCallExpression
expectedExpression.firstArg.replace(expectedArgument)
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
}
}
@@ -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)
}
}
@@ -0,0 +1,149 @@
package de.platon42.intellij.plugins.cajon.references
import com.intellij.lang.jvm.JvmModifier
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.hasModifier(JvmModifier.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.hasModifier(JvmModifier.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 compatiblity 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
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)
}
}
}
+33 -11
View File
@@ -1,28 +1,34 @@
<idea-plugin> <idea-plugin>
<id>de.platon42.cajon</id> <id>de.platon42.cajon</id>
<name>Concise AssertJ Optimizing Nitpicker (Cajon)</name> <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://github.com/chrisly42/cajon-plugin">Chris 'platon42' Hodges</vendor>
<description><![CDATA[ <description><![CDATA[
Cajon is an IntelliJ IDEA Plugin for shortening and optimizing AssertJ assertions. 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 It adds several inspections and quick fixes to fully use the fluent assertion methods
to make the intention clear and concise. It can also convert JUnit 4 assertions to AssertJ. and thus makes the intention clear and concise, also generating better messages on test failures.
It can also be used to convert JUnit 4 assertions and assumptions to AssertJ.
It supports referencing inside extracting()-methods with strings, adding refactoring safety.
<p>
<a href="https://github.com/chrisly42/cajon-plugin/blob/master/README.md">Full documentation here...</a>
]]></description> ]]></description>
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description --> <idea-version since-build="173.2696.26"/>
<idea-version since-build="172.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.lang</depends>
<depends>com.intellij.modules.platform</depends> <depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.java</depends> <depends>com.intellij.modules.java</depends>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<psi.referenceContributor implementation="de.platon42.intellij.plugins.cajon.references.ExtractorReferenceContributor"/>
<localInspection groupPath="Java" shortName="AssertThatObjectIsNullOrNotNull" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatObjectIsNullOrNotNull" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectIsNullOrNotNullInspection"/> implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatObjectIsNullOrNotNullInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBooleanIsTrueOrFalse" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatBooleanCondition" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBooleanIsTrueOrFalseInspection"/> 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="AssertThatInstanceOf" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatInstanceOfInspection"/>
<localInspection groupPath="Java" shortName="AssertThatStringIsEmpty" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatStringIsEmpty" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatStringIsEmptyInspection"/> implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatStringIsEmptyInspection"/>
<localInspection groupPath="Java" shortName="AssertThatEnumerableIsEmpty" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatEnumerableIsEmpty" enabledByDefault="true" level="WARNING"
@@ -31,14 +37,30 @@
<localInspection groupPath="Java" shortName="AssertThatSize" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatSize" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatSizeInspection"/> implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatSizeInspection"/>
<localInspection groupPath="Java" shortName="AssertThatBinaryExpressionIsTrueOrFalse" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatBinaryExpression" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBinaryExpressionIsTrueOrFalseInspection"/> 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="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="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" <localInspection groupPath="Java" shortName="AssertThatJava8Optional" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatJava8OptionalInspection"/> implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatJava8OptionalInspection"/>
<localInspection groupPath="Java" shortName="AssertThatGuavaOptional" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="AssertThatGuavaOptional" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatGuavaOptionalInspection"/> 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="JUnitAssertToAssertJ" enabledByDefault="true" level="WARNING" <localInspection groupPath="Java" shortName="JUnitAssertToAssertJ" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JUnitAssertToAssertJInspection"/> implementationClass="de.platon42.intellij.plugins.cajon.inspections.JUnitAssertToAssertJInspection"/>
</extensions> </extensions>
@@ -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). into assertThat(actual).is&lt;operator&gt;(expected).
<!-- tooltip end --> <!-- tooltip end -->
There are over 150 combinations that are found with this inspections. 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. Also works with constant expressions on the expected side.
Swaps actual and expected when actual is a constant expression (correctly transforming the used operator). Swaps actual and expected when actual is a constant expression (correctly transforming the used operator).
</body> </body>
@@ -2,6 +2,6 @@
<body> <body>
Turns assertThat(condition).isEqualTo(true/false) into assertThat(condition).isTrue()/isFalse(). Turns assertThat(condition).isEqualTo(true/false) into assertThat(condition).isTrue()/isFalse().
<!-- tooltip end --> <!-- tooltip end -->
Also works with constant expressions and Boolean.TRUE/FALSE. <br>Also works with constant expressions and Boolean.TRUE/FALSE.
</body> </body>
</html> </html>
@@ -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>
@@ -3,6 +3,6 @@
Looks at expected and actual expression being of Guava Optional type and whether the statement effectively tries to assert the 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(). presence, absence or content and then replaces the statement by isPresent(), isAbsent(), or contains().
<!-- tooltip end --> <!-- tooltip end -->
Requires AssertJ-Guava to be in classpath. <br>Requires AssertJ-Guava to be in classpath.
</body> </body>
</html> </html>
@@ -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>
@@ -0,0 +1,7 @@
<html>
<body>
Turns assertThat(!condition).isEqualTo(true/false) into assertThat(condition).isFalse()/isTrue().
<!-- tooltip end -->
<br>Also works with constant expressions and Boolean.TRUE/FALSE.
</body>
</html>
@@ -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>
@@ -1,6 +1,7 @@
<html> <html>
<body> <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 --> <!-- tooltip end -->
<br>Several more conversions are available with AssertJ 3.12.0 or later.
</body> </body>
</html> </html>
@@ -0,0 +1,7 @@
<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(), and endsWith().
</body>
</html>
@@ -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>
@@ -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>
@@ -1,10 +1,12 @@
<html> <html>
<body> <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 --> <!-- tooltip end -->
<br>Works for assertTrue(), assertFalse(), assertNull(), assertNotNull(), assertEquals(), assertNotEquals(), assertSame() assertNotSame(), assertArrayEquals(). <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). Copes with variants with message and without, handles special versions for double and float types (including arrays).
<p></p> 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. 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> </body>
</html> </html>
@@ -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>
@@ -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>
@@ -8,17 +8,17 @@ import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.*;
import com.intellij.testFramework.PsiTestUtil; import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy;
import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.extension.*; import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContext.Store;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Path; import java.nio.file.Path;
@@ -26,7 +26,7 @@ import java.nio.file.Paths;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Stream; import java.util.stream.Stream;
public class LightCodeInsightExtension implements ParameterResolver, AfterTestExecutionCallback { public class LightCodeInsightExtension implements ParameterResolver, AfterTestExecutionCallback, InvocationInterceptor {
private static final Logger LOG = Logger.getLogger(LightCodeInsightExtension.class.getName()); private static final Logger LOG = Logger.getLogger(LightCodeInsightExtension.class.getName());
@@ -77,7 +77,41 @@ public class LightCodeInsightExtension implements ParameterResolver, AfterTestEx
return context.getStore(Namespace.create(LightCodeInsightExtension.class, context.getRequiredTestMethod())); return context.getStore(Namespace.create(LightCodeInsightExtension.class, context.getRequiredTestMethod()));
} }
private static class LightCodeInsightFixtureTestCaseWrapper extends LightCodeInsightFixtureTestCase { @Override
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
Throwable[] throwables = new Throwable[1];
Runnable runnable = () -> {
try {
TestLoggerFactory.onTestStarted();
invocation.proceed();
TestLoggerFactory.onTestFinished(true);
} catch (Throwable e) {
TestLoggerFactory.onTestFinished(false);
throwables[0] = e;
}
};
invokeTestRunnable(runnable);
if (throwables[0] != null) {
throw throwables[0];
}
}
private static void invokeTestRunnable(@NotNull Runnable runnable) {
IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current();
if (policy != null && !policy.runInDispatchThread()) {
runnable.run();
} else {
EdtTestUtilKt.runInEdtAndWait(() -> {
runnable.run();
return null;
});
}
}
private static class LightCodeInsightFixtureTestCaseWrapper extends LightJavaCodeInsightFixtureTestCase {
private final ExtensionContext extensionContext; private final ExtensionContext extensionContext;
private LightCodeInsightFixtureTestCaseWrapper(ExtensionContext extensionContext) { private LightCodeInsightFixtureTestCaseWrapper(ExtensionContext extensionContext) {
@@ -124,7 +158,7 @@ public class LightCodeInsightExtension implements ParameterResolver, AfterTestEx
}; };
} }
protected void addJarContaining(ModifiableRootModel model, Class clazz) { void addJarContaining(ModifiableRootModel model, Class clazz) {
try { try {
Path jarPath = Paths.get(clazz.getProtectionDomain().getCodeSource().getLocation().toURI()); Path jarPath = Paths.get(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
@@ -1,44 +0,0 @@
package de.platon42.intellij.jupiter;
import com.intellij.testFramework.EdtTestUtilKt;
import com.intellij.testFramework.TestLoggerFactory;
import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy;
import org.jetbrains.annotations.NotNull;
// See https://github.com/junit-team/junit5/issues/157
public class WorkaroundUntilJupiterGetsExecutorProvider {
protected static void runTest(Runnable test) throws Throwable {
Throwable[] throwables = new Throwable[1];
Runnable runnable = () -> {
try {
TestLoggerFactory.onTestStarted();
test.run();
TestLoggerFactory.onTestFinished(true);
} catch (Throwable e) {
TestLoggerFactory.onTestFinished(false);
e.fillInStackTrace();
throwables[0] = e;
}
};
invokeTestRunnable(runnable);
if (throwables[0] != null) {
throw throwables[0];
}
}
private static void invokeTestRunnable(@NotNull Runnable runnable) {
IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current();
if (policy != null && !policy.runInDispatchThread()) {
runnable.run();
} else {
EdtTestUtilKt.runInEdtAndWait(() -> {
runnable.run();
return null;
});
}
}
}
@@ -1,296 +0,0 @@
package de.platon42.intellij.playground;
import org.assertj.core.api.ListAssert;
import org.assertj.core.data.Offset;
import java.util.ArrayList;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;
import static org.assertj.guava.api.Assertions.assertThat;
import static org.junit.Assert.*;
public class Playground {
private void sizeOfList() {
assertThat("string").as("foo").hasSize(0);
assertThat(new StringBuilder()).as("bar").hasSize(0);
ListAssert<String> etc = assertThat(new ArrayList<String>()).as("etc");
etc.hasSize(0);
assertThat(new Long[1]).as("etc").hasSize(0);
assertThat("string").as("foo").isEmpty();
assertThat(new StringBuilder()).as("bar").isEmpty();
assertThat(new ArrayList<Long>()).as("etc").isEmpty();
assertThat(new Long[1]).as("etc").isEmpty();
assertThat(new ArrayList<>().size()).isEqualTo(1);
assertThat(new ArrayList<String>().size()).isEqualTo(1);
assertThat(new ArrayList<String>().size()).isGreaterThanOrEqualTo(1);
assertThat(new ArrayList<String>().size()).isZero();
assertThat(new ArrayList<String>()).hasSizeGreaterThan(1);
assertThat(new ArrayList<String>()).hasSameSizeAs(new ArrayList<>());
assertThat(new Long[1]).as("etc").hasSameSizeAs(new Long[2]);
}
private void sizeOfArray() {
assertThat(new String[1].length).isLessThanOrEqualTo(1);
assertThat(new String[1]).hasSameSizeAs(new Object());
assertThat("").isEqualTo(null);
assertThat(true).isTrue();
assertThat(true).isEqualTo(true);
assertThat(Boolean.TRUE).isEqualTo(Boolean.FALSE);
assertThat(Boolean.TRUE).isEqualTo(true);
}
private void booleanIsTrueOrFalse() {
boolean primitive = false;
Boolean object = java.lang.Boolean.TRUE;
assertThat(primitive).isEqualTo(Boolean.TRUE);
assertThat(primitive).isEqualTo(Boolean.FALSE);
assertThat(object).isEqualTo(Boolean.TRUE);
assertThat(object).isEqualTo(Boolean.FALSE);
assertThat(primitive).isEqualTo(true);
assertThat(primitive).isEqualTo(false);
assertThat(object).isEqualTo(true);
assertThat(object).isEqualTo(false);
assertThat(primitive).isNotEqualTo(Boolean.TRUE);
assertThat(primitive).isNotEqualTo(Boolean.FALSE);
assertThat(object).isNotEqualTo(Boolean.TRUE);
assertThat(object).isNotEqualTo(Boolean.FALSE);
assertThat(primitive).isNotEqualTo(true);
assertThat(primitive).isNotEqualTo(false);
assertThat(object).isNotEqualTo(true);
assertThat(object).isNotEqualTo(false);
assertThat(primitive).as("nah").isEqualTo(true && !true);
assertThat(object).isEqualTo(Boolean.TRUE && !Boolean.TRUE);
assertThat("").isEqualTo(Boolean.TRUE);
}
private void binaryExpression() {
int primExp = 42;
int primAct = 1337;
Double numberObjExp = 42.0;
Double numberObjAct = 1337.0;
String stringExp = "foo";
String stringAct = "bar";
assertThat(primAct == primExp).isTrue();
assertThat(primAct == 1).isTrue();
assertThat(primAct == primExp).isEqualTo(false);
assertThat(primAct != primExp).isEqualTo(true);
assertThat(1 != primAct).isTrue();
assertThat(primAct != primExp).isNotEqualTo(true);
assertThat(primAct > primExp).isNotEqualTo(false);
assertThat(primAct > primExp).isFalse();
assertThat(primAct > 1).isFalse();
assertThat(primAct >= 1).isTrue();
assertThat(primAct >= primExp).isEqualTo(false);
assertThat(1 <= primAct).isFalse();
assertThat(1 > primAct).isTrue();
assertThat(primAct < primExp).isNotEqualTo(true);
assertThat(primAct <= primExp).isNotEqualTo(false);
assertThat(primAct <= primExp).isFalse();
assertThat(primAct <= 1).isFalse();
assertThat(numberObjAct == 1).isTrue();
assertThat(numberObjAct == numberObjExp).isEqualTo(false);
assertThat(numberObjAct != numberObjExp).isEqualTo(true);
assertThat(1 != numberObjAct).isTrue();
assertThat(numberObjAct != numberObjExp).isNotEqualTo(true);
assertThat(numberObjAct > numberObjExp).isNotEqualTo(false);
assertThat(numberObjAct > 1).isFalse();
assertThat(numberObjAct >= numberObjExp).isTrue();
assertThat(numberObjAct >= numberObjExp).isEqualTo(false);
assertThat(1 <= numberObjAct).isFalse();
assertThat(numberObjAct < numberObjExp).isEqualTo(true);
assertThat(numberObjAct < numberObjExp).isNotEqualTo(true);
assertThat(numberObjAct <= numberObjExp).isNotEqualTo(false);
assertThat(numberObjAct <= 1).isFalse();
assertThat(numberObjAct.equals(numberObjExp)).isFalse();
assertThat(stringAct == stringExp).isNotEqualTo(false);
assertThat(stringAct.equals(stringExp)).isEqualTo(true);
assertThat(stringAct != stringExp).isFalse();
assertThat(stringAct == null).isNotEqualTo(true);
assertThat(null == stringAct).isEqualTo(false);
assertThat(null == null).isTrue();
assertThat(!false).isTrue();
}
private void stringIsEmpty() {
String foo = "bar";
assertThat(foo).isEqualTo("");
assertThat(foo).hasSize(0);
}
private void java8Optional() {
Optional<String> opt = Optional.empty();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt).isEqualTo(Optional.of("foo"));
assertThat(opt).isEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isEqualTo(Optional.empty());
assertThat(opt).isNotEqualTo(Optional.empty());
assertThat(opt).isPresent();
}
private void assertThatGuavaOptional() {
com.google.common.base.Optional<String> opt = com.google.common.base.Optional.absent();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt.get()).isNotEqualTo("foo");
assertThat(opt.get()).isNotSameAs("foo");
assertThat(opt).isEqualTo(com.google.common.base.Optional.of("foo"));
assertThat(opt).isEqualTo(com.google.common.base.Optional.fromNullable("foo"));
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.of("foo"));
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.fromNullable("foo"));
assertThat(opt).isEqualTo(com.google.common.base.Optional.absent());
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.absent());
assertThat(opt).isAbsent();
}
private void junitAssertions() {
assertTrue(true);
assertTrue("message", true);
assertFalse(true);
assertFalse("message", true);
assertEquals(1L, 2L);
assertEquals("message", 1L, 2L);
assertNotEquals(1L, 2L);
assertNotEquals("message", 1L, 2L);
assertEquals(4.0, 4.4, 3.3);
assertThat(3.0).isCloseTo(4.0, Offset.offset(2.3));
assertThat(new int[1]).isEqualTo(new int[2]);
String foo = "foo";
String bar = "bar";
assertTrue(foo == "foo");
assertTrue("oh no!", foo == "foo");
assertFalse(foo == "bar");
assertFalse("boom!", foo == "bar");
assertNull(foo);
assertNull("oh no!", foo);
assertNotNull(foo);
assertNotNull("oh no!", foo);
assertEquals(bar, foo);
assertEquals("equals", bar, foo);
assertNotEquals(bar, foo);
assertNotEquals("equals", bar, foo);
assertSame(bar, foo);
assertSame("same", bar, foo);
assertNotSame(bar, foo);
assertNotSame("same", bar, foo);
assertEquals(1.0, 2.0, 0.1);
assertEquals("equals", 1.0, 2.0, 0.1);
assertEquals(1.0f, 2.0f, 0.1f);
assertEquals("equals", 1.0f, 2.0f, 0.1f);
assertNotEquals(1.0, 2.0);
assertNotEquals(1.0, 2.0, 0.1);
assertNotEquals("equals", 1.0, 2.0);
assertNotEquals("equals", 1.0, 2.0, 0.1);
assertNotEquals(1.0f, 2.0f);
assertNotEquals(1.0f, 2.0f, 0.1f);
assertNotEquals("equals", 1.0f, 2.0f);
assertNotEquals("equals", 1.0f, 2.0f, 0.1f);
assertArrayEquals(new int[2], new int[1]);
assertArrayEquals("array equals", new int[2], new int[1]);
assertArrayEquals(new double[2], new double[1], 1.0);
assertArrayEquals("array equals", new double[2], new double[1], 1.0);
assertArrayEquals(new float[2], new float[1], 1.0f);
assertArrayEquals("array equals", new float[2], new float[1], 1.0f);
assertThat(foo == "foo").isTrue();
assertThat(foo == "foo").as("oh no!").isTrue();
assertThat(foo == "bar").isFalse();
assertThat(foo == "bar").as("boom!").isFalse();
assertThat(foo).isNull();
assertThat(foo).as("oh no!").isNull();
assertThat(foo).isNotNull();
assertThat(foo).as("oh no!").isNotNull();
assertThat(foo).isEqualTo(bar);
assertThat(foo).as("equals").isEqualTo(bar);
assertThat(foo).isNotEqualTo(bar);
assertThat(foo).as("equals").isNotEqualTo(bar);
assertThat(foo).isSameAs(bar);
assertThat(foo).as("same").isSameAs(bar);
assertThat(foo).isNotSameAs(bar);
assertThat(foo).as("same").isNotSameAs(bar);
assertThat(2.0).isEqualTo(1.0);
assertThat(2.0).isCloseTo(1.0, offset(0.1));
assertThat(2.0).as("equals").isEqualTo(1.0);
assertThat(2.0).as("equals").isCloseTo(1.0, offset(0.1));
assertThat(2.0f).isEqualTo(1.0f);
assertThat(2.0f).isCloseTo(1.0f, offset(0.1f));
assertThat(2.0f).as("equals").isEqualTo(1.0f);
assertThat(2.0f).as("equals").isCloseTo(1.0f, offset(0.1f));
assertThat(2.0).isNotEqualTo(1.0);
assertThat(2.0).isNotCloseTo(1.0, offset(0.1));
assertThat(2.0).as("equals").isNotEqualTo(1.0);
assertThat(2.0).as("equals").isNotCloseTo(1.0, offset(0.1));
assertThat(2.0f).isNotEqualTo(1.0f);
assertThat(2.0f).isNotCloseTo(1.0f, offset(0.1f));
assertThat(2.0f).as("equals").isNotEqualTo(1.0f);
assertThat(2.0f).as("equals").isNotCloseTo(1.0f, offset(0.1f));
assertThat(new int[1]).isEqualTo(new int[2]);
assertThat(new int[1]).as("array equals").isEqualTo(new int[2]);
assertThat(new double[1]).containsExactly(new double[2], offset(1.0));
assertThat(new double[1]).as("array equals").containsExactly(new double[2], offset(1.0));
assertThat(new float[1]).containsExactly(new float[2], offset(1.0f));
assertThat(new float[1]).as("array equals").containsExactly(new float[2], offset(1.0f));
assertThat(new Object()).extracting(Object::toString, Object::hashCode);
}
}
@@ -1,67 +1,47 @@
package de.platon42.intellij.plugins.cajon package de.platon42.intellij.plugins.cajon
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.pom.java.LanguageLevel import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.TestLoggerFactory
import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.runInEdtAndWait
import de.platon42.intellij.jupiter.AddLocalJarToModule import de.platon42.intellij.jupiter.AddLocalJarToModule
import de.platon42.intellij.jupiter.LightCodeInsightExtension import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.TestDataPath import de.platon42.intellij.jupiter.TestDataPath
import de.platon42.intellij.jupiter.TestJdk import de.platon42.intellij.jupiter.TestJdk
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayNameGeneration
import org.junit.jupiter.api.DisplayNameGenerator
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method
@ExtendWith(LightCodeInsightExtension::class) @ExtendWith(LightCodeInsightExtension::class)
@TestDataPath("src/test/resources") @TestDataPath("src/test/resources")
@TestJdk(LanguageLevel.JDK_1_8, annotations = true, useInternal = true) @TestJdk(LanguageLevel.JDK_1_8, annotations = true, useInternal = true)
@AddLocalJarToModule(Assertions::class) @AddLocalJarToModule(Assertions::class)
@DisplayNameGeneration(AbstractCajonTest.CutOffFixtureDisplayNameGenerator::class)
abstract class AbstractCajonTest { abstract class AbstractCajonTest {
// See https://github.com/junit-team/junit5/issues/157
protected fun runTest(body: () -> Unit) {
val throwables = arrayOfNulls<Throwable>(1)
invokeTestRunnable {
try {
TestLoggerFactory.onTestStarted()
body()
TestLoggerFactory.onTestFinished(true)
} catch (e: InvocationTargetException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e.targetException
} catch (e: IllegalAccessException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e
} catch (e: Throwable) {
TestLoggerFactory.onTestFinished(false)
throwables[0] = e
}
}
if (throwables[0] != null) {
throw throwables[0]!!
}
}
private fun invokeTestRunnable(runnable: () -> Unit) {
val policy = IdeaTestExecutionPolicy.current()
if (policy != null && !policy.runInDispatchThread()) {
runnable()
} else {
runInEdtAndWait {
runnable()
}
}
}
protected fun executeQuickFixes(myFixture: JavaCodeInsightTestFixture, regex: Regex, expectedFixes: Int) { protected fun executeQuickFixes(myFixture: JavaCodeInsightTestFixture, regex: Regex, expectedFixes: Int) {
val quickfixes = myFixture.getAllQuickFixes().filter { it.familyName.matches(regex) } val quickfixes = getQuickFixes(myFixture, regex, expectedFixes)
assertThat(quickfixes).`as`("Fixes matched by $regex: ${myFixture.getAllQuickFixes().map { it.familyName }}").hasSize(expectedFixes) assertThat(quickfixes.groupBy { it.familyName }).hasSize(1)
quickfixes.forEach(myFixture::launchAction) quickfixes.forEach(myFixture::launchAction)
} }
protected fun executeQuickFixesNoFamilyNameCheck(myFixture: JavaCodeInsightTestFixture, regex: Regex, expectedFixes: Int) {
val quickfixes = getQuickFixes(myFixture, regex, expectedFixes)
quickfixes.forEach(myFixture::launchAction)
}
protected fun getQuickFixes(myFixture: JavaCodeInsightTestFixture, regex: Regex, expectedFixes: Int): List<IntentionAction> {
val quickfixes = myFixture.getAllQuickFixes().filter { it.text.matches(regex) }
assertThat(quickfixes).`as`("Fixes matched by $regex: ${myFixture.getAllQuickFixes().map { it.text }}").hasSize(expectedFixes)
return quickfixes
}
class CutOffFixtureDisplayNameGenerator : DisplayNameGenerator.ReplaceUnderscores() {
override fun generateDisplayNameForMethod(testClass: Class<*>?, testMethod: Method?): String {
val nameForMethod = super.generateDisplayNameForMethod(testClass, testMethod)
return nameForMethod.substringBefore("$")
}
}
} }
@@ -0,0 +1,19 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatBinaryExpressionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/BinaryExpression")
internal fun assertThat_of_binary_expression_can_be_moved_out(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatBinaryExpressionInspection::class.java)
myFixture.configureByFile("BinaryExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Split binary expression out of assertThat()"), 149)
myFixture.checkResultByFile("BinaryExpressionAfter.java")
}
}
@@ -1,22 +0,0 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatBinaryExpressionIsTrueOrFalseInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/BinaryExpression")
internal fun assertThat_of_binary_expression_can_be_moved_out(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatBinaryExpressionIsTrueOrFalseInspection::class.java)
myFixture.configureByFile("BinaryExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Split binary expression out of assertThat()"), 148)
executeQuickFixes(myFixture, Regex.fromLiteral("Split equals() expression out of assertThat()"), 12)
myFixture.checkResultByFile("BinaryExpressionAfter.java")
}
}
}
@@ -0,0 +1,22 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatBooleanConditionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/BooleanCondition")
internal fun assertThat_with_isEqualTo_true_or_false_can_use_isTrue_or_isFalse(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatBooleanConditionInspection::class.java)
myFixture.configureByFile("BooleanConditionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isTrue()"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isFalse()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isTrue()"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isFalse()"), 4)
myFixture.checkResultByFile("BooleanConditionAfter.java")
}
}
@@ -1,24 +0,0 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatBooleanIsTrueOrFalseInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/BooleanIsTrueOrFalse")
internal fun assertThat_with_isEqualTo_true_or_false_can_use_isTrue_or_isFalse(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatBooleanIsTrueOrFalseInspection::class.java)
myFixture.configureByFile("BooleanIsTrueOrFalseBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isTrue()"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isFalse()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isTrue()"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isFalse()"), 4)
myFixture.checkResultByFile("BooleanIsTrueOrFalseAfter.java")
}
}
}
@@ -0,0 +1,104 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
@TestDataSubPath("inspections/CollectionMapExpression")
internal class AssertThatCollectionOrMapExpressionInspectionTest : AbstractCajonTest() {
@Test
internal fun assertThat_with_certain_Collection_and_Map_methods(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatCollectionOrMapExpressionInspection::class.java)
myFixture.configureByFile("CollectionMapExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isEmpty() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().contains() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsAll() of actual expression and use assertThat().containsAll() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsKey() of actual expression and use assertThat().containsKey() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsValue() of actual expression and use assertThat().containsValue() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isNotEmpty() instead"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().doesNotContain() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsKey() of actual expression and use assertThat().doesNotContainKey() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsValue() of actual expression and use assertThat().doesNotContainValue() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainEntry() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsKey() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead"), 4)
myFixture.checkResultByFile("CollectionMapExpressionAfter.java")
}
@Test
internal fun assertThat_with_certain_Collection_and_Map_methods_with_Null_values(@MyFixture myFixture: JavaCodeInsightTestFixture) {
val inspection = AssertThatCollectionOrMapExpressionInspection()
inspection.behaviorForMapValueEqualsNull = 3
myFixture.enableInspections(inspection)
myFixture.configureByFile("CollectionMapExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isEmpty() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().contains() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsAll() of actual expression and use assertThat().containsAll() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsKey() of actual expression and use assertThat().containsKey() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsValue() of actual expression and use assertThat().containsValue() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isNotEmpty() instead"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().doesNotContain() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsKey() of actual expression and use assertThat().doesNotContainKey() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove containsValue() of actual expression and use assertThat().doesNotContainValue() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry() instead"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainEntry() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsKey() instead"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead"), 0)
myFixture.checkResultByFile("CollectionMapExpressionWithNullValuesAfter.java")
}
@Test
internal fun assertThat_with_certain_Collection_and_Map_methods_with_no_quickfixes_for_get_equals_null(@MyFixture myFixture: JavaCodeInsightTestFixture) {
val inspection = AssertThatCollectionOrMapExpressionInspection()
inspection.behaviorForMapValueEqualsNull = 0
myFixture.enableInspections(inspection)
myFixture.configureByFile("CollectionMapExpressionBefore.java")
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsKey() instead"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead"), 0)
}
@Test
internal fun assertThat_with_certain_Collection_and_Map_methods_with_only_warnings_for_get_equals_null(@MyFixture myFixture: JavaCodeInsightTestFixture) {
val inspection = AssertThatCollectionOrMapExpressionInspection()
inspection.behaviorForMapValueEqualsNull = 1
myFixture.enableInspections(inspection)
myFixture.configureByFile("CollectionMapExpressionBefore.java")
val highlights = myFixture.doHighlighting()
.asSequence()
.filter { it.description == "Moving get() expression out of assertThat() would be more concise" }
.filter {
it.quickFixActionRanges?.any { innerit -> innerit.first.action.text.contains("Inspection 'Asserting a collection or map specific expression") } ?: true
}
.toList()
assertThat(highlights).hasSize(4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsKey() instead"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead"), 0)
}
@Test
internal fun assertThat_with_certain_Collection_and_Map_methods_with_both_quickfixes_for_get_equals_null(@MyFixture myFixture: JavaCodeInsightTestFixture) {
val inspection = AssertThatCollectionOrMapExpressionInspection()
inspection.behaviorForMapValueEqualsNull = 4
myFixture.enableInspections(inspection)
myFixture.configureByFile("CollectionMapExpressionBefore.java")
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead (regular map)"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsEntry(key, null) instead (degenerated map)"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainEntry() instead"), 2)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsKey() instead"), 4)
getQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().doesNotContainKey() instead"), 0)
}
}
@@ -11,11 +11,9 @@ internal class AssertThatEnumerableIsEmptyInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/EnumerableIsEmpty") @TestDataSubPath("inspections/EnumerableIsEmpty")
internal fun assertThat_with_hasSize_zero_can_use_isEmpty(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_with_hasSize_zero_can_use_isEmpty(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatEnumerableIsEmptyInspection::class.java) myFixture.enableInspections(AssertThatEnumerableIsEmptyInspection::class.java)
myFixture.configureByFile("EnumerableIsEmptyBefore.java") myFixture.configureByFile("EnumerableIsEmptyBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace hasSize() with isEmpty()"), 4) executeQuickFixes(myFixture, Regex.fromLiteral("Replace hasSize() with isEmpty()"), 5)
myFixture.checkResultByFile("EnumerableIsEmptyAfter.java") myFixture.checkResultByFile("EnumerableIsEmptyAfter.java")
} }
} }
}
@@ -9,22 +9,40 @@ import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@AddLocalJarToModule(com.google.common.base.Optional::class, org.assertj.guava.api.Assertions::class, Assertions::class) @AddLocalJarToModule(com.google.common.base.Optional::class, org.assertj.guava.api.Assertions::class, Assertions::class)
@TestDataSubPath("inspections/GuavaOptional")
internal class AssertThatGuavaOptionalInspectionTest : AbstractCajonTest() { internal class AssertThatGuavaOptionalInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/AssertThatGuavaOptional")
internal fun assertThat_get_or_isPresent_for_Guava_Optional_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_get_or_isPresent_for_Guava_Optional_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatGuavaOptionalInspection::class.java) myFixture.enableInspections(AssertThatGuavaOptionalInspection::class.java)
myFixture.configureByFile("AssertThatGuavaOptionalBefore.java") myFixture.configureByFile("GuavaOptionalBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isPresent()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Remove isPresent() of actual expression and use assertThat().isPresent() instead"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isPresent()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Remove isPresent() of actual expression and use assertThat().isAbsent() instead"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isAbsent()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Remove orNull() of actual expression and use assertThat().isPresent() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isAbsent()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Remove orNull() of actual expression and use assertThat().isAbsent() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isTrue() with isPresent()"), 1) executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().contains() instead"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isFalse() with isAbsent()"), 1) executeQuickFixes(myFixture, Regex.fromLiteral("Unwrap expected expression and replace isEqualTo() with contains()"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with contains()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with Guava assertThat().isAbsent()"), 3)
myFixture.checkResultByFile("AssertThatGuavaOptionalAfter.java") executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with Guava assertThat().isPresent()"), 3)
} myFixture.checkResultByFile("GuavaOptionalAfter.java")
}
@Test
internal fun adds_missing_Guava_import_any_order(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatGuavaOptionalInspection::class.java)
myFixture.configureByFile("WithoutPriorGuavaImportBefore.java")
executeQuickFixesNoFamilyNameCheck(myFixture, Regex(".*eplace .* with .*"), 4)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Remove .*"), 3)
myFixture.checkResultByFile("WithoutPriorGuavaImportAfter.java")
}
@Test
internal fun adds_missing_Guava_import_isAbsent_first(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatGuavaOptionalInspection::class.java)
myFixture.configureByFile("WithoutPriorGuavaImportBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with Guava assertThat().isAbsent()"), 1)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex(".*eplace .* with .*"), 3)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Remove .*"), 3)
myFixture.checkResultByFile("WithoutPriorGuavaImportAfter.java")
} }
} }
@@ -0,0 +1,20 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatInstanceOfInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/InstanceOf")
internal fun assertThat_with_instanceof_can_be_moved_out(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatInstanceOfInspection::class.java)
myFixture.configureByFile("InstanceOfBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Remove instanceof in actual expression and use assertThat().isInstanceOf() instead"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove instanceof in actual expression and use assertThat().isNotInstanceOf() instead"), 6)
myFixture.checkResultByFile("InstanceOfAfter.java")
}
}
@@ -0,0 +1,19 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatInvertedBooleanConditionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/InvertedBooleanCondition")
internal fun assertThat_with_inverted_boolean_condition_can_be_inverted(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatInvertedBooleanConditionInspection::class.java)
myFixture.configureByFile("InvertedBooleanConditionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Invert condition in assertThat()"), 25)
myFixture.checkResultByFile("InvertedBooleanConditionAfter.java")
}
}
@@ -9,20 +9,19 @@ import org.junit.jupiter.api.Test
internal class AssertThatJava8OptionalInspectionTest : AbstractCajonTest() { internal class AssertThatJava8OptionalInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/AssertThatJava8Optional") @TestDataSubPath("inspections/Java8Optional")
internal fun assertThat_get_or_isPresent_for_Java8_Optional_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_get_or_isPresent_for_Java8_Optional_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatJava8OptionalInspection::class.java) myFixture.enableInspections(AssertThatJava8OptionalInspection::class.java)
myFixture.configureByFile("AssertThatJava8OptionalBefore.java") myFixture.configureByFile("Java8OptionalBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isPresent()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Remove isPresent() of actual expression and use assertThat().isPresent() instead"), 6)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isPresent()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Remove isPresent() of actual expression and use assertThat().isNotPresent() instead"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isNotPresent()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Remove orElse() of actual expression and use assertThat().isPresent() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isNotPresent()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Remove orElse() of actual expression and use assertThat().isNotPresent() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isTrue() with isPresent()"), 1) executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().contains() instead"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isFalse() with isNotPresent()"), 1) executeQuickFixes(myFixture, Regex.fromLiteral("Remove get() of actual expression and use assertThat().containsSame() instead"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with contains()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isNotPresent()"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isSameAs() with containsSame()"), 1) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isPresent()"), 1)
myFixture.checkResultByFile("AssertThatJava8OptionalAfter.java") executeQuickFixes(myFixture, Regex.fromLiteral("Unwrap expected expression and replace isEqualTo() with contains()"), 2)
} myFixture.checkResultByFile("Java8OptionalAfter.java")
} }
} }
@@ -0,0 +1,22 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatObjectExpressionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/ObjectExpression")
internal fun assertThat_with_certain_Object_methods(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatObjectExpressionInspection::class.java)
myFixture.configureByFile("ObjectExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equals() of actual expression and use assertThat().isEqualTo() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equals() of actual expression and use assertThat().isNotEqualTo() instead"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace calls to hashCode() with hasSameHashCodeAs()"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove toString() of actual expression and use assertThat().hasToString() instead"), 1)
myFixture.checkResultByFile("ObjectExpressionAfter.java")
}
}
@@ -11,12 +11,10 @@ internal class AssertThatObjectIsNullOrNotNullInspectionTest : AbstractCajonTest
@Test @Test
@TestDataSubPath("inspections/ObjectIsNullOrNotNull") @TestDataSubPath("inspections/ObjectIsNullOrNotNull")
internal fun assertThat_with_isEqualTo_null_can_use_isNull(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_with_isEqualTo_null_can_use_isNull(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatObjectIsNullOrNotNullInspection::class.java) myFixture.enableInspections(AssertThatObjectIsNullOrNotNullInspection::class.java)
myFixture.configureByFile("ObjectIsNullOrNotNullBefore.java") myFixture.configureByFile("ObjectIsNullOrNotNullBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isNull()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isNull()"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isNotNull()"), 3) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isNotNull()"), 5)
myFixture.checkResultByFile("ObjectIsNullOrNotNullAfter.java") myFixture.checkResultByFile("ObjectIsNullOrNotNullAfter.java")
} }
} }
}
@@ -4,30 +4,33 @@ import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.extrakting
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
internal class AssertThatSizeInspectionTest : AbstractCajonTest() { internal class AssertThatSizeInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/AssertThatSize") @TestDataSubPath("inspections/Size")
internal fun assertThat_size_of_array_or_collection_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_size_of_array_collection_or_map_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatSizeInspection::class.java) myFixture.enableInspections(AssertThatSizeInspection::class.java)
myFixture.configureByFile("AssertThatSizeBefore.java") myFixture.configureByFile("SizeBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isEmpty()"), 2) assertThat(myFixture.doHighlighting()).extrakting { it.description }.containsOnlyOnce("Try to operate on the iterable itself rather than its size")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isZero() with isEmpty()"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotZero() with isNotEmpty()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThan() with isNotEmpty()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isZero() with isEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThanOrEqualTo() with isNotEmpty()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotZero() with isNotEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThan() with isEmpty()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThan() with isNotEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThanOrEqualTo() with isEmpty()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThanOrEqualTo() with isNotEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with hasSameSizeAs()"), 4) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThan() with isEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with hasSize()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThanOrEqualTo() with isEmpty()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThan() with hasSizeGreaterThan()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with hasSameSizeAs()"), 15)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThanOrEqualTo() with hasSizeGreaterThanOrEqualTo()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with hasSize()"), 15)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThan() with hasSizeLessThan()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThan() with hasSizeGreaterThan()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThanOrEqualTo() with hasSizeLessThanOrEqualTo()"), 2) executeQuickFixes(myFixture, Regex.fromLiteral("Replace isGreaterThanOrEqualTo() with hasSizeGreaterThanOrEqualTo()"), 5)
myFixture.checkResultByFile("AssertThatSizeAfter.java") executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThan() with hasSizeLessThan()"), 5)
} executeQuickFixes(myFixture, Regex.fromLiteral("Replace isLessThanOrEqualTo() with hasSizeLessThanOrEqualTo()"), 5)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove size determination of expected expression and replace hasSize() with hasSameSizeAs()"), 17)
myFixture.checkResultByFile("SizeAfter.java")
} }
} }
@@ -0,0 +1,32 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatStringExpressionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/StringExpression")
internal fun assertThat_with_certain_String_methods(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssertThatStringExpressionInspection::class.java)
myFixture.configureByFile("StringExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isEmpty() instead"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equals() of actual expression and use assertThat().isEqualTo() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equalsIgnoreCase() of actual expression and use assertThat().isEqualToIgnoringCase() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contentEquals() of actual expression and use assertThat().isEqualTo() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().contains() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove startsWith() of actual expression and use assertThat().startsWith() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove endsWith() of actual expression and use assertThat().endsWith() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove isEmpty() of actual expression and use assertThat().isNotEmpty() instead"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equals() of actual expression and use assertThat().isNotEqualTo() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove equalsIgnoreCase() of actual expression and use assertThat().isNotEqualToIgnoringCase() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contentEquals() of actual expression and use assertThat().isNotEqualTo() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove contains() of actual expression and use assertThat().doesNotContain() instead"), 4)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove startsWith() of actual expression and use assertThat().doesNotStartWith() instead"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Remove endsWith() of actual expression and use assertThat().doesNotEndWith() instead"), 3)
myFixture.checkResultByFile("StringExpressionAfter.java")
}
}
@@ -4,6 +4,8 @@ import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.extrakting
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
internal class AssertThatStringIsEmptyInspectionTest : AbstractCajonTest() { internal class AssertThatStringIsEmptyInspectionTest : AbstractCajonTest() {
@@ -11,12 +13,15 @@ internal class AssertThatStringIsEmptyInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/StringIsEmpty") @TestDataSubPath("inspections/StringIsEmpty")
internal fun assertThat_with_isEqualTo_emptyString_can_use_isEmpty(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun assertThat_with_isEqualTo_emptyString_can_use_isEmpty(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatStringIsEmptyInspection::class.java) myFixture.enableInspections(AssertThatStringIsEmptyInspection::class.java)
myFixture.configureByFile("StringIsEmptyBefore.java") myFixture.configureByFile("StringIsEmptyBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isEmpty()"), 2) val highlights = myFixture.doHighlighting()
executeQuickFixes(myFixture, Regex.fromLiteral("Replace hasSize() with isEmpty()"), 2) .asSequence()
.filter { it.description?.contains(" can be simplified to") ?: false }
.toList()
assertThat(highlights).hasSize(6).extrakting { it.text }.doesNotContain("assertThat")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isEmpty()"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace hasSize() with isEmpty()"), 3)
myFixture.checkResultByFile("StringIsEmptyAfter.java") myFixture.checkResultByFile("StringIsEmptyAfter.java")
} }
} }
}
@@ -0,0 +1,22 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.AddLocalJarToModule
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
@AddLocalJarToModule(Assertions::class, Test::class, org.junit.Test::class)
internal class AssumeThatInsteadOfReturnInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/AssumeThat")
internal fun conditional_returns_can_be_replaced_by_assumeThat(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(AssumeThatInsteadOfReturnInspection::class.java)
myFixture.configureByFile("AssumeThatBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace if statement by assumeTrue()"), 5)
myFixture.checkResultByFile("AssumeThatAfter.java")
}
}
@@ -0,0 +1,24 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.AddLocalJarToModule
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
@AddLocalJarToModule(com.google.common.base.Optional::class, org.assertj.guava.api.Assertions::class, Assertions::class)
internal class ImplicitAssertionInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/ImplicitAssertion")
internal fun implicit_assertions_can_be_removed(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(ImplicitAssertionInspection::class.java)
myFixture.configureByFile("ImplicitAssertionBefore.java")
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Delete implicit isNotNull\\(\\) covered by .*"), 101)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Delete implicit isNotEmpty\\(\\) covered by .*"), 17)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Delete implicit isPresent\\(\\) covered by .*"), 8)
myFixture.checkResultByFile("ImplicitAssertionAfter.java")
}
}
@@ -15,11 +15,10 @@ internal class JUnitAssertToAssertJInspectionTest : AbstractCajonTest() {
@Test @Test
@TestDataSubPath("inspections/JUnitAssertToAssertJ") @TestDataSubPath("inspections/JUnitAssertToAssertJ")
internal fun junit_Assertions_can_be_converted_into_AssertJ(@MyFixture myFixture: JavaCodeInsightTestFixture) { internal fun junit_Assertions_can_be_converted_into_AssertJ(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(JUnitAssertToAssertJInspection::class.java) myFixture.enableInspections(JUnitAssertToAssertJInspection::class.java)
myFixture.configureByFile("JUnitAssertToAssertJInspectionBefore.java") myFixture.configureByFile("JUnitAssertToAssertJInspectionBefore.java")
executeQuickFixes(myFixture, Regex("Replace .*"), 38) executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Convert assert.*\\(\\) to assertThat\\(\\).*"), 48)
executeQuickFixesNoFamilyNameCheck(myFixture, Regex("Convert assume.*\\(\\) to assumeThat\\(\\).*"), 7)
myFixture.checkResultByFile("JUnitAssertToAssertJInspectionAfter.java") myFixture.checkResultByFile("JUnitAssertToAssertJInspectionAfter.java")
} }
} }
}
@@ -0,0 +1,19 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class JoinAssertThatStatementsInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/JoinStatements")
internal fun assertThat_statements_can_be_joined_together(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(JoinAssertThatStatementsInspection::class.java)
myFixture.configureByFile("JoinStatementsBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Join assertThat() statements"), 5)
myFixture.checkResultByFile("JoinStatementsAfter.java")
}
}
@@ -0,0 +1,19 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class JoinVarArgsContainsInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/JoinVarArgsContains")
internal fun join_contains_and_doesNotContain_together_where_possible(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.enableInspections(JoinVarArgsContainsInspection::class.java)
myFixture.configureByFile("JoinVarArgsContainsBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Join multiple arguments to variadic argument method calls"), 3)
myFixture.checkResultByFile("JoinVarArgsContainsAfter.java")
}
}
@@ -0,0 +1,80 @@
package de.platon42.intellij.plugins.cajon.references
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
@TestDataSubPath("references")
internal class ExtractorReferenceContributorTest : AbstractCajonTest() {
@Test
internal fun extractor_is_able_to_find_reference_for_field_extracting(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference1.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).isEqualTo("private String name;")
}
@Test
internal fun extractor_is_able_to_find_reference_for_first_part_of_a_path(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference2.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).isEqualTo("protected Address address;")
}
@Test
internal fun extractor_is_able_to_find_reference_for_second_part_of_a_path_and_both_getter_and_field(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference3.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("private String street;")
}
@Test
internal fun extractor_is_able_to_find_reference_on_a_bare_method_call(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference4.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("public Boolean getREALLYnoMAILINGS()")
}
@Test
internal fun extractor_is_able_to_find_reference_with_only_Getter_on_second_part(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference5.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("public boolean isNoMailings()")
}
@Test
internal fun extractor_is_able_to_find_reference_using_byName_extractor(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference6.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).isEqualTo("private String name;")
}
@Test
internal fun extractor_is_able_to_find_reference_using_resultOf_extractor(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference7.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("public String getStreetName()")
}
@Test
internal fun extractor_is_able_to_find_reference_for_field_extraction_on_list(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference8.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).isEqualTo("private String name;")
}
@Test
internal fun extractor_is_able_to_find_reference_for_field_flat_extraction_of_path_on_list(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference9.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("private String street;")
}
@Test
internal fun extractor_is_able_to_find_reference_for_extraction_on_result_of_method(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference10.java", "Address.java", "Contact.java")
assertThat(myFixture.elementAtCaret.text).startsWith("public String getStreetName()")
}
@Test
internal fun extractor_is_unable_to_find_reference(@MyFixture myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByFiles("FindReference11.java", "Address.java", "Contact.java")
assertThatThrownBy { myFixture.elementAtCaret.text }.isInstanceOf(AssertionError::class.java)
}
}
@@ -0,0 +1,19 @@
package org.assertj.core.api
import org.assertj.core.groups.FieldsOrPropertiesExtractor
import java.util.*
// Workaround for ambiguous method signature of .extracting() see https://github.com/joel-costigliola/assertj-core/issues/1499
fun <SELF : AbstractIterableAssert<SELF, ACTUAL, ELEMENT, ELEMENT_ASSERT>,
ACTUAL : Iterable<ELEMENT>,
ELEMENT,
ELEMENT_ASSERT : AbstractAssert<ELEMENT_ASSERT, ELEMENT>,
V>
AbstractIterableAssert<SELF, ACTUAL, ELEMENT, ELEMENT_ASSERT>.extrakting(extractor: (ELEMENT) -> V): AbstractListAssert<*, List<V>, V, ObjectAssert<V>> {
val values = FieldsOrPropertiesExtractor.extract(actual, extractor)
if (actual is SortedSet<*>) {
usingDefaultElementComparator()
}
@Suppress("UNCHECKED_CAST")
return newListAssertInstance(values).withAssertionState(myself) as AbstractListAssert<*, List<V>, V, ObjectAssert<V>>
}
@@ -1,35 +0,0 @@
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatJava8Optional {
private void assertThatJava8Optional() {
Optional<String> opt = Optional.empty();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).contains("foo");
assertThat(opt).containsSame("foo");
assertThat(opt.get()).isNotEqualTo("foo");
assertThat(opt.get()).isNotSameAs("foo");
assertThat(opt).contains("foo");
assertThat(opt).contains("foo");
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotPresent();
assertThat(opt).isPresent();
}
}
@@ -1,35 +0,0 @@
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatJava8Optional {
private void assertThatJava8Optional() {
Optional<String> opt = Optional.empty();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt.get()).isNotEqualTo("foo");
assertThat(opt.get()).isNotSameAs("foo");
assertThat(opt).isEqualTo(Optional.of("foo"));
assertThat(opt).isEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isEqualTo(Optional.empty());
assertThat(opt).isNotEqualTo(Optional.empty());
}
}
@@ -1,43 +0,0 @@
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatSize {
private void assertThatSize() {
ArrayList<String> list = new ArrayList<>();
ArrayList<String> otherList = new ArrayList<>();
long[] array = new long[5];
long[] otherArray = new long[4];
assertThat(list).isEmpty();
assertThat(list).isEmpty();
assertThat(list).isNotEmpty();
assertThat(list).as("hi").isNotEmpty();
assertThat(list).isNotEmpty();
assertThat(list).isEmpty();
assertThat(list).isEmpty();
assertThat(list).hasSameSizeAs(otherList);
assertThat(list).hasSameSizeAs(array);
assertThat(list).hasSize(1);
assertThat(list).hasSizeGreaterThan(list.size() * 2);
assertThat(list).hasSizeGreaterThanOrEqualTo(list.size() * 2);
assertThat(list).hasSizeLessThan(list.size() * 2);
assertThat(list).hasSizeLessThanOrEqualTo(list.size() * 2);
assertThat(array).isEmpty();
assertThat(array).isEmpty();
assertThat(array).isNotEmpty();
assertThat(array).as("hi").isNotEmpty();
assertThat(array).isNotEmpty();
assertThat(array).isEmpty();
assertThat(array).isEmpty();
assertThat(array).hasSameSizeAs(list);
assertThat(array).hasSameSizeAs(otherArray);
assertThat(array).hasSize(1);
assertThat(array).hasSizeGreaterThan(otherArray.length - 1);
assertThat(array).hasSizeGreaterThanOrEqualTo(otherArray.length + 1);
assertThat(array).hasSizeLessThan(otherArray.length - 3);
assertThat(array).hasSizeLessThanOrEqualTo(1 - otherArray.length);
}
}
@@ -1,43 +0,0 @@
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatSize {
private void assertThatSize() {
ArrayList<String> list = new ArrayList<>();
ArrayList<String> otherList = new ArrayList<>();
long[] array = new long[5];
long[] otherArray = new long[4];
assertThat(list.size()).isEqualTo(0);
assertThat(list.size()).isZero();
assertThat(list.size()).isNotZero();
assertThat(list.size()).as("hi").isGreaterThan(0);
assertThat(list.size()).isGreaterThanOrEqualTo(1);
assertThat(list.size()).isLessThan(1);
assertThat(list.size()).isLessThanOrEqualTo(0);
assertThat(list.size()).isEqualTo(otherList.size());
assertThat(list.size()).isEqualTo(array.length);
assertThat(list.size()).isEqualTo(1);
assertThat(list.size()).isGreaterThan(list.size() * 2);
assertThat(list.size()).isGreaterThanOrEqualTo(list.size() * 2);
assertThat(list.size()).isLessThan(list.size() * 2);
assertThat(list.size()).isLessThanOrEqualTo(list.size() * 2);
assertThat(array.length).isEqualTo(0);
assertThat(array.length).isZero();
assertThat(array.length).isNotZero();
assertThat(array.length).as("hi").isGreaterThan(0);
assertThat(array.length).isGreaterThanOrEqualTo(1);
assertThat(array.length).isLessThan(1);
assertThat(array.length).isLessThanOrEqualTo(0);
assertThat(array.length).isEqualTo(list.size());
assertThat(array.length).isEqualTo(otherArray.length);
assertThat(array.length).isEqualTo(1);
assertThat(array.length).isGreaterThan(otherArray.length - 1);
assertThat(array.length).isGreaterThanOrEqualTo(otherArray.length + 1);
assertThat(array.length).isLessThan(otherArray.length - 3);
assertThat(array.length).isLessThanOrEqualTo(1 - otherArray.length);
}
}
@@ -0,0 +1,158 @@
import org.junit.Test;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
public class AssumeThat {
@Test
public void junit4_return_very_early() {
assumeThat(new Random().nextBoolean()).isFalse();
System.out.println("sweet!"); // single else statement
String foobar = System.getProperty("foobar");
assertThat(foobar).isNotEmpty();
}
@Test
public void junit4_return_in_else_branch_with_declaration_in_code_block_and_lots_of_comments() {
// primary comments
assumeThat(new Random().nextBoolean()).isTrue();
{
// Block start comment will be retained
String anotherString = "narf"; // This one is likely to be kept
assertThat(foobar).isNotEmpty();
assertThat(foobar).isEqualTo(anotherString);
// Block end comment is also going to be retained
} /* weird places */
// well, well, let's do some checks
String foobar = System.getProperty("foobar");
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.Test
public void junit5_return_after_call_without_else_branch() {
String foobar = System.getProperty("car_manufacturer");
assumeThat(foobar.equals("Volkswagen")).isFalse();
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.Test
public void junit5_return_with_empty_else_block() {
String foobar = System.getProperty("car_manufacturer");
assumeThat(foobar.equals("Volkswagen")).isFalse();
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.TestTemplate
public void junit5_return_inside_recursion_with_comments() {
String foobar = System.getProperty("car_manufacturer");
if (foobar != null) {
assumeThat(!foobar.equals("Volkswagen")).isTrue();
// I doubted this comment will be retained -- but surprise!
assertThat(foobar).isNotEmpty();
// we might keep this one alright.
foobar = "narf";
// And the final one? Again what learned.
}
assertThat(foobar).startsWith("another ecology rapist");
if(foobar.length() == 0)
{
return;
}
assertThat(foobar).endsWith("!!!");
}
@Test
public void junit4_return_after_assertion() {
String foobar = System.getProperty("car_manufacturer");
assertThat(foobar).isNotNull();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_subroutine() {
String foobar = System.getProperty("car_manufacturer");
assertStuff(foobar);
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_deep_subroutine() {
String foobar = System.getProperty("car_manufacturer");
firstMethod();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_deep_subroutine_with_recursion() {
String foobar = System.getProperty("car_manufacturer");
firstMethodWithPotentialRecursion();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_no_harm_with_infinite_recursion() {
String foobar = System.getProperty("car_manufacturer");
infiniteRecursion();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
private void assertStuff(String foobar) {
if (new Random().nextBoolean()) {
assertThat(foobar).isNotEmpty();
} else {
assertThat(foobar).isNull();
}
}
private void firstMethod() {
if (new Random().nextBoolean()) {
assertStuff("narf");
}
}
private void firstMethodWithPotentialRecursion() {
if (new Random().nextBoolean()) {
assertStuff("narf");
} else {
infiniteRecursion();
}
}
private void infiniteRecursion() {
secondaryRecursion();
secondaryRecursion();
}
private void secondaryRecursion() {
infiniteRecursion();
infiniteRecursion();
}
}
@@ -0,0 +1,163 @@
import org.junit.Test;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
public class AssumeThat {
@Test
public void junit4_return_very_early() {
if (new Random().nextBoolean()) {
return;
} else System.out.println("sweet!"); // single else statement
String foobar = System.getProperty("foobar");
assertThat(foobar).isNotEmpty();
}
@Test
public void junit4_return_in_else_branch_with_declaration_in_code_block_and_lots_of_comments() {
// primary comments
if (new Random().nextBoolean()) /* strange place for a comment */ {
// Block start comment will be retained
String anotherString = "narf"; // This one is likely to be kept
assertThat(foobar).isNotEmpty();
assertThat(foobar).isEqualTo(anotherString);
// Block end comment is also going to be retained
} /* weird places */ else /* more weird places */ { // this one is lost
return; // Would be sweet to keep this comment, too.
} // another comment lost
// well, well, let's do some checks
String foobar = System.getProperty("foobar");
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.Test
public void junit5_return_after_call_without_else_branch() {
String foobar = System.getProperty("car_manufacturer");
if (foobar.equals("Volkswagen")) return; // no need for Volkswagen to perform tests
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.Test
public void junit5_return_with_empty_else_block() {
String foobar = System.getProperty("car_manufacturer");
if (foobar.equals("Volkswagen")) return; else {}
assertThat(foobar).isNotEmpty();
}
@org.junit.jupiter.api.TestTemplate
public void junit5_return_inside_recursion_with_comments() {
String foobar = System.getProperty("car_manufacturer");
if (foobar != null) {
if (!foobar.equals("Volkswagen")) {
// I doubted this comment will be retained -- but surprise!
assertThat(foobar).isNotEmpty();
// we might keep this one alright.
foobar = "narf";
// And the final one? Again what learned.
} else {
// ooops, how did that happen?
return;
}
}
assertThat(foobar).startsWith("another ecology rapist");
if(foobar.length() == 0)
{
return;
}
assertThat(foobar).endsWith("!!!");
}
@Test
public void junit4_return_after_assertion() {
String foobar = System.getProperty("car_manufacturer");
assertThat(foobar).isNotNull();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_subroutine() {
String foobar = System.getProperty("car_manufacturer");
assertStuff(foobar);
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_deep_subroutine() {
String foobar = System.getProperty("car_manufacturer");
firstMethod();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_return_after_assertions_in_deep_subroutine_with_recursion() {
String foobar = System.getProperty("car_manufacturer");
firstMethodWithPotentialRecursion();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
@Test
public void junit4_no_harm_with_infinite_recursion() {
String foobar = System.getProperty("car_manufacturer");
infiniteRecursion();
if (!foobar.equals("Volkswagen")) {
assertThat(foobar).isNotEmpty();
} else {
return;
}
assertThat(foobar).startsWith("another ecology rapist");
}
private void assertStuff(String foobar) {
if (new Random().nextBoolean()) {
assertThat(foobar).isNotEmpty();
} else {
assertThat(foobar).isNull();
}
}
private void firstMethod() {
if (new Random().nextBoolean()) {
assertStuff("narf");
}
}
private void firstMethodWithPotentialRecursion() {
if (new Random().nextBoolean()) {
assertStuff("narf");
} else {
infiniteRecursion();
}
}
private void infiniteRecursion() {
secondaryRecursion();
secondaryRecursion();
}
private void secondaryRecursion() {
infiniteRecursion();
infiniteRecursion();
}
}

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