diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml
index 5b5c7d3fd1d..f67bed5e645 100644
--- a/detekt-core/src/main/resources/default-detekt-config.yml
+++ b/detekt-core/src/main/resources/default-detekt-config.yml
@@ -625,6 +625,8 @@ style:
active: false
UnnecessaryApply:
active: true
+ UnnecessaryBackticks:
+ active: false
UnnecessaryFilter:
active: false
UnnecessaryInheritance:
diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt
index 83c1028690b..bd496f55cfe 100644
--- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt
+++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt
@@ -95,6 +95,7 @@ class StyleGuideProvider : DefaultRuleSetProvider {
UseIsNullOrEmpty(config),
UseOrEmpty(config),
UseAnyOrNoneInsteadOfFind(config),
+ UnnecessaryBackticks(config),
)
)
}
diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticks.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticks.kt
new file mode 100644
index 00000000000..8a496aea3f4
--- /dev/null
+++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticks.kt
@@ -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.
+ *
+ *
+ * class `HelloWorld`
+ *
+ *
+ *
+ * class HelloWorld
+ *
+ */
+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()
+ 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() }
+ }
+}
diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticksSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticksSpec.kt
new file mode 100644
index 00000000000..604f19c7e87
--- /dev/null
+++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnnecessaryBackticksSpec.kt
@@ -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)
+ }
+ }
+}