Skip to content

Commit

Permalink
Fix replace of redundant curly braces (#2617)
Browse files Browse the repository at this point in the history
Due to incorrect construction of the string template, it could happen that an import is removed as it was falsely flagged as unused as the "$" was added as prefix in the identifier.
  • Loading branch information
paul-dingemans authored Apr 6, 2024
1 parent 774a29f commit 9234dec
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLOSING_QUOTE
import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.LITERAL_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_STRING_TEMPLATE_ENTRY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_TEMPLATE_ENTRY_END
import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_TEMPLATE_ENTRY_START
import com.pinterest.ktlint.rule.engine.core.api.ElementType.REGULAR_STRING_PART
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.psi.KtThisExpression
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType

@SinceKtlint("0.9", STABLE)
public class StringTemplateRule : StandardRule("string-template") {
Expand All @@ -37,7 +42,7 @@ public class StringTemplateRule : StandardRule("string-template") {
// node.treeParent.treeParent.replaceChild(node.treeParent, entryStart.nextSibling.node)
// }
if (elementType == LONG_STRING_TEMPLATE_ENTRY) {
var entryExpression = (node.psi as? KtBlockStringTemplateEntry)?.expression
val entryExpression = (node.psi as? KtBlockStringTemplateEntry)?.expression
if (entryExpression is KtDotQualifiedExpression) {
val receiver = entryExpression.receiverExpression
if (entryExpression.selectorExpression?.text == "toString()" && receiver !is KtSuperExpression) {
Expand Down Expand Up @@ -84,14 +89,9 @@ public class StringTemplateRule : StandardRule("string-template") {
if (leftCurlyBraceNode != null && rightCurlyBraceNode != null) {
removeChild(leftCurlyBraceNode)
removeChild(rightCurlyBraceNode)
val remainingNode = firstChildNode
val newNode =
if (remainingNode.elementType == DOT_QUALIFIED_EXPRESSION) {
LeafPsiElement(REGULAR_STRING_PART, "\$${remainingNode.text}")
} else {
LeafPsiElement(remainingNode.elementType, "\$${remainingNode.text}")
}
replaceChild(firstChildNode, newNode)
firstChildNode
.toShortStringTemplateNode()
.let { replaceChild(firstChildNode, it) }
}
}
}
Expand All @@ -101,6 +101,22 @@ public class StringTemplateRule : StandardRule("string-template") {
text.substring(2, text.length - 1).isPartOfIdentifier()

private fun String.isPartOfIdentifier() = this == "_" || this.all { it.isLetterOrDigit() }

private fun ASTNode.toShortStringTemplateNode() =
PsiFileFactory
.getInstance(psi.project)
.createFileFromText(
KotlinLanguage.INSTANCE,
"""
val foo = "${'$'}$text"
""".trimIndent(),
).getChildOfType<KtScript>()
?.getChildOfType<KtBlockExpression>()
?.getChildOfType<KtProperty>()
?.getChildOfType<KtStringTemplateExpression>()
?.getChildOfType<KtSimpleNameStringTemplateEntry>()
?.node
?: throw IllegalStateException("Cannot create short string template for string '$text")
}

public val STRING_TEMPLATE_RULE_ID: RuleId = StringTemplateRule().ruleId
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,27 @@ class StringTemplateRuleTest {
""".trimIndent()
stringTemplateRuleAssertThat(code).hasNoLintViolations()
}

@Test
fun `Issue 2615 - Given a string template with redundant curly braces then do not remove the corresponding import`() {
// Interpret "$." in code samples below as "$". It is used whenever the code which has to be inspected should
// actually contain a string template. Using "$" instead of "$." would result in a String in which the string
// templates would have been evaluated before the code would actually be processed by the rule.
val code =
"""
import java.io.File.separator
val s = "$.{separator} is a file separator"
""".trimIndent().replaceStringTemplatePlaceholder()
val formattedCode =
"""
import java.io.File.separator
val s = "$.separator is a file separator"
""".trimIndent().replaceStringTemplatePlaceholder()
stringTemplateRuleAssertThat(code)
.addAdditionalRuleProvider { NoUnusedImportsRule() }
.hasNoLintViolationsForRuleId(NO_UNUSED_IMPORTS_RULE_ID)
.isFormattedAs(formattedCode)
}
}

0 comments on commit 9234dec

Please sign in to comment.