diff --git a/README.md b/README.md
index ea6680126..e41995853 100755
--- a/README.md
+++ b/README.md
@@ -220,6 +220,13 @@ This pattern is *sort of* possible in RxJava 1, but only on `Subscriber` (via `o
`CompletableObserver` (which matches RxJava 2's API). We are aggressively migrating our internal code
to RxJava 2, and do not plan to try to backport this to RxJava 1.
+Static analysis
+-------
+
+There is an optional error-prone checker you can use to enforce use of AutoDispose.
+Integration steps and more details
+can be found on the [wiki](https://github.com/uber/AutoDispose/wiki/Error-Prone-Checker)
+
Download
--------
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 4af5a9d4a..d9c29e333 100755
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -24,6 +24,10 @@ def versions = [
support: '26.1.0'
]
+def apt = [
+ autoService : "com.google.auto.service:auto-service:1.0-rc4",
+]
+
def build = [
buildToolsVersion: '26.0.2',
compileSdkVersion: 26,
@@ -33,6 +37,8 @@ def build = [
checkerFramework: 'org.checkerframework:dataflow:2.3.0',
errorProne: "com.google.errorprone:error_prone_core:${versions.errorProne}",
+ errorProneCheckApi: "com.google.errorprone:error_prone_check_api:${versions.errorProne}",
+ errorProneTestHelpers: "com.google.errorprone:error_prone_test_helpers:${versions.errorProne}",
nullAway: 'com.uber.nullaway:nullaway:0.2.0',
repositories: [
@@ -84,6 +90,7 @@ def test = [
]
ext.deps = [
+ "apt": apt,
"build": build,
"kotlin": kotlin,
"misc": misc,
diff --git a/settings.gradle b/settings.gradle
index a74b395c8..f18299270 100755
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,4 +26,5 @@ include ':autodispose-kotlin'
include 'autodispose-rxlifecycle'
include ':sample'
include ':test-utils'
+include ':static-analysis:autodispose-error-prone-checker'
diff --git a/static-analysis/autodispose-error-prone-checker/build.gradle b/static-analysis/autodispose-error-prone-checker/build.gradle
new file mode 100644
index 000000000..bbec49884
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/build.gradle
@@ -0,0 +1,31 @@
+plugins {
+ id "net.ltgt.errorprone" version "0.0.13"
+ id "java-library"
+}
+
+// we use this config to get the path of the JDK 9 javac jar, to
+// stick it in the bootclasspath when running tests
+configurations.maybeCreate("epJavac")
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+dependencies {
+ compileOnly deps.apt.autoService
+ compileOnly deps.build.errorProneCheckApi
+
+ testImplementation(deps.build.errorProneTestHelpers) {
+ exclude group: "junit", module: "junit"
+ }
+ testImplementation deps.rx.java
+ testImplementation deps.test.junit
+ testImplementation project(':autodispose')
+
+ epJavac deps.build.errorProneCheckApi
+}
+
+test {
+ jvmArgs "-Xbootclasspath/p:${configurations.epJavac.asPath}"
+}
+
+apply from: rootProject.file("gradle/gradle-mvn-push.gradle")
diff --git a/static-analysis/autodispose-error-prone-checker/gradle.properties b/static-analysis/autodispose-error-prone-checker/gradle.properties
new file mode 100644
index 000000000..e254e1781
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/gradle.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2017. Uber Technologies
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+POM_NAME=AutoDispose Error-Prone Checker
+POM_ARTIFACT_ID=autodispose-error-prone-checker
+POM_PACKAGING=jar
diff --git a/static-analysis/autodispose-error-prone-checker/src/main/java/com/uber/autodispose/error/prone/checker/UseAutoDispose.java b/static-analysis/autodispose-error-prone-checker/src/main/java/com/uber/autodispose/error/prone/checker/UseAutoDispose.java
new file mode 100644
index 000000000..1ced255e3
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/main/java/com/uber/autodispose/error/prone/checker/UseAutoDispose.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.ErrorProneFlags;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.matchers.method.MethodMatchers;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.tools.javac.code.Type;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
+import static com.google.errorprone.BugPattern.StandardTags.CONCURRENCY;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+
+/**
+ * Checker for subscriptions not binding to lifecycle in components with lifecycle.
+ * Use -XepOpt:AutoDisposeLeakCheck flag to add support for custom components with lifecycle.
+ * The sample configuration for Conductor:
+ *
+ * -XepOpt:AutoDisposeLeakCheck=com.bluelinelabs.conductor.Controller,android.app.Activity
+ *
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "UseAutoDispose",
+ summary = "Always apply an AutoDispose scope before "
+ + "subscribing within defined scoped elements.",
+ tags = CONCURRENCY,
+ severity = ERROR
+)
+public final class UseAutoDispose extends BugChecker
+ implements MethodInvocationTreeMatcher {
+
+ private static final String AS = "as";
+ private static final ImmutableList AS_CALL_MATCHERS;
+ private static final ImmutableList SUBSCRIBE_MATCHERS;
+ private static final String SUBSCRIBE = "subscribe";
+
+ private final Matcher matcher;
+
+ public UseAutoDispose() {
+ this(ErrorProneFlags.empty());
+ }
+
+ public UseAutoDispose(ErrorProneFlags flags) {
+ Optional> inputClasses = flags.getList("AutoDisposeLeakCheck");
+ ImmutableList defaultClassesWithLifecycle = new ImmutableList.Builder()
+ .add("android.app.Activity")
+ .add("android.app.Fragment")
+ .add("com.uber.autodispose.LifecycleScopeProvider")
+ .add("android.support.v4.app.Fragment")
+ .add("android.arch.lifecycle.LifecycleOwner")
+ .add("com.uber.autodispose.ScopeProvider")
+ .build();
+ ImmutableList classesWithLifecycle = inputClasses.orElse(defaultClassesWithLifecycle);
+ matcher = matcher(classesWithLifecycle);
+ }
+
+ static {
+ AS_CALL_MATCHERS = new ImmutableList.Builder()
+ .add(instanceMethod().onDescendantOf("io.reactivex.Single").named(AS))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Observable").named(AS))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Completable").named(AS))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Flowable").named(AS))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Maybe").named(AS))
+ .add(instanceMethod().onDescendantOf("io.reactivex.parallel.ParallelFlowable").named(AS))
+ .build();
+
+ SUBSCRIBE_MATCHERS = new ImmutableList.Builder()
+ .add(instanceMethod().onDescendantOf("io.reactivex.Single").named(SUBSCRIBE))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Observable").named(SUBSCRIBE))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Completable").named(SUBSCRIBE))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Flowable").named(SUBSCRIBE))
+ .add(instanceMethod().onDescendantOf("io.reactivex.Maybe").named(SUBSCRIBE))
+ .add(instanceMethod()
+ .onDescendantOf("io.reactivex.parallel.ParallelFlowable")
+ .named(SUBSCRIBE))
+ .build();
+ }
+
+ /**
+ * Matcher to find the as operator in the observable chain.
+ */
+ private static final Matcher METHOD_NAME_MATCHERS =
+ new Matcher() {
+ @Override
+ public boolean matches(ExpressionTree tree, VisitorState state) {
+ if (!(tree instanceof MethodInvocationTree)) {
+ return false;
+ }
+ MethodInvocationTree invTree = (MethodInvocationTree) tree;
+
+ ExpressionTree methodSelectTree = invTree.getMethodSelect();
+
+ // MemberSelectTree is used only for member access expression.
+ // This is not true for scenarios such as calling super constructor.
+ if (!(methodSelectTree instanceof MemberSelectTree)) {
+ return false;
+ }
+
+ final MemberSelectTree memberTree = (MemberSelectTree) methodSelectTree;
+ if (!memberTree.getIdentifier().contentEquals(AS)) {
+ return false;
+ }
+
+ return AS_CALL_MATCHERS
+ .stream()
+ .filter(methodNameMatcher -> methodNameMatcher.matches(invTree, state))
+ .map(methodNameMatcher -> {
+ ExpressionTree arg = invTree.getArguments().get(0);
+ final Type scoper = state
+ .getTypeFromString("com.uber.autodispose.AutoDisposeConverter");
+ return ASTHelpers.isSubtype(ASTHelpers.getType(arg), scoper, state);
+ })
+ // Filtering the method invocation with name as
+ // and has an argument of type AutoDisposeConverter.
+ .filter(Boolean::booleanValue)
+ .findFirst()
+ .orElse(false);
+ }
+ };
+
+ private static Matcher matcher(List classesWithLifecycle) {
+ return (Matcher) (tree, state) -> {
+
+ boolean matchFound = false;
+ ExpressionTree methodSelectTree = tree.getMethodSelect();
+
+ // MemberSelectTree is used only for member access expression.
+ // This is not true for scenarios such as calling super constructor.
+ if (!(methodSelectTree instanceof MemberSelectTree)) {
+ return false;
+ }
+
+ final MemberSelectTree memberTree = (MemberSelectTree) tree.getMethodSelect();
+ if (!memberTree.getIdentifier().contentEquals(SUBSCRIBE)) {
+ return false;
+ }
+
+ matchFound = SUBSCRIBE_MATCHERS
+ .stream()
+ .map(methodNameMatcher -> methodNameMatcher.matches(tree, state))
+ .filter(Boolean::booleanValue) // Filtering the method invocation with name subscribe
+ .findFirst()
+ .orElse(false);
+
+ if (!matchFound) {
+ return false;
+ }
+
+ ClassTree enclosingClass =
+ ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class);
+ Type.ClassType enclosingClassType = ASTHelpers.getType(enclosingClass);
+
+ return classesWithLifecycle
+ .stream()
+ .map(classWithLifecycle -> {
+ Type lifecycleType = state.getTypeFromString(classWithLifecycle);
+ return ASTHelpers.isSubtype(enclosingClassType, lifecycleType, state)
+ && !METHOD_NAME_MATCHERS.matches(memberTree.getExpression(), state);
+ })
+ // Filtering the method invocation which is a
+ // subtype of one of the classes with lifecycle and name as
+ .filter(Boolean::booleanValue)
+ .findFirst()
+ .orElse(false);
+ };
+ }
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (matcher.matches(tree, state)) {
+ return buildDescription(tree).build();
+ } else {
+ return Description.NO_MATCH;
+ }
+ }
+
+ @Override
+ public String linkUrl() {
+ return "https://github.com/uber/AutoDispose/wiki/Error-Prone-Checker";
+ }
+}
diff --git a/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/ComponentWithLifeCycle.java b/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/ComponentWithLifeCycle.java
new file mode 100644
index 000000000..880fdd9b5
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/ComponentWithLifeCycle.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+public class ComponentWithLifeCycle { }
diff --git a/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/UseAutoDisposeTest.java b/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/UseAutoDisposeTest.java
new file mode 100644
index 000000000..add55cbbe
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/test/java/com/uber/autodispose/error/prone/checker/UseAutoDisposeTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+import com.google.errorprone.CompilationTestHelper;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UseAutoDisposeTest {
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setup() {
+ compilationHelper =
+ CompilationTestHelper
+ .newInstance(UseAutoDispose.class, getClass());
+ }
+
+ @Test
+ public void test_autodisposePositiveCasesWithDefaultClass() {
+ compilationHelper
+ .addSourceFile("UseAutoDisposeDefaultClassPositiveCases.java")
+ .doTest();
+ }
+
+ @Test
+ public void test_autodisposePositiveCaseswithCustomClass() {
+ compilationHelper.setArgs(
+ Collections.singletonList("-XepOpt:AutoDisposeLeakCheck"
+ + "=com.uber.autodispose.error.prone.checker.ComponentWithLifeCycle"));
+ compilationHelper
+ .addSourceFile("UseAutoDisposeCustomClassPositiveCases.java")
+ .doTest();
+ }
+
+ @Test
+ public void test_autodisposeNegativeCases() {
+ compilationHelper
+ .addSourceFile("UseAutoDisposeNegativeCases.java")
+ .doTest();
+ }
+}
diff --git a/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeCustomClassPositiveCases.java b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeCustomClassPositiveCases.java
new file mode 100644
index 000000000..b3b12e82a
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeCustomClassPositiveCases.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+import com.uber.autodispose.AutoDispose;
+import com.uber.autodispose.ObservableScoper;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.reactivestreams.Subscriber;
+
+public class UseAutoDisposeCustomClassPositiveCases extends ComponentWithLifeCycle {
+ public void observable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Observable.empty().subscribe();
+ }
+
+ public void single_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Single.just(true).subscribe();
+ }
+
+ public void completable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Completable.complete().subscribe();
+ }
+
+ public void maybe_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Maybe.empty().subscribe();
+ }
+
+ public void flowable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Flowable.empty().subscribe();
+ }
+
+ public void parallelFlowable_subscribeWithoutAutoDispose() {
+ Subscriber[] subscribers = new Subscriber[] {};
+ Flowable.just(1, 2)
+ .parallel(2)
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ .subscribe(subscribers);
+ }
+}
diff --git a/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeDefaultClassPositiveCases.java b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeDefaultClassPositiveCases.java
new file mode 100644
index 000000000..794759b2d
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeDefaultClassPositiveCases.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+import com.uber.autodispose.LifecycleEndedException;
+import com.uber.autodispose.LifecycleScopeProvider;
+import com.uber.autodispose.TestLifecycleScopeProvider;
+
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.annotations.CheckReturnValue;
+import io.reactivex.functions.Function;
+import io.reactivex.subjects.BehaviorSubject;
+import javax.annotation.Nullable;
+import org.reactivestreams.Subscriber;
+
+/**
+ * Cases that don't use autodispose and should fail the {@link UseAutoDispose} check.
+ */
+public class UseAutoDisposeDefaultClassPositiveCases
+ implements LifecycleScopeProvider {
+
+ private final BehaviorSubject lifecycleSubject =
+ BehaviorSubject.create();
+
+ /**
+ * @return a sequence of lifecycle events.
+ */
+ @CheckReturnValue public Observable lifecycle() {
+ return lifecycleSubject.hide();
+ }
+
+ /**
+ * @return a sequence of lifecycle events. It's recommended to back this with a static instance to
+ * avoid unnecessary object allocation.
+ */
+ @CheckReturnValue
+ public Function correspondingEvents() {
+ return new Function() {
+ @Override public TestLifecycleScopeProvider.TestLifecycle apply(
+ TestLifecycleScopeProvider.TestLifecycle testLifecycle) {
+ switch (testLifecycle) {
+ case STARTED:
+ return TestLifecycleScopeProvider.TestLifecycle.STOPPED;
+ case STOPPED:
+ throw new LifecycleEndedException();
+ default:
+ throw new IllegalStateException("Unknown lifecycle event.");
+ }
+ }
+ };
+ }
+
+ /**
+ * @return the last seen lifecycle event, or {@code null} if none.
+ */
+ @Nullable public TestLifecycleScopeProvider.TestLifecycle peekLifecycle() {
+ return lifecycleSubject.getValue();
+ }
+
+ public void observable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Observable.empty().subscribe();
+ }
+
+ public void single_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Single.just(true).subscribe();
+ }
+
+ public void completable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Completable.complete().subscribe();
+ }
+
+ public void maybe_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Maybe.empty().subscribe();
+ }
+
+ public void flowable_subscribeWithoutAutoDispose() {
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ Flowable.empty().subscribe();
+ }
+
+ public void parallelFlowable_subscribeWithoutAutoDispose() {
+ Subscriber[] subscribers = new Subscriber[] {};
+ Flowable.just(1, 2)
+ .parallel(2)
+ // BUG: Diagnostic contains: Always apply an AutoDispose scope before subscribing within defined scoped elements.
+ .subscribe(subscribers);
+ }
+}
diff --git a/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeNegativeCases.java b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeNegativeCases.java
new file mode 100644
index 000000000..1f5a598c7
--- /dev/null
+++ b/static-analysis/autodispose-error-prone-checker/src/test/resources/com/uber/autodispose/error/prone/checker/UseAutoDisposeNegativeCases.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017. Uber Technologies
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.uber.autodispose.error.prone.checker;
+
+import com.uber.autodispose.AutoDispose;
+import com.uber.autodispose.LifecycleEndedException;
+import com.uber.autodispose.LifecycleScopeProvider;
+import com.uber.autodispose.TestLifecycleScopeProvider;
+
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.annotations.CheckReturnValue;
+import io.reactivex.functions.Function;
+import io.reactivex.subjects.BehaviorSubject;
+import javax.annotation.Nullable;
+import org.reactivestreams.Subscriber;
+
+/**
+ * Cases that use {@link AutoDispose} and should not fail the {@link UseAutoDispose} check.
+ */
+public class UseAutoDisposeNegativeCases
+ implements LifecycleScopeProvider {
+
+ private final BehaviorSubject lifecycleSubject =
+ BehaviorSubject.create();
+
+ /**
+ * @return a sequence of lifecycle events.
+ */
+ @CheckReturnValue public Observable lifecycle() {
+ return lifecycleSubject.hide();
+ }
+
+ /**
+ * @return a sequence of lifecycle events. It's recommended to back this with a static instance to
+ * avoid unnecessary object allocation.
+ */
+ @CheckReturnValue
+ public Function correspondingEvents() {
+ return new Function() {
+ @Override public TestLifecycleScopeProvider.TestLifecycle apply(
+ TestLifecycleScopeProvider.TestLifecycle testLifecycle) {
+ switch (testLifecycle) {
+ case STARTED:
+ return TestLifecycleScopeProvider.TestLifecycle.STOPPED;
+ case STOPPED:
+ throw new LifecycleEndedException();
+ default:
+ throw new IllegalStateException("Unknown lifecycle event.");
+ }
+ }
+ };
+ }
+
+ /**
+ * @return the last seen lifecycle event, or {@code null} if none.
+ */
+ @Nullable public TestLifecycleScopeProvider.TestLifecycle peekLifecycle() {
+ return lifecycleSubject.getValue();
+ }
+
+ public void observable_subscribeWithAutoDispose() {
+ Observable.just(1).as(AutoDispose.autoDisposable(this)).subscribe();
+ }
+
+ public void single_subscribeWithAutoDispose() {
+ Single.just(true).as(AutoDispose.autoDisposable(this)).subscribe();
+ }
+
+ public void completable_subscribeWithAutoDispose() {
+ Completable.complete().as(AutoDispose.autoDisposable(this)).subscribe();
+ }
+
+ public void maybe_subscribeWithAutoDispose() {
+ Maybe.just(1).as(AutoDispose.autoDisposable(this)).subscribe();
+ }
+
+ public void flowable_subscribeWithAutoDispose() {
+ Flowable.just(1).as(AutoDispose.autoDisposable(this)).subscribe();
+ }
+
+ public void parallelFlowable_subscribeWithAutoDispose() {
+ Subscriber[] subscribers = new Subscriber[] {};
+ Flowable.just(1, 2)
+ .parallel(2)
+ .as(AutoDispose.autoDisposable(this))
+ .subscribe(subscribers);
+ }
+}