com.pinterest.ktlint.ruleset.standard.rules.PropertyWrappingRule.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ktlint-ruleset-standard Show documentation
Show all versions of ktlint-ruleset-standard Show documentation
An anti-bikeshedding Kotlin linter with built-in formatter.
package com.pinterest.ktlint.ruleset.standard.rules
import com.pinterest.ktlint.logger.api.initKtLintKLogger
import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision
import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.NO_AUTOCORRECT
import com.pinterest.ktlint.rule.engine.core.api.ElementType
import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON
import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ
import com.pinterest.ktlint.rule.engine.core.api.ElementType.PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_REFERENCE
import com.pinterest.ktlint.rule.engine.core.api.IndentConfig
import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.Companion.DEFAULT_INDENT_CONFIG
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.editorconfig.EditorConfig
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf
import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed
import com.pinterest.ktlint.rule.engine.core.api.indent
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline
import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf
import com.pinterest.ktlint.rule.engine.core.api.leavesIncludingSelf
import com.pinterest.ktlint.rule.engine.core.api.prevLeaf
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe
import com.pinterest.ktlint.ruleset.standard.StandardRule
import io.github.oshai.kotlinlogging.KotlinLogging
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger()
/**
* This rule inserts missing newlines inside a property.
*
* Whenever a linebreak is inserted, this rules assumes that the parent node it indented correctly. So the indentation
* is fixed with respect to indentation of the parent. This is just a simple best effort for the case that the
* indentation rule is not run.
*
* This rule has many similarities with the [ParameterWrappingRule] but some subtle differences.
*/
@SinceKtlint("0.49", SinceKtlint.Status.STABLE)
public class PropertyWrappingRule :
StandardRule(
id = "property-wrapping",
usesEditorConfigProperties =
setOf(
INDENT_SIZE_PROPERTY,
INDENT_STYLE_PROPERTY,
MAX_LINE_LENGTH_PROPERTY,
),
) {
private var line = 1
private var indentConfig = DEFAULT_INDENT_CONFIG
private var maxLineLength: Int = MAX_LINE_LENGTH_PROPERTY.defaultValue
override fun beforeFirstNode(editorConfig: EditorConfig) {
line = 1
indentConfig =
IndentConfig(
indentStyle = editorConfig[INDENT_STYLE_PROPERTY],
tabWidth = editorConfig[INDENT_SIZE_PROPERTY],
)
maxLineLength = editorConfig.maxLineLength()
}
override fun beforeVisitChildNodes(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
) {
if (node.elementType == PROPERTY) {
rearrangeProperty(node, emit)
}
}
private fun rearrangeProperty(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
) {
require(node.elementType == PROPERTY)
val baseIndentLength = node.indent(false).length
// Find the first node after the indenting whitespace on the same line as the identifier
val nodeFirstChildLeafOrSelf = node.firstChildLeafOrSelf()
val fromNode =
node
.findChildByType(ElementType.IDENTIFIER)
?.leavesIncludingSelf(forward = false)
?.firstOrNull { it.prevLeaf().isWhiteSpaceWithNewline() || it == nodeFirstChildLeafOrSelf }
?: node
node
.findChildByType(COLON)
?.let { colon ->
if (baseIndentLength + fromNode.sumOfTextLengthUntil(colon) > maxLineLength) {
fromNode.sumOfTextLengthUntil(colon)
requireNewlineAfterLeaf(colon, emit)
return
}
}
node
.findChildByType(TYPE_REFERENCE)
?.let { typeReference ->
if (baseIndentLength + fromNode.sumOfTextLengthUntil(typeReference) > maxLineLength) {
requireNewlineBeforeLeaf(typeReference, emit)
return
}
}
node
.findChildByType(EQ)
?.let { equal ->
if (baseIndentLength + fromNode.sumOfTextLengthUntil(equal) > maxLineLength) {
requireNewlineAfterLeaf(equal, emit)
return
}
}
node
.findChildByType(CALL_EXPRESSION)
?.let { callExpression ->
if (baseIndentLength + fromNode.sumOfTextLengthUntil(callExpression) > maxLineLength) {
requireNewlineBeforeLeaf(callExpression, emit)
return
}
}
}
private fun ASTNode.sumOfTextLengthUntil(astNode: ASTNode): Int {
val stopAtLeaf = astNode.lastChildLeafOrSelf()
return leavesIncludingSelf()
.takeWhile { !it.isWhiteSpaceWithNewline() && it.prevLeaf() != stopAtLeaf }
.sumOf { it.textLength }
}
private fun requireNewlineBeforeLeaf(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
) {
emit(
node.startOffset - 1,
"""Missing newline before "${node.text}"""",
true,
).also { autocorrectDecision ->
LOGGER.trace {
"$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") + "inserted newline before ${node.text}"
}
}.ifAutocorrectAllowed {
node.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node))
}
}
private fun requireNewlineAfterLeaf(
nodeAfterWhichNewlineIsRequired: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
nodeToFix: ASTNode = nodeAfterWhichNewlineIsRequired,
) {
emit(
nodeAfterWhichNewlineIsRequired.startOffset + 1,
"""Missing newline after "${nodeAfterWhichNewlineIsRequired.text}"""",
true,
).also { autocorrectDecision ->
LOGGER.trace {
"$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") +
"inserted newline after ${nodeAfterWhichNewlineIsRequired.text}"
}
}.ifAutocorrectAllowed {
nodeToFix.upsertWhitespaceAfterMe(indentConfig.childIndentOf(nodeToFix))
}
}
}
public val PROPERTY_WRAPPING_RULE_ID: RuleId = PropertyWrappingRule().ruleId
© 2015 - 2024 Weber Informatics LLC | Privacy Policy