Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/indentation of multiline string #1364

Merged
merged 19 commits into from
Jun 16, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.cqfn.diktat.ruleset.utils.indentation.SuperTypeListChecker
import org.cqfn.diktat.ruleset.utils.indentation.ValueParameterListChecker

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
import com.pinterest.ktlint.core.ast.ElementType.ELSE
import com.pinterest.ktlint.core.ast.ElementType.FILE
Expand All @@ -32,13 +33,15 @@ 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.OPEN_QUOTE
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.RBRACKET
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
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.WHITE_SPACE
import com.pinterest.ktlint.core.ast.visit
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
Expand Down Expand Up @@ -78,6 +81,7 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
}
private lateinit var filePath: String
private lateinit var customIndentationCheckers: List<CustomIndentationChecker>
private lateinit var positionByOffset: (Int) -> Pair<Int, Int>

override fun logic(node: ASTNode) {
if (node.elementType == FILE) {
Expand Down Expand Up @@ -173,10 +177,25 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
}
}

private fun isCloseAndOpenQuoterOffset(nodeWhiteSpace: ASTNode, expectedIndent: Int): Boolean {
val nextNode = nodeWhiteSpace.treeNext
if (nextNode.elementType == VALUE_ARGUMENT) {
val nextNodeDot = getNextDotExpression(nextNode)
nextNodeDot?.getFirstChildWithType(STRING_TEMPLATE)?.let {
if (it.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY).size > 1) {
val closingQuote = it.getFirstChildWithType(CLOSING_QUOTE)?.treePrev?.text
?.length ?: -1
return expectedIndent == closingQuote
}
}
}
return true
}

@Suppress("ForbiddenComment")
private fun visitWhiteSpace(astNode: ASTNode, context: IndentContext) {
context.maybeIncrement()

positionByOffset = astNode.treeParent.calculateLineColByOffset()
val whiteSpace = astNode.psi as PsiWhiteSpace
if (astNode.treeNext.elementType in decreasingTokens) {
// if newline is followed by closing token, it should already be indented less
Expand All @@ -199,8 +218,15 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
context.addException(astNode.treeParent, abs(indentError.expected - indentError.actual), false)
}

if (checkResult?.isCorrect != true && expectedIndent != indentError.actual) {
WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "expected $expectedIndent but was ${indentError.actual}",
val difOffsetCloseAndOpenQuote = isCloseAndOpenQuoterOffset(astNode, indentError.actual)

if ((checkResult?.isCorrect != true && expectedIndent != indentError.actual) || !difOffsetCloseAndOpenQuote) {
val warnText = if (!difOffsetCloseAndOpenQuote){
Fixed Show fixed Hide fixed
"the same number of indents to the opening and closing quotes was expected"
} else {
"expected $expectedIndent but was ${indentError.actual}"
}
WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, warnText,
whiteSpace.startOffset + whiteSpace.text.lastIndexOf('\n') + 1, whiteSpace.node) {
checkStringLiteral(whiteSpace, expectedIndent, indentError.actual)
whiteSpace.node.indentBy(expectedIndent)
Expand All @@ -216,16 +242,16 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
expectedIndent: Int,
actualIndent: Int
) {
val nextNode = whiteSpace.node.treeNext
if (nextNode != null &&
nextNode.elementType == DOT_QUALIFIED_EXPRESSION &&
nextNode.firstChildNode.elementType == STRING_TEMPLATE &&
nextNode.firstChildNode.text.startsWith("\"\"\"") &&
nextNode.findChildByType(CALL_EXPRESSION)?.text?.let {
val nextNodeDot = getNextDotExpression(whiteSpace.node.treeNext)
if (nextNodeDot != null &&
nextNodeDot.elementType == DOT_QUALIFIED_EXPRESSION &&
nextNodeDot.firstChildNode.elementType == STRING_TEMPLATE &&
nextNodeDot.firstChildNode.text.startsWith("\"\"\"") &&
nextNodeDot.findChildByType(CALL_EXPRESSION)?.text?.let {
it == "trimIndent()" ||
it == "trimMargin()"
} == true) {
fixStringLiteral(whiteSpace, expectedIndent, actualIndent)
fixStringLiteral(nextNodeDot.firstChildNode, expectedIndent, actualIndent)
}
}

Expand All @@ -234,15 +260,12 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
*/
@Suppress("LOCAL_VARIABLE_EARLY_DECLARATION")
private fun fixStringLiteral(
whiteSpace: PsiWhiteSpace,
stringTemplate: ASTNode,
expectedIndent: Int,
actualIndent: Int
) {
val textIndent = " ".repeat(expectedIndent + INDENT_SIZE)
val templateEntries = whiteSpace.node
.treeNext
.firstChildNode
.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY)
val templateEntries = stringTemplate.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY)
templateEntries.forEach { node ->
if (!node.text.contains("\n")) {
fixFirstTemplateEntries(node, textIndent, actualIndent)
Expand All @@ -256,6 +279,12 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
.trim())
}

private fun getNextDotExpression(node:ASTNode) = if (node.elementType == DOT_QUALIFIED_EXPRESSION) {
Fixed Show fixed Hide fixed
node
} else {
node.getFirstChildWithType(DOT_QUALIFIED_EXPRESSION)
}

/**
* This method fixes all lines of string template except the last one
* Also it considers $foo insertions in string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ class IndentationRuleFixTest : FixTestBase("test/paragraph3/indentation",
fixAndCompare("ConstructorExpected.kt", "ConstructorTest.kt")
}

@Test
@Tag(WarningNames.WRONG_INDENTATION)
fun `multiline string`() {
fixAndCompare("MultilionStringExpected.kt", "MultilionStringTest.kt")
}

/**
* This test has a counterpart under [IndentationRuleWarnTest].
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package test.paragraph3.indentation

//test of correct opening quotation mark and incorrect closing quotation mark
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark1
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark2
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark with incorrect shift
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package test.paragraph3.indentation

//test of correct opening quotation mark and incorrect closing quotation mark
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark1
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark2
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}

//test of incorrect opening quotation mark and incorrect closing quotation mark with incorrect shift
fun multilionString() {
lintMethod(
"""
|val q = 1
|
""".trimMargin(),
fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts"
)
}