Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsyncAndSyncRule #707

Merged
merged 8 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@
# Checks that function use default values, instead overloading
- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS
enabled: true
# Checks that using runBlocking inside async block code
- name: RUN_BLOCKING_INSIDE_ASYNC
enabled: true
# Checks that property in constructor doesn't contains comment
- name: KDOC_NO_CONSTRUCTOR_PROPERTY
enabled: true
Expand Down
2 changes: 2 additions & 0 deletions diktat-rules/src/main/kotlin/generated/WarningNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ public object WarningNames {
public const val WRONG_OVERLOADING_FUNCTION_ARGUMENTS: String =
"WRONG_OVERLOADING_FUNCTION_ARGUMENTS"

public const val RUN_BLOCKING_INSIDE_ASYNC: String = "RUN_BLOCKING_INSIDE_ASYNC"

public const val SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY: String =
"SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ enum class Warnings(
TOO_MANY_PARAMETERS(false, "5.2.2", "function has too many parameters"),
NESTED_BLOCK(false, "5.1.2", "function has too many nested blocks and should be simplified"),
WRONG_OVERLOADING_FUNCTION_ARGUMENTS(false, "5.2.3", "use default argument instead of function overloading"),
RUN_BLOCKING_INSIDE_ASYNC(false, "5.2.4", "avoid using runBlocking in asynchronous code"),

// ======== chapter 6 ========
SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY(true, "6.1.1", "if a class has single constructor, it should be converted to a primary constructor"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.constants.Warnings.RUN_BLOCKING_INSIDE_ASYNC
import org.cqfn.diktat.ruleset.utils.hasChildOfType

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.parent
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier

/**
* This rule finds if using runBlocking in asynchronous code
*/
class AsyncAndSyncRule(private val configRules: List<RulesConfig>) : Rule("sync-in-async") {
private val asyncList = listOf("async", "launch")
private var isFixMode: Boolean = false
private lateinit var emitWarn: EmitType

override fun visit(node: ASTNode,
autoCorrect: Boolean,
emit: EmitType) {
emitWarn = emit
isFixMode = autoCorrect

if (node.isRunBlocking()) {
checkRunBlocking(node)
}
}

private fun checkRunBlocking(node: ASTNode) {
node.parent({it.isAsync() || it.isSuspend()})?.let {
RUN_BLOCKING_INSIDE_ASYNC.warn(configRules, emitWarn, isFixMode, node.text, node.startOffset, node)
}
}

private fun ASTNode.isAsync() = this.elementType == CALL_EXPRESSION && this.findChildByType(REFERENCE_EXPRESSION)?.text in asyncList

private fun ASTNode.isSuspend() = this.elementType == FUN && (this.psi as KtFunction).modifierList?.hasSuspendModifier() ?: false

private fun ASTNode.isRunBlocking() = this.elementType == REFERENCE_EXPRESSION && this.text == "runBlocking" && this.treeParent.hasChildOfType(LAMBDA_ARGUMENT)
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
::FunctionArgumentsSize,
::BlankLinesRule,
::FileSize,
::AsyncAndSyncRule,
::NullableTypeRule,
::NullChecksRule,
::ImmutableValNoVarRule,
Expand Down
3 changes: 3 additions & 0 deletions diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@
# Checks that function use default values, instead overloading
- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS
enabled: true
# Checks that using runBlocking inside async block code
- name: RUN_BLOCKING_INSIDE_ASYNC
enabled: true
# Checks that property in constructor doesn't contains comment
- name: KDOC_NO_CONSTRUCTOR_PROPERTY
enabled: true
Expand Down
3 changes: 3 additions & 0 deletions diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@
# Checks that function use default values, instead overloading
- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS
enabled: true
# Checks that using runBlocking inside async block code
- name: RUN_BLOCKING_INSIDE_ASYNC
enabled: true
# Checks that property in constructor doesn't contain comment
- name: KDOC_NO_CONSTRUCTOR_PROPERTY
enabled: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.cqfn.diktat.ruleset.chapter5

import org.cqfn.diktat.ruleset.constants.Warnings.RUN_BLOCKING_INSIDE_ASYNC
import org.cqfn.diktat.ruleset.rules.AsyncAndSyncRule
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.util.LintTestBase

import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

class AsyncAndSyncRuleTest : LintTestBase(::AsyncAndSyncRule) {
private val ruleId = "$DIKTAT_RULE_SET_ID:sync-in-async"

@Test
@Tag(WarningNames.RUN_BLOCKING_INSIDE_ASYNC)
fun `test wrong case`() {
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
lintMethod(
"""
|fun foo() {
| GlobalScope.launch {
| c.addAndGet(i)
| }
|
| GlobalScope.async {
| n
| }
|
| GlobalScope.async {
| runBlocking {
|
| }
| }
|}
|
|suspend fun foo() {
| runBlocking {
| delay(2000)
| }
|}
|
""".trimMargin(),
LintError(11, 8, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false),
LintError(18, 4, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false)
)
}

@Test
@Tag(WarningNames.RUN_BLOCKING_INSIDE_ASYNC)
fun `test dot qualified expression case`() {
lintMethod(
"""
|fun foo() {
| GlobalScope.async {
| node.runBlocking()
| runBlocking {
| n++
| }
| }
|}
|
|fun goo() {
| runBlocking {
| GlobalScope.async {
| n++
| }
| }
|}
""".trimMargin(),
LintError(4, 8, ruleId, "${RUN_BLOCKING_INSIDE_ASYNC.warnText()} runBlocking", false)
)
}
}
1 change: 1 addition & 0 deletions info/available-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
| 5 | 5.2.1 | LAMBDA_IS_NOT_LAST_PARAMETER | Checks that lambda inside function parameters isn't in the end | no | no |
| 5 | 5.2.2 | TOO_MANY_PARAMETERS | Check: if function contains more parameters than allowed | no | maxParameterListSize |
| 5 | 5.2.3 | WRONG_OVERLOADING_FUNCTION_ARGUMENTS | Check: function has overloading instead use default arguments | no | no |
| 5 | 5.2.4 | RUN_BLOCKING_INSIDE_ASYNC | Check: using runBlocking inside async block code | no | no | - |
| 6 | 6.1.1 | SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY | Check: warns if there is only one secondary constructor in a class<br>Fix: converts it to a primary constructor | yes | no | Support more complicated logic of constructor conversion |
| 6 | 6.1.2 | USE_DATA_CLASS | Check: if class can be made as data class | no | no | yes |
| 6 | 6.1.3 | EMPTY_PRIMARY_CONSTRUCTOR | Check: if there is empty primary constructor | yes | no | yes |
Expand Down
11 changes: 11 additions & 0 deletions info/guide/guide-chapter-5.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,14 @@ private fun foo() {
// ...
}
```
#### <a name="r5.2.4"></a> 5.2.4 Synchronizing code inside asynchronous code
Try to avoid using runBlocking in asynchronous code

**Invalid example**:
```kotlin
GlobalScope.async {
runBlocking {
count++
}
}
```