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

Wrapper in kts file #735

Merged
merged 12 commits into from
Feb 18, 2021
7 changes: 6 additions & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,9 @@
enabled: true
# If file contains class, then it can't contain extension functions for the same class
- name: EXTENSION_FUNCTION_WITH_CLASS
enabled: true
enabled: true
# Check if kts script contains other functions except run code
- name: RUN_IN_SCRIPT
enabled: true
configuration:
possibleWrapper: ""
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 @@ -245,4 +245,6 @@ public object WarningNames {
public const val INLINE_CLASS_CAN_BE_USED: String = "INLINE_CLASS_CAN_BE_USED"

public const val EXTENSION_FUNCTION_WITH_CLASS: String = "EXTENSION_FUNCTION_WITH_CLASS"

public const val RUN_IN_SCRIPT: String = "RUN_IN_SCRIPT"
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ enum class Warnings(
OBJECT_IS_PREFERRED(true, "6.4.2", "it is better to use object for stateless classes"),
INLINE_CLASS_CAN_BE_USED(true, "6.1.12", "inline class can be used"),
EXTENSION_FUNCTION_WITH_CLASS(false, "6.2.3", "do not use extension functions for the class defined in the same file"),
RUN_IN_SCRIPT(true, "6.5.1", "wrap blocks of code in top-level scope functions like `run`"),
;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule
import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsSameNameRule
import org.cqfn.diktat.ruleset.rules.chapter6.ImplicitBackingPropertyRule
import org.cqfn.diktat.ruleset.rules.chapter6.PropertyAccessorFields
import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript
import org.cqfn.diktat.ruleset.rules.chapter6.TrivialPropertyAccessors
import org.cqfn.diktat.ruleset.rules.chapter6.UselessSupertype
import org.cqfn.diktat.ruleset.rules.chapter6.classes.AbstractClassesRule
Expand Down Expand Up @@ -177,6 +178,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
::StringConcatenationRule,
::AccurateCalculationsRule,
::LineLength,
::RunInScript,
::TypeAliasRule,
::OverloadingArgumentsFunction,
::FunctionLength,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.cqfn.diktat.ruleset.rules.chapter6

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.constants.Warnings.RUN_IN_SCRIPT
import org.cqfn.diktat.ruleset.utils.*

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.SCRIPT_INITIALIZER
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement

/**
* Rule that checks if kts script contains other functions except run code
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
*/
class RunInScript(private val configRules: List<RulesConfig>) : Rule("run-script") {
private var isFixMode: Boolean = false
private lateinit var emitWarn: EmitType

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

if (node.elementType == SCRIPT_INITIALIZER && node.getRootNode().getFilePath().isKotlinScript()) {
checkScript(node)
}
}

private fun checkScript(node: ASTNode) {
val isLambdaArgument = node.firstChildNode.hasChildOfType(LAMBDA_ARGUMENT)
val isLambdaInsideValueArgument = node.firstChildNode.findChildByType(VALUE_ARGUMENT_LIST)?.findChildByType(VALUE_ARGUMENT)?.findChildByType(LAMBDA_EXPRESSION) != null
if (!(isLambdaArgument || isLambdaInsideValueArgument)) {
RUN_IN_SCRIPT.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) {
if (node.firstChildNode.elementType != DOT_QUALIFIED_EXPRESSION) {
val parent = node.treeParent
val newNode = KotlinParser().createNode("run {\n ${node.text}\n} \n")
val newScript = CompositeElement(SCRIPT_INITIALIZER)
parent.addChild(newScript, node)
newScript.addChild(newNode)
parent.removeChild(node)
}
}
}
}
}
7 changes: 6 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,9 @@
enabled: true
# If file contains class, then it can't contain extension functions for the same class
- name: EXTENSION_FUNCTION_WITH_CLASS
enabled: true
enabled: true
# Check if kts script contains other functions except run code
- name: RUN_IN_SCRIPT
enabled: true
configuration:
possibleWrapper: ""
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 6 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,9 @@
enabled: true
# If file contains class, then it can't contain extension functions for the same class
- name: EXTENSION_FUNCTION_WITH_CLASS
enabled: true
enabled: true
# Check if kts script contains other functions except run code
- name: RUN_IN_SCRIPT
enabled: true
configuration:
possibleWrapper: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.cqfn.diktat.ruleset.chapter6

import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript
import org.cqfn.diktat.util.FixTestBase

import generated.WarningNames
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

class RunInScriptFixTest : FixTestBase("test/chapter6/script", ::RunInScript) {
@Test
@Tag(WarningNames.RUN_IN_SCRIPT)
fun `should wrap into run`() {
fixAndCompare("SimpleRunInScriptExpected.kts", "SimpleRunInScriptTest.kts")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.cqfn.diktat.ruleset.chapter6

import org.cqfn.diktat.ruleset.constants.Warnings.RUN_IN_SCRIPT
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript
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 RunInScriptWarnTest : LintTestBase(::RunInScript) {
private val ruleId: String = "$DIKTAT_RULE_SET_ID:run-script"

@Test
@Tag(WarningNames.RUN_IN_SCRIPT)
fun `check simple example`() {
lintMethod(
"""
class A {}

fun foo() {
}

diktat {}

diktat({})

foo/*df*/()

foo( //dfdg
10
)
println("hello")

w.map { it -> it }

tasks.register("a") {
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
dependsOn("b")
doFirst {
generateCodeStyle(file("rootDir/guide"), file("rootDir/../wp"))
}
}

""".trimMargin(),
LintError(10, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} foo/*df*/()", true),
LintError(12, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} foo( //dfdg...", true),
LintError(15, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} println(\"hello\")", true),
LintError(17, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} w.map { it -> it }", true),
LintError(19, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} tasks.register(\"a\") {...", true),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

@Test
@Tag(WarningNames.RUN_IN_SCRIPT)
fun `check correct examples`() {
lintMethod(
"""
run {
println("hello")
}

run{println("hello")}

val task = tasks.register("a") {
}

""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

@Test
@Tag(WarningNames.RUN_IN_SCRIPT)
fun `check correct with custom wrapper`() {
lintMethod(
"""
custom {
println("hello")
}

oneMore{println("hello")}

another {
println("hello")
}
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

run {
println("hello world!")
}

fun foo() {
println()
}

val q = Config()

run {
println("a")
}

also {
println("a")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

println("hello world!")

fun foo() {
println()
}

val q = Config()

run {
println("a")
}

also {
println("a")
}
4 changes: 2 additions & 2 deletions diktat-rules/src/test/resources/test/smoke/build.gradle_.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ repositories {
maven { url = uri("https://example.com") }
}

tasks.register("generateAvailableRules") {
val generateAvailableRules = tasks.register("generateAvailableRules") {
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
dependsOn("generateRulesMapping")
doFirst {
generateAvailableRules(rootDir, file("$rootDir/../wp"))
}
}

tasks.register("updateDocumentation") {
val updateDocumentation = tasks.register("updateDocumentation") {
dependsOn(
"generateRulesMapping",
"generateAvailableRules",
Expand Down
3 changes: 2 additions & 1 deletion info/available-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,5 @@
| 6 | 6.2.2 | EXTENSION_FUNCTION_SAME_SIGNATURE | Checks if extension function has the same signature as another extension function and their classes are related | no | no | + |
| 6 | 6.4.1 | AVOID_USING_UTILITY_CLASS | Checks if there is class/object that can be replace with extension function | no | no | - |
| 6 | 6.4.2 | OBJECT_IS_PREFERRED | Checks: if class is stateless it is preferred to use `object` | yes | no | + |
| 6 | 6.2.3 | EXTENSION_FUNCTION_WITH_CLASS | Check: if file contains class, then it can not have extension functions for the same class | no | no | - |
| 6 | 6.2.3 | EXTENSION_FUNCTION_WITH_CLASS | Check: if file contains class, then it can not have extension functions for the same class | no | no | - |
| 6 | 6.5.1 | RUN_IN_SCRIPT | Checks : if kts script contains other functions except run code | yes | no | - |
15 changes: 15 additions & 0 deletions info/guide/guide-chapter-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,18 @@ object O: I {
override fun foo() {}
}
```

#### <a name="r6.5.1"></a> 6.5.1 kts files should wrap logic into top-level scope
It is still recommended wrapping logic inside functions and avoid using top-level statements for function calls or wrapping blocks of code
in top-level scope functions like `run`.

**Valid example**:
```
run {
// some code
}

fun foo() {

}
```