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,70 @@
package org.cqfn.diktat.ruleset.rules.chapter6

import org.cqfn.diktat.common.config.rules.RuleConfiguration
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.getRuleConfig
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.SCRIPT_INITIALIZER
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

val configuration = RunInScriptRuleConfiguration(
configRules.getRuleConfig(RUN_IN_SCRIPT)?.configuration ?: emptyMap()
)
if (node.elementType == SCRIPT_INITIALIZER && node.getRootNode().getFilePath().isKotlinScript()) {
checkNode(node, configuration)
}
}

private fun checkNode(node: ASTNode, configuration: RunInScriptRuleConfiguration) {
val possibleWrapperFromConfig = configuration.possibleWrapperConfig
.takeIf { it.isNotBlank() }
?.split(",")
?.map { it.trim() }
?: emptyList()
if (!(possibleWrapper + possibleWrapperFromConfig)
.any { node.text.replace("\\s".toRegex(), "").startsWith(it) }) {
RUN_IN_SCRIPT.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) {
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)
}
}
}

/**
* [RuleConfiguration] for possible wrapper
*/
class RunInScriptRuleConfiguration(config: Map<String, String>) : RuleConfiguration(config) {
/**
* Another possible wrapper that can be passed in config
*/
val possibleWrapperConfig = config["possibleWrapper"] ?: ""
}

companion object {
private val possibleWrapper = listOf("tasks.register", "run{", "also{", "repositories{", "plugins{", "diktat{")
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
}
}
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,92 @@
package org.cqfn.diktat.ruleset.chapter6

import org.cqfn.diktat.common.config.rules.RulesConfig
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"
private val rulesConfigList: List<RulesConfig> = listOf(
RulesConfig(
RUN_IN_SCRIPT.name, true,
mapOf("possibleWrapper" to "custom, oneMore"))
)

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

fun foo() {
}

diktat {
}

w.map { it -> it }

println("hello")

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(9, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} w.map { it -> it }", true),
LintError(11, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} println(\"hello\")", 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")}

tasks.register("a") {
}

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

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

oneMore{println("hello")}

another {
println("hello")
}
""".trimMargin(),
LintError(7, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} another {...", true),
rulesConfigList = rulesConfigList,
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")
}
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() {

}
```