├── .gitignore ├── .jitpack.yml ├── LICENSE ├── README.adoc ├── build.gradle ├── gradle ├── publishing.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── reactor-error-prone ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── lhotari │ │ └── reactor │ │ └── errorprone │ │ ├── AbstractReturnValueIgnored.java │ │ ├── ReactiveStreamsPublisherReturnValueIgnored.java │ │ ├── ReactorInnerPublisherIgnored.java │ │ └── ReactorStepVerifierReturnValueIgnored.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── lhotari │ │ └── reactor │ │ └── errorprone │ │ ├── ReactiveStreamsPublisherReturnValueIgnoredTest.java │ │ ├── ReactorInnerPublisherIgnoredTest.java │ │ └── ReactorStepVerifierReturnValueIgnoredTest.java │ └── resources │ └── com │ └── github │ └── lhotari │ └── reactor │ └── errorprone │ └── testdata │ ├── ReactiveStreamsPublisherReturnValueIgnoredNegativeCases.java │ ├── ReactiveStreamsPublisherReturnValueIgnoredPositiveCases.java │ ├── ReactorInnerPublisherIgnoredNegativeCases.java │ ├── ReactorInnerPublisherIgnoredPositiveCases.java │ ├── ReactorStepVerifierReturnValueIgnoredNegativeCases.java │ └── ReactorStepVerifierReturnValueIgnoredPositiveCases.java ├── reactor-sample ├── build.gradle └── src │ └── main │ └── java │ └── InvalidReactorCodeSample.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /.jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | :toc: macro 2 | 3 | [#top] 4 | = reactor-error-prone - Catch common Project Reactor mistakes as compile-time errors 5 | 6 | image:https://badges.gitter.im/reactor-error-prone/community.svg[Gitter, link=https://gitter.im/reactor-error-prone/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge] 7 | 8 | Consists of additional checks for Google https://errorprone.info/[Error Prone]. The goal is to catch common Project Reactor mistakes as compile-time errors 9 | 10 | toc::[] 11 | 12 | == Checks 13 | 14 | === `ReactiveStreamsPublisherReturnValueIgnored` 15 | 16 | Catches issues where a Reactive Streams Publisher (Flux|Mono) return value is ignored. 17 | 18 | The Project Reactor documentation contains this example in https://projectreactor.io/docs/core/release/reference/index.html#faq.chain["I used an operator on my Flux but it does not seem to apply. What gives?"] 19 | 20 | ``` 21 | Flux flux = Flux.just("something", "chain"); 22 | flux.map(secret -> secret.replaceAll(".", "*")); 23 | flux.subscribe(next -> System.out.println("Received: " + next)); 24 | ``` 25 | 26 | The check will warn about this type of mistakes. 27 | 28 | This check is based on Error Prone's https://errorprone.info/bugpattern/RxReturnValueIgnored[RxReturnValueIgnored]. 29 | 30 | === `ReactorInnerPublisherIgnored` 31 | 32 | Catches issues where `.then()` is called on a type of `Flux>` or `Mono>`. Generally this indicates an error since the "inner publishers" will never get executed since nothing will subscribe to them. 33 | 34 | Here's an example of such invalid code: 35 | ``` 36 | Flux.range(1, 10) 37 | .map(n -> 38 | Flux.range(1, n * n) 39 | .doOnNext(i -> System.out.println("NEVER PRINTED:" + n + " - " + i))) 40 | .then() 41 | .subscribe(); 42 | ``` 43 | 44 | The same rule is used for `then`, `thenEmpty`, `thenMany`, `thenReturn` and `and` methods. 45 | 46 | === `ReactorStepVerifierReturnValueIgnored` 47 | 48 | Catches issues where a Reactor StepVerifier / StepVerifier.LastStep return value is ignored. 49 | 50 | The https://projectreactor.io/docs/core/release/reference/index.html#_testing_a_scenario_with_stepverifier[Reactor reference guide has this notice] about the importance of calling `verify` (or one of the `verify*` methods): 51 | 52 | IMPORTANT: Remember the `verify()` step, which triggers the verification. To 53 | help, the API includes a few shortcut methods that combine the terminal expectations with 54 | a call to `verify()`: `verifyComplete()`, `verifyError()`, `verifyErrorMessage(String)`, 55 | and others. 56 | 57 | The check will warn about violations of this guideline. 58 | 59 | == Usage 60 | 61 | This library is available via Jitpack. The repository information is at https://jitpack.io/#lhotari/reactor-error-prone . 62 | 63 | === Using in Gradle with gradle-errorprone-plugin 64 | 65 | ``` 66 | plugins { 67 | // ... 68 | id "net.ltgt.errorprone" version "1.2.1" 69 | } 70 | 71 | repositories { 72 | // ... 73 | maven { 74 | url 'https://jitpack.io' 75 | content { 76 | // limits using the jitpack repository for specific artifacts 77 | includeGroup 'com.github.lhotari' 78 | } 79 | } 80 | } 81 | 82 | dependencies { 83 | errorprone 'com.google.errorprone:error_prone_core:2.4.0' 84 | errorprone 'com.github.lhotari:reactor-error-prone:0.1.4' 85 | // required when using Java 8 86 | errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1' 87 | // ... 88 | } 89 | ``` 90 | 91 | === Using in Maven 92 | 93 | Follow instructions from http://errorprone.info/docs/installation[Error Prone installation] for using Error Prone in Maven. 94 | 95 | required repository 96 | ``` 97 | 98 | 99 | jitpack.io 100 | https://jitpack.io 101 | 102 | 103 | ``` 104 | 105 | additional `path` for `annotationProcessorPaths` 106 | ``` 107 | 108 | com.github.lhotari 109 | reactor-error-prone 110 | 0.1.4 111 | 112 | ``` 113 | 114 | == Usage Tips 115 | 116 | === Changing severity of checks in Gradle 117 | 118 | https://github.com/tbroyer/gradle-errorprone-plugin[Gradle Error Prone] plugin has very flexible ways of configuring the checks. 119 | 120 | This is how to make the build fail when an issue is found in `ReactiveStreamsPublisherReturnValueIgnored`, `ReactorInnerPublisherIgnored` or `ReactorStepVerifierReturnValueIgnored` checks: 121 | 122 | ``` 123 | tasks.withType(JavaCompile).configureEach { 124 | options.errorprone { 125 | error 'ReactiveStreamsPublisherReturnValueIgnored', 'ReactorInnerPublisherIgnored', 'ReactorStepVerifierReturnValueIgnored' 126 | } 127 | } 128 | ``` 129 | 130 | This can be a powerful way to prevent the most obvious issues entering the code base. 131 | 132 | === Lombok and Error Prone 133 | 134 | Some of the default Error Prone checks make the compilation to crash. The workaround is to disable those checks that have problems with Lombok generated code. 135 | 136 | Example of custom configuration to workaround Lombok & Error Prone issues: 137 | ``` 138 | tasks.withType(JavaCompile).configureEach { 139 | options.errorprone { 140 | disable 'FallThrough', 'OverrideThrowableToString', 'UnusedVariable', 'UnusedMethod' 141 | } 142 | } 143 | ``` 144 | 145 | Another solution is to configure Lombok to generate `@javax.annotation.Generated("lombok")` annotations by setting `lombok.addJavaxGeneratedAnnotation = true` in `lombok.config` files (i.e. `src/main/java/lombok.config`, `src/test/java/lombok.config`): 146 | ``` 147 | lombok.addJavaxGeneratedAnnotation = true 148 | ``` 149 | You might have to add `compileOnly 'javax.annotation:javax.annotation-api:1.3.2'` dependency in the gradle build file if `javax.annotation.Generated` annotation class isn't already available on the compilation classpath. 150 | 151 | When Lombok has been configured with `lombok.addJavaxGeneratedAnnotation = true`, the `disableWarningsInGeneratedCode = true` setting of the Errorprone Gradle plugin will now be effective and Errorprone should no more crash when working with projects that use Lombok. 152 | 153 | Example configuration: 154 | ``` 155 | dependencies { 156 | compileOnly 'javax.annotation:javax.annotation-api:1.3.2' 157 | } 158 | 159 | tasks.withType(JavaCompile).configureEach { 160 | options.errorprone { 161 | disableWarningsInGeneratedCode = true 162 | } 163 | } 164 | ``` 165 | 166 | == License 167 | 168 | Reactor Error Prone is Open Source Software released under the https://www.apache.org/licenses/LICENSE-2.0[Apache License 2.0] 169 | 170 | == How to Contribute 171 | 172 | The library is Apache 2.0 licensed and accepts contributions via GitHub pull requests. 173 | 174 | . Fork it 175 | . Create your feature branch (`git checkout -b my-new-feature`) 176 | . Commit your changes (`git commit -am 'Added some feature'`) 177 | . Push to the branch (`git push origin my-new-feature`) 178 | . Create new Pull Request 179 | 180 | The development requires OpenJDK 11. You can https://sdkman.io/usage[use sdkman to install the JDK]. 181 | 182 | == Bugs and Feature Requests 183 | 184 | If you detect a bug or have a feature request or a good idea for catching common Project Reactor bug patterns as compile-time errors, please https://github.com/lhotari/reactor-error-prone/issues/new[open a GitHub issue] or ping one of the contributors on Twitter or image:https://badges.gitter.im/reactor-error-prone/community.svg[Gitter, link=https://gitter.im/reactor-error-prone/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge]. 185 | 186 | == Contributors 187 | 188 | * https://github.com/lhotari[Lari Hotari] (https://twitter.com/lhotari[@lhotari on Twitter]) 189 | * https://github.com/Stephan202[Stephan Schroevers] 190 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | apply plugin: 'java' 3 | 4 | group = 'com.github.lhotari.reactor-error-prone' 5 | version = '0.0.1-SNAPSHOT' 6 | sourceCompatibility = targetCompatibility = '8' 7 | 8 | repositories { 9 | jcenter() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle/publishing.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | task sourceJar(type: Jar, dependsOn: classes) { 4 | classifier 'sources' 5 | from sourceSets.main.allSource 6 | } 7 | 8 | publishing { 9 | publications { 10 | maven(MavenPublication) { publication -> 11 | from components.java 12 | artifact sourceJar 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lhotari/reactor-error-prone/7b31638d8507b21bb4f535102317c4299968cd1b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /reactor-error-prone/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | apply from: "$rootDir/gradle/publishing.gradle" 6 | 7 | dependencies { 8 | compileOnly 'com.google.errorprone:error_prone_core:2.3.3' 9 | compileOnly 'com.google.errorprone:error_prone_annotation:2.3.3' 10 | compileOnly 'com.google.auto.service:auto-service:1.0-rc6' 11 | annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' 12 | testImplementation 'com.google.errorprone:error_prone_test_helpers:2.3.3' 13 | testImplementation 'com.google.errorprone:error_prone_core:2.3.3' 14 | testImplementation 'io.projectreactor:reactor-core:3.3.0.RELEASE' 15 | } 16 | -------------------------------------------------------------------------------- /reactor-error-prone/src/main/java/com/github/lhotari/reactor/errorprone/AbstractReturnValueIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.errorprone.VisitorState; 4 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 5 | import com.google.errorprone.matchers.Description; 6 | import com.google.errorprone.util.ASTHelpers; 7 | import com.sun.source.tree.*; 8 | import com.sun.tools.javac.code.Symbol; 9 | 10 | import static com.google.errorprone.util.ASTHelpers.getSymbol; 11 | import static com.google.errorprone.util.ASTHelpers.hasAnnotation; 12 | 13 | abstract class AbstractReturnValueIgnored extends com.google.errorprone.bugpatterns.AbstractReturnValueIgnored { 14 | @Override 15 | public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { 16 | Description description = super.matchMethodInvocation(tree, state); 17 | return description.equals(Description.NO_MATCH) ? Description.NO_MATCH : describeMatch(tree); 18 | } 19 | 20 | @Override 21 | public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { 22 | Description description = super.matchMemberReference(tree, state); 23 | return description.equals(Description.NO_MATCH) ? Description.NO_MATCH : describeMatch(tree); 24 | } 25 | 26 | static boolean hasCanIgnoreReturnValueAnnotation(ExpressionTree tree, VisitorState state) { 27 | Symbol untypedSymbol = getSymbol(tree); 28 | if (!(untypedSymbol instanceof Symbol.MethodSymbol)) { 29 | return false; 30 | } 31 | 32 | Symbol.MethodSymbol sym = (Symbol.MethodSymbol) untypedSymbol; 33 | // Directly has @CanIgnoreReturnValue 34 | if (ASTHelpers.hasAnnotation(sym, CanIgnoreReturnValue.class, state)) { 35 | return true; 36 | } 37 | 38 | // If a super-class's method is annotated with @CanIgnoreReturnValue, we only honor that 39 | // if the super-type returned the exact same type. This lets us catch issues where a 40 | // superclass was annotated with @CanIgnoreReturnValue but the parent did not intend to 41 | // return an Reactive Streams publisher type 42 | return ASTHelpers.findSuperMethods(sym, state.getTypes()).stream() 43 | .anyMatch( 44 | superSym -> 45 | hasAnnotation(superSym, CanIgnoreReturnValue.class, state) 46 | && superSym.getReturnType().tsym.equals(sym.getReturnType().tsym)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /reactor-error-prone/src/main/java/com/github/lhotari/reactor/errorprone/ReactiveStreamsPublisherReturnValueIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.google.errorprone.BugPattern; 5 | import com.google.errorprone.bugpatterns.BugChecker; 6 | import com.google.errorprone.matchers.Matcher; 7 | import com.google.errorprone.matchers.Matchers; 8 | import com.sun.source.tree.ExpressionTree; 9 | import com.sun.source.tree.Tree.Kind; 10 | 11 | import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; 12 | import static com.google.errorprone.matchers.Matchers.*; 13 | 14 | /** 15 | * Checks that Reactive Streams Publisher return values aren't ignored 16 | *

17 | * Based on https://github.com/google/error-prone/blob/a2704300027454d2db5e701f8b2444fb35b765b4/core/src/main/java/com/google/errorprone/bugpatterns/RxReturnValueIgnored.java 18 | *

19 | * See {@link BugPattern} annotation. 20 | */ 21 | @BugPattern( 22 | name = "ReactiveStreamsPublisherReturnValueIgnored", 23 | summary = 24 | "Returned Reactive Streams Publisher must be checked. Ignoring a returned Publisher means it is never " 25 | + "scheduled for execution", 26 | explanation = 27 | "Methods that return an ignored Publisher generally " 28 | + "indicate errors.\n\nIf you don’t check the return value of these methods, the " 29 | + "Publisher may never execute. It also means the error case is not being handled", 30 | linkType = BugPattern.LinkType.CUSTOM, 31 | link = "https://github.com/lhotari/reactor-error-prone/wiki/ReactiveStreamsPublisherReturnValueIgnored", 32 | severity = WARNING) 33 | @AutoService(BugChecker.class) 34 | public final class ReactiveStreamsPublisherReturnValueIgnored extends AbstractReturnValueIgnored { 35 | private static final Matcher MATCHER = 36 | allOf( 37 | Matchers.kindIs(Kind.METHOD_INVOCATION), 38 | isSubtypeOf("org.reactivestreams.Publisher"), 39 | not(AbstractReturnValueIgnored::hasCanIgnoreReturnValueAnnotation)); 40 | 41 | @Override 42 | public Matcher specializedMatcher() { 43 | return MATCHER; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /reactor-error-prone/src/main/java/com/github/lhotari/reactor/errorprone/ReactorInnerPublisherIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.errorprone.BugPattern; 6 | import com.google.errorprone.VisitorState; 7 | import com.google.errorprone.bugpatterns.BugChecker; 8 | import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; 9 | import com.google.errorprone.matchers.Description; 10 | import com.google.errorprone.matchers.Matcher; 11 | import com.google.errorprone.matchers.Matchers; 12 | import com.google.errorprone.suppliers.Suppliers; 13 | import com.google.errorprone.util.ASTHelpers; 14 | import com.sun.source.tree.ExpressionTree; 15 | import com.sun.source.tree.MethodInvocationTree; 16 | import com.sun.tools.javac.code.*; 17 | 18 | import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; 19 | import static com.google.errorprone.matchers.Description.NO_MATCH; 20 | import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod; 21 | import static com.google.errorprone.predicates.TypePredicates.isDescendantOfAny; 22 | 23 | @BugPattern( 24 | name = "ReactorInnerPublisherIgnored", 25 | summary = 26 | "The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution", 27 | explanation = 28 | "Calling then|thenEmpty|thenMany|thenReturn|and on a Flux> type or Mono> generally " 29 | + "indicate errors.\n\nThe inner publisher will never execute." + 30 | "It also means the error case is not being handled", 31 | linkType = BugPattern.LinkType.CUSTOM, 32 | link = "https://github.com/lhotari/reactor-error-prone/wiki/ReactorInnerPublisherIgnored", 33 | severity = WARNING) 34 | @AutoService(BugChecker.class) 35 | public class ReactorInnerPublisherIgnored extends BugChecker implements MethodInvocationTreeMatcher { 36 | Matcher COMPLETES_IGNORING_ELEMENTS_METHOD = 37 | instanceMethod() 38 | .onClass( 39 | isDescendantOfAny( 40 | ImmutableList.of( 41 | "reactor.core.publisher.Flux", 42 | "reactor.core.publisher.Mono" 43 | ) 44 | ) 45 | ) 46 | .namedAnyOf("then", "thenEmpty", "thenMany", "thenReturn", "and"); 47 | 48 | @Override 49 | public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { 50 | if (!COMPLETES_IGNORING_ELEMENTS_METHOD.matches(tree, state)) { 51 | return NO_MATCH; 52 | } 53 | 54 | Type objectType = Suppliers.typeFromString("java.lang.Object").get(state); 55 | Type fluxType = Suppliers.typeFromString("reactor.core.publisher.Flux").get(state); 56 | Type monoType = Suppliers.typeFromString("reactor.core.publisher.Mono").get(state); 57 | Type receiverType = ASTHelpers.getReceiverType(tree); 58 | Type fluxOrMonoType = ASTHelpers.isSubtype(fluxType, receiverType, state) ? fluxType : monoType; 59 | 60 | Type typeArg = extractTypeArgAsMemberOfSupertype( 61 | receiverType, 62 | fluxOrMonoType.asElement(), 63 | 0, 64 | state.getTypes()); 65 | 66 | if (typeArg == null 67 | || ASTHelpers.isSubtype(objectType, typeArg, state) 68 | || !(ASTHelpers.isSubtype(fluxType, typeArg, state) || ASTHelpers.isSubtype(monoType, typeArg, state))) { 69 | return NO_MATCH; 70 | } 71 | 72 | return describeMatch(tree); 73 | } 74 | 75 | private static final Type extractTypeArgAsMemberOfSupertype( 76 | Type type, Symbol superTypeSym, int typeArgIndex, Types types) { 77 | Type superType = types.asSuper(type, superTypeSym); 78 | if (superType == null) { 79 | return null; 80 | } 81 | com.sun.tools.javac.util.List tyargs = superType.getTypeArguments(); 82 | if (tyargs.size() <= typeArgIndex) { 83 | // raw type 84 | return null; 85 | } 86 | return tyargs.get(typeArgIndex); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /reactor-error-prone/src/main/java/com/github/lhotari/reactor/errorprone/ReactorStepVerifierReturnValueIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.google.errorprone.BugPattern; 5 | import com.google.errorprone.bugpatterns.BugChecker; 6 | import com.google.errorprone.matchers.Matcher; 7 | import com.google.errorprone.matchers.Matchers; 8 | import com.sun.source.tree.ExpressionTree; 9 | import com.sun.source.tree.Tree.Kind; 10 | 11 | import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; 12 | import static com.google.errorprone.matchers.Matchers.*; 13 | 14 | /** 15 | * Checks that Reactor StepVerifier return values aren't ignored. 16 | */ 17 | @BugPattern( 18 | name = "ReactorStepVerifierReturnValueIgnored", 19 | summary = 20 | "Returned Reactor StepVerifier & StepVerifier.LastStep must be checked. Ignoring a returned StepVerifier or StepVerifier.LastStep means that the verification " 21 | + "isn't completed", 22 | explanation = 23 | "Methods that ignore StepVerifier or StepVerifier.Step return values typically " 24 | + "indicate errors.\n\nIf you don’t use the returned value and complete it with one of the verify* methods, " 25 | + "the verification checks will never be handled.", 26 | linkType = BugPattern.LinkType.CUSTOM, 27 | link = "https://github.com/lhotari/reactor-error-prone/wiki/ReactorStepVerifierReturnValueIgnored", 28 | severity = WARNING) 29 | @AutoService(BugChecker.class) 30 | public final class ReactorStepVerifierReturnValueIgnored extends AbstractReturnValueIgnored { 31 | private static final Matcher MATCHER = 32 | allOf( 33 | Matchers.kindIs(Kind.METHOD_INVOCATION), 34 | anyOf(isSubtypeOf("reactor.test.StepVerifier"), isSubtypeOf("reactor.test.StepVerifier.LastStep")), 35 | not(AbstractReturnValueIgnored::hasCanIgnoreReturnValueAnnotation)); 36 | 37 | @Override 38 | public Matcher specializedMatcher() { 39 | return MATCHER; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/java/com/github/lhotari/reactor/errorprone/ReactiveStreamsPublisherReturnValueIgnoredTest.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.errorprone.CompilationTestHelper; 4 | import org.junit.Test; 5 | 6 | public class ReactiveStreamsPublisherReturnValueIgnoredTest { 7 | private final CompilationTestHelper compilationHelper = 8 | CompilationTestHelper.newInstance(ReactiveStreamsPublisherReturnValueIgnored.class, getClass()); 9 | 10 | @Test 11 | public void positiveCases() { 12 | compilationHelper.addSourceFile("ReactiveStreamsPublisherReturnValueIgnoredPositiveCases.java").doTest(); 13 | } 14 | 15 | @Test 16 | public void negativeCases() { 17 | compilationHelper.addSourceFile("ReactiveStreamsPublisherReturnValueIgnoredNegativeCases.java").doTest(); 18 | } 19 | 20 | @Test 21 | public void reactiveStreamsPublisher() { 22 | compilationHelper 23 | .addSourceLines( 24 | "Test.java", 25 | "import org.reactivestreams.Publisher;", 26 | "class Test {", 27 | " Publisher getPublisher() { return null; }", 28 | " void f() {", 29 | " // BUG: Diagnostic contains: Reactive Streams Publisher must be checked.", 30 | " getPublisher();", 31 | " }", 32 | "}") 33 | .doTest(); 34 | } 35 | 36 | @Test 37 | public void reactorFlux() { 38 | compilationHelper 39 | .addSourceLines( 40 | "Test.java", 41 | "import reactor.core.publisher.Flux;", 42 | "class Test {", 43 | " Flux getFlux() { return null; }", 44 | " void f() {", 45 | " // BUG: Diagnostic contains: Reactive Streams Publisher must be checked.", 46 | " getFlux();", 47 | " }", 48 | "}") 49 | .doTest(); 50 | } 51 | 52 | @Test 53 | public void reactorMono() { 54 | compilationHelper 55 | .addSourceLines( 56 | "Test.java", 57 | "import reactor.core.publisher.Mono;", 58 | "class Test {", 59 | " Mono getMono() { return null; }", 60 | " void f() {", 61 | " // BUG: Diagnostic contains: Reactive Streams Publisher must be checked.", 62 | " getMono();", 63 | " }", 64 | "}") 65 | .doTest(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/java/com/github/lhotari/reactor/errorprone/ReactorInnerPublisherIgnoredTest.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.errorprone.CompilationTestHelper; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.JUnit4; 7 | 8 | @RunWith(JUnit4.class) 9 | public class ReactorInnerPublisherIgnoredTest { 10 | private final CompilationTestHelper compilationHelper = 11 | CompilationTestHelper.newInstance(ReactorInnerPublisherIgnored.class, getClass()); 12 | 13 | @Test 14 | public void positiveCases() { 15 | compilationHelper.addSourceFile("ReactorInnerPublisherIgnoredPositiveCases.java").doTest(); 16 | } 17 | 18 | @Test 19 | public void negativeCases() { 20 | compilationHelper.addSourceFile("ReactorInnerPublisherIgnoredNegativeCases.java").doTest(); 21 | } 22 | 23 | @Test 24 | public void reactorFluxOfFlux() { 25 | compilationHelper 26 | .addSourceLines( 27 | "Test.java", 28 | "import reactor.core.publisher.*;", 29 | "class Test {", 30 | " Flux> getFlux() { return null; }", 31 | " Mono then() {", 32 | " // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution", 33 | " return getFlux().then();", 34 | " }", 35 | " Mono thenEmpty() {", 36 | " // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution", 37 | " return getFlux().thenEmpty(null);", 38 | " }", 39 | " Flux thenMany() {", 40 | " // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution", 41 | " return getFlux().thenMany(null);", 42 | " }", 43 | " Mono and() {", 44 | " // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution", 45 | " return getFlux().then(null);", 46 | " }", 47 | "}") 48 | .doTest(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/java/com/github/lhotari/reactor/errorprone/ReactorStepVerifierReturnValueIgnoredTest.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone; 2 | 3 | import com.google.errorprone.CompilationTestHelper; 4 | import org.junit.Test; 5 | 6 | public class ReactorStepVerifierReturnValueIgnoredTest { 7 | private final CompilationTestHelper compilationHelper = CompilationTestHelper.newInstance(ReactorStepVerifierReturnValueIgnored.class, getClass()) 8 | .addSourceLines( 9 | "reactor/test/StepVerifier.java", 10 | "package reactor.test;", 11 | "public interface StepVerifier {", 12 | " java.time.Duration verify() throws AssertionError;", 13 | " static FirstStep create(org.reactivestreams.Publisher publisher) { return null; }", 14 | " interface FirstStep extends Step {}", 15 | " interface Step extends LastStep {", 16 | " Step expectNext(T t);", 17 | " }", 18 | " interface LastStep {", 19 | " StepVerifier expectComplete();", 20 | " java.time.Duration verifyComplete();", 21 | " }", 22 | "}" 23 | ); 24 | 25 | @Test 26 | public void positiveCases() { 27 | compilationHelper.addSourceFile("ReactorStepVerifierReturnValueIgnoredPositiveCases.java").doTest(); 28 | } 29 | 30 | @Test 31 | public void negativeCases() { 32 | compilationHelper.addSourceFile("ReactorStepVerifierReturnValueIgnoredNegativeCases.java").doTest(); 33 | } 34 | 35 | @Test 36 | public void firstStepReturnValueIgnored() { 37 | compilationHelper 38 | .addSourceLines( 39 | "Test.java", 40 | "import reactor.test.StepVerifier;", 41 | "class Test {", 42 | " void f() {", 43 | " // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked.", 44 | " StepVerifier.create(null);", 45 | " }", 46 | "}") 47 | .doTest(); 48 | } 49 | 50 | @Test 51 | public void stepVerifierReturnValueIgnored() { 52 | compilationHelper 53 | .addSourceLines( 54 | "Test.java", 55 | "import reactor.test.StepVerifier;", 56 | "class Test {", 57 | " void f() {", 58 | " // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked.", 59 | " StepVerifier.create(null).expectComplete();", 60 | " }", 61 | "}") 62 | .doTest(); 63 | } 64 | 65 | @Test 66 | public void stepReturnValueIgnored() { 67 | compilationHelper 68 | .addSourceLines( 69 | "Test.java", 70 | "import reactor.test.StepVerifier;", 71 | "class Test {", 72 | " void f() {", 73 | " // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked.", 74 | " StepVerifier.create(null).expectNext(null);", 75 | " }", 76 | "}") 77 | .doTest(); 78 | } 79 | 80 | @Test 81 | public void completedVerifierCompiles() { 82 | compilationHelper 83 | .addSourceLines( 84 | "Test.java", 85 | "import reactor.test.StepVerifier;", 86 | "class Test {", 87 | " void f() {", 88 | " StepVerifier.create(null).expectNext(null).expectComplete().verify();", 89 | " }", 90 | "}") 91 | .doTest(); 92 | } 93 | 94 | @Test 95 | public void completedStepCompiles() { 96 | compilationHelper 97 | .addSourceLines( 98 | "Test.java", 99 | "import reactor.test.StepVerifier;", 100 | "class Test {", 101 | " void f() {", 102 | " StepVerifier.create(null).expectNext(null).verifyComplete();", 103 | " }", 104 | "}") 105 | .doTest(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactiveStreamsPublisherReturnValueIgnoredNegativeCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 4 | import org.reactivestreams.Publisher; 5 | import reactor.core.publisher.*; 6 | 7 | public class ReactiveStreamsPublisherReturnValueIgnoredNegativeCases { 8 | interface CanIgnoreMethod { 9 | @CanIgnoreReturnValue 10 | Publisher getPublisher(); 11 | 12 | @CanIgnoreReturnValue 13 | Flux getFlux(); 14 | 15 | @CanIgnoreReturnValue 16 | Mono getMono(); 17 | } 18 | 19 | public static class CanIgnoreImpl implements CanIgnoreMethod { 20 | @Override 21 | public Publisher getPublisher() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public Flux getFlux() { 27 | return null; 28 | } 29 | 30 | @Override 31 | public Mono getMono() { 32 | return null; 33 | } 34 | } 35 | 36 | static void callIgnoredInterfaceMethod() { 37 | new CanIgnoreImpl().getPublisher(); 38 | new CanIgnoreImpl().getFlux(); 39 | new CanIgnoreImpl().getMono(); 40 | } 41 | 42 | @CanIgnoreReturnValue 43 | Publisher getPublisher() { 44 | return null; 45 | } 46 | 47 | @CanIgnoreReturnValue 48 | Flux getFlux() { 49 | return null; 50 | } 51 | 52 | @CanIgnoreReturnValue 53 | Mono getMono() { 54 | return null; 55 | } 56 | 57 | void checkIgnore() { 58 | getPublisher(); 59 | getFlux(); 60 | getMono(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactiveStreamsPublisherReturnValueIgnoredPositiveCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 4 | import org.reactivestreams.Publisher; 5 | import reactor.core.publisher.*; 6 | 7 | import java.util.Arrays; 8 | 9 | public class ReactiveStreamsPublisherReturnValueIgnoredPositiveCases { 10 | private static Publisher getPublisher() { 11 | return null; 12 | } 13 | 14 | private static Flux getFlux() { 15 | return null; 16 | } 17 | 18 | private static Mono getMono() { 19 | return null; 20 | } 21 | 22 | { 23 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 24 | getPublisher(); 25 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 26 | getFlux(); 27 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 28 | getMono(); 29 | 30 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 31 | Arrays.asList(1, 2, 3).forEach(n -> getPublisher()); 32 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 33 | Arrays.asList(1, 2, 3).forEach(n -> getFlux()); 34 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 35 | Arrays.asList(1, 2, 3).forEach(n -> getMono()); 36 | } 37 | 38 | private abstract static class IgnoringParent { 39 | @CanIgnoreReturnValue 40 | abstract T ignoringFunction(); 41 | } 42 | 43 | private class NonIgnoringPublisherChild extends IgnoringParent> { 44 | @Override 45 | Publisher ignoringFunction() { 46 | return null; 47 | } 48 | } 49 | 50 | private class NonIgnoringFluxChild extends IgnoringParent> { 51 | @Override 52 | Flux ignoringFunction() { 53 | return null; 54 | } 55 | } 56 | 57 | private class NonIgnoringMonoChild extends IgnoringParent> { 58 | @Override 59 | Mono ignoringFunction() { 60 | return null; 61 | } 62 | } 63 | 64 | public void inheritenceTest() { 65 | NonIgnoringPublisherChild publisherChild = new NonIgnoringPublisherChild(); 66 | NonIgnoringFluxChild fluxChild = new NonIgnoringFluxChild(); 67 | NonIgnoringMonoChild monoChild = new NonIgnoringMonoChild(); 68 | 69 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 70 | publisherChild.ignoringFunction(); 71 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 72 | fluxChild.ignoringFunction(); 73 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 74 | monoChild.ignoringFunction(); 75 | } 76 | 77 | public void conditional() { 78 | if (false) { 79 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 80 | getPublisher(); 81 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 82 | getFlux(); 83 | // BUG: Diagnostic contains: Reactive Streams Publisher must be checked. 84 | getMono(); 85 | } 86 | 87 | return; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactorInnerPublisherIgnoredNegativeCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import reactor.core.publisher.*; 4 | 5 | import java.util.Arrays; 6 | 7 | public class ReactorInnerPublisherIgnoredNegativeCases { 8 | private static Flux getFlux() { 9 | return null; 10 | } 11 | 12 | private static Mono getMono() { 13 | return null; 14 | } 15 | 16 | private static Flux getFluxAnyType() { 17 | return null; 18 | } 19 | 20 | private static Mono getMonoAnyType() { 21 | return null; 22 | } 23 | 24 | private static Flux getFluxRaw() { 25 | return null; 26 | } 27 | 28 | private static Mono getMonoRaw() { 29 | return null; 30 | } 31 | 32 | { 33 | getFlux().then().subscribe(); 34 | getFlux().thenMany(null).subscribe(); 35 | getFlux().thenEmpty(null).subscribe(); 36 | getFlux().then(null).subscribe(); 37 | getMono().then().subscribe(); 38 | getMono().thenMany(null).subscribe(); 39 | getMono().thenEmpty(null).subscribe(); 40 | getMono().thenReturn(null).subscribe(); 41 | getMono().then(null).subscribe(); 42 | 43 | Arrays.asList(1, 2, 3).forEach(n -> getFlux().then().subscribe()); 44 | Arrays.asList(1, 2, 3).forEach(n -> getFlux().thenMany(null).subscribe()); 45 | Arrays.asList(1, 2, 3).forEach(n -> getFlux().thenEmpty(null).subscribe()); 46 | Arrays.asList(1, 2, 3).forEach(n -> getFlux().then(null).subscribe()); 47 | Arrays.asList(1, 2, 3).forEach(n -> getMono().then().subscribe()); 48 | Arrays.asList(1, 2, 3).forEach(n -> getMono().thenMany(null).subscribe()); 49 | Arrays.asList(1, 2, 3).forEach(n -> getMono().thenEmpty(null).subscribe()); 50 | Arrays.asList(1, 2, 3).forEach(n -> getMono().thenReturn(null).subscribe()); 51 | Arrays.asList(1, 2, 3).forEach(n -> getMono().then(null).subscribe()); 52 | } 53 | 54 | { 55 | getFluxAnyType().then().subscribe(); 56 | getFluxAnyType().thenMany(null).subscribe(); 57 | getFluxAnyType().thenEmpty(null).subscribe(); 58 | getFluxAnyType().then(null).subscribe(); 59 | getMonoAnyType().then().subscribe(); 60 | getMonoAnyType().thenMany(null).subscribe(); 61 | getMonoAnyType().thenEmpty(null).subscribe(); 62 | getMonoAnyType().thenReturn(null).subscribe(); 63 | getMonoAnyType().then(null).subscribe(); 64 | 65 | Arrays.asList(1, 2, 3).forEach(n -> getFluxAnyType().then().subscribe()); 66 | Arrays.asList(1, 2, 3).forEach(n -> getFluxAnyType().thenMany(null).subscribe()); 67 | Arrays.asList(1, 2, 3).forEach(n -> getFluxAnyType().thenEmpty(null).subscribe()); 68 | Arrays.asList(1, 2, 3).forEach(n -> getFluxAnyType().then(null).subscribe()); 69 | Arrays.asList(1, 2, 3).forEach(n -> getMonoAnyType().then().subscribe()); 70 | Arrays.asList(1, 2, 3).forEach(n -> getMonoAnyType().thenMany(null).subscribe()); 71 | Arrays.asList(1, 2, 3).forEach(n -> getMonoAnyType().thenEmpty(null).subscribe()); 72 | Arrays.asList(1, 2, 3).forEach(n -> getMonoAnyType().thenReturn(null).subscribe()); 73 | Arrays.asList(1, 2, 3).forEach(n -> getMonoAnyType().then(null).subscribe()); 74 | } 75 | 76 | // raw types 77 | { 78 | getFluxRaw().then().subscribe(); 79 | getFluxRaw().thenMany(null).subscribe(); 80 | getFluxRaw().thenEmpty(null).subscribe(); 81 | getFluxRaw().then(null).subscribe(); 82 | getMonoRaw().then().subscribe(); 83 | getMonoRaw().thenMany(null).subscribe(); 84 | getMonoRaw().thenEmpty(null).subscribe(); 85 | getMonoRaw().thenReturn(null).subscribe(); 86 | getMonoRaw().then(null).subscribe(); 87 | 88 | Arrays.asList(1, 2, 3).forEach(n -> getFluxRaw().then().subscribe()); 89 | Arrays.asList(1, 2, 3).forEach(n -> getFluxRaw().thenMany(null).subscribe()); 90 | Arrays.asList(1, 2, 3).forEach(n -> getFluxRaw().thenEmpty(null).subscribe()); 91 | Arrays.asList(1, 2, 3).forEach(n -> getFluxRaw().then(null).subscribe()); 92 | Arrays.asList(1, 2, 3).forEach(n -> getMonoRaw().then().subscribe()); 93 | Arrays.asList(1, 2, 3).forEach(n -> getMonoRaw().thenMany(null).subscribe()); 94 | Arrays.asList(1, 2, 3).forEach(n -> getMonoRaw().thenEmpty(null).subscribe()); 95 | Arrays.asList(1, 2, 3).forEach(n -> getMonoRaw().thenReturn(null).subscribe()); 96 | Arrays.asList(1, 2, 3).forEach(n -> getMonoRaw().then(null).subscribe()); 97 | } 98 | 99 | public void conditional() { 100 | if (false) { 101 | getFlux().then().subscribe(); 102 | getFlux().thenMany(null).subscribe(); 103 | getFlux().thenEmpty(null).subscribe(); 104 | getFlux().then(null).subscribe(); 105 | getMono().then().subscribe(); 106 | getMono().thenMany(null).subscribe(); 107 | getMono().thenEmpty(null).subscribe(); 108 | getMono().thenReturn(null).subscribe(); 109 | getMono().then(null).subscribe(); 110 | } 111 | 112 | if (false) { 113 | getFluxAnyType().then().subscribe(); 114 | getFluxAnyType().thenMany(null).subscribe(); 115 | getFluxAnyType().thenEmpty(null).subscribe(); 116 | getFluxAnyType().then(null).subscribe(); 117 | getMonoAnyType().then().subscribe(); 118 | getMonoAnyType().thenMany(null).subscribe(); 119 | getMonoAnyType().thenEmpty(null).subscribe(); 120 | getMonoAnyType().thenReturn(null).subscribe(); 121 | getMonoAnyType().then(null).subscribe(); 122 | } 123 | 124 | // raw types 125 | if (false) { 126 | getFluxRaw().then().subscribe(); 127 | getFluxRaw().thenMany(null).subscribe(); 128 | getFluxRaw().thenEmpty(null).subscribe(); 129 | getFluxRaw().then(null).subscribe(); 130 | getMonoRaw().then().subscribe(); 131 | getMonoRaw().thenMany(null).subscribe(); 132 | getMonoRaw().thenEmpty(null).subscribe(); 133 | getMonoRaw().thenReturn(null).subscribe(); 134 | getMonoRaw().then(null).subscribe(); 135 | } 136 | 137 | return; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactorInnerPublisherIgnoredPositiveCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import reactor.core.publisher.*; 4 | 5 | import java.util.Arrays; 6 | 7 | public class ReactorInnerPublisherIgnoredPositiveCases { 8 | private static Flux> getFluxOfFlux() { 9 | return null; 10 | } 11 | 12 | private static Flux> getFluxOfMono() { 13 | return null; 14 | } 15 | 16 | private static Mono> getMonoOfMono() { 17 | return null; 18 | } 19 | 20 | private static Mono> getMonoOfFlux() { 21 | return null; 22 | } 23 | 24 | // then 25 | { 26 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 27 | getFluxOfFlux().then().subscribe(); 28 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 29 | getFluxOfMono().then().subscribe(); 30 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 31 | getMonoOfMono().then().subscribe(); 32 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 33 | getMonoOfFlux().then().subscribe(); 34 | 35 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 36 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfFlux().then().subscribe()); 37 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 38 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfMono().then().subscribe()); 39 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 40 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfMono().then().subscribe()); 41 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 42 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfFlux().then().subscribe()); 43 | } 44 | 45 | // thenEmpty 46 | { 47 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 48 | getFluxOfFlux().thenEmpty(null).subscribe(); 49 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 50 | getFluxOfMono().thenEmpty(null).subscribe(); 51 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 52 | getMonoOfMono().thenEmpty(null).subscribe(); 53 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 54 | getMonoOfFlux().thenEmpty(null).subscribe(); 55 | 56 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 57 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfFlux().thenEmpty(null).subscribe()); 58 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 59 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfMono().thenEmpty(null).subscribe()); 60 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 61 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfMono().thenEmpty(null).subscribe()); 62 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 63 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfFlux().thenEmpty(null).subscribe()); 64 | } 65 | 66 | // thenMany 67 | { 68 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 69 | getFluxOfFlux().thenMany(null).subscribe(); 70 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 71 | getFluxOfMono().thenMany(null).subscribe(); 72 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 73 | getMonoOfMono().thenMany(null).subscribe(); 74 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 75 | getMonoOfFlux().thenMany(null).subscribe(); 76 | 77 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 78 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfFlux().thenMany(null).subscribe()); 79 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 80 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfMono().thenMany(null).subscribe()); 81 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 82 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfMono().thenMany(null).subscribe()); 83 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 84 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfFlux().thenMany(null).subscribe()); 85 | } 86 | 87 | // thenReturn 88 | { 89 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 90 | getMonoOfMono().thenReturn(null).subscribe(); 91 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 92 | getMonoOfFlux().thenReturn(null).subscribe(); 93 | 94 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 95 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfMono().thenReturn(null).subscribe()); 96 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 97 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfFlux().thenReturn(null).subscribe()); 98 | } 99 | 100 | // and 101 | { 102 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 103 | getFluxOfFlux().then(null).subscribe(); 104 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 105 | getFluxOfMono().then(null).subscribe(); 106 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 107 | getMonoOfMono().then(null).subscribe(); 108 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 109 | getMonoOfFlux().then(null).subscribe(); 110 | 111 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 112 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfFlux().then(null).subscribe()); 113 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 114 | Arrays.asList(1, 2, 3).forEach(n -> getFluxOfMono().then(null).subscribe()); 115 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 116 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfMono().then(null).subscribe()); 117 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 118 | Arrays.asList(1, 2, 3).forEach(n -> getMonoOfFlux().then(null).subscribe()); 119 | } 120 | 121 | public void conditional() { 122 | // then 123 | if (false) { 124 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 125 | getFluxOfFlux().then().subscribe(); 126 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 127 | getFluxOfMono().then().subscribe(); 128 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 129 | getMonoOfMono().then().subscribe(); 130 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 131 | getMonoOfFlux().then().subscribe(); 132 | } 133 | 134 | // thenEmpty 135 | if (false) { 136 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 137 | getFluxOfFlux().thenEmpty(null).subscribe(); 138 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 139 | getFluxOfMono().thenEmpty(null).subscribe(); 140 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 141 | getMonoOfMono().thenEmpty(null).subscribe(); 142 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 143 | getMonoOfFlux().thenEmpty(null).subscribe(); 144 | } 145 | 146 | // thenMany 147 | if (false) { 148 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 149 | getFluxOfFlux().thenMany(null).subscribe(); 150 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 151 | getFluxOfMono().thenMany(null).subscribe(); 152 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 153 | getMonoOfMono().thenMany(null).subscribe(); 154 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 155 | getMonoOfFlux().thenMany(null).subscribe(); 156 | } 157 | 158 | // thenReturn 159 | if (false) { 160 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 161 | getMonoOfMono().thenReturn(null).subscribe(); 162 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 163 | getMonoOfFlux().thenReturn(null).subscribe(); 164 | } 165 | 166 | // and 167 | if (false) { 168 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 169 | getFluxOfFlux().then(null).subscribe(); 170 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 171 | getFluxOfMono().then(null).subscribe(); 172 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 173 | getMonoOfMono().then(null).subscribe(); 174 | // BUG: Diagnostic contains: The inner Flux|Mono (Publisher) is ignored and it is never scheduled for execution 175 | getMonoOfFlux().then(null).subscribe(); 176 | } 177 | 178 | return; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactorStepVerifierReturnValueIgnoredNegativeCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import reactor.test.StepVerifier; 4 | 5 | public class ReactorStepVerifierReturnValueIgnoredNegativeCases { 6 | void stepVerifierIsVerified() { 7 | StepVerifier.create(null) 8 | .expectNext(null) 9 | .expectComplete() 10 | .verify(); 11 | } 12 | 13 | void lastStepIsVerified() { 14 | StepVerifier.create(null) 15 | .expectNext(null) 16 | .verifyComplete(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /reactor-error-prone/src/test/resources/com/github/lhotari/reactor/errorprone/testdata/ReactorStepVerifierReturnValueIgnoredPositiveCases.java: -------------------------------------------------------------------------------- 1 | package com.github.lhotari.reactor.errorprone.testdata; 2 | 3 | import reactor.test.StepVerifier; 4 | 5 | public class ReactorStepVerifierReturnValueIgnoredPositiveCases { 6 | void firstStepReturnValueIgnored() { 7 | // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked. 8 | StepVerifier.create(null); 9 | } 10 | 11 | void stepVerifierReturnValueIgnored() { 12 | // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked. 13 | StepVerifier.create(null).expectComplete(); 14 | } 15 | 16 | void stepReturnValueIgnored() { 17 | // BUG: Diagnostic contains: Returned Reactor StepVerifier & StepVerifier.LastStep must be checked. 18 | StepVerifier.create(null).expectNext(null); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /reactor-sample/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "net.ltgt.errorprone" version "1.2.1" 4 | } 5 | 6 | dependencies { 7 | errorprone project(':reactor-error-prone') 8 | errorprone 'com.google.errorprone:error_prone_core:2.4.0' 9 | // required when using Java 8 10 | errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1' 11 | implementation 'io.projectreactor:reactor-core:3.3.0.RELEASE' 12 | } 13 | -------------------------------------------------------------------------------- /reactor-sample/src/main/java/InvalidReactorCodeSample.java: -------------------------------------------------------------------------------- 1 | import reactor.core.publisher.Flux; 2 | 3 | public class InvalidReactorCodeSample { 4 | @SuppressWarnings("InvalidPatternSyntax") 5 | public static void main(String[] args) throws InterruptedException { 6 | // example from https://projectreactor.io/docs/core/release/reference/index.html#faq.chain 7 | Flux flux = Flux.just("something", "chain"); 8 | flux.map(secret -> secret.replaceAll(".", "*")); 9 | flux.subscribe(next -> System.out.println("Received: " + next)); 10 | 11 | // demonstrate issue where "inner flux" is ignored 12 | Flux.range(1, 10) 13 | .map(n -> 14 | Flux.range(1, n * n) 15 | .doOnNext(i -> System.out.println("NEVER PRINTED:" + n + " - " + i))) 16 | .then() 17 | .subscribe(); 18 | 19 | Flux.range(1, 10) 20 | .flatMapSequential(n -> 21 | Flux.range(1, n * n) 22 | .doOnNext(i -> System.out.println(n + " - " + i))) 23 | .then() 24 | .subscribe(); 25 | 26 | // examples above complete asynchronously (and they don't have proper chaining to demonstrate the checks), 27 | // might need a short delay here to demonstrate the behaviour 28 | Thread.sleep(1000L); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reactor-error-prone' 2 | include 'reactor-error-prone' 3 | include 'reactor-sample' 4 | --------------------------------------------------------------------------------