-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ignore modifier of backing property in
android_studio
code style (#…
…2552) Extract `backing-property-name` from `property-naming` rule. This allows users to disable this rule, but keep the `property-naming` rule in place. For `android_studio` code style the restrictions regarding the modifier of the correlated property or function is ignored entirely as the Android Kotlin Styleguide does not require it to be public. Closes #2528
- Loading branch information
1 parent
33396eb
commit a88a2e5
Showing
7 changed files
with
538 additions
and
244 deletions.
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
133 changes: 133 additions & 0 deletions
133
.../src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule.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,133 @@ | ||
package com.pinterest.ktlint.ruleset.standard.rules | ||
|
||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.INTERNAL_KEYWORD | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.PRIVATE_KEYWORD | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.PROPERTY | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.PROTECTED_KEYWORD | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER | ||
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST | ||
import com.pinterest.ktlint.rule.engine.core.api.RuleId | ||
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint | ||
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL | ||
import com.pinterest.ktlint.rule.engine.core.api.children | ||
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY | ||
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.android_studio | ||
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig | ||
import com.pinterest.ktlint.rule.engine.core.api.hasModifier | ||
import com.pinterest.ktlint.ruleset.standard.StandardRule | ||
import com.pinterest.ktlint.ruleset.standard.rules.internal.regExIgnoringDiacriticsAndStrokesOnLetters | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
|
||
/** | ||
* https://kotlinlang.org/docs/coding-conventions.html#property-names | ||
* https://developer.android.com/kotlin/style-guide#backing_properties | ||
*/ | ||
@SinceKtlint("1.2.0", EXPERIMENTAL) | ||
public class BackingPropertyNamingRule : | ||
StandardRule( | ||
id = "backing-property-naming", | ||
usesEditorConfigProperties = setOf(CODE_STYLE_PROPERTY), | ||
) { | ||
private var codeStyle = CODE_STYLE_PROPERTY.defaultValue | ||
|
||
override fun beforeFirstNode(editorConfig: EditorConfig) { | ||
codeStyle = editorConfig[CODE_STYLE_PROPERTY] | ||
} | ||
|
||
override fun beforeVisitChildNodes( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, | ||
) { | ||
node | ||
.takeIf { node.elementType == PROPERTY } | ||
?.let { property -> visitProperty(property, emit) } | ||
} | ||
|
||
private fun visitProperty( | ||
property: ASTNode, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, | ||
) { | ||
property | ||
.findChildByType(IDENTIFIER) | ||
?.takeIf { it.text.startsWith("_") } | ||
?.let { identifier -> | ||
visitBackingProperty(identifier, emit) | ||
} | ||
} | ||
|
||
private fun visitBackingProperty( | ||
identifier: ASTNode, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, | ||
) { | ||
identifier | ||
.text | ||
.takeUnless { it.matches(BACKING_PROPERTY_LOWER_CAMEL_CASE_REGEXP) } | ||
?.let { | ||
emit(identifier.startOffset, "Backing property should start with underscore followed by lower camel case", false) | ||
} | ||
|
||
if (!identifier.treeParent.hasModifier(PRIVATE_KEYWORD)) { | ||
emit(identifier.startOffset, "Backing property not allowed when 'private' modifier is missing", false) | ||
} | ||
|
||
// A backing property can only exist when a correlated public property or function exists | ||
val correlatedPropertyOrFunction = identifier.findCorrelatedPropertyOrFunction() | ||
if (correlatedPropertyOrFunction == null) { | ||
emit(identifier.startOffset, "Backing property is only allowed when a matching property or function exists", false) | ||
} else { | ||
if (codeStyle == android_studio || correlatedPropertyOrFunction.isPublic()) { | ||
return | ||
} else { | ||
emit(identifier.startOffset, "Backing property is only allowed when the matching property or function is public", false) | ||
} | ||
} | ||
} | ||
|
||
private fun ASTNode.findCorrelatedPropertyOrFunction() = findCorrelatedProperty() ?: findCorrelatedFunction() | ||
|
||
private fun ASTNode.findCorrelatedProperty() = | ||
treeParent | ||
?.treeParent | ||
?.children() | ||
?.filter { it.elementType == PROPERTY } | ||
?.mapNotNull { it.findChildByType(IDENTIFIER) } | ||
?.firstOrNull { it.text == text.removePrefix("_") } | ||
?.treeParent | ||
|
||
private fun ASTNode.findCorrelatedFunction(): ASTNode? { | ||
val correlatedFunctionName = "get${capitalizeFirstChar()}" | ||
return treeParent | ||
?.treeParent | ||
?.children() | ||
?.filter { it.elementType == FUN } | ||
?.filter { it.hasNonEmptyParameterList() } | ||
?.mapNotNull { it.findChildByType(IDENTIFIER) } | ||
?.firstOrNull { it.text == correlatedFunctionName } | ||
?.treeParent | ||
} | ||
|
||
private fun ASTNode.hasNonEmptyParameterList() = | ||
findChildByType(VALUE_PARAMETER_LIST) | ||
?.children() | ||
?.none { it.elementType == VALUE_PARAMETER } | ||
?: false | ||
|
||
private fun ASTNode.capitalizeFirstChar() = | ||
text | ||
.removePrefix("_") | ||
.replaceFirstChar { it.uppercase() } | ||
|
||
private fun ASTNode.isPublic() = | ||
!hasModifier(PRIVATE_KEYWORD) && | ||
!hasModifier(PROTECTED_KEYWORD) && | ||
!hasModifier(INTERNAL_KEYWORD) | ||
|
||
private companion object { | ||
val BACKING_PROPERTY_LOWER_CAMEL_CASE_REGEXP = "_[a-z][a-zA-Z0-9]*".regExIgnoringDiacriticsAndStrokesOnLetters() | ||
} | ||
} | ||
|
||
public val BACKING_PROPERTY_NAMING_RULE_ID: RuleId = BackingPropertyNamingRule().ruleId |
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
Oops, something went wrong.