Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
RxJava2SubscribeOnErrorDetector
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
configurations {
lintChecks
}
dependencies {
compile 'com.android.tools.lint:lint-api:24.3.1'
compile 'com.android.tools.lint:lint-checks:24.3.1'
compile 'com.android.tools.lint:lint:24.3.1'
testCompile 'junit:junit:4.11'
testCompile 'com.android.tools.lint:lint-tests:24.3.1'
testCompile 'com.android.tools:testutils:24.3.1'
lintChecks files(jar)
}
jar {
manifest {
// define your registry
attributes("Lint-Registry": "com.kgmshin.lint.LintIssueRegistry")
}
}
defaultTasks 'assemble'
task lintInstall(type: Copy, dependsOn: build) {
from configurations.lintChecks
into System.getProperty('user.home') + '/.android/lint/'
}
package com.kgmshin.lint;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
import java.util.Collections;
import java.util.List;
public class LintIssueRegistry extends IssueRegistry {
public LintIssueRegistry() {
}
@Override
public List<Issue> getIssues() {
return Collections.singletonList(RxJava2SubscribeOnErrorDetector.ISSUE);
}
}
package com.kgmshin.lint;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import lombok.ast.AstVisitor;
import lombok.ast.Expression;
import lombok.ast.MethodInvocation;
import lombok.ast.StrictListAccessor;
public class RxJava2SubscribeOnErrorDetector extends Detector implements Detector.JavaScanner {
public static final Issue ISSUE = Issue.create(
"RxJava2SubscribeOnError",
"not subscribe onError",
"must subscribe onError",
Category.PERFORMANCE,
7,
Severity.ERROR,
new Implementation(
RxJava2SubscribeOnErrorDetector.class,
Scope.JAVA_FILE_SCOPE
)
);
private static final String CLS_FLOWABLE = "io.reactivex.Flowable";
private static final String CLS_OBSERAVLE = "io.reactivex.Observable";
private static final String CLS_SINGLE = "io.reactivex.Single";
private static final String CLS_COMPLETABLE = "io.reactivex.Completable";
private static final String CLS_OBJECT = "java.lang.Object";
private static final String CLS_CONSUMER = "io.reactivex.functions.Consumer";
private static final String CLS_ACTION = "io.reactivex.functions.Action";
private static final String CLS_CONSUMER_SIMPLE = "Consumer";
private static final String CLS_ACTION_SIMPLE = "Action";
private static final String ANONYMOUS_NEW_CONSUMER = "new io.reactivex.functions.Consumer(){}";
private static final String ANONYMOUS_NEW_ACTION = "new io.reactivex.functions.Action(){}";
private static final String METHOD_SUBSCRIBE = "subscribe";
@Override
public Speed getSpeed() {
return Speed.FAST;
}
// ---- Implements JavaScanner ----
@Override
public List<String> getApplicableConstructorTypes() {
return Arrays.asList(CLS_FLOWABLE, CLS_OBSERAVLE, CLS_SINGLE, CLS_COMPLETABLE);
}
@Override
public List<String> getApplicableMethodNames() {
return Collections.singletonList(METHOD_SUBSCRIBE);
}
@Override
public void visitMethod(
@NonNull JavaContext context,
@Nullable AstVisitor visitor,
@NonNull MethodInvocation node
) {
boolean isNG = false;
StrictListAccessor<Expression, MethodInvocation> expressions = node.astArguments();
int size = expressions.size();
if (size == 0) {
// Any.subscribe() -> NG
isNG = true;
} else if (size == 1) {
// Any.subscribe(Consumer<T> onNext) -> NG
Expression expression = expressions.first();
JavaParser.ResolvedClass param1Class = context.getType(expression).getTypeClass();
while (!param1Class.getName().equals(CLS_OBJECT)) {
if (param1Class.getName().startsWith(CLS_CONSUMER)
|| param1Class.getName().startsWith(CLS_CONSUMER_SIMPLE)
|| param1Class.getName().startsWith(ANONYMOUS_NEW_CONSUMER)
|| param1Class.getName().startsWith(CLS_ACTION)
|| param1Class.getName().startsWith(CLS_ACTION_SIMPLE)
|| param1Class.getName().startsWith(ANONYMOUS_NEW_ACTION)) {
isNG = true;
break;
}
param1Class = param1Class.getSuperClass();
}
}
if (isNG) {
context.report(ISSUE, node, context.getLocation(node), "you must subscribe onError");
}
}
}
package com.kgmshin.lint;
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import com.google.common.collect.ImmutableList;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
import java.util.List;
public class RxJava2SubscribeOnErrorDetectorTest extends LintDetectorTest {
private static final String PACKAGE = "package com.kgmyshin.lint;";
private static final String IMPORT = "import org.reactivestreams.Subscriber;\n" +
"import org.reactivestreams.Subscription;\n" +
"import io.reactivex.Completable;\n" +
"import io.reactivex.CompletableObserver;\n" +
"import io.reactivex.Flowable;\n" +
"import io.reactivex.Observable;\n" +
"import io.reactivex.Observer;\n" +
"import io.reactivex.Single;\n" +
"import io.reactivex.SingleObserver;\n" +
"import io.reactivex.disposables.Disposable;\n" +
"import io.reactivex.functions.Action;\n" +
"import io.reactivex.functions.BiConsumer;\n" +
"import io.reactivex.functions.Consumer;\n";
static final String SOURCE_PATH = "src/com/kgmyshin/lint/";
@Override
protected Detector getDetector() {
return new RxJava2SubscribeOnErrorDetector();
}
@Override
protected List<Issue> getIssues() {
return ImmutableList.of(RxJava2SubscribeOnErrorDetector.ISSUE);
}
@Test
public void testSingleSubscribeNoParam() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Single.just(\"test\").subscribe()" +
"}\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n"
+ "Single.just(\"test\").subscribe()}\n"
+ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
result
);
}
@Test
public void testSingleSubscribeOnNextOnly() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Single.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n" +
"Single.just(\"test\").subscribe(new Consumer<String>() {\n" +
"^\n" +
"1 errors, 0 warnings\n",
result
);
}
@Test
public void testSingleSubscribeOnNextAndOnError() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Single.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"}, new Consumer<Throwable>() {\n" +
"@Override\n" +
"public void accept(Throwable throwable) throws Exception {\n" +
"System.out.print(throwable);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testSingleSubscribeBiConsumer() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Single.just(\"test\").subscribe(new BiConsumer<String, Throwable>() {\n" +
"@Override\n" +
"public void accept(\n" +
"String s,\n" +
"Throwable throwable\n" +
") throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testSingleSubscribeObserver() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Single.just(\"test\").subscribe(new SingleObserver<String>() {\n" +
"@Override\n" +
"public void onSubscribe(Disposable d) {\n" +
"}\n" +
"@Override\n" +
"public void onNext(String value) {\n" +
"System.out.print(value);\n" +
"}\n" +
"@Override\n" +
"public void onComplete() {\n" +
"}\n" +
"@Override\n" +
"public void onError(Throwable e) {\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testFlowableSubscribeNoParam() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Flowable.just(\"test\").subscribe()" +
"}\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n"
+ "Flowable.just(\"test\").subscribe()}\n"
+ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
result
);
}
@Test
public void testFlowableSubscribeOnNextOnly() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Flowable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n" +
"Flowable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"^\n" +
"1 errors, 0 warnings\n",
result
);
}
@Test
public void testFlowableSubscribeOnNextAndOnError() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Flowable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"}, new Consumer<Throwable>() {\n" +
"@Override\n" +
"public void accept(Throwable throwable) throws Exception {\n" +
"System.out.print(throwable);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testFlowableSubscribeObserver() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Flowable.just(\"test\").subscribe(new Subscriber<String>() {\n" +
"@Override\n" +
"public void onSubscribe(Disposable d) {\n" +
"}\n" +
"@Override\n" +
"public void onSuccess(String value) {\n" +
"System.out.print(value);\n" +
"}\n" +
"@Override\n" +
"public void onError(Throwable e) {\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testObservableSubscribeNoParam() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Observable.just(\"test\").subscribe()" +
"}\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n"
+ "Observable.just(\"test\").subscribe()}\n"
+ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
result
);
}
@Test
public void testObservableSubscribeOnNextOnly() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Observable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n" +
"Observable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"^\n" +
"1 errors, 0 warnings\n",
result
);
}
@Test
public void testObservableSubscribeOnNextAndOnError() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Observable.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"}, new Consumer<Throwable>() {\n" +
"@Override\n" +
"public void accept(Throwable throwable) throws Exception {\n" +
"System.out.print(throwable);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testObservableSubscribeObserver() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Observable.just(\"test\").subscribe(new Observer<String>() {\n" +
"@Override\n" +
"public void onSubscribe(Disposable d) {\n" +
"}\n" +
"@Override\n" +
"public void onSuccess(String value) {\n" +
"System.out.print(value);\n" +
"}\n" +
"@Override\n" +
"public void onError(Throwable e) {\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testMaybeSubscribeNoParam() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Maybe.just(\"test\").subscribe()" +
"}\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n"
+ "Maybe.just(\"test\").subscribe()}\n"
+ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
result
);
}
@Test
public void testMaybeSubscribeOnNextOnly() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Maybe.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n" +
"Maybe.just(\"test\").subscribe(new Consumer<String>() {\n" +
"^\n" +
"1 errors, 0 warnings\n",
result
);
}
@Test
public void testMaybeSubscribeOnNextAndOnError() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Maybe.just(\"test\").subscribe(new Consumer<String>() {\n" +
"@Override\n" +
"public void accept(String s) throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"}, new Consumer<Throwable>() {\n" +
"@Override\n" +
"public void accept(Throwable throwable) throws Exception {\n" +
"System.out.print(throwable);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testMaybeSubscribeBiConsumer() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Maybe.just(\"test\").subscribe(new BiConsumer<String, Throwable>() {\n" +
"@Override\n" +
"public void accept(\n" +
"String s,\n" +
"Throwable throwable\n" +
") throws Exception {\n" +
"System.out.print(s);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testMaybeSubscribeObserver() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Maybe.just(\"test\").subscribe(new MaybeObserver<String>() {\n" +
"@Override\n" +
"public void onSubscribe(Disposable d) {\n" +
"}\n" +
"@Override\n" +
"public void onNext(String value) {\n" +
"System.out.print(value);\n" +
"}\n" +
"@Override\n" +
"public void onComplete() {\n" +
"}\n" +
"@Override\n" +
"public void onError(Throwable e) {\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testCompletableSubscribeNoParam() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Completable.complete().subscribe()" +
"}\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n"
+ "Completable.complete().subscribe()}\n"
+ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
result
);
}
@Test
public void testCompletableSubscribeOnNextOnly() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Completable.complete().subscribe(new Action() {\n" +
"@Override\n" +
"public void run() throws Exception {\n" +
"System.out.print(\"success\");\n" +
"}\n" +
"});\n" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"src/com/kgmyshin/lint/Foo.java:16: Error: you must subscribe onError [RxJava2SubscribeOnError]\n" +
"Completable.complete().subscribe(new Action() {\n" +
"^\n" +
"1 errors, 0 warnings\n",
result
);
}
@Test
public void testCompletableSubscribeOnNextAndOnError() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Completable.complete().subscribe(new Action() {\n" +
"@Override\n" +
"public void run() throws Exception {\n" +
"System.out.print(\"success\");\n" +
"}\n" +
"}, new Consumer<Throwable>() {\n" +
"@Override\n" +
"public void accept(Throwable throwable) throws Exception {\n" +
"System.out.print(throwable);\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testCompletableSubscribeBiConsumer() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Completable.complete().subscribe(new BiConsumer<String, Throwable>() {\n" +
"@Override\n" +
"public void accept(\n" +
"String s,\n" +
"Throwable throwable\n" +
") throws Exception {\n" +
"System.out.print(\"success\");\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
@Test
public void testCompletableSubscribeObserver() throws Exception {
@Language("JAVA") String foo = "" +
PACKAGE +
IMPORT +
"public class Foo {\n" +
"public void test() {\n" +
"Completable.complete().subscribe(new CompletableObserver() {\n" +
"@Override\n" +
"public void onSubscribe(Disposable d) {\n" +
"}\n" +
"@Override\n" +
"public void onNext(String value) {\n" +
"System.out.print(value);\n" +
"}\n" +
"@Override\n" +
"public void onComplete() {\n" +
"}\n" +
"@Override\n" +
"public void onError(Throwable e) {\n" +
"}\n" +
"});" +
"}";
String result = lintProject(java(SOURCE_PATH + "Foo.java", foo));
assertEquals(
"No warnings.",
result
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment