Skip to content

Commit

Permalink
Add UnnecessaryBackticks rule (#4764)
Browse files Browse the repository at this point in the history
  • Loading branch information
t-kameyama committed Apr 25, 2022
1 parent 3c9010c commit f44947c
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 0 deletions.
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)
}
}
}

1 comment on commit f44947c

@vercel
Copy link

@vercel vercel bot commented on f44947c Apr 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

detekt – ./

detekt.vercel.app
detekt-detekt.vercel.app
detekt-git-main-detekt.vercel.app

Please sign in to comment.