/
githubmirror
/
RxJava
Обзор
Документация
Войти
/
githubmirror
/
RxJava
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
4.x
src/test/java/io/reactivex/rxjava4/validators/CheckSourceAnnotationTest.java
503 строки
18 KB
David Karnok
4.x: Streamable + takeUntil + groupBy + refactor + helpers (#8215)
05 июл 2026, 01:45
Не верифицирован
05 июл 2026, 01:45
580b39c
Код
Авторство
О чём код?
/* * Copyright (c) 2016-present, RxJava Contributors. * * 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 io.reactivex.rxjava4.validators; import java.io.File; import java.nio.file.Files; import java.util.*; import org.junit.jupiter.api.Test; import io.reactivex.rxjava4.core.*; import io.reactivex.rxjava4.core.Observable; import io.reactivex.rxjava4.disposables.Disposable; import io.reactivex.rxjava4.parallel.ParallelFlowable; import io.reactivex.rxjava4.plugins.RxJavaPlugins; import io.reactivex.rxjava4.processors.*; import io.reactivex.rxjava4.schedulers.Schedulers; import io.reactivex.rxjava4.subjects.*; import io.reactivex.rxjava4.testsupport.TestHelper; /** * Parse the given files and check if all public final and public static methods have * @NonNull or @Nullable annotations specified on their return type and object-type parameters * as well as @SafeVarargs for varargs. */ public class CheckSourceAnnotationTest extends RxJavaTest { @Test public void checkCompletable() throws Exception { processFile(Completable.class); } @Test public void checkSingle() throws Exception { processFile(Single.class); } @Test public void checkMaybe() throws Exception { processFile(Maybe.class); } @Test public void checkObservable() throws Exception { processFile(Observable.class); } @Test public void checkFlowable() throws Exception { processFile(Flowable.class); } @Test public void checkParallelFlowable() throws Exception { processFile(ParallelFlowable.class); } @Test public void checkConnectableObservable() throws Exception { processFile(ConnectableObservable.class); } @Test public void checkConnectableFlowable() throws Exception { processFile(ConnectableFlowable.class); } @Test public void checkSubject() throws Exception { processFile(Subject.class); } @Test public void checkFlowableProcessor() throws Exception { processFile(FlowableProcessor.class); } @Test public void checkDisposable() throws Exception { processFile(Disposable.class); } @Test public void checkScheduler() throws Exception { processFile(Scheduler.class); } @Test public void checkSchedulers() throws Exception { processFile(Schedulers.class); } @Test public void checkAsyncSubject() throws Exception { processFile(AsyncSubject.class); } @Test public void checkBehaviorSubject() throws Exception { processFile(BehaviorSubject.class); } @Test public void checkPublishSubject() throws Exception { processFile(PublishSubject.class); } @Test public void checkReplaySubject() throws Exception { processFile(ReplaySubject.class); } @Test public void checkUnicastSubject() throws Exception { processFile(UnicastSubject.class); } @Test public void checkSingleSubject() throws Exception { processFile(SingleSubject.class); } @Test public void checkMaybeSubject() throws Exception { processFile(MaybeSubject.class); } @Test public void checkCompletableSubject() throws Exception { processFile(CompletableSubject.class); } @Test public void checkAsyncProcessor() throws Exception { processFile(AsyncProcessor.class); } @Test public void checkBehaviorProcessor() throws Exception { processFile(BehaviorProcessor.class); } @Test public void checkPublishProcessor() throws Exception { processFile(PublishProcessor.class); } @Test public void checkReplayProcessor() throws Exception { processFile(ReplayProcessor.class); } @Test public void checkUnicastProcessor() throws Exception { processFile(UnicastProcessor.class); } @Test public void checkMulticastProcessor() throws Exception { processFile(MulticastProcessor.class); } @Test public void checkRxJavaPlugins() throws Exception { processFile(RxJavaPlugins.class); } static void processFile(Class<?> clazz) throws Exception { String baseClassName = clazz.getSimpleName(); File f = TestHelper.findSource(baseClassName, clazz.getPackage().getName()); if (f == null) { return; } String fullClassName = clazz.getName(); int errorCount = 0; StringBuilder errors = new StringBuilder(); List<String> lines = Files.readAllLines(f.toPath()); for (int j = 0; j < lines.size(); j++) { String line = lines.get(j).trim(); if (line.contains("class")) { continue; } if (line.startsWith("public static") || line.startsWith("public final") || line.startsWith("protected final") || line.startsWith("protected abstract") || line.startsWith("public abstract")) { int methodArgStart = line.indexOf("("); int isBoolean = line.indexOf(" boolean "); int isInt = line.indexOf(" int "); int isLong = line.indexOf(" long "); int isVoid = line.indexOf(" void "); int isElementType = line.indexOf(" R "); boolean hasSafeVarargsAnnotation = false; if (!((isBoolean > 0 && isBoolean < methodArgStart) || (isInt > 0 && isInt < methodArgStart) || (isLong > 0 && isLong < methodArgStart) || (isVoid > 0 && isVoid < methodArgStart) || (isElementType > 0 && isElementType < methodArgStart) )) { boolean annotationFound = false; for (int k = j - 1; k >= 0; k--) { String prevLine = lines.get(k).trim(); if (prevLine.startsWith("}") || prevLine.startsWith("*/")) { break; } if (prevLine.startsWith("@NonNull") || prevLine.startsWith("@Nullable")) { annotationFound = true; } if (prevLine.startsWith("@SafeVarargs")) { hasSafeVarargsAnnotation = true; } } if (!annotationFound) { errorCount++; errors.append("L") .append(j) .append(" : Missing return type nullability annotation | ") .append(line) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } // Extract arguments StringBuilder arguments = new StringBuilder(); int methodArgEnd = line.indexOf(")", methodArgStart); if (methodArgEnd > 0) { arguments.append(line, methodArgStart + 1, methodArgEnd); } else { arguments.append(line.substring(methodArgStart + 1)); for (int k = j + 1; k < lines.size(); k++) { String ln = lines.get(k).trim(); int idx = ln.indexOf(")"); if (idx > 0) { arguments.append(ln, 0, idx); break; } arguments.append(ln).append(" "); } } // Strip generics arguments StringBuilder strippedArguments = new StringBuilder(); int skippingDepth = 0; for (int k = 0; k < arguments.length(); k++) { char c = arguments.charAt(k); if (c == '<') { skippingDepth++; } else if (c == '>') { skippingDepth--; } else if (skippingDepth == 0) { strippedArguments.append(c); } } String strippedArgumentsStr = strippedArguments.toString(); String[] args = strippedArgumentsStr.split("\\s*,\\s*"); for (int k = 0; k < args.length; k++) { String typeDef = args[k]; for (String typeName : CLASS_NAMES) { String typeNameSpaced = typeName + " "; if (typeDef.contains(typeNameSpaced) && !typeDef.contains("@NonNull") && !typeDef.contains("@Nullable")) { if (!line.contains("@Nullable " + typeName) && !line.contains("@NonNull " + typeName)) { errorCount++; errors.append("L") .append(j) .append(" - argument ").append(k + 1) .append(" : Missing argument type nullability annotation\r\n ") .append(typeDef).append("\r\n ") .append(strippedArgumentsStr) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } } if (typeDef.contains("final ")) { errorCount++; errors.append("L") .append(j) .append(" - argument ").append(k + 1) .append(" : unnecessary final on argument\r\n ") .append(typeDef).append("\r\n ") .append(strippedArgumentsStr) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } if (typeDef.contains("@NonNull int") || typeDef.contains("@NonNull long") || typeDef.contains("@Nullable int") || typeDef.contains("@Nullable long") ) { errorCount++; errors.append("L") .append(j) .append(" - argument ").append(k + 1) .append(" : unnecessary nullability annotation\r\n ") .append(typeDef).append("\r\n ") .append(strippedArgumentsStr) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } if (strippedArgumentsStr.contains("...") && !hasSafeVarargsAnnotation && !strippedArgumentsStr.contains("CompletableSource...")) { errorCount++; errors.append("L") .append(j) .append(" : Missing @SafeVarargs annotation\r\n ") .append(strippedArgumentsStr) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } for (String typeName : TYPES_REQUIRING_NONNULL_TYPE_ARG) { String pattern = typeName + "<?"; String patternRegex = ".*" + typeName + "<\\? (extends|super) " + COMMON_TYPE_ARG_NAMES + ">.*"; if (line.contains(pattern) && !line.matches(patternRegex)) { errorCount++; errors.append("L") .append(j) .append(" : Missing @NonNull type argument annotation on ") .append(typeName) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } for (String typeName : TYPES_FORBIDDEN_NONNULL_TYPE_ARG) { String patternRegex = ".*" + typeName + "<@NonNull (\\? (extends|super) )?" + COMMON_TYPE_ARG_NAMES + ">.*"; if (line.matches(patternRegex)) { errorCount++; errors.append("L") .append(j) .append(" : @NonNull type argument should be on the arg declaration ") .append(typeName) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } for (String typeName : TYPES_REQUIRING_NONNULL_TYPE_ARG_ON_FUNC) { if (line.matches(".*Function\\d?<.*, (\\? (extends|super) )?" + typeName + "[,>].*")) { errorCount++; errors.append("L") .append(j) .append(" : Missing @NonNull type argument annotation on Function argument ") .append(typeName) .append("\r\n") .append(" at ") .append(fullClassName) .append(".method(") .append(f.getName()) .append(":") .append(j + 1) .append(")\r\n") ; } } } if (errorCount != 0) { errors.insert(0, errorCount + " problems\r\n"); errors.setLength(errors.length() - 2); throw new AssertionError(errors.toString()); } } static final List<String> CLASS_NAMES = Arrays.asList( "TimeUnit", "Scheduler", "Emitter", "Completable", "CompletableSource", "CompletableObserver", "CompletableOnSubscribe", "CompletableTransformer", "CompletableOperator", "CompletableEmitter", "CompletableConverter", "Single", "SingleSource", "SingleObserver", "SingleOnSubscribe", "SingleTransformer", "SingleOperator", "SingleEmitter", "SingleConverter", "Maybe", "MaybeSource", "MaybeObserver", "MaybeOnSubscribe", "MaybeTransformer", "MaybeOperator", "MaybeEmitter", "MaybeConverter", "Observable", "ObservableSource", "Observer", "ObservableOnSubscribe", "ObservableTransformer", "ObservableOperator", "ObservableEmitter", "ObservableConverter", "Flowable", "Publisher", "Subscriber", "FlowableSubscriber", "FlowableOnSubscribe", "FlowableTransformer", "FlowableOperator", "FlowableEmitter", "FlowableConverter", "Function", "BiFunction", "Function3", "Function4", "Function5", "Function6", "Function7", "Function8", "Function9", "Action", "Runnable", "Consumer", "BiConsumer", "Supplier", "Callable", "Void", "Throwable", "Optional", "CompletionStage", "BooleanSupplier", "LongConsumer", "Predicate", "BiPredicate", "Object", "Iterable", "Stream", "Iterator", "BackpressureOverflowStrategy", "BackpressureStrategy", "Subject", "Processor", "FlowableProcessor", "T", "R", "U", "V", "Streamable", "Streamer" ); static final List<String> TYPES_REQUIRING_NONNULL_TYPE_ARG = Arrays.asList( "Iterable", "Stream", "Publisher", "Processor", "Subscriber", "Optional" ); static final List<String> TYPES_FORBIDDEN_NONNULL_TYPE_ARG = Arrays.asList( "Iterable", "Stream", "Publisher", "Processor", "Subscriber", "Optional" ); static final List<String> TYPES_REQUIRING_NONNULL_TYPE_ARG_ON_FUNC = Arrays.asList( "Iterable", "Stream", "Publisher", "Processor", "Subscriber", "Optional", "Observer", "SingleObserver", "MaybeObserver", "CompletableObserver" ); static final String COMMON_TYPE_ARG_NAMES = "([A-Z][0-9]?|TOpening|TClosing|TLeft|TLeftEnd|TRight|TRightEnd)"; }