diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index fe23d1a14b01..b90b65ac5a37 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -515,6 +515,7 @@ + + + + diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index 495a10c52eeb..acc1dfbbfa95 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -670,6 +670,7 @@ inspection.extension.registered.as.component.message=A class must not be registe inspection.cancellation.check.in.loops.display.name=Cancellation check in loops inspection.cancellation.check.in.loops.message=Cancellation check ''{0}'' should be placed in the first line +inspection.insert.cancellation.check.fix.message=Insert cancellation check inspections.application.service.as.static.final.field.display.name=Application service assigned to a static final field/property inspections.application.service.as.static.final.field.message=Application service must not be assigned to a static final field diff --git a/plugins/devkit/devkit-core/src/inspections/CancellationCheckInLoopsInspection.kt b/plugins/devkit/devkit-core/src/inspections/CancellationCheckInLoopsInspection.kt index ccab7b26b534..e8f9f50fa32e 100644 --- a/plugins/devkit/devkit-core/src/inspections/CancellationCheckInLoopsInspection.kt +++ b/plugins/devkit/devkit-core/src/inspections/CancellationCheckInLoopsInspection.kt @@ -3,14 +3,13 @@ package org.jetbrains.idea.devkit.inspections import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.PsiMember -import com.intellij.psi.util.PsiUtil.getMemberQualifiedName import com.intellij.uast.UastHintedVisitorAdapter +import com.intellij.util.concurrency.annotations.RequiresReadLock import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.idea.devkit.inspections.quickfix.CancellationCheckInLoopsFixProviders import org.jetbrains.uast.* import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor -private const val REQUIRES_READ_LOCK_FQN = "com.intellij.util.concurrency.annotations.RequiresReadLock" class CancellationCheckInLoopsInspection : DevKitUastInspectionBase() { @@ -20,12 +19,27 @@ class CancellationCheckInLoopsInspection : DevKitUastInspectionBase() { return UastHintedVisitorAdapter.create( holder.file.language, object : AbstractUastNonRecursiveVisitor() { + override fun visitForExpression(node: UForExpression): Boolean { + inspectLoopExpression(node, checkProvider, holder) + return true + } + override fun visitForEachExpression(node: UForEachExpression): Boolean { - inspectForEachExpression(node, checkProvider, holder) + inspectLoopExpression(node, checkProvider, holder) + return true + } + + override fun visitWhileExpression(node: UWhileExpression): Boolean { + inspectLoopExpression(node, checkProvider, holder) + return true + } + + override fun visitDoWhileExpression(node: UDoWhileExpression): Boolean { + inspectLoopExpression(node, checkProvider, holder) return true } }, - arrayOf(UForEachExpression::class.java) + arrayOf(ULoopExpression::class.java) ) } @@ -33,44 +47,47 @@ class CancellationCheckInLoopsInspection : DevKitUastInspectionBase() { * If the first expression in a loop is not another loop, finds the right cancellation check based on the context of a loop, * and if it's missing, registers the problem. */ - private fun inspectForEachExpression(forEachExpression: UForEachExpression, - checkProvider: CancellationCheckProvider, - holder: ProblemsHolder) { - val sourcePsi = forEachExpression.sourcePsi ?: return + private fun inspectLoopExpression(loopExpression: ULoopExpression, + checkProvider: CancellationCheckProvider, + holder: ProblemsHolder) { + val sourcePsi = loopExpression.sourcePsi ?: return - if (!shouldBeRunOn(forEachExpression)) return + if (!shouldBeRunOn(loopExpression)) return - val firstExpressionInLoop = getFirstExpressionInLoop(forEachExpression) + val firstExpressionInLoop = loopExpression.bodyExpressions.firstOrNull() // Don't insert a check between nested loops if there is nothing in between - if (firstExpressionInLoop is UForEachExpression) return + if (firstExpressionInLoop is ULoopExpression) return - val cancellationCheckFqn = checkProvider.findCancellationCheckFqn(sourcePsi) - if (firstExpressionInLoop.isCancellationCheck(cancellationCheckFqn)) return + val cancellationCheckFqn = checkProvider.findCancellationCheckCall(sourcePsi) + if (checkProvider.isCancellationCheckCall(firstExpressionInLoop?.sourcePsi, cancellationCheckFqn)) return val anchor = sourcePsi.firstChild - holder.registerProblem(anchor, DevKitBundle.message("inspection.cancellation.check.in.loops.message", cancellationCheckFqn)) + val fixProvider = CancellationCheckInLoopsFixProviders.forLanguage(holder.file.language) ?: return + val fixes = fixProvider.getFixes(anchor, cancellationCheckFqn) + holder.registerProblem( + anchor, + DevKitBundle.message("inspection.cancellation.check.in.loops.message", cancellationCheckFqn), + *fixes.toTypedArray() + ) } /** - * For now, insert a cancellation check in loops with [com.intellij.util.concurrency.annotations.RequiresReadLock] + * For now, insert a cancellation check in loops with [com.intellij.util.concurrency.annotations.RequiresReadLock]. */ private fun shouldBeRunOn(uElement: UElement): Boolean { val containingMethod = uElement.getParentOfType() ?: return false val superMethods = containingMethod.javaPsi.findSuperMethods() - return superMethods.plus(containingMethod).any { it.hasAnnotation(REQUIRES_READ_LOCK_FQN) } + return superMethods.plus(containingMethod).any { it.hasAnnotation(RequiresReadLock::class.java.canonicalName) } } - private fun getFirstExpressionInLoop(loop: UForEachExpression): UExpression? { - return when (val body = loop.body) { - is UBlockExpression -> body.expressions.firstOrNull() - else -> body + private val ULoopExpression.bodyExpressions: List + get() { + return when (val loopBody = body) { + is UBlockExpression -> loopBody.expressions + else -> listOf(body) + } } - } - - private fun UExpression?.isCancellationCheck(cancellationCheckFqn: String): Boolean { - val resolved = this?.tryResolve() as? PsiMember ?: return false - return getMemberQualifiedName(resolved) == cancellationCheckFqn - } } + diff --git a/plugins/devkit/devkit-core/src/inspections/CancellationCheckProvider.kt b/plugins/devkit/devkit-core/src/inspections/CancellationCheckProvider.kt index 8f548614040f..4da0ab2e47c3 100644 --- a/plugins/devkit/devkit-core/src/inspections/CancellationCheckProvider.kt +++ b/plugins/devkit/devkit-core/src/inspections/CancellationCheckProvider.kt @@ -4,39 +4,44 @@ package org.jetbrains.idea.devkit.inspections import com.intellij.lang.LanguageExtension import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName.Companion.create +import com.intellij.openapi.util.IntellijInternalApi import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.util.PsiUtil +import org.jetbrains.annotations.ApiStatus + + +private const val PROGRESS_MANAGER_CHECKED_CANCELED = "com.intellij.openapi.progress.ProgressManager.checkCanceled" private val EP_NAME: ExtensionPointName = create("DevKit.lang.cancellationCheckProvider") + internal object CancellationCheckProviders : LanguageExtension(EP_NAME.name) -private const val CANCELLATION_CHECK_BLOCKING_FQN = "com.intellij.openapi.progress.ProgressManager.checkCanceled" -private const val CANCELLATION_CHECK_SUSPENDING_FQN = "com.intellij.openapi.progress.CoroutinesKt.checkCancelled" /** * Provides the right cancellation check based on the context - * (see [com.intellij.openapi.progress.ProgressManager.checkCanceled], - * [com.intellij.openapi.progress.checkCancelled]). + * (see [com.intellij.openapi.progress.ProgressManager.checkCanceled], [com.intellij.openapi.progress.checkCancelled]) + * and checks expressions for cancellation check calls. */ +@IntellijInternalApi +@ApiStatus.Internal interface CancellationCheckProvider { - enum class Context { - BLOCKING, SUSPENDING - } + fun findCancellationCheckCall(element: PsiElement): String - fun findCancellationCheckFqn(element: PsiElement): String { - return when (findContext(element)) { - Context.BLOCKING -> CANCELLATION_CHECK_BLOCKING_FQN - Context.SUSPENDING -> CANCELLATION_CHECK_SUSPENDING_FQN - } - } - - fun findContext(element: PsiElement): Context + fun isCancellationCheckCall(element: PsiElement?, cancellationCheckFqn: String): Boolean } -class JavaCancellationCheckProvider : CancellationCheckProvider { +internal class JavaCancellationCheckProvider : CancellationCheckProvider { - override fun findContext(element: PsiElement) = CancellationCheckProvider.Context.BLOCKING + override fun findCancellationCheckCall(element: PsiElement): String { + return PROGRESS_MANAGER_CHECKED_CANCELED + } + override fun isCancellationCheckCall(element: PsiElement?, cancellationCheckFqn: String): Boolean { + val resolvedMethod = (element as? PsiMethodCallExpression)?.resolveMethod() ?: return false + return PsiUtil.getMemberQualifiedName(resolvedMethod) == cancellationCheckFqn + } } diff --git a/plugins/devkit/devkit-core/src/inspections/quickfix/CancellationCheckInLoopsFixProvider.kt b/plugins/devkit/devkit-core/src/inspections/quickfix/CancellationCheckInLoopsFixProvider.kt new file mode 100644 index 000000000000..dbd2524b2149 --- /dev/null +++ b/plugins/devkit/devkit-core/src/inspections/quickfix/CancellationCheckInLoopsFixProvider.kt @@ -0,0 +1,73 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.inspections.quickfix + +import com.intellij.codeInsight.BlockUtils.expandSingleStatementToBlockStatement +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.LocalQuickFixOnPsiElement +import com.intellij.lang.LanguageExtension +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.psi.* +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.parentOfType +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.idea.devkit.DevKitBundle + + +private val EP_NAME: ExtensionPointName = ExtensionPointName.create( + "DevKit.lang.cancellationCheckInLoopsFixProvider") + +internal object CancellationCheckInLoopsFixProviders : LanguageExtension(EP_NAME.name) + + +@IntellijInternalApi +@ApiStatus.Internal +interface CancellationCheckInLoopsFixProvider { + + fun getFixes(loopKeyword: PsiElement, cancellationCheckFqn: String): List + +} + + +internal class JavaCancellationCheckInLoopsFixProvider : CancellationCheckInLoopsFixProvider { + + override fun getFixes(loopKeyword: PsiElement, cancellationCheckFqn: String): List { + return listOf(InsertCancellationCheckFix(cancellationCheckFqn, loopKeyword)) + } + +} + + +class InsertCancellationCheckFix( + private val cancellationCheckCallFqn: String, + loopKeyword: PsiElement, +) : LocalQuickFixOnPsiElement(loopKeyword) { + + override fun getFamilyName(): String = DevKitBundle.message("inspection.insert.cancellation.check.fix.message") + + override fun getText(): String = familyName + + override fun isAvailable(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement): Boolean { + return PsiTreeUtil.getParentOfType(startElement, PsiLoopStatement::class.java) != null + } + + override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val loopStatement = PsiTreeUtil.getParentOfType(startElement, PsiLoopStatement::class.java) ?: return + val factory = PsiElementFactory.getInstance(project) + + val cancellationCheckText = "${cancellationCheckCallFqn}();" + val cancellationCheckStatement = factory.createStatementFromText(cancellationCheckText, loopStatement) + + val body = loopStatement.body ?: return + val bodyBlock = expandSingleStatementToBlockStatement(body).parentOfType(withSelf = true) + + val insertedElement = bodyBlock?.codeBlock?.addBefore(cancellationCheckStatement, bodyBlock.codeBlock.firstBodyElement) + + insertedElement?.let { + JavaCodeStyleManager.getInstance(project).shortenClassReferences(it) + } + } + +} diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.java new file mode 100644 index 000000000000..2fb21612ba3e --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.java @@ -0,0 +1,44 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + int j = 0; + do { + // nested loops with something in between + do { + doSomething(); + do { + doSomething(); + i++; + } while (i < 10); + i++; + } while (i < 5); + + // nested loops with a block in between + do { + if (i != 3) { + //empty loop + do { + } while (j > 5); + } + i++; + } while (i < 15); + + // single-line loop + do System.out.println(i); while (i < 20); + + // no body loop + do; while(i < 0); + + i++; + } while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.java similarity index 88% rename from plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.java rename to plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.java index ba732d5d8b02..4090132d1ab7 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.java +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.java @@ -32,6 +32,9 @@ class Clazz { // single-line loop for (String item6: items) System.out.println(item); + + // no body loop + for(String item5 : items); } } } \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForLoops.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForLoops.java new file mode 100644 index 000000000000..1de9537c1b93 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/ForLoops.java @@ -0,0 +1,38 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + + // nested loops with something in between + for (int j = 0; j < 5; j++) { + doSomething(); + for (int k = 0; k < 5; k++) { + doSomething(); + } + } + + // nested loops with a block in between + for (int j = 0; j < 5; j++) { + if (j != 3) { + //empty loop + for (int k = 0; k < 5; k++) { + } + } + } + + // single-line loop + for (int j = 0; j < 5; j++) System.out.println(j); + + // no body loop + for (int y = 0; y < 10; y++); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.java new file mode 100644 index 000000000000..6ce130fe78ea --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.java @@ -0,0 +1,32 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + +class Clazz { + + @RequiresReadLock + public static void foo() { + int j = 0; + String[] items = {""}; + + // nested loops of different kinds + while (j < 100) { + for (int i = 0; i < 5; i++) { + for (String item : items) { + do { + doSomething(); + j++; + } while (j < 5); + } + } + j++; + } + + // single line nested loops + for (int i = 0; i < 5; i++) for (String item : items) { + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.java index ee83820af98c..5aa3fa70c578 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.java +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.java @@ -15,5 +15,23 @@ class Clazz { ProgressManager.checkCanceled(); doSomething(); } + + for (int i = 0; i < 5; i++) { + ProgressManager.checkCanceled(); + doSomething(); + } + + int i = 0; + while (i < 5) { + ProgressManager.checkCanceled(); + doSomething(); + i++; + } + + do { + ProgressManager.checkCanceled(); + doSomething(); + i++; + } while (i < 10); } } \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.java b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.java new file mode 100644 index 000000000000..de8038d0f264 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.java @@ -0,0 +1,44 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + int j = 0; + while (i < 100) { + // nested loops with something in between + while (i < 5) { + doSomething(); + while (i < 10) { + doSomething(); + i++; + } + i++; + } + + // nested loops with a block in between + while (i < 15) { + if (i != 3) { + //empty loop + while (j > 5) { + } + } + i++; + } + + // single-line loop + while (i < 20) System.out.println(i); + + // no body loop + while(i < 0); + + i++; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.java new file mode 100644 index 000000000000..7b29c71c7353 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.java @@ -0,0 +1,19 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do { + doSomething(); + i++; + } + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.java new file mode 100644 index 000000000000..ee1e78d4e92f --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.java @@ -0,0 +1,21 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do { + ProgressManager.checkCanceled(); + doSomething(); + i++; + } + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.java new file mode 100644 index 000000000000..b4e4b5f25e28 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.java new file mode 100644 index 000000000000..f5c5702a411f --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.java @@ -0,0 +1,20 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + ProgressManager.checkCanceled(); + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop.java new file mode 100644 index 000000000000..f76a15411355 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop_after.java new file mode 100644 index 000000000000..f204cfbff046 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockForLoop_after.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + ProgressManager.checkCanceled(); + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.java new file mode 100644 index 000000000000..1ef27ba2053e --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + doSomething(); + i++; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.java new file mode 100644 index 000000000000..a10b0e1bbad3 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.java @@ -0,0 +1,20 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + ProgressManager.checkCanceled(); + doSomething(); + i++; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.java new file mode 100644 index 000000000000..90c0ffa3f06c --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do { + // check comments + } + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.java new file mode 100644 index 000000000000..e939ae490c1c --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do { + ProgressManager.checkCanceled(); + // check comments + } + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.java new file mode 100644 index 000000000000..821df6fa4839 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.java new file mode 100644 index 000000000000..58421990b723 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + ProgressManager.checkCanceled(); + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop.java new file mode 100644 index 000000000000..455cccb71e33 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop.java @@ -0,0 +1,14 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop_after.java new file mode 100644 index 000000000000..8204af475af4 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyForLoop_after.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + ProgressManager.checkCanceled(); + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.java new file mode 100644 index 000000000000..55cfc003e740 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.java @@ -0,0 +1,17 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.java new file mode 100644 index 000000000000..9e2e65dba8d0 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.java @@ -0,0 +1,19 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + ProgressManager.checkCanceled(); + // check comments + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.java new file mode 100644 index 000000000000..d701a9fe7405 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.java new file mode 100644 index 000000000000..571cc81082ca --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.java @@ -0,0 +1,20 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + ProgressManager.checkCanceled(); + ; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop.java new file mode 100644 index 000000000000..07c7da1ffcd5 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop.java @@ -0,0 +1,14 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop_after.java new file mode 100644 index 000000000000..52d103a68b70 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyForLoop_after.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + ProgressManager.checkCanceled(); + ; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.java new file mode 100644 index 000000000000..9c2e30861692 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.java @@ -0,0 +1,15 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.java new file mode 100644 index 000000000000..b999cdb253dc --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.java @@ -0,0 +1,19 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + ProgressManager.checkCanceled(); + ; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.java new file mode 100644 index 000000000000..76e17ebab520 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do i++; + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.java new file mode 100644 index 000000000000..fc4321724bfa --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.java @@ -0,0 +1,20 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + do { + ProgressManager.checkCanceled(); + i++; + } + while (i < 100); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.java new file mode 100644 index 000000000000..c61d209378f5 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.java @@ -0,0 +1,16 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) doSomething(); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.java new file mode 100644 index 000000000000..f5c5702a411f --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.java @@ -0,0 +1,20 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + String[] items = {""}; + + for (String item : items) { + ProgressManager.checkCanceled(); + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop.java new file mode 100644 index 000000000000..621af3ecfcc5 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop.java @@ -0,0 +1,14 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) doSomething(); + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop_after.java new file mode 100644 index 000000000000..f204cfbff046 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineForLoop_after.java @@ -0,0 +1,18 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + for (int i = 0; i < 5; i++) { + ProgressManager.checkCanceled(); + doSomething(); + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.java new file mode 100644 index 000000000000..b6d87cbdeaa6 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.java @@ -0,0 +1,15 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) i++; + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.java new file mode 100644 index 000000000000..7d3f27861c39 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.java @@ -0,0 +1,19 @@ +package inspections.cancellationCheckInLoops; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.concurrency.annotations.RequiresReadLock; + +import static inspections.cancellationCheckInLoops.Foo.doSomething; + + +class Clazz { + + @RequiresReadLock + public static void foo() { + int i = 0; + while (i < 5) { + ProgressManager.checkCanceled(); + i++; + } + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTest.kt index 813862bce890..f4585c129299 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTest.kt +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTest.kt @@ -7,23 +7,36 @@ import org.jetbrains.idea.devkit.DevkitJavaTestsUtil @TestDataPath("\$CONTENT_ROOT/testData/inspections/cancellationCheckInLoops") class CancellationCheckInLoopsInspectionTest : CancellationCheckInLoopsInspectionTestBase() { - override val fileType - get() = "java" + override fun getFileExtension(): String = "java" - override fun getBasePath(): String { - return DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/cancellationCheckInLoops" - } + override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/cancellationCheckInLoops" fun testRunCondition() { doTest() } - fun testMultipleNestedLoops() { - doTest() - } - fun testPresentCancellationCheck() { doTest() } + fun testForEachLoops() { + doTest() + } + + fun testForLoops() { + doTest() + } + + fun testWhileLoops() { + doTest() + } + + fun testDoWhileLoops() { + doTest() + } + + fun testNestedLoops() { + doTest() + } + } diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/quickfix/InsertCancellationCheckFixTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/quickfix/InsertCancellationCheckFixTest.kt new file mode 100644 index 000000000000..1434d8546f6d --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/quickfix/InsertCancellationCheckFixTest.kt @@ -0,0 +1,79 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.inspections.quickfix + +import com.intellij.testFramework.TestDataPath +import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.idea.devkit.DevkitJavaTestsUtil +import org.jetbrains.idea.devkit.inspections.CancellationCheckInLoopsInspectionTestBase + + +@TestDataPath("\$CONTENT_ROOT/testData/inspections/insertCancellationCheckFix") +class InsertCancellationCheckFixTest : CancellationCheckInLoopsInspectionTestBase() { + + override fun getFileExtension(): String = "java" + + override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/insertCancellationCheckFix" + + private val fixName = DevKitBundle.message("inspection.insert.cancellation.check.fix.message") + + fun testBlockDoWhileLoop() { + doTest(fixName) + } + + fun testBlockForEachLoop() { + doTest(fixName) + } + + fun testBlockForLoop() { + doTest(fixName) + } + + fun testBlockWhileLoop() { + doTest(fixName) + } + + fun testEmptyDoWhileLoop() { + doTest(fixName) + } + + fun testEmptyForEachLoop() { + doTest(fixName) + } + + fun testEmptyForLoop() { + doTest(fixName) + } + + fun testEmptyWhileLoop() { + doTest(fixName) + } + + fun testSingleLineDoWhileLoop() { + doTest(fixName) + } + + fun testSingleLineForEachLoop() { + doTest(fixName) + } + + fun testSingleLineForLoop() { + doTest(fixName) + } + + fun testSingleLineWhileLoop() { + doTest(fixName) + } + + fun testNoBodyForEachLoop() { + doTest(fixName) + } + + fun testNoBodyForLoop() { + doTest(fixName) + } + + fun testNoBodyWhileLoop() { + doTest(fixName) + } + +} diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.kt new file mode 100644 index 000000000000..81727a06236c --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/DoWhileLoops.kt @@ -0,0 +1,41 @@ +package inspections.cancellationCheckInLoops + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + do { + // nested loops with something in between + do { + doSomething() + do { + doSomething() + i++ + } while (i < 20) + i++ + } while (i < 10) + + // sibling loop + do { + doSomething() + i++ + } while (i < 30) + + // nested loops with a block in between + do { + if (i < 5) { + // empty loop + do { + } while (i < 3) + } + } while (i < 40) + + // single-line loop + do doSomething() while (i < 50) + i++ + } while (i < 100) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.kt similarity index 93% rename from plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.kt rename to plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.kt index 532ac3279336..0d0b8142ea76 100644 --- a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/MultipleNestedLoops.kt +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/ForEachLoops.kt @@ -25,7 +25,7 @@ fun main() { for (j in 1..10) { if (i < 5) { // empty loop - for (j in 1..10) { + for (k in 1..10) { } } } diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.kt new file mode 100644 index 000000000000..4274c92b21b3 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/NestedLoops.kt @@ -0,0 +1,28 @@ +package inspections.cancellationCheckInLoops + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var j = 0 + // nested loops of different kinds + for (i in 1..10) { + while (j < 5) { + do { + doSomething() + j++ + } while (j < 3) + j++ + } + } + + // single line nested loops + for (i in 1..10) while (j < 5) { + doSomething() + j++ + } + +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.kt index 334497bdfb6a..2350e27d1a9c 100644 --- a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.kt +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/PresentCancellationCheck.kt @@ -10,6 +10,17 @@ fun main() { for (i in 1..10) { ProgressManager.checkCanceled() } + + var i = 0; + while (i < 5) { + ProgressManager.checkCanceled() + i++ + } + + do { + ProgressManager.checkCanceled() + i++ + } while (i < 10) } @RequiresReadLock @@ -20,7 +31,7 @@ suspend fun foo() { } // wrong cancellation check - for (i in 1..10) { + for (i in 1..10) { ProgressManager.checkCanceled() } } \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/SuspendingContext.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/SuspendingContext.kt index cc278b8a66ac..7d9d08139b92 100644 --- a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/SuspendingContext.kt +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/SuspendingContext.kt @@ -5,11 +5,11 @@ import com.intellij.util.concurrency.annotations.RequiresReadLock import inspections.cancellationCheckInLoops.Foo.doSomething @Suppress("UNUSED_PARAMETER") -fun withSuspendLambda(l: suspend ()-> Any) { } +fun withSuspendLambda(l: suspend () -> Any) { } @RequiresReadLock suspend fun mySuspendFun() { - for (i in 1..10) { + for (i in 1..10) { doSomething() } } @@ -17,9 +17,9 @@ suspend fun mySuspendFun() { @RequiresReadLock fun main() { withSuspendLambda { - for (i in 1..10) { - doSomething() - } + for (i in 1..10) { + doSomething() + } } } diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.kt new file mode 100644 index 000000000000..3fbc9d53ebc5 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/cancellationCheckInLoops/WhileLoops.kt @@ -0,0 +1,41 @@ +package inspections.cancellationCheckInLoops + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 100) { + // nested loops with something in between + while (i < 10) { + doSomething() + while (i < 20) { + doSomething() + i++ + } + i++ + } + + // sibling loop + while (i < 30) { + doSomething() + i++ + } + + // nested loops with a block in between + while (i < 40) { + if (i < 5) { + // empty loop + while (i < 3) { + } + } + } + + // single-line loop + while (i < 50) doSomething() + i++ + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.kt new file mode 100644 index 000000000000..b5fb32f6e66e --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop.kt @@ -0,0 +1,16 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + do { + // comments + doSomething() + i++ + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.kt new file mode 100644 index 000000000000..e795286b3b48 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockDoWhileLoop_after.kt @@ -0,0 +1,18 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + do { + ProgressManager.checkCanceled() + // comments + doSomething() + i++ + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.kt new file mode 100644 index 000000000000..e6db3746b536 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop.kt @@ -0,0 +1,14 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + // comments + doSomething() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.kt new file mode 100644 index 000000000000..bdac70e18502 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockForEachLoop_after.kt @@ -0,0 +1,16 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + ProgressManager.checkCanceled() + // comments + doSomething() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.kt new file mode 100644 index 000000000000..eefdf6d87f83 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop.kt @@ -0,0 +1,16 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + // comments + doSomething() + i++ + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.kt new file mode 100644 index 000000000000..ab320345ebfb --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/BlockWhileLoop_after.kt @@ -0,0 +1,18 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + ProgressManager.checkCanceled() + // comments + doSomething() + i++ + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.kt new file mode 100644 index 000000000000..df4863384a31 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop.kt @@ -0,0 +1,12 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + do { + // comments + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.kt new file mode 100644 index 000000000000..ab075bc4c882 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyDoWhileLoop_after.kt @@ -0,0 +1,14 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + do { + ProgressManager.checkCanceled() + // comments + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.kt new file mode 100644 index 000000000000..09ddba08ceec --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop.kt @@ -0,0 +1,11 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.kt new file mode 100644 index 000000000000..9914bb743343 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyForEachLoop_after.kt @@ -0,0 +1,13 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + ProgressManager.checkCanceled() + // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.kt new file mode 100644 index 000000000000..e513c3cd081e --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop.kt @@ -0,0 +1,12 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.kt new file mode 100644 index 000000000000..bc8b3458363e --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/EmptyWhileLoop_after.kt @@ -0,0 +1,14 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + ProgressManager.checkCanceled() + // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.kt new file mode 100644 index 000000000000..1dda17ad99e8 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop.kt @@ -0,0 +1,9 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + for (i in 1..10); // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.kt new file mode 100644 index 000000000000..e38f9ad46e85 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyForEachLoop_after.kt @@ -0,0 +1,12 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + ProgressManager.checkCanceled() + } // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.kt new file mode 100644 index 000000000000..534132c4bf99 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop.kt @@ -0,0 +1,10 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10); +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.kt new file mode 100644 index 000000000000..caa0dffc80db --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/NoBodyWhileLoop_after.kt @@ -0,0 +1,13 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + ProgressManager.checkCanceled() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.kt new file mode 100644 index 000000000000..254abd7c2f55 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop.kt @@ -0,0 +1,10 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + do i++ while (i < 10) // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.kt new file mode 100644 index 000000000000..4ffbca5f7322 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineDoWhileLoop_after.kt @@ -0,0 +1,14 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + + +@RequiresReadLock +fun main() { + var i = 0 + do { + ProgressManager.checkCanceled() + i++ + } while (i < 10) // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.kt new file mode 100644 index 000000000000..fca8ce663bad --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop.kt @@ -0,0 +1,11 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + for (i in 1..10) doSomething() // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.kt new file mode 100644 index 000000000000..3247531a0ecb --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineForEachLoop_after.kt @@ -0,0 +1,15 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +fun main() { + for (i in 1..10) { + ProgressManager.checkCanceled() + doSomething() // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.kt new file mode 100644 index 000000000000..c92c09ee688c --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop.kt @@ -0,0 +1,9 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) i++ // comments +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.kt new file mode 100644 index 000000000000..1b43a184e4b6 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SingleLineWhileLoop_after.kt @@ -0,0 +1,13 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.util.concurrency.annotations.RequiresReadLock + +@RequiresReadLock +fun main() { + var i = 0 + while (i < 10) { + ProgressManager.checkCanceled() + i++ // comments + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop.kt new file mode 100644 index 000000000000..6080317b8e26 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop.kt @@ -0,0 +1,16 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + var i = 0 + do { + // comments + doSomething() + i++ + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop_after.kt new file mode 100644 index 000000000000..aaf1de02c0b6 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingDoWhileLoop_after.kt @@ -0,0 +1,17 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + var i = 0 + do { + com.intellij.openapi.progress.checkCancelled() + // comments + doSomething() + i++ + } while (i < 10) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop.kt new file mode 100644 index 000000000000..d95811cfbe7b --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop.kt @@ -0,0 +1,14 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + for (i in 1..10) { + // comments + doSomething() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop_after.kt new file mode 100644 index 000000000000..8e7ebf5b34e0 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingForEachLoop_after.kt @@ -0,0 +1,15 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + for (i in 1..10) { + com.intellij.openapi.progress.checkCancelled() + // comments + doSomething() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop.kt new file mode 100644 index 000000000000..e653e5f1ec19 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop.kt @@ -0,0 +1,16 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + var i = 0 + while (i < 10) { + // comments + doSomething() + i++ + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop_after.kt new file mode 100644 index 000000000000..ac2a18aeaae4 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/insertCancellationCheckFix/SuspendingWhileLoop_after.kt @@ -0,0 +1,17 @@ +package inspections.insertCancellationCheckFix + +import com.intellij.util.concurrency.annotations.RequiresReadLock + +import inspections.cancellationCheckInLoops.Foo.doSomething + + +@RequiresReadLock +suspend fun main() { + var i = 0 + while (i < 10) { + com.intellij.openapi.progress.checkCancelled() + // comments + doSomething() + i++ + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtCancellationCheckInLoopsInspectionTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtCancellationCheckInLoopsInspectionTest.kt index 2fbb9e938cf6..319aa987c5bd 100644 --- a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtCancellationCheckInLoopsInspectionTest.kt +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtCancellationCheckInLoopsInspectionTest.kt @@ -8,32 +8,45 @@ import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil @TestDataPath("\$CONTENT_ROOT/testData/inspections/cancellationCheckInLoops") class KtCancellationCheckInLoopsInspectionTest : CancellationCheckInLoopsInspectionTestBase() { - override val fileType - get() = "kt" - override fun getBasePath(): String { - return DevkitKtTestsUtil.TESTDATA_PATH + "inspections/cancellationCheckInLoops" - } + override fun getFileExtension(): String = "kt" + + override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/cancellationCheckInLoops" override fun setUp() { super.setUp() - myFixture.addFileToProject("Coroutines.kt", """ + myFixture.addFileToProject( + "Coroutines.kt", + //language=kotlin + """ package com.intellij.openapi.progress suspend fun checkCancelled() { } - """.trimIndent()) + """.trimIndent()) } fun testSuspendingContext() { doTest() } - fun testMultipleNestedLoops() { - doTest() - } - fun testPresentCancellationCheck() { doTest() } + fun testForEachLoops() { + doTest() + } + + fun testWhileLoops() { + doTest() + } + + fun testDoWhileLoops() { + doTest() + } + + fun testNestedLoops() { + doTest() + } + } diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/quickfix/KtInsertCancellationCheckFixTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/quickfix/KtInsertCancellationCheckFixTest.kt new file mode 100644 index 000000000000..8c238dd6a0bd --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/quickfix/KtInsertCancellationCheckFixTest.kt @@ -0,0 +1,75 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.kotlin.inspections.quickfix + +import com.intellij.testFramework.TestDataPath +import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.idea.devkit.inspections.CancellationCheckInLoopsInspectionTestBase +import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil + + +@TestDataPath("\$CONTENT_ROOT/testData/inspections/insertCancellationCheckFix") +class KtInsertCancellationCheckFixTest : CancellationCheckInLoopsInspectionTestBase() { + + override fun getFileExtension(): String = "kt" + + override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/insertCancellationCheckFix" + + private val fixName = DevKitBundle.message("inspection.insert.cancellation.check.fix.message") + + fun testBlockDoWhileLoop() { + doTest(fixName) + } + + fun testBlockForEachLoop() { + doTest(fixName) + } + + fun testBlockWhileLoop() { + doTest(fixName) + } + + fun testEmptyDoWhileLoop() { + doTest(fixName) + } + + fun testEmptyForEachLoop() { + doTest(fixName) + } + + fun testEmptyWhileLoop() { + doTest(fixName) + } + + fun testSingleLineDoWhileLoop() { + doTest(fixName) + } + + fun testSingleLineForEachLoop() { + doTest(fixName) + } + + fun testSingleLineWhileLoop() { + doTest(fixName) + } + + fun testNoBodyForEachLoop() { + doTest(fixName) + } + + fun testNoBodyWhileLoop() { + doTest(fixName) + } + + fun testSuspendingDoWhileLoop() { + doTest(fixName) + } + + fun testSuspendingForEachLoop() { + doTest(fixName) + } + + fun testSuspendingWhileLoop() { + doTest(fixName) + } + +} diff --git a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTestBase.kt b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTestBase.kt index 1e7eb9f52512..1aae20facd04 100644 --- a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTestBase.kt +++ b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/CancellationCheckInLoopsInspectionTestBase.kt @@ -1,11 +1,9 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.inspections -import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase +import org.jetbrains.idea.devkit.inspections.quickfix.LightDevKitInspectionFixTestBase -abstract class CancellationCheckInLoopsInspectionTestBase : LightJavaCodeInsightFixtureTestCase() { - - protected abstract val fileType: String +abstract class CancellationCheckInLoopsInspectionTestBase : LightDevKitInspectionFixTestBase() { override fun setUp() { super.setUp() @@ -19,7 +17,7 @@ abstract class CancellationCheckInLoopsInspectionTestBase : LightJavaCodeInsight myFixture.addClass(""" package inspections.cancellationCheckInLoops; - public class Foo { + public final class Foo { public static void doSomething() { } } """.trimIndent() @@ -34,11 +32,7 @@ abstract class CancellationCheckInLoopsInspectionTestBase : LightJavaCodeInsight """.trimIndent() ) - myFixture.enableInspections(CancellationCheckInLoopsInspection::class.java) - } - - protected open fun doTest() { - myFixture.testHighlighting("${getTestName(false)}.$fileType") + myFixture.enableInspections(CancellationCheckInLoopsInspection()) } } diff --git a/plugins/devkit/intellij.kotlin.devkit/intellij.kotlin.devkit.iml b/plugins/devkit/intellij.kotlin.devkit/intellij.kotlin.devkit.iml index 04969f65f65e..82ef35dd8117 100644 --- a/plugins/devkit/intellij.kotlin.devkit/intellij.kotlin.devkit.iml +++ b/plugins/devkit/intellij.kotlin.devkit/intellij.kotlin.devkit.iml @@ -17,5 +17,7 @@ + + \ No newline at end of file diff --git a/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml b/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml index e1ab301de096..0bfaba19a5e7 100644 --- a/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml +++ b/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml @@ -41,7 +41,10 @@ - + + (false) - - // if lambda is null, check the containing method - if (containingLambda == null) { - val containingMethod = element.getParentOfType(false) - return if (containingMethod.hasSuspendModifier()) CancellationCheckProvider.Context.SUSPENDING else CancellationCheckProvider.Context.BLOCKING - } - - // otherwise, check containing argument (whether the corresponding parameter has `suspend` modifier) - val containingArgument = containingLambda.getParentOfType(true, KtCallableDeclaration::class.java) - if (containingArgument != null) { - val callExpression = containingArgument.getStrictParentOfType() ?: return CancellationCheckProvider.Context.BLOCKING - val resolvedCall = callExpression.resolveToCall(BodyResolveMode.PARTIAL) ?: return CancellationCheckProvider.Context.BLOCKING - - val parameterForArgument = resolvedCall.getParameterForArgument(containingArgument) ?: return CancellationCheckProvider.Context.BLOCKING - val type = parameterForArgument.returnType ?: return CancellationCheckProvider.Context.BLOCKING - return if (type.isSuspendFunctionType) CancellationCheckProvider.Context.SUSPENDING else CancellationCheckProvider.Context.BLOCKING - } - - // otherwise, check if it's a property or a function - val containingPropertyOrFunction: KtCallableDeclaration? = - containingLambda.getParentOfTypes(true, KtProperty::class.java, KtNamedFunction::class.java) - if (containingPropertyOrFunction?.typeReference.hasSuspendModifier()) return CancellationCheckProvider.Context.SUSPENDING - return if (containingPropertyOrFunction.hasSuspendModifier()) CancellationCheckProvider.Context.SUSPENDING else CancellationCheckProvider.Context.BLOCKING - } - - private fun KtModifierListOwner?.hasSuspendModifier(): Boolean { - return this?.hasModifier(KtTokens.SUSPEND_KEYWORD) == true - } - -} diff --git a/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckInLoopsFixProvider.kt b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckInLoopsFixProvider.kt new file mode 100644 index 000000000000..7daf2532f5dd --- /dev/null +++ b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckInLoopsFixProvider.kt @@ -0,0 +1,88 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.kotlin.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.LocalQuickFixOnPsiElement +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.idea.devkit.inspections.quickfix.CancellationCheckInLoopsFixProvider +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility +import org.jetbrains.kotlin.idea.codeInsight.intentions.shared.AddBracesIntention +import org.jetbrains.kotlin.idea.codeinsight.utils.isRedundantSemicolon +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtPsiUtil.findChildByType +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + + +internal class KtCancellationCheckInLoopsFixProvider : CancellationCheckInLoopsFixProvider { + + override fun getFixes(loopKeyword: PsiElement, cancellationCheckFqn: String): List { + return listOf(KtInsertCancellationCheckFix(cancellationCheckFqn, loopKeyword)) + } + +} + + +internal class KtInsertCancellationCheckFix( + private val cancellationCheckCallFqn: String, + loopKeyword: PsiElement, +) : LocalQuickFixOnPsiElement(loopKeyword) { + + override fun getFamilyName(): String = DevKitBundle.message("inspection.insert.cancellation.check.fix.message") + + override fun getText(): String = familyName + + override fun isAvailable(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement): Boolean { + return PsiTreeUtil.getParentOfType(startElement, KtLoopExpression::class.java) != null + } + + override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val loopStatement = PsiTreeUtil.getParentOfType(startElement, KtLoopExpression::class.java) ?: return + + val factory = KtPsiFactory(project) + val cancellationCheckExpression = factory.createExpression("${cancellationCheckCallFqn}()") + + val bodyBlock = loopStatement.getOrCreateBodyBlock(project) + + bodyBlock?.addExpressionToFirstLine(cancellationCheckExpression)?.let { + ShortenReferencesFacility.getInstance().shorten(it) + } + } + + private fun KtLoopExpression.getOrCreateBodyBlock(project: Project): KtBlockExpression? { + val factory = KtPsiFactory(project) + + return when (val loopBody = body) { + is KtBlockExpression -> loopBody + is KtExpression -> { + AddBracesIntention.addBraces(this, loopBody) + body as KtBlockExpression + } + else -> { + val containerNode = findChildByType(this, KtNodeTypes.BODY) ?: return null + containerNode.add(factory.createEmptyBody()) + deleteRedundantSemicolon(this) + body as KtBlockExpression + } + } + } + + private fun deleteRedundantSemicolon(loop: KtLoopExpression) { + val nextSibling = loop.nextSibling + if (nextSibling.node.elementType == KtTokens.SEMICOLON && isRedundantSemicolon(nextSibling)) { + nextSibling.delete() + } + } + + private fun KtBlockExpression.addExpressionToFirstLine(expression: KtExpression): KtExpression? { + CodeStyleManager.getInstance(project).reformat(this) + return addAfter(expression, lBrace).safeAs() + } + +} diff --git a/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckProvider.kt b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckProvider.kt new file mode 100644 index 000000000000..a3a93c9f1cbc --- /dev/null +++ b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KtCancellationCheckProvider.kt @@ -0,0 +1,82 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.kotlin.inspections + +import com.intellij.psi.PsiElement +import org.jetbrains.idea.devkit.inspections.CancellationCheckProvider +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.calls.singleFunctionCallOrNull +import org.jetbrains.kotlin.analysis.api.calls.symbol +import org.jetbrains.kotlin.builtins.isSuspendFunctionType +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.calls.util.getParameterForArgument +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + + +private const val PROGRESS_MANAGER_CHECKED_CANCELED = "com.intellij.openapi.progress.ProgressManager.checkCanceled" +private const val COROUTINE_CHECK_CANCELLED = "com.intellij.openapi.progress.checkCancelled" + +class KtCancellationCheckProvider : CancellationCheckProvider { + + enum class Context { + BLOCKING, SUSPENDING + } + + override fun findCancellationCheckCall(element: PsiElement): String { + return when (findContext(element)) { + Context.BLOCKING -> PROGRESS_MANAGER_CHECKED_CANCELED + Context.SUSPENDING -> COROUTINE_CHECK_CANCELLED + } + } + + override fun isCancellationCheckCall(element: PsiElement?, cancellationCheckFqn: String): Boolean { + val callExpression = when (element) { + is KtCallExpression -> element + is KtDotQualifiedExpression -> element.getChildOfType() ?: return false + else -> return false + } + + analyze(callExpression) { + val functionCalledSymbol = callExpression.resolveCall().singleFunctionCallOrNull()?.symbol ?: return false + return functionCalledSymbol.callableIdIfNonLocal?.asSingleFqName() == FqName(cancellationCheckFqn) + } + } + + private fun findContext(element: PsiElement): Context { + val containingLambda = element.getParentOfType(false) + + // if lambda is null, check the containing method + if (containingLambda == null) { + val containingMethod = element.getParentOfType(false) + return if (containingMethod.hasSuspendModifier()) Context.SUSPENDING else Context.BLOCKING + } + + // otherwise, check containing argument (whether the corresponding parameter has `suspend` modifier) + val containingArgument = containingLambda.getParentOfType(true, KtCallableDeclaration::class.java) + if (containingArgument != null) { + val callExpression = containingArgument.getStrictParentOfType() ?: return Context.BLOCKING + val resolvedCall = callExpression.resolveToCall(BodyResolveMode.PARTIAL) ?: return Context.BLOCKING + + val parameterForArgument = resolvedCall.getParameterForArgument(containingArgument) ?: return Context.BLOCKING + val type = parameterForArgument.returnType ?: return Context.BLOCKING + return if (type.isSuspendFunctionType) Context.SUSPENDING else Context.BLOCKING + } + + // otherwise, check if it's a property or a function + val containingPropertyOrFunction: KtCallableDeclaration? = containingLambda.getParentOfTypes(true, KtProperty::class.java, + KtNamedFunction::class.java) + if (containingPropertyOrFunction?.typeReference.hasSuspendModifier()) return Context.SUSPENDING + return if (containingPropertyOrFunction.hasSuspendModifier()) Context.SUSPENDING else Context.BLOCKING + } + + private fun KtModifierListOwner?.hasSuspendModifier(): Boolean { + return this?.hasModifier(KtTokens.SUSPEND_KEYWORD) == true + } + +} diff --git a/plugins/kotlin/code-insight/intentions-shared/src/org/jetbrains/kotlin/idea/codeInsight/intentions/shared/AddBracesIntention.kt b/plugins/kotlin/code-insight/intentions-shared/src/org/jetbrains/kotlin/idea/codeInsight/intentions/shared/AddBracesIntention.kt index 80a5b6716a77..bae73ab4a889 100644 --- a/plugins/kotlin/code-insight/intentions-shared/src/org/jetbrains/kotlin/idea/codeInsight/intentions/shared/AddBracesIntention.kt +++ b/plugins/kotlin/code-insight/intentions-shared/src/org/jetbrains/kotlin/idea/codeInsight/intentions/shared/AddBracesIntention.kt @@ -3,8 +3,10 @@ package org.jetbrains.kotlin.idea.codeInsight.intentions.shared import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.IntellijInternalApi import com.intellij.psi.PsiComment import com.intellij.psi.PsiWhiteSpace +import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.kotlin.idea.base.psi.getLineNumber import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention @@ -14,7 +16,9 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* -internal class AddBracesIntention : SelfTargetingIntention(KtElement::class.java, KotlinBundle.lazyMessage("add.braces")) { +@Internal +@IntellijInternalApi +class AddBracesIntention : SelfTargetingIntention(KtElement::class.java, KotlinBundle.lazyMessage("add.braces")) { override fun isApplicableTo(element: KtElement, caretOffset: Int): Boolean { val expression = element.getTargetExpression(caretOffset) ?: return false if (expression is KtBlockExpression) return false