All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cqfn.diktat.ruleset.rules.chapter3.TrailingCommaRule.kt Maven / Gradle / Ivy

There is a newer version: 1.2.5
Show newest version
package org.cqfn.diktat.ruleset.rules.chapter3

import org.cqfn.diktat.common.config.rules.RuleConfiguration
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.getCommonConfiguration
import org.cqfn.diktat.common.config.rules.getRuleConfig
import org.cqfn.diktat.common.utils.loggerWithKtlintConfig
import org.cqfn.diktat.ruleset.constants.Warnings.TRAILING_COMMA
import org.cqfn.diktat.ruleset.rules.DiktatRule

import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.COLLECTION_LITERAL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.COMMA
import com.pinterest.ktlint.core.ast.ElementType.DESTRUCTURING_DECLARATION
import com.pinterest.ktlint.core.ast.ElementType.DESTRUCTURING_DECLARATION_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.INDICES
import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE
import com.pinterest.ktlint.core.ast.ElementType.TYPE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PROJECTION
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_IN_RANGE
import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_IS_PATTERN
import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_WITH_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.WHEN_ENTRY
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
import mu.KotlinLogging
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.psiUtil.siblings

/**
 * [1] Enumerations (In another rule)
 * [2] Value arguments
 * [3] Class properties and parameters
 * [4] Function value parameters
 * [5] Parameters with optional type (including setters)
 * [6] Indexing suffix
 * [7] Lambda parameters
 * [8] when entry
 * [9] Collection literals (in annotations)Type arguments
 * [10] Type arguments
 * [11] Type parameters
 * [12] Destructuring declarations
 */
@Suppress("TOO_LONG_FUNCTION")
class TrailingCommaRule(configRules: List) : DiktatRule(
    NAME_ID,
    configRules,
    listOf(TRAILING_COMMA)
) {
    private val commonConfig = configRules.getCommonConfiguration()
    private val trailingConfig = this.configRules.getRuleConfig(TRAILING_COMMA)?.configuration ?: emptyMap()
    private val configuration by lazy {
        if (trailingConfig.isEmpty()) {
            log.warn("You have enabled TRAILING_COMMA, but rule will remain inactive until you explicitly set" +
                    " configuration options. See [available-rules.md] for possible configuration options.")
        }
        TrailingCommaConfiguration(trailingConfig)
    }

    override fun logic(node: ASTNode) {
        if (commonConfig.kotlinVersion >= ktVersion) {
            val (type, config) = when (node.elementType) {
                VALUE_ARGUMENT_LIST -> Pair(VALUE_ARGUMENT, configuration.getParam("valueArgument"))
                VALUE_PARAMETER_LIST -> Pair(VALUE_PARAMETER, configuration.getParam("valueParameter"))
                INDICES -> Pair(REFERENCE_EXPRESSION, configuration.getParam("referenceExpression"))
                WHEN_ENTRY -> {
                    val lastChildType = node
                        .children()
                        .toList()
                        .findLast { it.elementType in whenChildrenTypes }
                        ?.elementType
                    Pair(lastChildType, configuration.getParam("whenConditions"))
                }
                COLLECTION_LITERAL_EXPRESSION -> Pair(STRING_TEMPLATE, configuration.getParam("collectionLiteral"))
                TYPE_ARGUMENT_LIST -> Pair(TYPE_PROJECTION, configuration.getParam("typeArgument"))
                TYPE_PARAMETER_LIST -> Pair(TYPE_PARAMETER, configuration.getParam("typeParameter"))
                DESTRUCTURING_DECLARATION -> Pair(
                    DESTRUCTURING_DECLARATION_ENTRY,
                    configuration.getParam("destructuringDeclaration")
                )
                else -> return
            }
            val astNode = node
                .children()
                .toList()
                .lastOrNull { it.elementType == type }
            astNode?.checkTrailingComma(config)
        }
    }

    private fun ASTNode.checkTrailingComma(config: Boolean) {
        val shouldFix = this.siblings(true).toList().run {
            !this.map { it.elementType }.contains(COMMA) && this.any { it.isWhiteSpaceWithNewline() || it.isPartOfComment() }
        }
        if (shouldFix && config) {
            // we should write type of node in warning, to make it easier for user to find the parameter
            TRAILING_COMMA.warnAndFix(configRules, emitWarn, isFixMode, "after ${this.elementType}: ${this.text}", this.startOffset, this) {
                val parent = this.treeParent

                // In case, when we got VALUE_PARAMETER, it may contain comments, which follows the actual parameter and all of them are actually in the same node
                // Ex: `class A(val a: Int, val b: Int  // comment)`
                // `val b: Int  // comment` --> the whole expression is VALUE_PARAMETER
                // So, in this case we must insert comma before the comment, in other cases we will insert it after current node
                val comments = listOf(EOL_COMMENT, BLOCK_COMMENT, KDOC)
                val firstCommentNodeOrNull = if (this.elementType == VALUE_PARAMETER) this.children().firstOrNull { it.elementType in comments } else null
                firstCommentNodeOrNull?.let {
                    this.addChild(LeafPsiElement(COMMA, ","), firstCommentNodeOrNull)
                }
                    ?: parent.addChild(LeafPsiElement(COMMA, ","), this.treeNext)
            }
        }
    }

    /**
     * Configuration for trailing comma
     */
    class TrailingCommaConfiguration(config: Map) : RuleConfiguration(config) {
        /**
         * @param name parameters name
         * @return param based on its name
         */
        fun getParam(name: String) = config[name]?.toBoolean() ?: false
    }

    companion object {
        private val log = KotlinLogging.loggerWithKtlintConfig(TrailingCommaRule::class)
        const val NAME_ID = "trailing-comma"
        val ktVersion = KotlinVersion(1, 4)
        val whenChildrenTypes = listOf(WHEN_CONDITION_WITH_EXPRESSION, WHEN_CONDITION_IS_PATTERN, WHEN_CONDITION_IN_RANGE)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy