-
Notifications
You must be signed in to change notification settings - Fork 39
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 warning to suggest to use inline classes when a class has single property #716
Merged
Merged
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
bf7133e
feature/inline-classes(#698)
aktsay6 0db1a08
feature/inline-classes(#698)
aktsay6 3a3e1a2
feature/inline-classes(#698)
aktsay6 7816893
feature/inline-classes(#698)
aktsay6 41da95c
feature/inline-classes(#698)
aktsay6 262c513
feature/inline-classes(#698)
aktsay6 8869988
feature/inline-classes(#698)
aktsay6 91568a2
feature/inline-classes(#698)
aktsay6 ca994dd
feature/inline-classes(#698)
aktsay6 79d7a2c
feature/inline-classes(#698)
aktsay6 edc0fa9
Merge branch 'master' into feature/inline-classes(#698)
aktsay6 00e135b
feature/inline-classes(#698)
aktsay6 5d41510
feature/inline-classes(#698)
aktsay6 f4d0ca4
feature/inline-classes(#698)
aktsay6 fea5172
Merge branch 'master' into feature/inline-classes(#698)
aktsay6 fadb4b2
feature/inline-classes(#698)
aktsay6 7bc2826
feature/inline-classes(#698)
aktsay6 50c12b8
Merge branch 'master' into feature/inline-classes(#698)
aktsay6 a786c82
feature/inline-classes(#698)
aktsay6 55295a2
feature/inline-classes(#698)
aktsay6 824af31
Merge branch 'master' into feature/inline-classes(#698)
aktsay6 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
...rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/InlineClassesRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package org.cqfn.diktat.ruleset.rules.chapter6.classes | ||
|
||
import org.cqfn.diktat.common.config.rules.RulesConfig | ||
import org.cqfn.diktat.ruleset.constants.EmitType | ||
import org.cqfn.diktat.ruleset.constants.Warnings.INLINE_CLASS_CAN_BE_USED | ||
import org.cqfn.diktat.ruleset.utils.getFirstChildWithType | ||
import org.cqfn.diktat.ruleset.utils.hasChildOfType | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS | ||
import com.pinterest.ktlint.core.ast.ElementType.CONSTRUCTOR_CALLEE | ||
import com.pinterest.ktlint.core.ast.ElementType.FINAL_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.INTERNAL_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST | ||
import com.pinterest.ktlint.core.ast.ElementType.PRIVATE_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.PROTECTED_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.PUBLIC_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST | ||
import com.pinterest.ktlint.core.ast.ElementType.VAR_KEYWORD | ||
import com.pinterest.ktlint.core.ast.children | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
import org.jetbrains.kotlin.psi.KtClass | ||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType | ||
|
||
/** | ||
* This rule checks if inline class can be used. | ||
*/ | ||
class InlineClassesRule(private val configRule: List<RulesConfig>) : Rule("inline-classes") { | ||
private var isFixMode: Boolean = false | ||
private lateinit var emitWarn: EmitType | ||
|
||
override fun visit( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: EmitType | ||
) { | ||
emitWarn = emit | ||
isFixMode = autoCorrect | ||
|
||
if (node.elementType == CLASS) { | ||
handleClasses(node.psi as KtClass) | ||
} | ||
} | ||
|
||
private fun handleClasses(classPsi: KtClass) { | ||
// Fixme: In Kotlin 1.4.30 inline classes may be used with internal constructors. When it will be released need to check it | ||
if (hasValidProperties(classPsi) && | ||
!isExtendingClass(classPsi.node) && | ||
classPsi.node.getFirstChildWithType(MODIFIER_LIST)?.getChildren(null)?.all { it.elementType in goodModifiers } != false) { | ||
INLINE_CLASS_CAN_BE_USED.warnAndFix(configRule, emitWarn, isFixMode, "class ${classPsi.name}", classPsi.node.startOffset, classPsi.node) { | ||
// Fixme: since it's an experimental feature we shouldn't do fixer | ||
} | ||
} | ||
} | ||
|
||
private fun hasValidProperties(classPsi: KtClass): Boolean { | ||
if (classPsi.getProperties().size == 1 && !classPsi.hasExplicitPrimaryConstructor()) { | ||
return !classPsi.getProperties().single().isVar | ||
} else if (classPsi.getProperties().isEmpty() && classPsi.hasExplicitPrimaryConstructor()) { | ||
return classPsi.primaryConstructorParameters.size == 1 && | ||
!classPsi.primaryConstructorParameters.first().node.hasChildOfType(VAR_KEYWORD) && | ||
classPsi.primaryConstructor?.visibilityModifierType()?.value?.let { it == "public" } ?: true | ||
} | ||
return false | ||
} | ||
|
||
private fun isExtendingClass(node: ASTNode): Boolean = | ||
node | ||
.getFirstChildWithType(SUPER_TYPE_LIST) | ||
?.children() | ||
?.any { it.hasChildOfType(CONSTRUCTOR_CALLEE) } | ||
?: false | ||
|
||
companion object { | ||
val goodModifiers = listOf(PUBLIC_KEYWORD, PRIVATE_KEYWORD, FINAL_KEYWORD, PROTECTED_KEYWORD, INTERNAL_KEYWORD) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/InlineClassesWarnTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package org.cqfn.diktat.ruleset.chapter6 | ||
|
||
import org.cqfn.diktat.ruleset.constants.Warnings.INLINE_CLASS_CAN_BE_USED | ||
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID | ||
import org.cqfn.diktat.ruleset.rules.chapter6.classes.InlineClassesRule | ||
import org.cqfn.diktat.util.LintTestBase | ||
|
||
import com.pinterest.ktlint.core.LintError | ||
import generated.WarningNames | ||
import org.junit.jupiter.api.Tag | ||
import org.junit.jupiter.api.Test | ||
|
||
class InlineClassesWarnTest : LintTestBase(::InlineClassesRule) { | ||
private val ruleId = "$DIKTAT_RULE_SET_ID:inline-classes" | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on inline class`() { | ||
lintMethod( | ||
""" | ||
|inline class Name(val s: String) {} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on regular class`() { | ||
lintMethod( | ||
""" | ||
|class Some { | ||
| val config = Config() | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class Some", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on class with appropriate modifiers`() { | ||
lintMethod( | ||
""" | ||
|final class Some { | ||
| val config = Config() | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class Some", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on class with inappropriate modifiers`() { | ||
lintMethod( | ||
""" | ||
|abstract class Some { | ||
| val config = Config() | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on class with val prop in constructor`() { | ||
lintMethod( | ||
""" | ||
|class Some(val anything: Int) { | ||
| | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class Some", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on class with var prop #1`() { | ||
lintMethod( | ||
""" | ||
|class Some(var anything: Int) { | ||
| | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on class with var prop #2`() { | ||
lintMethod( | ||
""" | ||
|class Some { | ||
| var some = 3 | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on class that extends class`() { | ||
lintMethod( | ||
""" | ||
|class Some : Any() { | ||
| val some = 3 | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on class that extends interface`() { | ||
lintMethod( | ||
""" | ||
|class Some : Any { | ||
| val some = 3 | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class Some", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should not trigger on class with internal constructor`() { | ||
lintMethod( | ||
""" | ||
|class LocalCommandExecutor internal constructor(private val command: String) { | ||
| | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on class with public constructor`() { | ||
lintMethod( | ||
""" | ||
|class LocalCommandExecutor public constructor(private val command: String) { | ||
| | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class LocalCommandExecutor", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED) | ||
fun `should trigger on class with annotation before the constructor`() { | ||
lintMethod( | ||
""" | ||
|class LocalCommandExecutor @Inject constructor(private val command: String) { | ||
| | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${INLINE_CLASS_CAN_BE_USED.warnText()} class LocalCommandExecutor", true) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -357,6 +357,23 @@ fun main() { | |
} | ||
``` | ||
|
||
### <a name="r6.1.12"></a> 6.1.12 Prefer Inline classes when a class has a single property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. example? and more info about inline classes |
||
If a class has only one immutable property, then it can be converted to the inline class. | ||
|
||
Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don't get any special treatment. | ||
|
||
**Invalid example**: | ||
```kotlin | ||
class Password { | ||
val value: String | ||
} | ||
``` | ||
|
||
**Valid example**: | ||
```kotlin | ||
inline class Password(val value: String) | ||
``` | ||
|
||
<!-- =============================================================================== --> | ||
### <a name="c6.2"></a>6.2 Extension functions | ||
This section describes the rules of using extension functions in your code. | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure it's prohibited? IDEA doesn't seem to highlight it in red.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As IDEA highlighted, constructor should be public, otherwise class can't be inline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idk, my idea doesn't :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange, I have this
IntelliJ IDEA 2020.3 (Ultimate Edition)
Build #IU-203.5981.155, built on December 1, 2020
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, do you try this code in diktat? I would expect results to be the same with same kotlin version. Which version of kotlin plugin do you have in idea? My is 203-1.4.30-RC-IJ6682.9 for idea 2020.3.2. Let's check if this code compiles with kotlin 1.4.21.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I have 1.4.21 and this code doesn't compile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that is a bug in IDEA plugin. Maybe, it'll be allowed in 1.4.30, but let's allow only public for now.