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