[reactive-streams] IDEA-273877 Treat @Transactional like @Blocking if return type is not reactive streams Publisher

GitOrigin-RevId: 5e54b17510a88027b3fbffc330fc363b5845dd0b
This commit is contained in:
Yuriy Artamonov
2021-09-03 18:56:03 +03:00
committed by intellij-monorepo-bot
parent 95a94a545d
commit 4f044b833f
6 changed files with 83 additions and 19 deletions

View File

@@ -23,6 +23,10 @@ public interface BlockingMethodChecker {
boolean isMethodBlocking(@NotNull PsiMethod method);
default boolean isMethodNonBlocking(@NotNull PsiMethod method) {
return false;
}
/**
* @param element PsiElement (e.g. method call or reference) which is located in "non-blocking" code fragment
* @return empty array if cannot provide any fixes, non-empty array of quick fixes otherwise

View File

@@ -14,9 +14,11 @@ import static com.intellij.codeInspection.blockingCallsDetection.BlockingMethodI
public final class AnnotationBasedBlockingMethodChecker implements BlockingMethodChecker {
private final List<String> myBlockingAnnotations;
private final List<String> myNonBlockingAnnotations;
public AnnotationBasedBlockingMethodChecker(List<String> blockingAnnotations) {
public AnnotationBasedBlockingMethodChecker(List<String> blockingAnnotations, List<String> nonBlockingAnnotations) {
myBlockingAnnotations = blockingAnnotations;
myNonBlockingAnnotations = nonBlockingAnnotations;
}
@Override
@@ -31,4 +33,9 @@ public final class AnnotationBasedBlockingMethodChecker implements BlockingMetho
public boolean isMethodBlocking(@NotNull PsiMethod method) {
return AnnotationUtil.findAnnotation(method, myBlockingAnnotations, false) != null;
}
@Override
public boolean isMethodNonBlocking(@NotNull PsiMethod method) {
return AnnotationUtil.findAnnotation(method, myNonBlockingAnnotations, false) != null;
}
}

View File

@@ -18,11 +18,9 @@ import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UCallExpression;
import javax.swing.*;
@@ -30,7 +28,7 @@ import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUastLocalInspectionTool {
public final class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUastLocalInspectionTool {
public static final List<String> DEFAULT_BLOCKING_ANNOTATIONS = List.of(
"org.jetbrains.annotations.Blocking",
@@ -46,7 +44,6 @@ public class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUa
public List<String> myBlockingAnnotations = new ArrayList<>(DEFAULT_BLOCKING_ANNOTATIONS);
public List<String> myNonBlockingAnnotations = new ArrayList<>(DEFAULT_NONBLOCKING_ANNOTATIONS);
@Nullable
@Override
public JComponent createOptionsPanel() {
return new OptionsPanel();
@@ -55,25 +52,29 @@ public class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUa
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
List<BlockingMethodChecker> blockingMethodCheckers =
ContainerUtil.append(BlockingMethodChecker.EP_NAME.getExtensionList(),
new AnnotationBasedBlockingMethodChecker(myBlockingAnnotations));
List<NonBlockingContextChecker> nonBlockingContextCheckers = getNonBlockingContextCheckers(holder.getFile());
if (nonBlockingContextCheckers.isEmpty()) return PsiElementVisitor.EMPTY_VISITOR;
List<NonBlockingContextChecker> nonBlockingContextCheckers =
ContainerUtil.append(NonBlockingContextChecker.EP_NAME.getExtensionList(),
new AnnotationBasedNonBlockingContextChecker(myNonBlockingAnnotations));
List<BlockingMethodChecker> blockingMethodCheckers = getBlockingMethodCheckers(holder.getFile());
if (blockingMethodCheckers.isEmpty()) return PsiElementVisitor.EMPTY_VISITOR;
if (!isInspectionActive(holder.getFile(), blockingMethodCheckers, nonBlockingContextCheckers)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new BlockingMethodInNonBlockingContextVisitor(holder, blockingMethodCheckers, nonBlockingContextCheckers);
}
private static boolean isInspectionActive(PsiFile file,
List<BlockingMethodChecker> myBlockingMethodCheckers,
List<NonBlockingContextChecker> myNonBlockingContextCheckers) {
return myBlockingMethodCheckers.stream().anyMatch(extension -> extension.isApplicable(file)) &&
myNonBlockingContextCheckers.stream().anyMatch(extension -> extension.isApplicable(file));
@NotNull
private List<NonBlockingContextChecker> getNonBlockingContextCheckers(@NotNull PsiFile file) {
List<NonBlockingContextChecker> nonBlockingContextCheckers = new ArrayList<>(NonBlockingContextChecker.EP_NAME.getExtensionList());
nonBlockingContextCheckers.add(new AnnotationBasedNonBlockingContextChecker(myNonBlockingAnnotations));
nonBlockingContextCheckers.removeIf(checker -> !checker.isApplicable(file));
return nonBlockingContextCheckers;
}
@NotNull
private List<BlockingMethodChecker> getBlockingMethodCheckers(@NotNull PsiFile file) {
List<BlockingMethodChecker> blockingMethodCheckers = new ArrayList<>(BlockingMethodChecker.EP_NAME.getExtensionList());
blockingMethodCheckers.add(new AnnotationBasedBlockingMethodChecker(myBlockingAnnotations, myNonBlockingAnnotations));
blockingMethodCheckers.removeIf(checker -> !checker.isApplicable(file));
return blockingMethodCheckers;
}
private final class OptionsPanel extends JPanel {
@@ -146,6 +147,9 @@ public class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUa
if (!isMethodOrSupersBlocking(referencedMethod, myBlockingMethodCheckers)) return;
// if some implementation believes this method is non-blocking then we don't report it
if (isMethodNonBlocking(referencedMethod, myBlockingMethodCheckers)) return;
PsiElement elementToHighLight = AnalysisUastUtil.getMethodIdentifierSourcePsi(callExpression);
if (elementToHighLight == null) return;
@@ -172,6 +176,14 @@ public class BlockingMethodInNonBlockingContextInspection extends AbstractBaseUa
});
}
private static boolean isMethodNonBlocking(PsiMethod method,
List<? extends BlockingMethodChecker> blockingMethodCheckers) {
return blockingMethodCheckers.stream().anyMatch(extension -> {
ProgressIndicatorProvider.checkCanceled();
return extension.isMethodNonBlocking(method);
});
}
private static boolean isContextNonBlockingFor(PsiElement element,
List<? extends NonBlockingContextChecker> nonBlockingContextCheckers) {
return nonBlockingContextCheckers.stream().anyMatch(extension -> {

View File

@@ -0,0 +1,37 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.inspections.blockingCallsDetection
import com.intellij.codeInspection.blockingCallsDetection.BlockingMethodChecker
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.idea.project.getLanguageVersionSettings
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier
import org.jetbrains.uast.toUElement
internal class CoroutineBlockingMethodChecker : BlockingMethodChecker {
override fun isApplicable(file: PsiFile): Boolean {
if (file !is KtFile) return false
val languageVersionSettings = getLanguageVersionSettings(file)
return languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines)
}
override fun isMethodBlocking(method: PsiMethod): Boolean = false
override fun isMethodNonBlocking(method: PsiMethod): Boolean {
val uMethod = method.toUElement()
val sourcePsi = uMethod?.sourcePsi ?: return false
return sourcePsi is KtNamedFunction && sourcePsi.modifierList?.hasSuspendModifier() == true
}
private fun getLanguageVersionSettings(psiElement: PsiElement): LanguageVersionSettings {
return psiElement.module?.languageVersionSettings ?: psiElement.project.getLanguageVersionSettings()
}
}

View File

@@ -46,6 +46,8 @@ import org.jetbrains.kotlin.types.KotlinType
class CoroutineNonBlockingContextChecker : NonBlockingContextChecker {
override fun isApplicable(file: PsiFile): Boolean {
if (file !is KtFile) return false
val languageVersionSettings = getLanguageVersionSettings(file)
return languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines)
}

View File

@@ -137,6 +137,8 @@
<codeInsight.nonBlockingContextChecker
implementation="org.jetbrains.kotlin.idea.inspections.blockingCallsDetection.CoroutineNonBlockingContextChecker"/>
<codeInsight.blockingMethodChecker
implementation="org.jetbrains.kotlin.idea.inspections.blockingCallsDetection.CoroutineBlockingMethodChecker"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>