Skip to content

Commit

Permalink
feature/inline-classes(#698)
Browse files Browse the repository at this point in the history
### What's done:
  * Fixed bugs
  • Loading branch information
aktsay6 committed Jan 20, 2021
1 parent 262c513 commit 8869988
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 14 deletions.
2 changes: 1 addition & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,6 @@
# If there exists negated version of function you should prefer it instead of !functionCall
- name: INVERSE_FUNCTION_PREFERRED
enabled: true
# Checks if class can be transferred to inline class
# Checks if class can be converted to inline class
- name: INLINE_CLASS_CAN_BE_USED
enabled: true
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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
Expand All @@ -16,8 +17,10 @@ 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.
Expand All @@ -40,9 +43,8 @@ class InlineClassesRule(private val configRule: List<RulesConfig>) : Rule("inlin
}

private fun handleClasses(classPsi: KtClass) {
// Fixme: for now we can't understand whether it extends class or interface
if (hasValidProperties(classPsi) &&
!classPsi.node.hasChildOfType(SUPER_TYPE_LIST) &&
!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
Expand All @@ -52,15 +54,19 @@ class InlineClassesRule(private val configRule: List<RulesConfig>) : Rule("inlin

private fun hasValidProperties(classPsi: KtClass): Boolean {
if (classPsi.getProperties().size == 1 && !classPsi.hasExplicitPrimaryConstructor()) {
return !classPsi.getProperties().first().isVar
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.primaryConstructorModifierList == null
classPsi.primaryConstructor?.visibilityModifierType()?.value ?: true == "public"
}
return false
}

private fun isExtendingClass(node: ASTNode): Boolean {
return 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)
}
Expand Down
2 changes: 1 addition & 1 deletion diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,6 @@
# If there exists negated version of function you should prefer it instead of !functionCall
- name: INVERSE_FUNCTION_PREFERRED
enabled: true
# Checks if class can be transferred to inline class
# Checks if class can be converted to inline class
- name: INLINE_CLASS_CAN_BE_USED
enabled: true
2 changes: 1 addition & 1 deletion diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,6 @@
# If there exists negated version of function you should prefer it instead of !functionCall
- name: INVERSE_FUNCTION_PREFERRED
enabled: true
# Checks if class can be transferred to inline class
# Checks if class can be converted to inline class
- name: INLINE_CLASS_CAN_BE_USED
enabled: true
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.cqfn.diktat.util.LintTestBase

import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

Expand Down Expand Up @@ -99,28 +98,66 @@ class InlineClassesWarnTest : LintTestBase(::InlineClassesRule) {
)
}

@Disabled
@Test
@Tag(WarningNames.INLINE_CLASS_CAN_BE_USED)
fun `should not trigger on class that extends class or interface`() {
fun `should not trigger on class that extends class`() {
lintMethod(
"""
|class Some : Any {
|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) {
| val some = 3
|
|}
""".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)
)
}
}
1 change: 1 addition & 0 deletions info/guide/guide-TOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ I [Preface](#c0)
* [6.1.9 Never use the name of a variable in the custom getter or setter (possible_bug)](#r6.1.9)
* [6.1.10 No trivial getters and setters are allowed in the code](#r6.1.10)
* [6.1.11 Use 'apply' for grouping object initialization](#r6.1.11)
* [6.1.12 Prefer Inline classes when a class has a single property](#r6.1.12)
* [6.2 Classes](#c6.2)
* [6.2.1 Use extension functions for making logic of classes less coupled](#r6.2.1)
* [6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)](#r6.2.2)
Expand Down
4 changes: 2 additions & 2 deletions info/guide/guide-chapter-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ fun main() {
}
```

### <a name="c6.1.12"></a> 6.1.12 Prefer Inline classes when a class has a single property
If a class has only one property, then it can be transferred to the inline class.
### <a name="r6.1.12"></a> 6.1.12 Prefer Inline classes when a class has a single property
If a class has only one immutable property, then it can be converted to the inline class.

<!-- =============================================================================== -->
### <a name="c6.2"></a>6.2 Extension functions
Expand Down

0 comments on commit 8869988

Please sign in to comment.