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

Fix replacement of redundant curly braces #2617

Merged
merged 1 commit into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}