diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 969a9f8f50..af4695f9e4 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -356,6 +356,11 @@ # Checks that using runBlocking inside async block code - name: RUN_BLOCKING_INSIDE_ASYNC enabled: true +# Checks that the long lambda has parameters +- name: TOO_MANY_LINES_IN_LAMBDA + enabled: true + configuration: + maxLambdaLength: 10 # max length of lambda without parameters # Checks that property in constructor doesn't contains comment - name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index ffe5893e2a..5332a3e099 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -235,4 +235,6 @@ public object WarningNames { public const val OBJECT_IS_PREFERRED: String = "OBJECT_IS_PREFERRED" public const val INVERSE_FUNCTION_PREFERRED: String = "INVERSE_FUNCTION_PREFERRED" + + public const val TOO_MANY_LINES_IN_LAMBDA: String = "TOO_MANY_LINES_IN_LAMBDA" } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt index 6725faede4..e8e088167b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt @@ -133,6 +133,7 @@ enum class Warnings( 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"), + TOO_MANY_LINES_IN_LAMBDA(false, "5.2.5", "long lambdas should have a parameter name instead of it"), // ======== 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"), diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt index acf64f92ac..e381be9235 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/BlockStructureBraces.kt @@ -221,14 +221,14 @@ class BlockStructureBraces(private val configRules: List) : Rule("b allMiddleSpace: List, node: ASTNode, keyword: IElementType) { - allMiddleSpace.forEach { - if (checkBraceNode(it, true)) { + allMiddleSpace.forEach { space -> + if (checkBraceNode(space, true)) { BRACES_BLOCK_STRUCTURE_ERROR.warnAndFix(configRules, emitWarn, isFixMode, "incorrect new line after closing brace", - it.startOffset, it) { - if (it.elementType != WHITE_SPACE) { + space.startOffset, space) { + if (space.elementType != WHITE_SPACE) { node.addChild(PsiWhiteSpaceImpl(" "), node.findChildByType(keyword)) } else { - (it as LeafPsiElement).replaceWithText(" ") + (space as LeafPsiElement).replaceWithText(" ") } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index f05a80f906..60aba56ab6 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -143,6 +143,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::ImmutableValNoVarRule, ::AvoidNestedFunctionsRule, ::ExtensionFunctionsSameNameRule, + ::LambdaLengthRule, // formatting: moving blocks, adding line breaks, indentations etc. ::BlockStructureBraces, ::ConsecutiveSpacesRule, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt index 82d3f02592..a9c619fae2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FunctionLength.kt @@ -43,11 +43,10 @@ class FunctionLength(private val configRules: List) : Rule("functio ?.node ?.clone() ?: return) as ASTNode } - copyNode.findAllNodesWithCondition({ it.elementType in commentType }).forEach { it.treeParent.removeChild(it) } - val functionText = copyNode.text.lines().filter { it.isNotBlank() } - if (functionText.size > configuration.maxFunctionLength) { + val sizeFun = countCodeLines(copyNode) + if (sizeFun > configuration.maxFunctionLength) { TOO_LONG_FUNCTION.warn(configRules, emitWarn, isFixMode, - "max length is ${configuration.maxFunctionLength}, but you have ${functionText.size}", + "max length is ${configuration.maxFunctionLength}, but you have $sizeFun", node.startOffset, node) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt new file mode 100644 index 0000000000..681b9bd077 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LambdaLengthRule.kt @@ -0,0 +1,71 @@ +package org.cqfn.diktat.ruleset.rules + +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 +import org.cqfn.diktat.ruleset.utils.* + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * Rule 5.2.5 check lambda length without parameters + */ +class LambdaLengthRule(private val configRules: List) : Rule("lambda-length") { + private val configuration by lazy { + LambdaLengthConfiguration( + this.configRules.getRuleConfig(Warnings.TOO_MANY_LINES_IN_LAMBDA)?.configuration ?: emptyMap() + ) + } + 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.elementType == ElementType.LAMBDA_EXPRESSION) { + checkLambda(node, configuration) + } + } + + private fun checkLambda(node: ASTNode, configuration: LambdaLengthConfiguration) { + val copyNode = node.clone() as ASTNode + val sizeLambda = countCodeLines(copyNode) + if (sizeLambda > configuration.maxLambdaLength) { + copyNode.findAllNodesWithCondition({ it.elementType == ElementType.LAMBDA_EXPRESSION }).forEachIndexed { index, node -> + if (index > 0) { + node.treeParent.removeChild(node) + } + } + val isIt = copyNode.findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION).map {re -> re.text}.contains("it") + val parameters = node.findChildByType(ElementType.FUNCTION_LITERAL)?.findChildByType(ElementType.VALUE_PARAMETER_LIST) + if (parameters == null && isIt) { + Warnings.TOO_MANY_LINES_IN_LAMBDA.warn(configRules, emitWarn, isFixMode, + "max length lambda without arguments is ${configuration.maxLambdaLength}, but you have $sizeLambda", + node.startOffset, node) + } + } + } + + /** + * [RuleConfiguration] for lambda length + */ + class LambdaLengthConfiguration(config: Map) : RuleConfiguration(config) { + /** + * Maximum allowed lambda length + */ + val maxLambdaLength = config["maxLambdaLength"]?.toLong() ?: MAX_LINES_IN_LAMBDA + } + + companion object { + private const val MAX_LINES_IN_LAMBDA = 10L + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt index f7685c1e2b..e6ad7f6dbb 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/LineLength.kt @@ -84,21 +84,21 @@ class LineLength(private val configRules: List) : Rule("line-length @Suppress("UnsafeCallOnNullableType") private fun checkLength(node: ASTNode, configuration: LineLengthConfiguration) { var offset = 0 - node.text.lines().forEach { - if (it.length > configuration.lineLength) { + node.text.lines().forEach { line -> + if (line.length > configuration.lineLength) { val newNode = node.psi.findElementAt(offset + configuration.lineLength.toInt())!!.node if ((newNode.elementType != KDOC_TEXT && newNode.elementType != KDOC_MARKDOWN_INLINE_LINK) || !isKdocValid(newNode)) { positionByOffset = node.treeParent.calculateLineColByOffset() val fixableType = isFixable(newNode, configuration) LONG_LINE.warnAndFix(configRules, emitWarn, isFixMode, - "max line length ${configuration.lineLength}, but was ${it.length}", + "max line length ${configuration.lineLength}, but was ${line.length}", offset + node.startOffset, node, fixableType != LongLineFixableCases.None) { fixError(fixableType) } } } - offset += it.length + 1 + offset += line.length + 1 } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt index 1a58810826..c1c6ba14e4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/MultipleModifiersSequence.kt @@ -59,19 +59,19 @@ class MultipleModifiersSequence(private val configRules: List) : Ru node .getChildren(null) .filterIndexed { index, astNode -> astNode.elementType == ANNOTATION_ENTRY && index > firstModifierIndex } - .forEach { + .forEach { astNode -> WRONG_MULTIPLE_MODIFIERS_ORDER.warnAndFix(configRules, emitWarn, isFixMode, - "${it.text} annotation should be before all modifiers", - it.startOffset, it) { - val spaceBefore = it.treePrev - node.removeChild(it) + "${astNode.text} annotation should be before all modifiers", + astNode.startOffset, astNode) { + val spaceBefore = astNode.treePrev + node.removeChild(astNode) if (spaceBefore != null && spaceBefore.elementType == WHITE_SPACE) { node.removeChild(spaceBefore) node.addChild(spaceBefore, node.firstChildNode) - node.addChild(it.clone() as ASTNode, spaceBefore) + node.addChild(astNode.clone() as ASTNode, spaceBefore) } else { node.addChild(PsiWhiteSpaceImpl(" "), node.getChildren(null).first()) - node.addChild(it.clone() as ASTNode, node.getChildren(null).first()) + node.addChild(astNode.clone() as ASTNode, node.getChildren(null).first()) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt index 439dc40442..b1a3e3bd4c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SingleLineStatementsRule.kt @@ -33,18 +33,18 @@ class SingleLineStatementsRule(private val configRules: List) : Rul } private fun checkSemicolon(node: ASTNode) { - node.getChildren(semicolonToken).forEach { - if (!it.isFollowedByNewline()) { - MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix(configRules, emitWarn, isFixMode, it.extractLineOfText(), - it.startOffset, it) { - if (it.treeParent.elementType == ENUM_ENTRY) { + node.getChildren(semicolonToken).forEach { astNode -> + if (!astNode.isFollowedByNewline()) { + MORE_THAN_ONE_STATEMENT_PER_LINE.warnAndFix(configRules, emitWarn, isFixMode, astNode.extractLineOfText(), + astNode.startOffset, astNode) { + if (astNode.treeParent.elementType == ENUM_ENTRY) { node.treeParent.addChild(PsiWhiteSpaceImpl("\n"), node.treeNext) } else { - if (!it.isBeginByNewline()) { - val nextNode = it.parent({ parent -> parent.treeNext != null }, strict = false)?.treeNext - node.appendNewlineMergingWhiteSpace(nextNode, it) + if (!astNode.isBeginByNewline()) { + val nextNode = astNode.parent({ parent -> parent.treeNext != null }, strict = false)?.treeNext + node.appendNewlineMergingWhiteSpace(nextNode, astNode) } - node.removeChild(it) + node.removeChild(astNode) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt index 60497b7e81..9e5b3c07c0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/SmartCastRule.kt @@ -90,16 +90,16 @@ class SmartCastRule(private val configRules: List) : Rule("smart-ca */ @Suppress("NestedBlockDepth", "TYPE_ALIAS") private fun handleGroups(groups: Map>) { - groups.keys.forEach { - val parentText = it.node.treeParent.text + groups.keys.forEach { key -> + val parentText = key.node.treeParent.text if (parentText.contains(" is ")) { - groups.getValue(it).forEach { asCall -> + groups.getValue(key).forEach { asCall -> if (asCall.node.hasParent(THEN)) { raiseWarning(asCall.node) } } } else if (parentText.contains(" !is ")) { - groups.getValue(it).forEach { asCall -> + groups.getValue(key).forEach { asCall -> if (asCall.node.hasParent(ELSE)) { raiseWarning(asCall.node) } @@ -234,12 +234,12 @@ class SmartCastRule(private val configRules: List) : Rule("smart-ca val identifier = node.getFirstChildWithType(REFERENCE_EXPRESSION)?.text - node.getAllChildrenWithType(WHEN_ENTRY).forEach { - if (it.hasChildOfType(WHEN_CONDITION_IS_PATTERN) && identifier != null) { - val type = it.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! + node.getAllChildrenWithType(WHEN_ENTRY).forEach { entry -> + if (entry.hasChildOfType(WHEN_CONDITION_IS_PATTERN) && identifier != null) { + val type = entry.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! .getFirstChildWithType(TYPE_REFERENCE)?.text - val callExpr = it.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() + val callExpr = entry.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() val blocks = listOf(IsExpressions(identifier, type ?: "")) callExpr?.let { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt index ade3e358e9..4a15f4ad12 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt @@ -106,11 +106,11 @@ class SingleInitRule(private val configRule: List) : Rule("multiple !(property.psi as KtProperty).hasBody() && assignments.size == 1 } .takeIf { it.isNotEmpty() } - ?.let { + ?.let { map -> Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, "`init` block has assignments that can be moved to declarations", initBlock.startOffset, initBlock ) { - it.forEach { (property, assignments) -> + map.forEach { (property, assignments) -> val assignment = assignments.single() property.addChild(PsiWhiteSpaceImpl(" "), null) property.addChild(LeafPsiElement(EQ, "="), null) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt index f32760c2ae..c2e86a3767 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt @@ -471,15 +471,15 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" } val callsByNewLine: ListOfList = mutableListOf() var callsInOneNewLine: MutableList = mutableListOf() - this.forEach { - if (it.treePrev.isFollowedByNewline() || it.treePrev.isWhiteSpaceWithNewline()) { + this.forEach { node -> + if (node.treePrev.isFollowedByNewline() || node.treePrev.isWhiteSpaceWithNewline()) { callsByNewLine.add(callsInOneNewLine) callsInOneNewLine = mutableListOf() - callsInOneNewLine.add(it) + callsInOneNewLine.add(node) } else { - callsInOneNewLine.add(it) + callsInOneNewLine.add(node) } - if (it.treePrev.elementType == POSTFIX_EXPRESSION && !it.treePrev.isFollowedByNewline() && configuration.maxCallsInOneLine == 1) { + if (node.treePrev.elementType == POSTFIX_EXPRESSION && !node.treePrev.isFollowedByNewline() && configuration.maxCallsInOneLine == 1) { return true } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt index 157dddcb5f..ebd6f425b7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt @@ -789,3 +789,14 @@ private fun ASTNode.calculateLineNumber() = getRootNode() require(it >= 0) { "Cannot calculate line number correctly, node's offset $startOffset is greater than file length ${getRootNode().textLength}" } it + 1 } + +/** + * Count number of lines in code block. Note: only *copy* of a node should be passed to this method, because the method changes the node. + * + * @return the number of lines in a block of code. + */ +fun countCodeLines(copyNode: ASTNode): Int { + copyNode.findAllNodesWithCondition({ it.isPartOfComment() }).forEach { it.treeParent.removeChild(it) } + val text = copyNode.text.lines().filter { it.isNotBlank() } + return text.size +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt index a3e907f4d9..775da020d2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringCaseUtils.kt @@ -145,24 +145,24 @@ private fun convertUnknownCaseToCamel(str: String, isFirstLetterCapital: Boolean // [p]a[SC]a[_]l -> [P]a[Sc]a[L] var isPreviousLetterCapital = isFirstLetterCapital var isPreviousLetterUnderscore = false - return str.map { - if (it.isUpperCase()) { - val result = if (isPreviousLetterCapital && !isPreviousLetterUnderscore) it.toLowerCase() else it + return str.map { char -> + if (char.isUpperCase()) { + val result = if (isPreviousLetterCapital && !isPreviousLetterUnderscore) char.toLowerCase() else char isPreviousLetterCapital = true isPreviousLetterUnderscore = false result.toString() } else { - val result = if (it == '_') { + val result = if (char == '_') { isPreviousLetterUnderscore = true "" } else if (isPreviousLetterUnderscore) { isPreviousLetterCapital = true isPreviousLetterUnderscore = false - it.toUpperCase().toString() + char.toUpperCase().toString() } else { isPreviousLetterCapital = false isPreviousLetterUnderscore = false - it.toString() + char.toString() } result } @@ -172,17 +172,17 @@ private fun convertUnknownCaseToCamel(str: String, isFirstLetterCapital: Boolean private fun convertUnknownCaseToUpperSnake(str: String): String { // [p]a[SC]a[_]l -> [P]A_[SC]_A_[L] var alreadyInsertedUnderscore = true - return str.map { - if (it.isUpperCase()) { + return str.map { char -> + if (char.isUpperCase()) { if (!alreadyInsertedUnderscore) { alreadyInsertedUnderscore = true - "_$it" + "_$char" } else { - it.toString() + char.toString() } } else { - alreadyInsertedUnderscore = (it == '_') - it.toUpperCase().toString() + alreadyInsertedUnderscore = (char == '_') + char.toUpperCase().toString() } }.joinToString("") } diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 281df43f9b..58c2966f91 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -356,6 +356,11 @@ # Checks that using runBlocking inside async block code - name: RUN_BLOCKING_INSIDE_ASYNC enabled: true +# Checks that the long lambda has parameters +- name: TOO_MANY_LINES_IN_LAMBDA + enabled: true + configuration: + maxLambdaLength: 10 # max length of lambda without parameters # Checks that property in constructor doesn't contains comment - name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index cdff6358f4..84f0a7275f 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -356,6 +356,11 @@ # Checks that property in constructor doesn't contain comment - name: KDOC_NO_CONSTRUCTOR_PROPERTY enabled: true +# Checks that the long lambda has parameters +- name: TOO_MANY_LINES_IN_LAMBDA + enabled: true + configuration: + maxLambdaLength: 10 # max length of lambda without parameters # Checks that property in KDoc present in class - name: KDOC_EXTRA_PROPERTY enabled: true diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt new file mode 100644 index 0000000000..5280bf48d8 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter5/LambdaLengthWarnTest.kt @@ -0,0 +1,175 @@ +package org.cqfn.diktat.ruleset.chapter5 + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.LambdaLengthRule +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 LambdaLengthWarnTest : LintTestBase(::LambdaLengthRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:lambda-length" + private val rulesConfigList: List = listOf( + RulesConfig( + Warnings.TOO_MANY_LINES_IN_LAMBDA.name, true, + mapOf("maxLambdaLength" to "3")) + ) + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `less than max`() { + lintMethod( + """ + |fun foo() { + | val x = 10 + | val list = listOf(1, 2, 3, 4, 5) + | .map {element -> element + x} + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `nested lambda with implicit parameter`() { + lintMethod( + """ + |fun foo() { + | private val allTestsFromResources: List by lazy { + | val fileUrl: URL? = javaClass.getResource("123") + | val resource = fileUrl + | ?.let { File(it.file) } + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `lambda doesn't expect parameters`() { + lintMethod( + """ + |fun foo() { + | private val allTestsFromResources: List by lazy { + | val fileUrl: URL? = javaClass.getResource("123") + | list = listOf(1, 2, 3, 4, 5) + | .removeAt(1) + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `less than max without argument`() { + lintMethod( + """ + |fun foo() { + | val x = 10 + | val list = listOf(1, 2, 3, 4, 5) + | .map {it + x} + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `more than max with argument`() { + lintMethod( + """ + |fun foo() { + | val calculateX = { x : Int -> + | when(x) { + | in 0..40 -> "Fail" + | in 41..70 -> "Pass" + | in 71..100 -> "Distinction" + | else -> false + | } + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `more than maximum without argument`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(1, 2, 3, 4, 5) + | .map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | it + z + | + | + | + | + | } + |} + """.trimMargin(), + LintError(3, 13, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `two lambda more than maximum without argument`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(1, 2, 3, 4, 5) + | .filter { n -> n % 2 == 1 } + | .map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | it + z + | + | + | + | + | } + |} + """.trimMargin(), + LintError(4, 13, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } + + @Test + @Tag(WarningNames.TOO_MANY_LINES_IN_LAMBDA) + fun `lambda in lambda`() { + lintMethod( + """ + |fun foo() { + | val list = listOf(listOf(1,2,3), listOf(4,5,6)) + | .map {l -> l.map { + | val x = 0 + | val y = x + 1 + | val z = y + 1 + | println(it) + | } + | } + | } + """.trimMargin(), + LintError(3, 25, ruleId, "${Warnings.TOO_MANY_LINES_IN_LAMBDA.warnText()} max length lambda without arguments is 3, but you have 6", false), + rulesConfigList = rulesConfigList + ) + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt index 4fbc326d14..053e56d20d 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt @@ -34,24 +34,24 @@ internal fun List.assertEquals(vararg expectedLintErrors: LintError) Assertions.assertThat(this) .allSatisfy { actual -> val expected = expectedLintErrors[this.indexOf(actual)] - SoftAssertions.assertSoftly { - it + SoftAssertions.assertSoftly { sa -> + sa .assertThat(actual.line) .`as`("Line") .isEqualTo(expected.line) - it + sa .assertThat(actual.col) .`as`("Column") .isEqualTo(expected.col) - it + sa .assertThat(actual.ruleId) .`as`("Rule id") .isEqualTo(expected.ruleId) - it + sa .assertThat(actual.detail) .`as`("Detailed message") .isEqualTo(expected.detail) - it + sa .assertThat(actual.canBeAutoCorrected) .`as`("Can be autocorrected") .isEqualTo(expected.canBeAutoCorrected) diff --git a/info/available-rules.md b/info/available-rules.md index f2ce918049..88375589e2 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -97,6 +97,7 @@ | 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 | - | +| 5 | 5.2.5 | TOO_MANY_LINES_IN_LAMBDA | Check: that the long lambda has parameters | no | maxLambdaLength | | 6 | 6.1.1 | SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY | Check: warns if there is only one secondary constructor in a class
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 | diff --git a/info/guide/guide-TOC.md b/info/guide/guide-TOC.md index 6b693af349..e8e7a8c9a9 100644 --- a/info/guide/guide-TOC.md +++ b/info/guide/guide-TOC.md @@ -88,6 +88,7 @@ I [Preface](#c0) * [5.2.1 The lambda parameter of the function should be placed at the end of the argument list](#r5.2.1) * [5.2.2 Number of function parameters should be limited to five](#r5.2.2) * [5.2.3 Use default values for function arguments instead of overloading them](#r5.2.3) + * [5.2.5 Long lambdas should have explicit parameters](#r5.2.4) [6. Classes, interfaces, and extension functions](#c6) * [6.1 Classes](#c6.1) diff --git a/info/guide/guide-chapter-5.md b/info/guide/guide-chapter-5.md index 6eef6ca3a7..ea27034443 100644 --- a/info/guide/guide-chapter-5.md +++ b/info/guide/guide-chapter-5.md @@ -142,4 +142,7 @@ GlobalScope.async { count++ } } -``` \ No newline at end of file +``` +#### 5.2.5 Long lambdas should have explicit parameters +The lambda without parameters shouldn't be too long. +If a lambda is too long, it can confuse the user. Lambda without parameters should consist of 10 lines (non-empty and non-comment) in total.