/
githubmirror
/
RxJava
Обзор
Документация
Войти
/
githubmirror
/
RxJava
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
4.x
src/test/java/io/reactivex/rxjava4/validators/CheckJavadocCodesAndLinksTest.java
435 строк
17 KB
David Karnok
4.x: Streamable: +delay +timeout +zip +onErrorResumeNext, cleanup (#8216)
06 июл 2026, 00:10
Не верифицирован
06 июл 2026, 00:10
79a67f1
Код
Авторство
О чём код?
/* * 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.RxJavaTest; import io.reactivex.rxjava4.testsupport.TestHelper; /** * Scan the Javadocs of a source and check if mentions of other classes, * interfaces and enums are using at-link and at-code wrapping for style. * <p> * The check ignores HTML tag content on a line, @see and @throws entries * and <code></code> lines. */ public class CheckJavadocCodesAndLinksTest extends RxJavaTest { @Test public void checkFlowable() throws Exception { checkSource("Flowable", "io.reactivex.rxjava4.core"); } @Test public void checkCompletable() throws Exception { checkSource("Completable", "io.reactivex.rxjava4.core"); } @Test public void checkSingle() throws Exception { checkSource("Single", "io.reactivex.rxjava4.core"); } @Test public void checkMaybe() throws Exception { checkSource("Maybe", "io.reactivex.rxjava4.core"); } @Test public void checkObservable() throws Exception { checkSource("Observable", "io.reactivex.rxjava4.core"); } @Test public void checkParallelFlowable() throws Exception { checkSource("ParallelFlowable", "io.reactivex.rxjava4.parallel"); } @Test public void checkCompositeDisposable() throws Exception { checkSource("CompositeDisposable", "io.reactivex.rxjava4.disposables"); } @Test public void checkConnectableFlowable() throws Exception { checkSource("ConnectableFlowable", "io.reactivex.rxjava4.core"); } @Test public void checkConnectableObservable() throws Exception { checkSource("ConnectableObservable", "io.reactivex.rxjava4.core"); } @Test public void checkSchedulers() throws Exception { checkSource("Schedulers", "io.reactivex.rxjava4.schedulers"); } static void checkSource(String baseClassName, String packageName) throws Exception { File f = TestHelper.findSource(baseClassName, packageName); if (f == null) { return; } StringBuilder errors = new StringBuilder(2048); int errorCount = 0; List<String> lines = Files.readAllLines(f.toPath()); List<String> docs = new ArrayList<>(); // i = 1 skip the header Javadoc for (int i = 1; i < lines.size(); i++) { if (lines.get(i).trim().equals("/**")) { docs.clear(); boolean skipCode = false; for (int j = i + 1; j < lines.size(); j++) { String line = lines.get(j).trim(); if (line.contains("<code>")) { skipCode = true; } if (line.equals("*/")) { break; } if (!skipCode) { // strip <a> </a> line = stripTags(line); if (line.startsWith("@see")) { docs.add(""); } else if (line.startsWith("@throws") || line.startsWith("@param")) { int space = line.indexOf(' '); if (space < 0) { docs.add(""); } else { space = line.indexOf(" ", space + 1); if (space < 0) { docs.add(""); } else { docs.add(line.substring(space + 1)); } } } else { docs.add(line); } } else { docs.add(""); } if (line.contains("</code>")) { skipCode = false; } } for (String name : NAMES) { boolean isHostType = name.equals(baseClassName); boolean isAlwaysCode = ALWAYS_CODE.contains(name); String asLink = "{@link " + name + "}"; String asCode = "{@code " + name + "}"; boolean seenBefore = false; for (int j = 0; j < docs.size(); j++) { String line = docs.get(j); int idxLink = line.indexOf(asLink); if (idxLink >= 0 && !isHostType && !isAlwaysCode) { int k = idxLink + asLink.length(); for (;;) { int jdxLink = line.indexOf(asLink, k); if (jdxLink < 0) { break; } errorCount++; errors.append("The subsequent mention should be code: ") .append("{@code ").append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); k = jdxLink + asLink.length(); } } if (seenBefore) { if (idxLink >= 0 && !isHostType && !isAlwaysCode) { errorCount++; errors.append("The subsequent mention should be code: ") .append("{@code ").append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } } else { int idxCode = line.indexOf(asCode); if (isHostType) { if (idxLink >= 0) { errorCount++; errors.append("The host type mention should be code: ") .append("{@code ").append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } } else { if ((idxLink < 0 && idxCode >= 0 && !isAlwaysCode) || (idxCode >= 0 && idxCode < idxLink)) { errorCount++; if (isAlwaysCode) { errors.append("The first mention should be code: ") .append("{@code ") ; } else { errors.append("The first mention should be link: ") .append("{@link ") ; } errors .append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } } seenBefore = idxLink >= 0 || idxCode >= 0; } // strip out all existing {@code } and {@link } instances String noCurly = removeCurlies(line); int k = 0; for (;;) { int idx = noCurly.indexOf(name, k); if (idx < 0) { break; } k = idx + name.length(); if (isHostType) { errorCount++; errors.append("The host type mention should be code: ") .append("{@code ").append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } else if (!seenBefore) { errorCount++; if (isAlwaysCode) { errors.append("The first mention should be code: ") .append("{@code "); } else { errors.append("The first mention should be link: ") .append("{@link "); } errors .append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } else { errorCount++; errors.append("The subsequent mention should be code: ") .append("{@code ").append(name) .append("}\r\n at ") .append(packageName) .append(".") .append(baseClassName) .append(".method(") .append(baseClassName) .append(".java:") .append(i + 2 + j) .append(")\r\n"); } seenBefore = true; } } } i += docs.size(); if (errorCount >= ERROR_LIMIT) { break; } } } if (errorCount != 0) { errors.insert(0, "Found " + (errorCount > ERROR_LIMIT ? ERROR_LIMIT + "+" : errorCount + "") + " cases\r\n"); throw new AssertionError(errors.toString()); } } static String removeCurlies(String input) { StringBuilder result = new StringBuilder(input.length()); boolean skip = false; for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (c == '{') { skip = true; } if (!skip) { result.append(c); } if (c == '}') { skip = false; } } return result.toString(); } static String stripTags(String input) { StringBuilder result = new StringBuilder(input.length()); result.append(input, input.length() > 1 ? 2 : 1, input.length()); clearTag(result, "<a ", "</a>"); clearTag(result, "<b>", "</b>"); clearTag(result, "<strong>", "</strong>"); clearTag(result, "<em>", "</em>"); clearTag(result, "<img ", ">"); return result.toString(); } static void clearTag(StringBuilder builder, String startTag, String endTag) { int k = 0; for (;;) { int j = builder.indexOf(startTag, k); if (j < 0) { break; } int e = builder.indexOf(endTag, j); if (e < 0) { e = builder.length(); } blankRange(builder, j, e); k = e + endTag.length(); } } static void blankRange(StringBuilder builder, int start, int end) { for (int i = start; i < end; i++) { int c = builder.charAt(i); if (c != '\r' && c != '\n') { builder.setCharAt(i, ' '); } } } static final List<String> NAMES = Arrays.asList( "Flowable", "Observable", "Maybe", "Single", "Completable", "ParallelFlowable", "Publisher", "ObservableSource", "MaybeSource", "SingleSource", "CompletableSource", "FlowableSubscriber", "Subscriber", "Observer", "MaybeObserver", "SingleObserver", "CompletableObserver", "FlowableOperator", "ObservableOperator", "MaybeOperator", "SingleOperator", "CompletableOperator", "FlowableOnSubscribe", "ObservableOnSubscribe", "MaybeOnSubscribe", "SingleOnSubscribe", "CompletableOnSubscribe", "FlowableTransformer", "ObservableTransformer", "MaybeTransformer", "SingleTransformer", "CompletableTransformer", "ParallelTransformer", "FlowableConverter", "ObservableConverter", "MaybeConverter", "SingleConverter", "CompletableConverter", "FlowableEmitter", "ObservableEmitter", "MaybeEmitter", "SingleEmitter", "CompletableEmitter", "Iterable", "Stream", "Function", "BiFunction", "Function3", "Function4", "Function5", "Function6", "Function7", "Function8", "Function9", "Action", "Runnable", "Disposable", "Subscription", "Consumer", "BiConsumer", "Future", "Supplier", "Callable", "TimeUnit", "BackpressureOverflowStrategy", "ParallelFailureHandling", "Exception", "Throwable", "NullPointerException", "IllegalStateException", "IllegalArgumentException", "MissingBackpressureException", "UndeliverableException", "OutOfMemoryError", "StackOverflowError", "NoSuchElementException", "ClassCastException", "CompositeException", "RuntimeException", "Error", "TimeoutException", "OnErrorNotImplementedException", "false", "true", "onNext", "onError", "onComplete", "onSuccess", "onSubscribe", "null", "ConnectableFlowable", "ConnectableObservable", "Subject", "FlowableProcessor", "Processor", "Scheduler", "Optional", "CompletionStage", "Collector", "Collectors", "Schedulers", "RxJavaPlugins", "CompletableFuture", "Object", "Integer", "Long", "Boolean", "LongConsumer", "BooleanSupplier", "GroupedFlowable", "GroupedObservable", "UnicastSubject", "UnicastProcessor", "Notification", "Comparable", "Comparator", "Collection", "SafeSubscriber", "SafeObserver", "List", "ArrayList", "HashMap", "HashSet", "CharSequence", "TestSubscriber", "TestObserver", "Class", "ThreadFactory", "Runnable", "Executor", "ExecutorService", "Executors", "RejectedExecutionException" ); static final Set<String> ALWAYS_CODE = new HashSet<>(Arrays.asList( "false", "true", "null", "onSuccess", "onNext", "onError", "onComplete", "onSubscribe" )); static final int ERROR_LIMIT = 5000; }