Skip to content

Commit

Permalink
Add UnnecessaryBackticks rule
Browse files Browse the repository at this point in the history
  • Loading branch information
t-kameyama committed Apr 25, 2022
1 parent 0380716 commit 745a45f
Show file tree
Hide file tree
Showing 385 changed files with 14,180 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,63 @@
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 + KtTokens.SOFT_KEYWORDS.types).map { it.toString() } + listOf("yield")
}
}
@@ -0,0 +1,140 @@
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
val `yield` = 3
""".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)
}
}
}
167 changes: 167 additions & 0 deletions docs/pages/documentation/comments.md
@@ -0,0 +1,167 @@
---
title: Comments Rule Set
sidebar: home_sidebar
keywords: rules, comments
permalink: comments.html
toc: true
folder: documentation
---
This rule set provides rules that address issues in comments and documentation
of the code.

### AbsentOrWrongFileLicense

This rule will report every Kotlin source file which doesn't have the required license header.
The rule validates each Kotlin source and operates in two modes: if `licenseTemplateIsRegex = false` (or missing)
the rule checks whether the input file header starts with the read text from the passed file in the
`licenseTemplateFile` configuration option. If `licenseTemplateIsRegex = true` the rule matches the header with
a regular expression produced from the passed template license file (defined via `licenseTemplateFile` configuration
option).

**Active by default**: No

**Debt**: 5min

#### Configuration options:

* ``licenseTemplateFile`` (default: ``'license.template'``)

path to file with license header template resolved relatively to config file

* ``licenseTemplateIsRegex`` (default: ``false``)

whether or not the license header template is a regex template

### CommentOverPrivateFunction

This rule reports comments and documentation that has been added to private functions. These comments get reported
because they probably explain the functionality of the private function. However private functions should be small
enough and have an understandable name so that they are self-explanatory and do not need this comment in the first
place.

Instead of simply removing this comment to solve this issue prefer to split up the function into smaller functions
with better names if necessary. Giving the function a better, more descriptive name can also help in
solving this issue.

**Active by default**: No

**Debt**: 20min

### CommentOverPrivateProperty

This rule reports comments and documentation above private properties. This can indicate that the property has a
confusing name or is not in a small enough context to be understood.
Private properties should be named in a self-explanatory way and readers of the code should be able to understand
why the property exists and what purpose it solves without the comment.

Instead of simply removing the comment to solve this issue prefer renaming the property to a more self-explanatory
name. If this property is inside a bigger class it could make senes to refactor and split up the class. This can
increase readability and make the documentation obsolete.

**Active by default**: No

**Debt**: 20min

### DeprecatedBlockTag

This rule reports use of the `@deprecated` block tag in KDoc comments. Deprecation must be specified using a
`@Deprecated` annotation as adding a `@deprecated` block tag in KDoc comments
[has no effect and is not supported](https://kotlinlang.org/docs/kotlin-doc.html#suppress). The `@Deprecated`
annotation constructor has dedicated fields for a message and a type (warning, error, etc.). You can also use the
`@ReplaceWith` annotation to specify how to solve the deprecation automatically via the IDE.

**Active by default**: No

**Debt**: 5min

#### Noncompliant Code:

```kotlin
/**
* This function prints a message followed by a new line.
*
* @deprecated Useless, the Kotlin standard library can already do this. Replace with println.
*/
fun printThenNewline(what: String) {
// ...
}
```

#### Compliant Code:

```kotlin
/**
* This function prints a message followed by a new line.
*/
@Deprecated("Useless, the Kotlin standard library can already do this.")
@ReplaceWith("println(what)")
fun printThenNewline(what: String) {
// ...
}
```

### EndOfSentenceFormat

This rule validates the end of the first sentence of a KDoc comment.
It should end with proper punctuation or with a correct URL.

**Active by default**: No

**Debt**: 5min

#### Configuration options:

* ``endOfSentenceFormat`` (default: ``'([.?!][ \t\n\r\f<])|([.?!:]$)'``)

regular expression which should match the end of the first sentence in the KDoc

### UndocumentedPublicClass

This rule reports public classes, objects and interfaces which do not have the required documentation.
Enable this rule if the codebase should have documentation on every public class, interface and object.

By default this rule also searches for nested and inner classes and objects. This default behavior can be changed
with the configuration options of this rule.

**Active by default**: No

**Debt**: 20min

#### Configuration options:

* ``searchInNestedClass`` (default: ``true``)

if nested classes should be searched

* ``searchInInnerClass`` (default: ``true``)

if inner classes should be searched

* ``searchInInnerObject`` (default: ``true``)

if inner objects should be searched

* ``searchInInnerInterface`` (default: ``true``)

if inner interfaces should be searched

### UndocumentedPublicFunction

This rule will report any public function which does not have the required documentation.
If the codebase should have documentation on all public functions enable this rule to enforce this.
Overridden functions are excluded by this rule.

**Active by default**: No

**Debt**: 20min

### UndocumentedPublicProperty

This rule will report any public property which does not have the required documentation.
This also includes public properties defined in a primary constructor.
If the codebase should have documentation on all public properties enable this rule to enforce this.
Overridden properties are excluded by this rule.

**Active by default**: No

**Debt**: 20min

0 comments on commit 745a45f

Please sign in to comment.