diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt index dd69de36fe..35ae0d2ccb 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt @@ -5,17 +5,20 @@ import org.cqfn.diktat.ruleset.constants.Warnings.COMPLEX_BOOLEAN_EXPRESSION import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser import org.cqfn.diktat.ruleset.utils.findAllNodesWithCondition -import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType import org.cqfn.diktat.ruleset.utils.logicalInfixMethods - +import com.bpodgursky.jbool_expressions.And import com.bpodgursky.jbool_expressions.Expression +import com.bpodgursky.jbool_expressions.NExpression +import com.bpodgursky.jbool_expressions.Or import com.bpodgursky.jbool_expressions.options.ExprOptions import com.bpodgursky.jbool_expressions.parsers.ExprParser import com.bpodgursky.jbool_expressions.parsers.TokenMapper +import com.bpodgursky.jbool_expressions.rules.DeMorgan +import com.bpodgursky.jbool_expressions.rules.Rule +import com.bpodgursky.jbool_expressions.rules.RuleList import com.bpodgursky.jbool_expressions.rules.RulesHelper import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CONDITION -import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE import com.pinterest.ktlint.core.ast.ElementType.PARENTHESIZED import com.pinterest.ktlint.core.ast.ElementType.PREFIX_EXPRESSION import com.pinterest.ktlint.core.ast.isLeaf @@ -26,7 +29,7 @@ import org.jetbrains.kotlin.psi.KtParenthesizedExpression import org.jetbrains.kotlin.psi.KtPrefixExpression import org.jetbrains.kotlin.psi.psiUtil.parents -import java.lang.RuntimeException +typealias ExpressionCreator = (List?>) -> Expression /** * Rule that checks if the boolean expression can be simplified. @@ -63,11 +66,7 @@ class BooleanExpressionsRule(configRules: List) : DiktatRule( throw exc } } - val distributiveLawString = checkDistributiveLaw(expr, expressionsReplacement, node) - val simplifiedExpression = distributiveLawString?.let { - ExprParser.parse(distributiveLawString) - } - ?: RulesHelper.applySet(expr, RulesHelper.demorganRules(), ExprOptions.noCaching()) + val simplifiedExpression = RulesHelper.applySet(expr, allRules(), ExprOptions.noCaching()) if (expr != simplifiedExpression) { COMPLEX_BOOLEAN_EXPRESSION.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) { fixBooleanExpression(node, simplifiedExpression, expressionsReplacement) @@ -174,103 +173,6 @@ class BooleanExpressionsRule(configRules: List) : DiktatRule( KotlinParser().createNode(expressionsReplacement.restoreFullExpression(correctKotlinBooleanExpression))) } - /** - * Checks if boolean expression can be simplified with distributive law. - * - * @return String? null if it cannot be simplified. Simplified string otherwise. - */ - private fun checkDistributiveLaw( - expr: Expression, - expressionsReplacement: ExpressionsReplacement, - node: ASTNode - ): String? { - // checking that expression can be considered as distributive law - val commonDistributiveOperand = getCommonDistributiveOperand(node, expr.toString(), expressionsReplacement)?.toString() ?: return null - val correctSymbolsSequence = expressionsReplacement.getTokens().toMutableList() - correctSymbolsSequence.remove(commonDistributiveOperand) - correctSymbolsSequence.add(0, commonDistributiveOperand) - val expressionsLogicalOperator = expr.toString().first { it == '&' || it == '|' } - // we return expression depending on second operator - return returnNeededDistributiveExpression(expressionsLogicalOperator, correctSymbolsSequence) - } - - /** - * Returns correct result string in distributive law - */ - private fun returnNeededDistributiveExpression(firstLogicalOperator: Char, symbols: List): String { - val secondSymbol = if (firstLogicalOperator == '&') '|' else '&' // this is used to alter symbols - val resultString = StringBuilder() - symbols.forEachIndexed { index, symbol -> - if (index == 0) { - resultString.append("$symbol $firstLogicalOperator (") - } else { - resultString.append("$symbol $secondSymbol ") - } - } - // remove last space and last operate - return StringBuilder(resultString.dropLast(2)).append(")").toString() - } - - /** - * Method that checks that the expression can be simplified by distributive law. - * Distributive law - A && B || A && C -> A && (B || C) or (A || B) && (A || C) -> A || (B && C) - * - * @return common operand for distributed law - */ - private fun getCommonDistributiveOperand( - node: ASTNode, - expression: String, - expressionsReplacement: ExpressionsReplacement - ): Char? { - val operationSequence = expression.filter { it == '&' || it == '|' } - val numberOfOperationReferences = operationSequence.length - // There should be three operands and three operation references in order to consider the expression - // Moreover the operation references between operands should alternate. - if (expressionsReplacement.size() < DISTRIBUTIVE_LAW_MIN_EXPRESSIONS || - numberOfOperationReferences < DISTRIBUTIVE_LAW_MIN_OPERATIONS || - !isSequenceAlternate(operationSequence)) { - return null - } - return if (operationSequence.first() == '&') { - getCommonOperand(expression, '|', '&') - } else { - // this is done for excluding A || B && A || C without parenthesis. - val parenthesizedExpressions = node.findAllNodesWithCondition { it.elementType == PARENTHESIZED } - parenthesizedExpressions.forEach { - it.findLeafWithSpecificType(OPERATION_REFERENCE) ?: run { - return null - } - } - getCommonOperand(expression, '&', '|') - } - } - - private fun isSequenceAlternate(seq: String) = seq.zipWithNext().all { it.first != it.second } - - /** - * This method returns common operand in distributive law. - * We need common operand for special case, when the first expression is not common. - * For example: (some != null && a) || (a && c) || (a && d). When the expressions are mapped to `char`s, `some != null` points to `A` character - */ - private fun getCommonOperand( - expression: String, - firstSplitDelimiter: Char, - secondSplitDelimiter: Char - ): Char? { - val expressions = expression.split(firstSplitDelimiter) - val listOfPairs: MutableList> = mutableListOf() - expressions.forEach { expr -> - listOfPairs.add(expr.filterNot { it == ' ' || it == '(' || it == ')' }.split(secondSplitDelimiter)) - } - val firstOperands = listOfPairs.first() - listOfPairs.removeFirst() - return when { - listOfPairs.all { it.contains(firstOperands.first()) } -> firstOperands.first().first() - listOfPairs.all { it.contains(firstOperands.last()) } -> firstOperands.last().first() - else -> null - } - } - private fun KtBinaryExpression.isXorExpression() = operationReference.text == "xor" /** @@ -350,22 +252,91 @@ class BooleanExpressionsRule(configRules: List) : DiktatRule( return resultExpression } - /** - * Returns collection of token are used to construct full expression in jbool format. - * - * @return collection of token are used to construct full expression in jbool format - */ - fun getTokens(): Collection = expressionToToken.values - private fun getLetter(letters: HashMap, key: String) = letters .computeIfAbsent(key) { ('A'.code + letters.size).toChar().toString() } } + /** + * Rule that checks that the expression can be simplified by distributive law. + * Distributive law - A && B || A && C -> A && (B || C) or (A || B) && (A || C) -> A || (B && C) + */ + @Suppress("UnsafeCallOnNullableType") + private class DistributiveLaw : Rule, K>() { + override fun applyInternal(input: NExpression, options: ExprOptions): Expression { + val exprFactory = options.exprFactory!! + val orExpressionCreator: ExpressionCreator = { expressions -> exprFactory.or(expressions.toTypedArray()) } + val andExpressionCreator: ExpressionCreator = { expressions -> exprFactory.and(expressions.toTypedArray()) } + return when (input) { + is And -> applyInternal(input, orExpressionCreator, andExpressionCreator) + is Or -> applyInternal(input, andExpressionCreator, orExpressionCreator) + else -> throw UnsupportedOperationException("Not supported input expression: ${input.exprType}") + } + } + + private fun applyInternal( + input: NExpression, + upperExpressionCreator: ExpressionCreator, + innerExpressionCreator: ExpressionCreator + ): Expression { + // we can be here only after `isApply` -- common exists + val commonExpression = findCommonExpression(input.children)!! + return upperExpressionCreator( + listOf(commonExpression, + innerExpressionCreator( + input.expressions.map { excludeChild(it, upperExpressionCreator, commonExpression) } + ))) + } + + private fun excludeChild( + expression: Expression, + expressionCreator: ExpressionCreator, + childToExclude: Expression + ): Expression { + val leftChildren = expression.children.filterNot { it.equals(childToExclude) } + return if (leftChildren.size == 1) { + leftChildren.first() + } else { + expressionCreator(leftChildren) + } + } + + /** + * Checks the input expression + */ + override fun isApply(inputNullable: Expression?): Boolean = inputNullable?.let { input -> + when (input) { + is And -> isApplicable, Or>(input) + is Or -> isApplicable, And>(input) + else -> false + } + } ?: false + + private inline fun , reified C : NExpression> isApplicable(input: E): Boolean { + val children = input.children ?: return false + if (children.size < 2 || children.any { it !is C }) { + return false + } + return findCommonExpression(children) != null + } + + private fun findCommonExpression(children: List>): Expression? = children.drop(1) + .fold(children[0].children) { commons, child -> + commons.filter { childResult -> + child.children.any { it.equals(childResult) } + } + }.firstOrNull() + } + companion object { - const val DISTRIBUTIVE_LAW_MIN_EXPRESSIONS = 3 - const val DISTRIBUTIVE_LAW_MIN_OPERATIONS = 3 - const val NAME_ID = "acm-boolean-expressions-rule" + const val NAME_ID = "boolean-expressions-rule" + + private fun allRules(): RuleList { + val rules: MutableList> = ArrayList(RulesHelper.simplifyRules().rules) + rules.add(DeMorgan()) + rules.add(DistributiveLaw()) + return RuleList(rules) + } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt new file mode 100644 index 0000000000..ff00e2b5cd --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationError.kt @@ -0,0 +1,7 @@ +package org.cqfn.diktat.ruleset.rules.chapter3.files + +/** + * @property expected expected indentation as a number of spaces + * @property actual actual indentation as a number of spaces + */ +internal data class IndentationError(val expected: Int, val actual: Int) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt index 30bab7fc64..7fe952fac8 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt @@ -8,7 +8,15 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.* +import org.cqfn.diktat.ruleset.utils.NEWLINE +import org.cqfn.diktat.ruleset.utils.SPACE +import org.cqfn.diktat.ruleset.utils.TAB +import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getAllLeafsWithSpecificType +import org.cqfn.diktat.ruleset.utils.getFilePath +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.indentBy import org.cqfn.diktat.ruleset.utils.indentation.ArrowInWhenChecker import org.cqfn.diktat.ruleset.utils.indentation.AssignmentOperatorChecker import org.cqfn.diktat.ruleset.utils.indentation.ConditionalsAndLoopsWithoutBracesChecker @@ -20,6 +28,9 @@ import org.cqfn.diktat.ruleset.utils.indentation.IndentationConfig import org.cqfn.diktat.ruleset.utils.indentation.KdocIndentationChecker import org.cqfn.diktat.ruleset.utils.indentation.SuperTypeListChecker import org.cqfn.diktat.ruleset.utils.indentation.ValueParameterListChecker +import org.cqfn.diktat.ruleset.utils.isSpaceCharacter +import org.cqfn.diktat.ruleset.utils.lastIndent +import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CLOSING_QUOTE @@ -35,6 +46,7 @@ import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_START import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACKET +import com.pinterest.ktlint.core.ast.ElementType.REGULAR_STRING_PART import com.pinterest.ktlint.core.ast.ElementType.RPAR import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.SHORT_STRING_TEMPLATE_ENTRY @@ -58,8 +70,6 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.slf4j.LoggerFactory -import java.lang.StringBuilder - import kotlin.math.abs /** @@ -117,15 +127,15 @@ class IndentationRule(configRules: List) : DiktatRule( val whiteSpaceNodes: MutableList = mutableListOf() node.getAllLeafsWithSpecificType(WHITE_SPACE, whiteSpaceNodes) whiteSpaceNodes - .filter { it.textContains('\t') } + .filter { it.textContains(TAB) } .apply { if (isEmpty()) { return true } } .forEach { - WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "tabs are not allowed for indentation", it.startOffset + it.text.indexOf('\t'), it) { - (it as LeafPsiElement).rawReplaceWithText(it.text.replace("\t", " ".repeat(configuration.indentationSize))) + WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "tabs are not allowed for indentation", it.startOffset + it.text.indexOf(TAB), it) { + (it as LeafPsiElement).rawReplaceWithText(it.text.replace(TAB.toString(), configuration.indentationSize.spaces)) } } return isFixMode // true if we changed all tabs to spaces @@ -137,7 +147,7 @@ class IndentationRule(configRules: List) : DiktatRule( private fun checkNewlineAtEnd(node: ASTNode) { if (configuration.newlineAtEnd) { val lastChild = generateSequence(node) { it.lastChildNode }.last() - val numBlankLinesAfter = lastChild.text.count { it == '\n' } + val numBlankLinesAfter = lastChild.text.count { it == NEWLINE } if (lastChild.elementType != WHITE_SPACE || numBlankLinesAfter != 1) { val warnText = if (lastChild.elementType != WHITE_SPACE || numBlankLinesAfter == 0) "no newline" else "too many blank lines" val fileName = filePath.substringAfterLast(File.separator) @@ -145,10 +155,10 @@ class IndentationRule(configRules: List) : DiktatRule( // however, the text length does not consider it, since it's blank and line appeared only because of `\n` // But ktlint synthetically increase length in aim to have ability to point to this line, so in this case // offset will be `node.textLength`, otherwise we will point to the last symbol, i.e `node.textLength - 1` - val offset = if (lastChild.elementType == WHITE_SPACE && lastChild.textContains('\n')) node.textLength else node.textLength - 1 + val offset = if (lastChild.elementType == WHITE_SPACE && lastChild.textContains(NEWLINE)) node.textLength else node.textLength - 1 WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "$warnText at the end of file $fileName", offset, node) { if (lastChild.elementType != WHITE_SPACE) { - node.addChild(PsiWhiteSpaceImpl("\n"), null) + node.addChild(PsiWhiteSpaceImpl(NEWLINE.toString()), null) } else { lastChild.leaveOnlyOneNewLine() } @@ -166,10 +176,10 @@ class IndentationRule(configRules: List) : DiktatRule( context.checkAndReset(astNode) if (astNode.elementType in increasingTokens) { context.storeIncrementingToken(astNode.elementType) - } else if (astNode.elementType in decreasingTokens && !astNode.treePrev.let { it.elementType == WHITE_SPACE && it.textContains('\n') }) { + } else if (astNode.elementType in decreasingTokens && !astNode.treePrev.let { it.elementType == WHITE_SPACE && it.textContains(NEWLINE) }) { // if decreasing token is after WHITE_SPACE with \n, indents are corrected in visitWhiteSpace method context.dec(astNode.elementType) - } else if (astNode.elementType == WHITE_SPACE && astNode.textContains('\n') && astNode.treeNext != null) { + } else if (astNode.elementType == WHITE_SPACE && astNode.textContains(NEWLINE) && astNode.treeNext != null) { // we check only WHITE_SPACE nodes with newlines, other than the last line in file; correctness of newlines should be checked elsewhere visitWhiteSpace(astNode, context) } @@ -226,7 +236,7 @@ class IndentationRule(configRules: List) : DiktatRule( "expected $expectedIndent but was ${indentError.actual}" } WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, warnText, - whiteSpace.startOffset + whiteSpace.text.lastIndexOf('\n') + 1, whiteSpace.node) { + whiteSpace.startOffset + whiteSpace.text.lastIndexOf(NEWLINE) + 1, whiteSpace.node) { checkStringLiteral(whiteSpace, expectedIndent, indentError.actual) whiteSpace.node.indentBy(expectedIndent) } @@ -255,7 +265,23 @@ class IndentationRule(configRules: List) : DiktatRule( } /** - * If it is triple-quoted string template we need to indent all its parts + * Indents each [entry][LITERAL_STRING_TEMPLATE_ENTRY] in a (potentially, + * multi-line) triple-quoted [string template][STRING_TEMPLATE]. + * + * String templates usually have the following structure: + * + * * `STRING_TEMPLATE` + * * `OPEN_QUOTE` + * * `LITERAL_STRING_TEMPLATE_ENTRY` + * * `REGULAR_STRING_PART` + * * … + * * `LITERAL_STRING_TEMPLATE_ENTRY` + * * `REGULAR_STRING_PART` + * * `CLOSING_QUOTE` + * + * @param stringTemplate the string template. + * @see STRING_TEMPLATE + * @see LITERAL_STRING_TEMPLATE_ENTRY */ @Suppress("LOCAL_VARIABLE_EARLY_DECLARATION") private fun fixStringLiteral( @@ -263,19 +289,42 @@ class IndentationRule(configRules: List) : DiktatRule( expectedIndent: Int, actualIndent: Int ) { - val textIndent = " ".repeat(expectedIndent + INDENT_SIZE) val templateEntries = stringTemplate.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY) - templateEntries.forEach { node -> - if (!node.text.contains("\n")) { - fixFirstTemplateEntries(node, textIndent, actualIndent) + templateEntries.asSequence().filterIndexed { index, templateEntry -> + val text = templateEntry.text + val containsNewline = text.contains(NEWLINE) + + if (containsNewline) { + /* + * In real-life cases observed, whenever a `LITERAL_STRING_TEMPLATE_ENTRY` + * _contains_ a newline character, it is _exactly_ a newline character. + */ + check(text.length == 1) { + val escapedText = text.replace(NEWLINE.toString(), "\\n") + + "A LITERAL_STRING_TEMPLATE_ENTRY at index $index contains extra characters in addition to the newline, " + + "entry: \"$escapedText\", " + + "string template: ${stringTemplate.text}" + } } + + !containsNewline + }.forEach { templateEntry -> + fixFirstTemplateEntries( + templateEntry, + expectedIndent = expectedIndent, + actualIndent = actualIndent) + } + + /* + * This is the last string template fragment which is usually followed + * with the closing `"""` and the `.trimIndent()` or `.trimMargin()` call. + */ + val lastRegularStringPart = templateEntries.last().firstChildNode as LeafPsiElement + lastRegularStringPart.checkRegularStringPart().apply { + val textWithoutIndent = text.trimStart() + rawReplaceWithText(expectedIndent.spaces + textWithoutIndent) } - (templateEntries.last().firstChildNode as LeafPsiElement) - .rawReplaceWithText(" ".repeat(expectedIndent) + templateEntries - .last() - .firstChildNode - .text - .trim()) } private fun getNextDotExpression(node: ASTNode) = if (node.elementType == DOT_QUALIFIED_EXPRESSION) { @@ -285,41 +334,70 @@ class IndentationRule(configRules: List) : DiktatRule( } /** - * This method fixes all lines of string template except the last one - * Also it considers $foo insertions in string + * Modifies [templateEntry] by correcting its indentation level. + * + * This method can be used to fix all [lines][LITERAL_STRING_TEMPLATE_ENTRY] + * of a [string template][STRING_TEMPLATE] except for the last one. + * + * Also, it considers `$foo` insertions in a string. + * + * @param templateEntry a [LITERAL_STRING_TEMPLATE_ENTRY] node. + * @param expectedIndent the expected indent level, as returned by + * [IndentationError.expected]. + * @param actualIndent the actual indent level, as returned by + * [IndentationError.actual]. */ private fun fixFirstTemplateEntries( - node: ASTNode, - textIndent: String, + templateEntry: ASTNode, + expectedIndent: Int, actualIndent: Int ) { - val correctedText = StringBuilder() - // shift of the node depending on its initial string template indent - val nodeStartIndent = if (node.firstChildNode - .text - .takeWhile { it == ' ' } - .count() - actualIndent - INDENT_SIZE > 0) { - node.firstChildNode - .text - .takeWhile { it == ' ' } - .count() - actualIndent - INDENT_SIZE - } else { - 0 + require(templateEntry.elementType == LITERAL_STRING_TEMPLATE_ENTRY) { + "The elementType of this node is ${templateEntry.elementType} while $LITERAL_STRING_TEMPLATE_ENTRY expected" } - val isPrevStringTemplate = node.treePrev.elementType in stringLiteralTokens - val isNextStringTemplate = node.treeNext.elementType in stringLiteralTokens - when { - // if string template is before literal_string - isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(node.firstChildNode.text.trimEnd()) - // if string template is after literal_string - !isPrevStringTemplate && isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trimStart()) - // if there is no string template in literal_string - !isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trim()) - isPrevStringTemplate && isNextStringTemplate -> correctedText.append(node.firstChildNode.text) - node.text.isBlank() -> correctedText.append(textIndent) - else -> {} + + /* + * Quite possible, do nothing in this case. + */ + if (expectedIndent == actualIndent) { + return } - (node.firstChildNode as LeafPsiElement).rawReplaceWithText(correctedText.toString()) + + /* + * A `REGULAR_STRING_PART`. + */ + val regularStringPart = templateEntry.firstChildNode as LeafPsiElement + val regularStringPartText = regularStringPart.checkRegularStringPart().text + val nodeStartIndentOrNegative = (regularStringPartText.leadingSpaceCount() - actualIndent).unindent() + // shift of the node depending on its initial string template indent + val nodeStartIndent = nodeStartIndentOrNegative.zeroIfNegative() + + val isPrevStringTemplate = templateEntry.treePrev.elementType in stringLiteralTokens + val isNextStringTemplate = templateEntry.treeNext.elementType in stringLiteralTokens + + val correctedText = when { + isPrevStringTemplate -> when { + isNextStringTemplate -> regularStringPartText + + // if string template is before literal_string + else -> regularStringPartText.trimEnd() + + } + + else -> { + val textIndent = expectedIndent.indent().spaces + + when { + // if string template is after literal_string + isNextStringTemplate -> textIndent + nodeStartIndent.spaces + regularStringPartText.trimStart() + + // if there is no string template in literal_string + else -> textIndent + nodeStartIndent.spaces + regularStringPartText.trim() + } + } + } + + regularStringPart.rawReplaceWithText(correctedText) } private fun ASTNode.getExceptionalIndentInitiator() = treeParent.let { parent -> @@ -333,6 +411,30 @@ class IndentationRule(configRules: List) : DiktatRule( } } + /** + * Increases the indentation level by [level] * [IndentationConfig.indentationSize]. + * + * @param level the indentation level, 1 by default. + * @see unindent + * @see IndentationConfig.indentationSize + * @see IndentContext.maybeIncrement + * @see IndentContext.dec + */ + private fun Int.indent(level: Int = 1): Int = + this + level * configuration.indentationSize + + /** + * Decreases the indentation level by [level] * [IndentationConfig.indentationSize]. + * + * @param level the indentation level, 1 by default. + * @see indent + * @see IndentationConfig.indentationSize + * @see IndentContext.maybeIncrement + * @see IndentContext.dec + */ + private fun Int.unindent(level: Int = 1): Int = + this - level * configuration.indentationSize + /** * Class that contains state needed to calculate indent and keep track of exceptional indents. * Tokens from [increasingTokens] are stored in stack [activeTokens]. When [WHITE_SPACE] with line break is encountered, @@ -354,6 +456,10 @@ class IndentationRule(configRules: List) : DiktatRule( /** * Checks whether indentation needs to be incremented and increments in this case. + * + * @see dec + * @see Int.indent + * @see Int.unindent */ fun maybeIncrement() { if (activeTokens.isNotEmpty() && activeTokens.peek() != WHITE_SPACE) { @@ -364,6 +470,10 @@ class IndentationRule(configRules: List) : DiktatRule( /** * @param token a token that caused indentation decrement, for example a closing brace + * + * @see maybeIncrement + * @see Int.indent + * @see Int.unindent */ fun dec(token: IElementType) { if (activeTokens.peek() == WHITE_SPACE) { @@ -395,7 +505,7 @@ class IndentationRule(configRules: List) : DiktatRule( ) = exceptionalIndents.add(ExceptionalIndent(initiator, indent, includeLastChild)) /** - * @param astNode the node which is used to determine whether exceptinoal indents are still active + * @param astNode the node which is used to determine whether exceptional indents are still active * @return boolean result */ fun checkAndReset(astNode: ASTNode) = exceptionalIndents.retainAll { it.isActive(astNode) } @@ -424,22 +534,53 @@ class IndentationRule(configRules: List) : DiktatRule( companion object { private val log = LoggerFactory.getLogger(IndentationRule::class.java) - const val INDENT_SIZE = 4 const val NAME_ID = "zct-indentation" private val increasingTokens = listOf(LPAR, LBRACE, LBRACKET, LONG_TEMPLATE_ENTRY_START) private val decreasingTokens = listOf(RPAR, RBRACE, RBRACKET, LONG_TEMPLATE_ENTRY_END) private val matchingTokens = increasingTokens.zip(decreasingTokens) private val stringLiteralTokens = listOf(SHORT_STRING_TEMPLATE_ENTRY, LONG_STRING_TEMPLATE_ENTRY) - } -} -/** - * @property expected expected indentation as a number of spaces - * @property actual actual indentation as a number of spaces - */ -internal data class IndentationError(val expected: Int, val actual: Int) + /** + * @return a string which consists of `N` [space][SPACE] characters. + */ + @Suppress("CUSTOM_GETTERS_SETTERS") + private val Int.spaces: String + get() = + SPACE.toString().repeat(n = this) -/** - * @return indentation of the last line of this string - */ -internal fun String.lastIndent() = substringAfterLast('\n').count { it == ' ' } + /** + * Checks this [REGULAR_STRING_PART] child of a [LITERAL_STRING_TEMPLATE_ENTRY]. + * + * @return this `REGULAR_STRING_PART` PSI element. + */ + private fun LeafPsiElement.checkRegularStringPart(): LeafPsiElement { + val lastRegularStringPartType = elementType + + check(lastRegularStringPartType == REGULAR_STRING_PART) { + "Unexpected type of the 1st child of the string template entry, " + + "expected: $REGULAR_STRING_PART, " + + "actual: $lastRegularStringPartType, " + + "string template: ${parent.parent.text}" + } + + return this + } + + /** + * @return the number of leading space characters in this string. + */ + private fun String.leadingSpaceCount(): Int = + asSequence() + .takeWhile(::isSpaceCharacter) + .count() + + /** + * @return this very integer if non-negative, 0 otherwise. + */ + private fun Int.zeroIfNegative(): Int = + when { + this > 0 -> this + else -> 0 + } + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt index 378eee58b1..4a783d87e0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt @@ -1,9 +1,16 @@ package org.cqfn.diktat.ruleset.rules.chapter3.files import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.common.config.rules.getRuleConfig +import org.cqfn.diktat.ruleset.constants.Warnings.LONG_LINE +import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_WHITESPACE import org.cqfn.diktat.ruleset.rules.DiktatRule +import org.cqfn.diktat.ruleset.rules.chapter3.LineLength import org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization +import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace +import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset +import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY @@ -23,8 +30,10 @@ import com.pinterest.ktlint.core.ast.ElementType.CONSTRUCTOR_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.DOT import com.pinterest.ktlint.core.ast.ElementType.DO_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.ELSE_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.ELVIS import com.pinterest.ktlint.core.ast.ElementType.EQ import com.pinterest.ktlint.core.ast.ElementType.EXCLEXCL +import com.pinterest.ktlint.core.ast.ElementType.FILE import com.pinterest.ktlint.core.ast.ElementType.FINALLY_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.FOR_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.FUN @@ -96,8 +105,14 @@ import org.slf4j.LoggerFactory class WhiteSpaceRule(configRules: List) : DiktatRule( NAME_ID, configRules, - listOf(WRONG_WHITESPACE) + listOf(WRONG_WHITESPACE, LONG_LINE, WRONG_INDENTATION) ) { + private val configuration by lazy { + LineLength.LineLengthConfiguration( + configRules.getRuleConfig(LONG_LINE)?.configuration ?: emptyMap() + ) + } + private lateinit var positionByOffset: (Int) -> Pair @Suppress("ComplexMethod") override fun logic(node: ASTNode) { when (node.elementType) { @@ -274,7 +289,6 @@ class WhiteSpaceRule(configRules: List) : DiktatRule( if (node.elementType == OPERATION_REFERENCE && node.treeParent.elementType.let { it == BINARY_EXPRESSION || it == POSTFIX_EXPRESSION || it == PROPERTY } || node.elementType != OPERATION_REFERENCE) { val requiredNumSpaces = if (operatorNode.elementType in operatorsWithNoWhitespace) 0 else 1 - handleToken(node, requiredNumSpaces, requiredNumSpaces) } } @@ -366,15 +380,28 @@ class WhiteSpaceRule(configRules: List) : DiktatRule( } } + @Suppress("UnsafeCallOnNullableType") + private fun ASTNode.isNeedNewLineInOperatorReferences(): Boolean { + positionByOffset = this.findParentNodeWithSpecificType(FILE)!!.calculateLineColByOffset() + val offset = positionByOffset(this.startOffset).second + return offset + this.text.length >= configuration.lineLength + } + + @Suppress("UnsafeCallOnNullableType") private fun ASTNode.fixSpaceAround(requiredSpacesBefore: Int?, requiredSpacesAfter: Int?) { if (requiredSpacesBefore == 1) { selfOrParentsTreePrev()?.let { if (it.elementType == WHITE_SPACE) it.treePrev else it }?.leaveSingleWhiteSpace() + if (this.isNeedNewLineInOperatorReferences() && this.firstChildNode.elementType == ELVIS) { + this.treePrev.let { this.treeParent.appendNewlineMergingWhiteSpace(it, it) } + } } else if (requiredSpacesBefore == 0) { selfOrParentsTreePrev()?.removeIfWhiteSpace() } - if (requiredSpacesAfter == 1) { leaveSingleWhiteSpace() + if (this.isNeedNewLineInOperatorReferences() && this.firstChildNode.elementType != ELVIS) { + this.treeNext.let { this.treeParent.appendNewlineMergingWhiteSpace(it, it) } + } } else if (requiredSpacesAfter == 0) { // for `!!` and possibly other postfix expressions treeNext can be null (treeNext ?: treeParent.treeNext).removeIfWhiteSpace() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt index 5550ae8e43..cf1eb0416c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt @@ -6,6 +6,12 @@ package org.cqfn.diktat.ruleset.utils import org.jetbrains.kotlin.lexer.KtTokens +internal const val NEWLINE = '\n' + +internal const val SPACE = ' ' + +internal const val TAB = '\t' + @Suppress("VARIABLE_NAME_INCORRECT_FORMAT") val JAVA = arrayOf("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", @@ -97,3 +103,15 @@ fun String.removePrefix(): String { } return this } + +/** + * @return the indentation of the last line of this string. + */ +internal fun String.lastIndent() = substringAfterLast(NEWLINE).count(::isSpaceCharacter) + +/** + * @param ch the character to examine. + * @return `true` if [ch] is a [SPACE], `false` otherwise. + */ +internal fun isSpaceCharacter(ch: Char): Boolean = + ch == SPACE diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt index 019c0abb06..5ab0f78d31 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt @@ -5,8 +5,8 @@ package org.cqfn.diktat.ruleset.utils.indentation import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationError -import org.cqfn.diktat.ruleset.rules.chapter3.files.lastIndent import org.cqfn.diktat.ruleset.utils.hasParent +import org.cqfn.diktat.ruleset.utils.lastIndent import com.pinterest.ktlint.core.ast.ElementType.ARROW import com.pinterest.ktlint.core.ast.ElementType.AS_KEYWORD diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt index 184ac437b5..c147808e50 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/IndentationConfig.kt @@ -1,7 +1,6 @@ package org.cqfn.diktat.ruleset.utils.indentation import org.cqfn.diktat.common.config.rules.RuleConfiguration -import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule /** * [RuleConfiguration] for indentation logic @@ -37,5 +36,13 @@ internal class IndentationConfig(config: Map) : RuleConfiguratio /** * The indentation size for each file */ - val indentationSize = config["indentationSize"]?.toInt() ?: IndentationRule.INDENT_SIZE + val indentationSize = config["indentationSize"]?.toInt() ?: DEFAULT_INDENT_SIZE + + private companion object { + /** + * The default indent size (space characters), configurable via + * `indentationSize`. + */ + private const val DEFAULT_INDENT_SIZE = 4 + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt index 6195a34387..be35a8afaa 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleFixTest.kt @@ -241,7 +241,7 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation", if (!lintResult.isSuccessful) { softly.assertThat(lintResult.actualContent) - .describedAs("lint result for \"$actual\"") + .describedAs("lint result for ${actual.describe()}") .isEqualTo(lintResult.expectedContent) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt index b329f68ba8..a68af9f3b5 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestMixin.kt @@ -284,6 +284,26 @@ internal interface IndentationRuleTestMixin { } } + /** + * @return a brief description of this code fragment. + */ + fun String.describe(): String { + val lines = splitToSequence('\n') + + var first: String? = null + + val count = lines.onEachIndexed { index, line -> + if (index == 0) { + first = line + } + }.count() + + return when (count) { + 1 -> "\"$this\"" + else -> "\"$first\u2026\" ($count line(s))" + } + } + /** * @return `true` if known-to-fail unit tests can be muted on the CI server. */ diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt index 7dda79cdaf..66ef42b840 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt @@ -757,7 +757,7 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule), IndentationRule assertSoftly { softly -> expressionBodyFunctionsSingleIndent.forEach { code -> softly.assertThat(lintResult(code, customConfig.asRulesConfigList())) - .describedAs("lint result for \"$code\"") + .describedAs("lint result for ${code.describe()}") .isNotEmpty .hasSizeBetween(1, 3).allSatisfy(Consumer { lintError -> assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId) @@ -782,7 +782,7 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule), IndentationRule assertSoftly { softly -> expressionBodyFunctionsContinuationIndent.forEach { code -> softly.assertThat(lintResult(code, customConfig.asRulesConfigList())) - .describedAs("lint result for \"$code\"") + .describedAs("lint result for ${code.describe()}") .isNotEmpty .hasSizeBetween(1, 3).allSatisfy(Consumer { lintError -> assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index 1060bfe248..d92fd08eb2 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -342,6 +342,12 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", } } + @Test + @Tag("DiktatRuleSetProvider") + fun `fix can cause long line`() { + fixAndCompareSmokeTest("ManyLineTransformInLongLineExpected.kt", "ManyLineTransformInLongLineTest.kt") + } + companion object { private const val DEFAULT_CONFIG_PATH = "../diktat-analysis.yml" private val unfixedLintErrors: MutableList = mutableListOf() diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt index 1007c609ec..8f440abf57 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt @@ -13,6 +13,7 @@ import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider +import org.assertj.core.api.Assertions.assertThat import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -43,6 +44,7 @@ class DiktatRuleSetProviderTest { .map { it.nameWithoutExtension } .filterNot { it in ignoreFile } val rulesName = DiktatRuleSetProvider().get() + .asSequence() .onEachIndexed { index, rule -> if (index != 0) { Assertions.assertTrue( @@ -53,8 +55,9 @@ class DiktatRuleSetProviderTest { } .map { (it as? DiktatRuleSetProvider.OrderedRule)?.rule ?: it } .map { it::class.simpleName!! } - .filter { it != "DummyWarning" } - Assertions.assertEquals(filesName.sorted().toList(), rulesName.sorted()) + .filterNot { it == "DummyWarning" } + .toList() + assertThat(rulesName.sorted()).containsExactlyElementsOf(filesName.sorted().toList()) } @Test @@ -122,6 +125,9 @@ class DiktatRuleSetProviderTest { } companion object { - private val ignoreFile = listOf("DiktatRuleSetProvider", "DiktatRule") + private val ignoreFile = listOf( + "DiktatRuleSetProvider", + "DiktatRule", + "IndentationError") } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawExpected.kt index 37244e76d6..297a8d8d58 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawExpected.kt @@ -21,4 +21,19 @@ fun some() { if (a > 5 && (b > 6 || c > 7 || d > 8)) { } + + // long case + if (a > 5 && ((b > 6 && z > 3) || (c > 7 && y > 4) || (d > 8 && w > 5))) { + + } + + // long case #2.1 + if (b > 6 && a > 5 && (z > 3 || c > 7 || w > 5)) { + + } + + // long case #2.2 + if (b > 6 || a > 5 || (z > 3 && c > 7 && w > 5)) { + + } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawTest.kt b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawTest.kt index ce07c8e3cf..cf2a8e3343 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/DistributiveLawTest.kt @@ -21,4 +21,19 @@ fun some() { if ((b > 6 && a > 5) || (c > 7 && a > 5) || (a > 5 && d > 8)) { } + + // long case + if ((b > 6 && a > 5 && z > 3) || (c > 7 && a > 5 && y > 4) || (a > 5 && d > 8 && w > 5)) { + + } + + // long case #2.1 + if ((b > 6 && a > 5 && z > 3) || (c > 7 && a > 5 && b > 6) || (a > 5 && b > 6 && w > 5)) { + + } + + // long case #2.2 + if ((b > 6 || a > 5 || z > 3) && (c > 7 || a > 5 || b > 6) && (a > 5 || b > 6 || w > 5)) { + + } } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineExpected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineExpected.kt new file mode 100644 index 0000000000..bfde8a2237 --- /dev/null +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineExpected.kt @@ -0,0 +1,11 @@ +package org.cqfn.diktat + +fun foo() { + (1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or 12 or 13 or 14 or 15 or 16 or 17 or 18 or 19 or 20 or 21 or 22 or 23 or 24 or 25 or 26 or 27 or 28 or 29 or 30 or 31 + ?: 32 or 33 or 34 or 35 or 36 or 37 or 38 or 39 ?: 40 or 41 or 42 or 43 or 44 or 45 or 46 or 47 or 48 or 49 or 50 or 51 or 52 or 53 + 54 or 55 or 56 or 57 or 58 or 59 + ?: 60 or 61 or 62 or 63 or 64 or 65 or 66 - 67 or 68 or 69 or 70 or 1 + 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or 12 or 13 or 14 or 15 or 16 or 17 or 18 + 19 - + 20 or 21 or 22 or 23 or 24 or 25 or 26 or 27 or 28 or 29 or 30 or 31 or 32 or 33 or 34 or 35 or 36 or 37 or 38 or 39 or 40 or 41 || 42 or 43 or 44 or 45 or 46 or 47 && 48 or 49 || + 50 or 51 or 52 or 53 or 54 or 55 or 56 or 57 or 58 or 59 or 60 or 61 or 62 or 63 or 64 or 65 or 66 or 67 or 68 or 69 or 70 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or + 12 or 13 or 14 or 15 or 16 or 17 or 18 or 19 or 20 or 21 or 22 or 23 or 24 or 25 or 26 or 27 or 28 or 29 or 30 or 31 or 32 or 33 or 34 or 35 or 36 or 37 or 38 or 39 or 40 or 41 or + 42 or 43 or 44 or 45 or 46 or 47 or 48 or 49 or 50 or 51 or 52 or 53 or 54 or 55 or 56 or 57 or 58 or 59 or 60 or 61 or 62 or 63 or 64 or 65 or 66 or 67 or 68 or 69 or 70) +} diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineTest.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineTest.kt new file mode 100644 index 0000000000..d5b4f26d1b --- /dev/null +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/ManyLineTransformInLongLineTest.kt @@ -0,0 +1,213 @@ +package org.cqfn.diktat + +fun foo(){ + (1 + or 2 + or 3 + or 4 + or 5 + or 6 + or 7 + or 8 + or 9 + or 10 + or 11 + or 12 + or 13 + or 14 + or 15 + or 16 + or 17 + or 18 + or 19 + or 20 + or 21 + or 22 + or 23 + or 24 + or 25 + or 26 + or 27 + or 28 + or 29 + or 30 + or 31 + ?: 32 + or 33 + or 34 + or 35 + or 36 + or 37 + or 38 + or 39 ?: 40 + or 41 + or 42 + or 43 + or 44 + or 45 + or 46 + or 47 + or 48 + or 49 + or 50 + or 51 + or 52 + or 53 + + 54 + or 55 + or 56 + or 57 + or 58 + or 59 + ?: 60 + or 61 + or 62 + or 63 + or 64 + or 65 + or 66 + - 67 + or 68 + or 69 + or 70 + or 1 + + 2 + or 3 + or 4 + or 5 + or 6 + or 7 + or 8 + or 9 + or 10 + or 11 + or 12 + or 13 + or 14 + or 15 + or 16 + or 17 + or 18 + + 19 + - 20 + or 21 + or 22 + or 23 + or 24 + or 25 + or 26 + or 27 + or 28 + or 29 + or 30 + or 31 + or 32 + or 33 + or 34 + or 35 + or 36 + or 37 + or 38 + or 39 + or 40 + or 41 + || 42 + or 43 + or 44 + or 45 + or 46 + or 47 + && 48 + or 49 + || 50 + or 51 + or 52 + or 53 + or 54 + or 55 + or 56 + or 57 + or 58 + or 59 + or 60 + or 61 + or 62 + or 63 + or 64 + or 65 + or 66 + or 67 + or 68 + or 69 + or 70 + or 1 + or 2 + or 3 + or 4 + or 5 + or 6 + or 7 + or 8 + or 9 + or 10 + or 11 + or 12 + or 13 + or 14 + or 15 + or 16 + or 17 + or 18 + or 19 + or 20 + or 21 + or 22 + or 23 + or 24 + or 25 + or 26 + or 27 + or 28 + or 29 + or 30 + or 31 + or 32 + or 33 + or 34 + or 35 + or 36 + or 37 + or 38 + or 39 + or 40 + or 41 + or 42 + or 43 + or 44 + or 45 + or 46 + or 47 + or 48 + or 49 + or 50 + or 51 + or 52 + or 53 + or 54 + or 55 + or 56 + or 57 + or 58 + or 59 + or 60 + or 61 + or 62 + or 63 + or 64 + or 65 + or 66 + or 67 + or 68 + or 69 + or 70) +}