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

Add UnnecessaryBackticks rule #4764

Merged
merged 1 commit into from Apr 25, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions detekt-core/src/main/resources/default-detekt-config.yml
Expand Up @@ -625,6 +625,8 @@ style:
active: false
UnnecessaryApply:
active: true
UnnecessaryBackticks:
active: false
UnnecessaryFilter:
active: false
UnnecessaryInheritance:
Expand Down
Expand Up @@ -95,6 +95,7 @@ class StyleGuideProvider : DefaultRuleSetProvider {
UseIsNullOrEmpty(config),
UseOrEmpty(config),
UseAnyOrNoneInsteadOfFind(config),
UnnecessaryBackticks(config),
)
)
}
@@ -0,0 +1,62 @@
package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier

/**
* This rule reports unnecessary backticks.
*
* <noncompliant>
* class `HelloWorld`
* </noncompliant>
*
* <compliant>
* class HelloWorld
* </compliant>
*/
class UnnecessaryBackticks(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue(
javaClass.simpleName,
Severity.Style,
"Backticks are unnecessary.",
Debt.FIVE_MINS
)

override fun visitKtElement(element: KtElement) {
element.allChildren
.filter { it.node.elementType == KtTokens.IDENTIFIER && it.hasUnnecessaryBackticks() }
.forEach { report(CodeSmell(issue, Entity.from(it), "Backticks are unnecessary.")) }
super.visitKtElement(element)
}

@Suppress("ReturnCount")
private fun PsiElement.hasUnnecessaryBackticks(): Boolean {
val text = this.text
if (!text.startsWith("`") || !text.endsWith("`")) return false

val unquoted = text.drop(1).dropLast(1)
if (!unquoted.isIdentifier() || unquoted.isKeyword()) return false

val stringTemplateEntry = getStrictParentOfType<KtSimpleNameStringTemplateEntry>()
return stringTemplateEntry == null || canPlaceAfterSimpleNameEntry(stringTemplateEntry.nextSibling)
}

private fun String.isKeyword() = this in KEYWORDS || this.all { it == '_' }

companion object {
private val KEYWORDS = KtTokens.KEYWORDS.types.map { it.toString() }
}
}
@@ -0,0 +1,139 @@
package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import io.gitlab.arturbosch.detekt.test.lint
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class UnnecessaryBackticksSpec {
val subject = UnnecessaryBackticks(Config.empty)

@Nested
inner class `Reports UnnecessaryInnerClass Rule` {
@Test
fun `class`() {
val code = """
class `Foo` {
val x: `Foo` = `Foo`()
val y = ::`Foo`
}
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(4)
}

@Test
fun function() {
val code = """
fun `foo`() = 1
val x = `foo`()
val y = ::`foo`
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(3)
}

@Test
fun property() {
val code = """
val `foo` = ""
val x = `foo`
val y = ::`foo`
val z = `foo`.length
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(4)
}

@Test
fun import() {
val code = """
import kotlin.`let`
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(1)
}

@Test
fun `in string template`() {
val code = """
val foo = ""
val x = "${'$'}`foo`"
val y = "${'$'}`foo` bar"
val z = "${'$'}{`foo`}bar"
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(3)
}
}

@Nested
inner class `Does not report UnnecessaryInnerClass Rule` {
@Test
fun `class with spaces`() {
val code = """
class `Foo Bar`
val x: `Foo Bar` = `Foo Bar`()
val y = ::`Foo Bar`
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(0)
}

@Test
fun `function with spaces`() {
val code = """
fun `foo bar`() = 1
val x = `foo bar`()
val y = ::`foo bar`
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(0)
}

@Test
fun `property with spaces`() {
val code = """
val `foo bar` = ""
val x = `foo bar`
val y = ::`foo bar`
val z = `foo bar`.length
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(0)
}

@Test
fun keyword() {
val code = """
val `is` = 1
val `fun` = 2
""".trimIndent()
assertThat(subject.compileAndLint(code)).isEmpty()
}

@Test
fun underscore() {
val code = """
val `_` = 1
val `__` = 2
""".trimIndent()
assertThat(subject.compileAndLint(code)).isEmpty()
}

@Test
fun import() {
val code = """
package test
import test.`Foo Bar`
class `Foo Bar`
""".trimIndent()
assertThat(subject.lint(code)).hasSize(0)
}

@Test
fun `in string template`() {
val code = """
val foo = ""
val x = "${'$'}`foo`bar"
val `bar baz` = ""
val y = "${'$'}`bar baz`"
""".trimIndent()
assertThat(subject.compileAndLint(code)).hasSize(0)
}
}
}