Repository: cqfn/diKTat (kotlin)

Issue:
`WRONG_INDENTATION`: false positives for parentheses-surrounded infix expressions
The following code fragment triggers no errors, provided that `expectedIndentAfterOperators` is **off**:

```kotlin
val value = 1 to
    2
```

Now, let's surround the expression (which is an infix function call) with parentheses, preserving the indent:

```kotlin
val value = (1 to
    2)
```

_DiKTat_ will report the following errors:

```
[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 8 but was 4
[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 8 but was 4
```

Strangely, if we move the expression onto the next line (and increment the indent), the errors immediately go away:

```kotlin
val value =
    (1 to
        2)
```

The issue is caused by the incomplete implementation of `ExpressionIndentationChecker` as a part of #75 (see 7d913ed0).

> **Note**
> 
> The fix for this issue will also have the following side-effect. Previously (1.2.0), _DiKTat_ would format expressions wrapped _before_ an infix function (see also #1340) like this:
>
> ```kotlin
> fun f() {
>     true
>     || false
> }
> ```
> &mdash; unless those expressions are surrounded with parentheses, requiring an extra indent:
> ```kotlin
> fun f() {
>     (true
>         || false)
> }
> ```
> From now on (1.2.1+), no extra indent will be required:
> ```kotlin
> fun f() {
>     (true
>     || false)
> }
> ```
> &mdash; which is still incorrect, but will be fixed as a part of #1340.

## Environment information
* diktat version: 1.2.0
* build tool (maven/gradle): Maven
* how is diktat run (CLI, plugin, etc.): Maven plug-in
* kotlin version: 1.7.0
* operating system: Windows

Pull request description:
### What's done:

 * From now on, parentheses (`LPAR`/`RPAR`) don't increment or decrement the
   indent unless they're a part of a function declaration or a function call.
 * Parentheses which just enclose an expression don't affect the indent at all.
 * This fixes #1409.

## Actions checklist
* [X] Added tests on checks
* [X] Added tests on fixers

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt
index c3b9a3efc0..54610c1b5e 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt
@@ -99,8 +99,11 @@ class StringTemplateFormatRule(configRules: List<RulesConfig>) : DiktatRule(
             (!(node.treeNext
                 .text
                 .first()
-                .isLetterOrDigit()  // checking if first letter is valid
-                || node.treeNext.text.startsWith("_")) || node.treeNext.elementType == CLOSING_QUOTE)
+                // checking if first letter is valid
+                .isLetterOrDigit() ||
+                node.treeNext.text.startsWith("_")) ||
+                node.treeNext.elementType == CLOSING_QUOTE
+            )
         } else if (!isArrayAccessExpression) {
             node.hasAnyChildOfTypes(FLOAT_CONSTANT, INTEGER_CONSTANT)  // it also fixes "${1.0}asd" cases
         } else {
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 745c5e0315..a876b82be4 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
@@ -32,6 +32,7 @@ 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.BINARY_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.CLOSING_QUOTE
 import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
@@ -45,6 +46,7 @@ import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY
 import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_END
 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.PARENTHESIZED
 import com.pinterest.ktlint.core.ast.ElementType.RBRACE
 import com.pinterest.ktlint.core.ast.ElementType.RBRACKET
 import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
@@ -55,7 +57,10 @@ import com.pinterest.ktlint.core.ast.ElementType.SHORT_STRING_TEMPLATE_ENTRY
 import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE
 import com.pinterest.ktlint.core.ast.ElementType.THEN
 import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
+import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
+import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
+import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
 import com.pinterest.ktlint.core.ast.visit
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
@@ -73,6 +78,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
 import org.slf4j.LoggerFactory
 
 import kotlin.math.abs
+import kotlin.reflect.KCallable
 
 /**
  * Rule that checks indentation. The following general rules are checked:
@@ -176,9 +182,9 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
         val context = IndentContext(configuration)
         node.visit { astNode ->
             context.checkAndReset(astNode)
-            if (astNode.elementType in increasingTokens) {
+            if (astNode.isIndentIncrementing()) {
                 context.storeIncrementingToken(astNode.elementType)
-            } else if (astNode.elementType in decreasingTokens && !astNode.treePrev.let { it.elementType == WHITE_SPACE && it.textContains(NEWLINE) }) {
+            } else if (astNode.isIndentDecrementing() && !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(NEWLINE) && astNode.treeNext != null) {
@@ -208,7 +214,7 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
         context.maybeIncrement()
         positionByOffset = astNode.treeParent.calculateLineColByOffset()
         val whiteSpace = astNode.psi as PsiWhiteSpace
-        if (astNode.treeNext.elementType in decreasingTokens) {
+        if (astNode.treeNext.isIndentDecrementing()) {
             // if newline is followed by closing token, it should already be indented less
             context.dec(astNode.treeNext.elementType)
         }
@@ -493,7 +499,7 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
                 }
                 regularIndent -= config.indentationSize
             }
-            if (activeTokens.isNotEmpty() && activeTokens.peek() == matchingTokens.find { it.second == token }?.first) {
+            if (activeTokens.isNotEmpty() && activeTokens.peek() == token.braceMatchOrNull()) {
                 activeTokens.pop()
             }
         }
@@ -546,11 +552,20 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
     companion object {
         private val log = LoggerFactory.getLogger(IndentationRule::class.java)
         const val NAME_ID = "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 increasingTokens: Set<IElementType> = linkedSetOf(LPAR, LBRACE, LBRACKET, LONG_TEMPLATE_ENTRY_START)
+        private val decreasingTokens: Set<IElementType> = linkedSetOf(RPAR, RBRACE, RBRACKET, LONG_TEMPLATE_ENTRY_END)
+
+        /**
+         * This is essentially a bi-map, which allows to look up a closing brace
+         * type by an opening brace type, or vice versa.
+         */
+        private val matchingTokens = (increasingTokens.asSequence() zip decreasingTokens.asSequence()).flatMap { (opening, closing) ->
+            sequenceOf(opening to closing, closing to opening)
+        }.toMap()
         private val stringLiteralTokens = listOf(SHORT_STRING_TEMPLATE_ENTRY, LONG_STRING_TEMPLATE_ENTRY)
-        private val knownTrimFunctionPatterns = setOf("trimIndent", "trimMargin")
+        private val knownTrimFunctionPatterns = sequenceOf(String::trimIndent, String::trimMargin)
+            .map(KCallable<String>::name)
+            .toSet()
 
         /**
          * @return a string which consists of `N` [space][SPACE] characters.
@@ -560,6 +575,87 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
             get() =
                 SPACE.toString().repeat(n = this)
 
+        /**
+         * @return `true` if the indent should be incremented once this node is
+         *   encountered, `false` otherwise.
+         * @see isIndentDecrementing
+         */
+        private fun ASTNode.isIndentIncrementing(): Boolean =
+            when (elementType) {
+                /*
+                 * This is a special case of an opening parenthesis which *may* or
+                 * *may not* be indent-incrementing.
+                 */
+                LPAR -> isParenthesisAffectingIndent()
+
+                else -> elementType in increasingTokens
+            }
+
+        /**
+         * @return `true` if the indent should be decremented once this node is
+         *   encountered, `false` otherwise.
+         * @see isIndentIncrementing
+         */
+        private fun ASTNode.isIndentDecrementing(): Boolean =
+            when (elementType) {
+                /*
+                 * This is a special case of a closing parenthesis which *may* or
+                 * *may not* be indent-decrementing.
+                 */
+                RPAR -> isParenthesisAffectingIndent()
+
+                else -> elementType in decreasingTokens
+            }
+
+        /**
+         * Parentheses always affect indent when they're a part of a
+         * [VALUE_PARAMETER_LIST] (formal arguments) or a [VALUE_ARGUMENT_LIST]
+         * (effective function call arguments).
+         *
+         * When they're children of a [PARENTHESIZED] (often inside a
+         * [BINARY_EXPRESSION]), contribute to the indent depending on whether
+         * there's a newline after the opening parenthesis.
+         *
+         * @return whether this [LPAR] or [RPAR] node affects indent.
+         * @see BINARY_EXPRESSION
+         * @see PARENTHESIZED
+         * @see VALUE_ARGUMENT_LIST
+         * @see VALUE_PARAMETER_LIST
+         */
+        private fun ASTNode.isParenthesisAffectingIndent(): Boolean {
+            require(elementType in arrayOf(LPAR, RPAR)) {
+                elementType.toString()
+            }
+
+            return when (treeParent.elementType) {
+                PARENTHESIZED -> when (elementType) {
+                    /*
+                     * `LPAR` inside a binary expression only contributes to the
+                     * indent if it's immediately followed by a newline.
+                     */
+                    LPAR -> treeNext.isWhiteSpaceWithNewline()
+
+                    /*
+                     * `RPAR` inside a binary expression affects the indent only
+                     * if its matching `LPAR` node does so.
+                     */
+                    else -> {
+                        val openingParenthesis = elementType.braceMatchOrNull()?.let { braceMatch ->
+                            treeParent.findChildByType(braceMatch)
+                        }
+                        openingParenthesis?.isParenthesisAffectingIndent() ?: false
+                    }
+                }
+
+                /*
+                 * Either a control-flow statement (one of IF, WHEN, FOR or
+                 * DO_WHILE), a function declaration (VALUE_PARAMETER_LIST or
+                 * PROPERTY_ACCESSOR), or a function call (VALUE_ARGUMENT_LIST).
+                 */
+                else -> true
+            }
+        }
+
         /**
          * @return `true` if this is a [String.trimIndent] or [String.trimMargin]
          * call, `false` otherwise.
@@ -584,6 +680,13 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
             return identifier.text in knownTrimFunctionPatterns
         }
 
+        /**
+         * @return the matching closing brace type for this opening brace type,
+         *   or vice versa.
+         */
+        private fun IElementType.braceMatchOrNull(): IElementType? =
+            matchingTokens[this]
+
         /**
          * Checks this [REGULAR_STRING_PART] child of a [LITERAL_STRING_TEMPLATE_ENTRY].
          *


Test patch:
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 671b12d262..2117f95773 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
@@ -4,15 +4,14 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.IndentationConfig
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.asRulesConfigList
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.asSequenceWithConcatenation
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.assertNotNull
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.describe
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.extendedIndent
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.withCustomParameters
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctionsContinuationIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctionsSingleIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperatorContinuationIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperatorSingleIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.whitespaceInStringLiteralsContinuationIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.whitespaceInStringLiteralsSingleIndent
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctions
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperator
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.parenthesesSurroundedInfixExpressions
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.whitespaceInStringLiterals
 import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION
 import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule
 import org.cqfn.diktat.util.FixTestBase
@@ -127,13 +126,8 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionBodyFunctions = when {
-                extendedIndentAfterOperators -> expressionBodyFunctionsContinuationIndent
-                else -> expressionBodyFunctionsSingleIndent
-            }
-
             lintMultipleMethods(
-                expressionBodyFunctions,
+                expressionBodyFunctions[extendedIndentAfterOperators].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
@@ -145,18 +139,9 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionBodyFunctionsActual = when {
-                extendedIndentAfterOperators -> expressionBodyFunctionsSingleIndent
-                else -> expressionBodyFunctionsContinuationIndent
-            }
-            val expressionBodyFunctionsExpected = when {
-                extendedIndentAfterOperators -> expressionBodyFunctionsContinuationIndent
-                else -> expressionBodyFunctionsSingleIndent
-            }
-
             lintMultipleMethods(
-                actualContent = expressionBodyFunctionsActual,
-                expectedContent = expressionBodyFunctionsExpected,
+                actualContent = expressionBodyFunctions[!extendedIndentAfterOperators].assertNotNull(),
+                expectedContent = expressionBodyFunctions[extendedIndentAfterOperators].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
@@ -175,13 +160,8 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters(*extendedIndent(enabled = extendedIndent))
 
-            val whitespaceInStringLiterals = when {
-                extendedIndent -> whitespaceInStringLiteralsContinuationIndent
-                else -> whitespaceInStringLiteralsSingleIndent
-            }
-
             lintMultipleMethods(
-                whitespaceInStringLiterals,
+                whitespaceInStringLiterals[extendedIndent].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
@@ -193,18 +173,9 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters(*extendedIndent(enabled = extendedIndent))
 
-            val whitespaceInStringLiteralsActual = when {
-                extendedIndent -> whitespaceInStringLiteralsSingleIndent
-                else -> whitespaceInStringLiteralsContinuationIndent
-            }
-            val whitespaceInStringLiteralsExpected = when {
-                extendedIndent -> whitespaceInStringLiteralsContinuationIndent
-                else -> whitespaceInStringLiteralsSingleIndent
-            }
-
             lintMultipleMethods(
-                actualContent = whitespaceInStringLiteralsActual,
-                expectedContent = whitespaceInStringLiteralsExpected,
+                actualContent = whitespaceInStringLiterals[!extendedIndent].assertNotNull(),
+                expectedContent = whitespaceInStringLiterals[extendedIndent].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
@@ -223,13 +194,8 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionsWrappedAfterOperator = when {
-                extendedIndentAfterOperators -> expressionsWrappedAfterOperatorContinuationIndent
-                else -> expressionsWrappedAfterOperatorSingleIndent
-            }
-
             lintMultipleMethods(
-                expressionsWrappedAfterOperator,
+                expressionsWrappedAfterOperator[extendedIndentAfterOperators].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
@@ -241,18 +207,43 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionsWrappedAfterOperatorActual = when {
-                extendedIndentAfterOperators -> expressionsWrappedAfterOperatorSingleIndent
-                else -> expressionsWrappedAfterOperatorContinuationIndent
-            }
-            val expressionsWrappedAfterOperatorExpected = when {
-                extendedIndentAfterOperators -> expressionsWrappedAfterOperatorContinuationIndent
-                else -> expressionsWrappedAfterOperatorSingleIndent
-            }
+            lintMultipleMethods(
+                actualContent = expressionsWrappedAfterOperator[!extendedIndentAfterOperators].assertNotNull(),
+                expectedContent = expressionsWrappedAfterOperator[extendedIndentAfterOperators].assertNotNull(),
+                tempDir = tempDir,
+                rulesConfigList = customConfig.asRulesConfigList())
+        }
+    }
+
+    /**
+     * See [#1409](https://github.com/saveourtool/diktat/issues/1409).
+     */
+    @Nested
+    @TestMethodOrder(DisplayName::class)
+    inner class `Parentheses-surrounded infix expressions` {
+        @ParameterizedTest(name = "extendedIndentAfterOperators = {0}")
+        @ValueSource(booleans = [false, true])
+        @Tag(WarningNames.WRONG_INDENTATION)
+        fun `should be properly indented`(extendedIndentAfterOperators: Boolean, @TempDir tempDir: Path) {
+            val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+            val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
+
+            lintMultipleMethods(
+                parenthesesSurroundedInfixExpressions[extendedIndentAfterOperators].assertNotNull(),
+                tempDir = tempDir,
+                rulesConfigList = customConfig.asRulesConfigList())
+        }
+
+        @ParameterizedTest(name = "extendedIndentAfterOperators = {0}")
+        @ValueSource(booleans = [false, true])
+        @Tag(WarningNames.WRONG_INDENTATION)
+        fun `should be reformatted if mis-indented`(extendedIndentAfterOperators: Boolean, @TempDir tempDir: Path) {
+            val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+            val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
             lintMultipleMethods(
-                actualContent = expressionsWrappedAfterOperatorActual,
-                expectedContent = expressionsWrappedAfterOperatorExpected,
+                actualContent = parenthesesSurroundedInfixExpressions[!extendedIndentAfterOperators].assertNotNull(),
+                expectedContent = parenthesesSurroundedInfixExpressions[extendedIndentAfterOperators].assertNotNull(),
                 tempDir = tempDir,
                 rulesConfigList = customConfig.asRulesConfigList())
         }
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 81fcb2df33..77a9065ced 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
@@ -110,4 +110,18 @@ internal object IndentationRuleTestMixin {
             else -> "\"$first\u2026\" ($count line(s))"
         }
     }
+
+    /**
+     * Casts a nullable value to a non-`null` one, similarly to the `!!`
+     * operator.
+     *
+     * @return a non-`null` value.
+     */
+    fun <T> T?.assertNotNull(): T {
+        check(this != null) {
+            "Expecting actual not to be null"
+        }
+
+        return this
+    }
 }
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
index a4b9005deb..d1f97b6a74 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleTestResources.kt
@@ -17,7 +17,7 @@ internal object IndentationRuleTestResources {
      * @see expressionBodyFunctionsContinuationIndent
      */
     @Language("kotlin")
-    val expressionBodyFunctionsSingleIndent: Array<String> = arrayOf(
+    private val expressionBodyFunctionsSingleIndent = arrayOf(
         """
         |@Test
         |fun `checking that suppression with ignore everything works`() {
@@ -91,6 +91,17 @@ internal object IndentationRuleTestResources {
         |fun foo() =
         |    println()
         """.trimMargin(),
+
+        """
+        |fun f() = x + (y +
+        |    g(x)
+        |)
+        """.trimMargin(),
+
+        """
+        |fun f() = (1 +
+        |    2)
+        """.trimMargin(),
     )
 
     /**
@@ -99,7 +110,7 @@ internal object IndentationRuleTestResources {
      * @see expressionBodyFunctionsSingleIndent
      */
     @Language("kotlin")
-    val expressionBodyFunctionsContinuationIndent: Array<String> = arrayOf(
+    private val expressionBodyFunctionsContinuationIndent = arrayOf(
         """
         |@Test
         |fun `checking that suppression with ignore everything works`() {
@@ -173,15 +184,33 @@ internal object IndentationRuleTestResources {
         |fun foo() =
         |        println()
         """.trimMargin(),
+
+        """
+        |fun f() = x + (y +
+        |        g(x)
+        |)
+        """.trimMargin(),
+
+        """
+        |fun f() = (1 +
+        |        2)
+        """.trimMargin(),
     )
 
+    /**
+     * See [#1330](https://github.com/saveourtool/diktat/issues/1330).
+     */
+    val expressionBodyFunctions = mapOf(
+        false to expressionBodyFunctionsSingleIndent,
+        true to expressionBodyFunctionsContinuationIndent)
+
     /**
      * See [#1347](https://github.com/saveourtool/diktat/issues/1347).
      *
      * @see whitespaceInStringLiteralsContinuationIndent
      */
     @Language("kotlin")
-    val whitespaceInStringLiteralsSingleIndent: Array<String> = arrayOf(
+    private val whitespaceInStringLiteralsSingleIndent = arrayOf(
         """
         |@Test
         |fun `test method name`() {
@@ -256,7 +285,7 @@ internal object IndentationRuleTestResources {
      * @see whitespaceInStringLiteralsSingleIndent
      */
     @Language("kotlin")
-    val whitespaceInStringLiteralsContinuationIndent: Array<String> = arrayOf(
+    private val whitespaceInStringLiteralsContinuationIndent = arrayOf(
         """
         |@Test
         |fun `test method name`() {
@@ -325,6 +354,13 @@ internal object IndentationRuleTestResources {
         """.trimMargin(),
     )
 
+    /**
+     * See [#1347](https://github.com/saveourtool/diktat/issues/1347).
+     */
+    val whitespaceInStringLiterals = mapOf(
+        false to whitespaceInStringLiteralsSingleIndent,
+        true to whitespaceInStringLiteralsContinuationIndent)
+
     /**
      * Expressions wrapped on an operator or an infix function, single indent
      * (`extendedIndentAfterOperators` is **off**).
@@ -337,7 +373,7 @@ internal object IndentationRuleTestResources {
      * @see expressionsWrappedAfterOperatorContinuationIndent
      */
     @Language("kotlin")
-    val expressionsWrappedAfterOperatorSingleIndent: Array<String> = arrayOf(
+    private val expressionsWrappedAfterOperatorSingleIndent = arrayOf(
         """
         |fun f() {
         |    systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or
@@ -422,6 +458,12 @@ internal object IndentationRuleTestResources {
         |    3
         """.trimMargin(),
 
+        """
+        |val value1a = (1 to
+        |    2 to
+        |    3)
+        """.trimMargin(),
+
         """
         |val value2 =
         |    1 to
@@ -568,7 +610,7 @@ internal object IndentationRuleTestResources {
      * @see expressionsWrappedAfterOperatorSingleIndent
      */
     @Language("kotlin")
-    val expressionsWrappedAfterOperatorContinuationIndent: Array<String> = arrayOf(
+    private val expressionsWrappedAfterOperatorContinuationIndent = arrayOf(
         """
         |fun f() {
         |    systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or
@@ -653,6 +695,12 @@ internal object IndentationRuleTestResources {
         |        3
         """.trimMargin(),
 
+        """
+        |val value1a = (1 to
+        |        2 to
+        |        3)
+        """.trimMargin(),
+
         """
         |val value2 =
         |        1 to
@@ -786,4 +834,148 @@ internal object IndentationRuleTestResources {
         |                    3))
         """.trimMargin(),
     )
+
+    /**
+     * Expressions wrapped on an operator or an infix function.
+     *
+     * See [#1340](https://github.com/saveourtool/diktat/issues/1340).
+     */
+    val expressionsWrappedAfterOperator = mapOf(
+        false to expressionsWrappedAfterOperatorSingleIndent,
+        true to expressionsWrappedAfterOperatorContinuationIndent)
+
+    /**
+     * Parenthesized expressions, single indent
+     * (`extendedIndentAfterOperators` is **off**).
+     *
+     * When adding new code fragments to this list, be sure to also add their
+     * counterparts (preserving order) to
+     * [parenthesesSurroundedInfixExpressionsContinuationIndent].
+     *
+     * See [#1409](https://github.com/saveourtool/diktat/issues/1409).
+     *
+     * @see parenthesesSurroundedInfixExpressionsContinuationIndent
+     */
+    @Language("kotlin")
+    private val parenthesesSurroundedInfixExpressionsSingleIndent = arrayOf(
+        """
+        |fun f1() = (
+        |    1 + 2
+        |)
+        """.trimMargin(),
+
+        """
+        |fun f2() = (
+        |    1 + 2)
+        """.trimMargin(),
+
+        """
+        |fun f3() =
+        |    (
+        |        1 + 2
+        |    )
+        """.trimMargin(),
+
+        """
+        |fun f4() =
+        |    (
+        |        1 + 2)
+        """.trimMargin(),
+
+        """
+        |const val v1 = (
+        |    1 + 2
+        |)
+        """.trimMargin(),
+
+        """
+        |const val v2 = (
+        |    1 + 2)
+        """.trimMargin(),
+
+        """
+        |const val v3 =
+        |    (
+        |        1 + 2
+        |    )
+        """.trimMargin(),
+
+        """
+        |const val v4 =
+        |    (
+        |        1 + 2)
+        """.trimMargin(),
+    )
+
+    /**
+     * Parenthesized expressions, continuation indent
+     * (`extendedIndentAfterOperators` is **on**).
+     *
+     * When adding new code fragments to this list, be sure to also add their
+     * counterparts (preserving order) to
+     * [parenthesesSurroundedInfixExpressionsSingleIndent].
+     *
+     * See [#1409](https://github.com/saveourtool/diktat/issues/1409).
+     *
+     * @see parenthesesSurroundedInfixExpressionsSingleIndent
+     */
+    @Language("kotlin")
+    private val parenthesesSurroundedInfixExpressionsContinuationIndent = arrayOf(
+        """
+        |fun f1() = (
+        |    1 + 2
+        |)
+        """.trimMargin(),
+
+        """
+        |fun f2() = (
+        |    1 + 2)
+        """.trimMargin(),
+
+        """
+        |fun f3() =
+        |        (
+        |            1 + 2
+        |        )
+        """.trimMargin(),
+
+        """
+        |fun f4() =
+        |        (
+        |            1 + 2)
+        """.trimMargin(),
+
+        """
+        |const val v1 = (
+        |    1 + 2
+        |)
+        """.trimMargin(),
+
+        """
+        |const val v2 = (
+        |    1 + 2)
+        """.trimMargin(),
+
+        """
+        |const val v3 =
+        |        (
+        |            1 + 2
+        |        )
+        """.trimMargin(),
+
+        """
+        |const val v4 =
+        |        (
+        |            1 + 2)
+        """.trimMargin(),
+    )
+
+    /**
+     * Parenthesized expressions.
+     *
+     * See [#1409](https://github.com/saveourtool/diktat/issues/1409).
+     */
+    val parenthesesSurroundedInfixExpressions = mapOf(
+        false to parenthesesSurroundedInfixExpressionsSingleIndent,
+        true to parenthesesSurroundedInfixExpressionsContinuationIndent)
 }
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 9885a29254..f5037e4f57 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
@@ -5,12 +5,12 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.IndentationConfig
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.asRulesConfigList
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.asSequenceWithConcatenation
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.assertNotNull
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.describe
 import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestMixin.withCustomParameters
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctionsContinuationIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctionsSingleIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperatorContinuationIndent
-import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperatorSingleIndent
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionBodyFunctions
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.expressionsWrappedAfterOperator
+import org.cqfn.diktat.ruleset.chapter3.spaces.IndentationRuleTestResources.parenthesesSurroundedInfixExpressions
 import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION
 import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule
 import org.cqfn.diktat.util.LintTestBase
@@ -825,13 +825,8 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionBodyFunctions = when {
-                extendedIndentAfterOperators -> expressionBodyFunctionsContinuationIndent
-                else -> expressionBodyFunctionsSingleIndent
-            }
-
             lintMultipleMethods(
-                expressionBodyFunctions,
+                expressionBodyFunctions[extendedIndentAfterOperators].assertNotNull(),
                 lintErrors = emptyArray(),
                 customConfig.asRulesConfigList()
             )
@@ -844,13 +839,8 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionBodyFunctions = when {
-                extendedIndentAfterOperators -> expressionBodyFunctionsSingleIndent
-                else -> expressionBodyFunctionsContinuationIndent
-            }
-
             assertSoftly { softly ->
-                expressionBodyFunctions.forEach { code ->
+                expressionBodyFunctions[!extendedIndentAfterOperators].assertNotNull().forEach { code ->
                     softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
                         .describedAs("lint result for ${code.describe()}")
                         .isNotEmpty
@@ -877,13 +867,8 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionsWrappedAfterOperator = when {
-                extendedIndentAfterOperators -> expressionsWrappedAfterOperatorContinuationIndent
-                else -> expressionsWrappedAfterOperatorSingleIndent
-            }
-
             lintMultipleMethods(
-                expressionsWrappedAfterOperator,
+                expressionsWrappedAfterOperator[extendedIndentAfterOperators].assertNotNull(),
                 lintErrors = emptyArray(),
                 customConfig.asRulesConfigList()
             )
@@ -896,13 +881,8 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
             val defaultConfig = IndentationConfig("newlineAtEnd" to false)
             val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
 
-            val expressionsWrappedAfterOperator = when {
-                extendedIndentAfterOperators -> expressionsWrappedAfterOperatorSingleIndent
-                else -> expressionsWrappedAfterOperatorContinuationIndent
-            }
-
             assertSoftly { softly ->
-                expressionsWrappedAfterOperator.forEach { code ->
+                expressionsWrappedAfterOperator[!extendedIndentAfterOperators].assertNotNull().forEach { code ->
                     softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
                         .describedAs("lint result for ${code.describe()}")
                         .isNotEmpty
@@ -915,4 +895,45 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
             }
         }
     }
+
+    /**
+     * See [#1409](https://github.com/saveourtool/diktat/issues/1409).
+     */
+    @Nested
+    @TestMethodOrder(DisplayName::class)
+    inner class `Parentheses-surrounded infix expressions` {
+        @ParameterizedTest(name = "extendedIndentAfterOperators = {0}")
+        @ValueSource(booleans = [false, true])
+        @Tag(WarningNames.WRONG_INDENTATION)
+        fun `should be properly indented`(extendedIndentAfterOperators: Boolean) {
+            val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+            val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
+
+            lintMultipleMethods(
+                parenthesesSurroundedInfixExpressions[extendedIndentAfterOperators].assertNotNull(),
+                lintErrors = emptyArray(),
+                customConfig.asRulesConfigList()
+            )
+        }
+
+        @ParameterizedTest(name = "extendedIndentAfterOperators = {0}")
+        @ValueSource(booleans = [false, true])
+        @Tag(WarningNames.WRONG_INDENTATION)
+        fun `should be reported if mis-indented`(extendedIndentAfterOperators: Boolean) {
+            val defaultConfig = IndentationConfig("newlineAtEnd" to false)
+            val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to extendedIndentAfterOperators)
+
+            assertSoftly { softly ->
+                parenthesesSurroundedInfixExpressions[!extendedIndentAfterOperators].assertNotNull().forEach { code ->
+                    softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
+                        .describedAs("lint result for ${code.describe()}")
+                        .hasSizeBetween(0, 3).allSatisfy(Consumer { lintError ->
+                            assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId)
+                            assertThat(lintError.canBeAutoCorrected).describedAs("canBeAutoCorrected").isTrue
+                            assertThat(lintError.detail).matches(warnTextRegex)
+                        })
+                }
+            }
+        }
+    }
 }