diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationConfigAware.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationConfigAware.kt index f27677b7ba..8ecf3c5504 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationConfigAware.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationConfigAware.kt @@ -61,16 +61,113 @@ internal interface IndentationConfigAware { operator fun Int.minus(amount: IndentationAmount): Int = unindent(level = amount.level()) + /** + * Allows the `+` operation between an Int and an IndentationAmount to be + * commutative. Now, the following are equivalent: + * + * ```kotlin + * val i = 42 + IndentationAmount.SINGLE + * val j = IndentationAmount.SINGLE + 42 + * ``` + * + * — as are these: + * + * ```kotlin + * val i = 42 + IndentationAmount.SINGLE + * val j = IndentationAmount.SINGLE + 42 + * ``` + * + * @receiver the indentation amount. + * @param indentationSpaces the indentation level (in space characters). + * @return the new (increased) indentation level. + * @see IndentationAmount.minus + */ + operator fun IndentationAmount.plus(indentationSpaces: Int): Int = + indentationSpaces + this + + /** + * Allows expressions like this: + * + * ```kotlin + * 42 - IndentationAmount.SINGLE + 4 + * ``` + * + * to be rewritten this way: + * + * ```kotlin + * 42 - (IndentationAmount.SINGLE - 4) + * ``` + * + * @receiver the indentation amount. + * @param indentationSpaces the indentation level (in space characters). + * @return the new (decreased) indentation level. + * @see IndentationAmount.plus + */ + operator fun IndentationAmount.minus(indentationSpaces: Int): Int = + this + (-indentationSpaces) + + /** + * @receiver the 1st term. + * @param other the 2nd term. + * @return the two indentation amounts combined, as the indentation level + * (in space characters). + * @see IndentationAmount.minus + */ + operator fun IndentationAmount.plus(other: IndentationAmount): Int = + this + (+other) + + /** + * @receiver the minuend. + * @param other the subtrahend. + * @return one amount subtracted from the other, as the indentation level + * (in space characters). + * @see IndentationAmount.plus + */ + operator fun IndentationAmount.minus(other: IndentationAmount): Int = + this + (-other) + + /** + * @receiver the indentation amount. + * @return the indentation level (in space characters). + * @see IndentationAmount.unaryMinus + */ + operator fun IndentationAmount.unaryPlus(): Int = + level() * configuration.indentationSize + + /** + * @receiver the indentation amount. + * @return the negated indentation level (in space characters). + * @see IndentationAmount.unaryPlus + */ + operator fun IndentationAmount.unaryMinus(): Int = + -(+this) + companion object Factory { /** * Creates a new instance. * + * While you may call this function directly, consider using + * [withIndentationConfig] instead. + * * @param configuration the configuration this instance will wrap. * @return the newly created instance. + * @see withIndentationConfig */ operator fun invoke(configuration: IndentationConfig): IndentationConfigAware = object : IndentationConfigAware { override val configuration = configuration } + + /** + * Calls the specified function [block] with [IndentationConfigAware] as + * its receiver and returns its result. + * + * @param configuration the indentation configuration. + * @param block the function block to call. + * @return the result returned by the function block. + */ + inline fun withIndentationConfig(configuration: IndentationConfig, + block: IndentationConfigAware.() -> T): T = + with(IndentationConfigAware(configuration), block) } } 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 8ce3841c91..b1ebd5c8ef 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 @@ -11,6 +11,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.NONE import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.SINGLE +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationConfigAware.Factory.withIndentationConfig import org.cqfn.diktat.ruleset.utils.NEWLINE import org.cqfn.diktat.ruleset.utils.SPACE import org.cqfn.diktat.ruleset.utils.TAB @@ -395,14 +396,14 @@ class IndentationRule(configRules: List) : DiktatRule( return } - with(IndentationConfigAware(configuration)) { + withIndentationConfig(configuration) { /* * A `REGULAR_STRING_PART`. */ val regularStringPart = templateEntry.firstChildNode as LeafPsiElement val regularStringPartText = regularStringPart.checkRegularStringPart().text // shift of the node depending on its initial string template indentation - val nodeStartIndent = (regularStringPartText.leadingSpaceCount() - actualIndentation).unindent().zeroIfNegative() + val nodeStartIndent = (regularStringPartText.leadingSpaceCount() - actualIndentation - SINGLE).zeroIfNegative() val isPrevStringTemplate = templateEntry.treePrev.elementType in stringLiteralTokens val isNextStringTemplate = templateEntry.treeNext.elementType in stringLiteralTokens @@ -417,7 +418,7 @@ class IndentationRule(configRules: List) : DiktatRule( // if string template is after literal_string // or if there is no string template in literal_string - else -> (expectedIndentation.indent() + nodeStartIndent).spaces + regularStringPartText.trimStart() + else -> (expectedIndentation + SINGLE + nodeStartIndent).spaces + regularStringPartText.trimStart() } regularStringPart.rawReplaceWithText(correctedText) 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 06f1ca314e..99ebd2458d 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,7 +5,7 @@ package org.cqfn.diktat.ruleset.utils.indentation import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount -import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationConfigAware +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.SINGLE import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationError import org.cqfn.diktat.ruleset.utils.hasParent import org.cqfn.diktat.ruleset.utils.lastIndent @@ -66,7 +66,7 @@ internal class AssignmentOperatorChecker(configuration: IndentationConfig) : Cus val prevNode = whiteSpace.prevSibling?.node if (prevNode?.elementType == EQ && prevNode.treeNext.let { it.elementType == WHITE_SPACE && it.textContains('\n') }) { return CheckResult.from(indentError.actual, (whiteSpace.parentIndent() - ?: indentError.expected) + (if (configuration.extendedIndentForExpressionBodies) 2 else 1) * configuration.indentationSize, true) + ?: indentError.expected) + IndentationAmount.valueOf(configuration.extendedIndentForExpressionBodies), true) } return null } @@ -121,7 +121,7 @@ internal class ValueParameterListChecker(configuration: IndentationConfig) : Cus } .let { (_, line) -> line.substringBefore(parameterAfterLpar.text).length } } else if (configuration.extendedIndentOfParameters) { - indentError.expected + configuration.indentationSize + indentError.expected + SINGLE } else { indentError.expected } @@ -137,16 +137,14 @@ internal class ValueParameterListChecker(configuration: IndentationConfig) : Cus */ internal class ExpressionIndentationChecker(configuration: IndentationConfig) : CustomIndentationChecker(configuration) { override fun checkNode(whiteSpace: PsiWhiteSpace, indentError: IndentationError): CheckResult? = - with(IndentationConfigAware(configuration)) { - when { - whiteSpace.parent.node.elementType == BINARY_EXPRESSION && whiteSpace.prevSibling.node.elementType == OPERATION_REFERENCE -> { - val parentIndent = whiteSpace.parentIndent() ?: indentError.expected - val expectedIndent = parentIndent + IndentationAmount.valueOf(configuration.extendedIndentAfterOperators) - CheckResult.from(indentError.actual, expectedIndent, true) - } - - else -> null + when { + whiteSpace.parent.node.elementType == BINARY_EXPRESSION && whiteSpace.prevSibling.node.elementType == OPERATION_REFERENCE -> { + val parentIndent = whiteSpace.parentIndent() ?: indentError.expected + val expectedIndent = parentIndent + IndentationAmount.valueOf(configuration.extendedIndentAfterOperators) + CheckResult.from(indentError.actual, expectedIndent, true) } + + else -> null } } @@ -176,10 +174,10 @@ internal class SuperTypeListChecker(config: IndentationConfig) : CustomIndentati .treePrev .takeIf { it.elementType == WHITE_SPACE } ?.textContains('\n') ?: false - val expectedIndent = indentError.expected + (if (hasNewlineBeforeColon) 2 else 1) * configuration.indentationSize + val expectedIndent = indentError.expected + IndentationAmount.valueOf(extendedIndent = hasNewlineBeforeColon) return CheckResult.from(indentError.actual, expectedIndent) } else if (whiteSpace.parent.node.elementType == SUPER_TYPE_LIST) { - val expectedIndent = whiteSpace.parentIndent() ?: (indentError.expected + configuration.indentationSize) + val expectedIndent = whiteSpace.parentIndent() ?: (indentError.expected + SINGLE) return CheckResult.from(indentError.actual, expectedIndent) } return null @@ -219,7 +217,7 @@ internal class DotCallChecker(config: IndentationConfig) : CustomIndentationChec } || nextNode.isCommentBeforeDot()) && whiteSpace.parents.none { it.node.elementType == LONG_STRING_TEMPLATE_ENTRY } } ?.let { node -> - val indentIncrement = (if (configuration.extendedIndentBeforeDot) 2 else 1) * configuration.indentationSize + val indentIncrement = IndentationAmount.valueOf(configuration.extendedIndentBeforeDot) if (node.isFromStringTemplate()) { return CheckResult.from(indentError.actual, indentError.expected + indentIncrement, true) @@ -266,7 +264,7 @@ internal class ConditionalsAndLoopsWithoutBracesChecker(config: IndentationConfi } .takeIf { it } ?.let { - CheckResult.from(indentError.actual, indentError.expected + configuration.indentationSize, true) + CheckResult.from(indentError.actual, indentError.expected + SINGLE, true) } } } @@ -279,7 +277,7 @@ internal class CustomGettersAndSettersChecker(config: IndentationConfig) : Custo val parent = whiteSpace.parent if (parent is KtProperty && whiteSpace.nextSibling is KtPropertyAccessor) { return CheckResult.from(indentError.actual, (parent.parentIndent() - ?: indentError.expected) + configuration.indentationSize, true) + ?: indentError.expected) + SINGLE, true) } return null } @@ -293,7 +291,7 @@ internal class ArrowInWhenChecker(configuration: IndentationConfig) : CustomInde val prevNode = whiteSpace.prevSibling?.node if (prevNode?.elementType == ARROW && whiteSpace.parent is KtWhenEntry) { return CheckResult.from(indentError.actual, (whiteSpace.parentIndent() - ?: indentError.expected) + configuration.indentationSize, true) + ?: indentError.expected) + SINGLE, true) } return null } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/CustomIndentationChecker.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/CustomIndentationChecker.kt index 8ab8c88a85..c1cae755ae 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/CustomIndentationChecker.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/CustomIndentationChecker.kt @@ -4,6 +4,7 @@ package org.cqfn.diktat.ruleset.utils.indentation +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationConfigAware import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationError import org.cqfn.diktat.ruleset.utils.NEWLINE @@ -12,7 +13,7 @@ import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace /** * @property configuration configuration of indentation rule */ -internal abstract class CustomIndentationChecker(protected val configuration: IndentationConfig) { +internal abstract class CustomIndentationChecker(override val configuration: IndentationConfig) : IndentationConfigAware { /** * This method checks if this white space is an exception from general rule * If true, checks if it is properly indented and fixes diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationConfigAwareTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationConfigAwareTest.kt new file mode 100644 index 0000000000..60a3048c9c --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationConfigAwareTest.kt @@ -0,0 +1,126 @@ +package org.cqfn.diktat.ruleset.chapter3.spaces + +import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.IndentationConfig +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.EXTENDED +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.NONE +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationAmount.SINGLE +import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationConfigAware.Factory.withIndentationConfig + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.MethodOrderer.DisplayName +import org.junit.jupiter.api.TestMethodOrder +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@TestMethodOrder(DisplayName::class) +class IndentationConfigAwareTest { + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `Int + IndentationAmount`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(42 + NONE).isEqualTo(42) + assertThat(42 + SINGLE).isEqualTo(42 + indentationSize) + assertThat(42 + EXTENDED).isEqualTo(42 + 2 * indentationSize) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `Int - IndentationAmount`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(42 - NONE).isEqualTo(42) + assertThat(42 - SINGLE).isEqualTo(42 - indentationSize) + assertThat(42 - EXTENDED).isEqualTo(42 - 2 * indentationSize) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `IndentationAmount + Int`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(NONE + 42).isEqualTo(42 + NONE) + assertThat(SINGLE + 42).isEqualTo(42 + SINGLE) + assertThat(EXTENDED + 42).isEqualTo(42 + EXTENDED) + + assertThat(42 + (SINGLE + 2)).isEqualTo((42 + SINGLE) + 2) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `IndentationAmount - Int`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(NONE - 42).isEqualTo(-(42 - NONE)) + assertThat(SINGLE - 42).isEqualTo(-(42 - SINGLE)) + assertThat(EXTENDED - 42).isEqualTo(-(42 - EXTENDED)) + + assertThat(42 - (SINGLE - 2)).isEqualTo(42 - SINGLE + 2) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `IndentationAmount + IndentationAmount`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(NONE + SINGLE).isEqualTo(0 + SINGLE) + assertThat(SINGLE + SINGLE).isEqualTo(0 + EXTENDED) + + assertThat(42 + SINGLE + SINGLE).isEqualTo(42 + EXTENDED) + assertThat(42 + (SINGLE + SINGLE)).isEqualTo(42 + EXTENDED) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun `IndentationAmount - IndentationAmount`(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(NONE - SINGLE).isEqualTo(0 - SINGLE) + assertThat(SINGLE - SINGLE).isEqualTo(0) + assertThat(EXTENDED - SINGLE).isEqualTo(0 + SINGLE) + assertThat(NONE - EXTENDED).isEqualTo(0 - EXTENDED) + + assertThat(42 + (SINGLE - SINGLE)).isEqualTo(42 + SINGLE - SINGLE) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun unaryPlus(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(+NONE).isEqualTo(0) + assertThat(+SINGLE).isEqualTo(indentationSize) + assertThat(+EXTENDED).isEqualTo(2 * indentationSize) + + assertThat(EXTENDED - SINGLE).isEqualTo(+SINGLE) + } + } + + @ParameterizedTest(name = "indentationSize = {0}") + @ValueSource(ints = [2, 4, 8]) + fun unaryMinus(indentationSize: Int) { + val config = IndentationConfig("indentationSize" to indentationSize) + + withIndentationConfig(config) { + assertThat(-NONE).isEqualTo(0) + assertThat(-SINGLE).isEqualTo(-indentationSize) + assertThat(-EXTENDED).isEqualTo(-2 * indentationSize) + + assertThat(NONE - SINGLE).isEqualTo(-SINGLE) + assertThat(NONE - EXTENDED).isEqualTo(-EXTENDED) + } + } +} 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 0b5759aab5..e7cbb8336d 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 @@ -21,7 +21,9 @@ internal object IndentationRuleTestMixin { */ @Suppress("TestFunctionName", "FUNCTION_NAME_INCORRECT_CASE") fun IndentationConfig(vararg configEntries: Pair): IndentationConfig = - IndentationConfig(mapOf(*configEntries).mapValues(Any::toString)) + IndentationConfig(mapOf(*configEntries).mapValues { (_, value) -> + value.toString() + }) /** * @param configEntries the optional values which override the state of this