Skip to content

Commit

Permalink
Wrap KtLint classes (#1648)
Browse files Browse the repository at this point in the history
  • Loading branch information
nulls authored Mar 29, 2023
1 parent d2a22f0 commit 08f3e2a
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package org.cqfn.diktat.plugin.maven

import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider
import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript

Expand Down Expand Up @@ -118,7 +119,7 @@ abstract class DiktatBaseMojo : AbstractMojo() {
)

val ruleSets by lazy {
listOf(DiktatRuleSetProvider(configFile).get())
listOf(DiktatRuleSetProvider(configFile).toKtLint().get())
}
val baselineResults = baseline?.let { loadBaseline(it.absolutePath) }
?: CurrentBaseline(emptyMap(), false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.cqfn.diktat.ktlint

import org.cqfn.diktat.ktlint.KtLintRuleSetWrapper.Companion.toKtLint
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider

/**
* This is a wrapper around __KtLint__'s [RuleSetProvider].
*/
class KtLintRuleSetProviderWrapper private constructor(
private val diktatRuleSetFactory: DiktatRuleSetProvider,
) : RuleSetProvider {
override fun get(): RuleSet = diktatRuleSetFactory().toKtLint()

companion object {
/**
* @return __KtLint__'s [RuleSetProvider] created from [DiktatRuleSetProvider]
*/
fun DiktatRuleSetProvider.toKtLint(): RuleSetProvider = KtLintRuleSetProviderWrapper(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.cqfn.diktat.ktlint

import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.ruleset.rules.DiktatRule
import org.cqfn.diktat.ruleset.rules.DiktatRuleSet
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.RuleSet

/**
* This is a wrapper around __KtLint__'s [RuleSet] which adjusts visitorModifiers for all rules to keep order with prevRule
* Added as a workaround after introducing a new logic for sorting KtLint Rules: https://github.com/pinterest/ktlint/issues/1478
*
* @param diktatRuleSet the rules which belong to the current [DiktatRuleSet].
*/
class KtLintRuleSetWrapper private constructor(
diktatRuleSet: DiktatRuleSet,
) : RuleSet(DIKTAT_RULE_SET_ID, rules = wrapRules(diktatRuleSet.rules)) {
companion object {
/**
* @return __KtLint__'s [RuleSet] created from [DiktatRuleSet]
*/
fun DiktatRuleSet.toKtLint(): RuleSet = KtLintRuleSetWrapper(this)

private fun wrapRules(rules: List<DiktatRule>): Array<Rule> {
if (rules.isEmpty()) {
return emptyArray()
}
return rules.runningFold(null as KtLintRuleWrapper?) { prevRule, diktatRule ->
KtLintRuleWrapper(diktatRule, prevRule)
}
.filterNotNull()
.toTypedArray()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.cqfn.diktat.ktlint

import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.rules.DiktatRule
import com.pinterest.ktlint.core.Rule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

/**
* This is a wrapper around __KtLint__'s [Rule] which adjusts visitorModifiers to keep order with prevRule.
* @property rule
*/
class KtLintRuleWrapper(
val rule: DiktatRule,
prevRule: KtLintRuleWrapper? = null,
) : Rule(
id = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID),
visitorModifiers = createVisitorModifiers(rule, prevRule),
) {
@Deprecated(
"Marked for deletion in ktlint 0.48.0",
replaceWith = ReplaceWith("beforeVisitChildNodes(node, autoCorrect, emit)"),
)
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: EmitType,
) = rule.visit(node, autoCorrect, emit)

companion object {
private fun createVisitorModifiers(
rule: DiktatRule,
prevRule: KtLintRuleWrapper?,
): Set<VisitorModifier> = prevRule?.id?.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID)
?.let { previousRuleId ->
val ruleId = rule.id.qualifiedWithRuleSetId(DIKTAT_RULE_SET_ID)
require(ruleId != previousRuleId) {
"PrevRule has same ID as rule: $ruleId"
}
setOf(
VisitorModifier.RunAfterRule(
ruleId = previousRuleId,
loadOnlyWhenOtherRuleIsLoaded = false,
runOnlyWhenOtherRuleIsEnabled = false
)
)
} ?: emptySet()

/**
* @return a rule to which a logic is delegated
*/
internal fun Rule.delegatee(): DiktatRule = (this as? KtLintRuleWrapper)?.rule ?: error("Provided rule ${javaClass.simpleName} is not wrapped by diktat")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.isRuleEnabled
import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId
import org.cqfn.diktat.common.utils.loggerWithKtlintConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.utils.getFilePath
Expand All @@ -16,17 +15,16 @@ private typealias DiktatConfigRule = org.cqfn.diktat.common.config.rules.Rule
/**
* This is a wrapper around _KtLint_ `Rule`.
*
* @param id id of the rule
* @property id id of the rule
* @property configRules all rules from configuration
* @property inspections warnings that are used in the rule's code
*/
@Suppress("TooGenericExceptionCaught")
abstract class DiktatRule(
id: String,
val id: String,
val configRules: List<RulesConfig>,
private val inspections: List<DiktatConfigRule>,
visitorModifiers: Set<VisitorModifier> = emptySet(),
) : Rule(id.qualifiedWithRuleSetId(), visitorModifiers) {
) {
/**
* Default value is false
*/
Expand All @@ -47,8 +45,14 @@ abstract class DiktatRule(
*/
lateinit var emitWarn: EmitType

/**
* @param node
* @param autoCorrect
* @param emit
* @throws Error
*/
@Suppress("TooGenericExceptionThrown")
final override fun visit(
fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: EmitType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cqfn.diktat.ruleset.rules

/**
* A group of [DiktatRule]'s as a single set.
*
* @property rules diktat rules.
*/
data class DiktatRuleSet(
val rules: List<DiktatRule>
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.RulesConfigReader
import org.cqfn.diktat.common.utils.loggerWithKtlintConfig
import org.cqfn.diktat.ruleset.constants.Warnings
import org.cqfn.diktat.ruleset.rules.OrderedRuleSet.Companion.ordered
import org.cqfn.diktat.ruleset.rules.chapter1.FileNaming
import org.cqfn.diktat.ruleset.rules.chapter1.IdentifierNaming
import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming
Expand Down Expand Up @@ -87,22 +86,24 @@ import org.cqfn.diktat.ruleset.rules.chapter6.classes.InlineClassesRule
import org.cqfn.diktat.ruleset.rules.chapter6.classes.SingleConstructorRule
import org.cqfn.diktat.ruleset.rules.chapter6.classes.SingleInitRule
import org.cqfn.diktat.ruleset.rules.chapter6.classes.StatelessClassesRule
import com.pinterest.ktlint.core.Rule

import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
import mu.KotlinLogging
import org.jetbrains.kotlin.org.jline.utils.Levenshtein

import java.io.File

/**
* [RuleSetProvider] that provides diKTat ruleset.
* By default, it is expected to have diktat-analysis.yml configuration in the root folder where 'ktlint' is run
* otherwise it will use default configuration where some rules are disabled
* _KtLint_-agnostic factory which creates a [DiktatRuleSet].
*
* @param diktatConfigFile - configuration file where all configurations for inspections and rules are stored
* By default, it is expected to have `diktat-analysis.yml` configuration in the root folder where 'ktlint' is run
* otherwise it will use default configuration where some rules are disabled.
*
* @param diktatConfigFile the configuration file where all configurations for
* inspections and rules are stored.
*/
class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : RuleSetProvider {
@Suppress("ForbiddenComment")
class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) {
private val possibleConfigs: Sequence<String?> = sequence {
yield(resolveDefaultConfig())
yield(resolveConfigFileFromJarLocation())
Expand Down Expand Up @@ -134,18 +135,35 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
?: emptyList()
}

/**
* This method is going to be called once for each file (which means if any
* of the rules have state or are not thread-safe - a new [DiktatRuleSet] must
* be created).
*
* TODO: comments for 0.47.x
* For each invocation of [com.pinterest.ktlint.core.KtLintRuleEngine.lint] and [com.pinterest.ktlint.core.KtLintRuleEngine.format] the [DiktatRuleSet]
* is retrieved.
* This results in new instances of each [Rule] for each file being
* processed.
* As of that a [Rule] does not need to be thread-safe.
*
* However, [com.pinterest.ktlint.core.KtLintRuleEngine.format] requires the [Rule] to be executed twice on a
* file in case at least one violation has been autocorrected.
* As the same [Rule] instance is reused for the second execution of the
* [Rule], the state of the [Rule] is shared.
* As of this [Rule] have to clear their internal state.
*
* @return a default [DiktatRuleSet]
*/
@Suppress(
"LongMethod",
"TOO_LONG_FUNCTION",
)
@Deprecated(
"Marked for removal in KtLint 0.48. See changelog or KDoc for more information.",
)
override fun get(): RuleSet {
operator fun invoke(): DiktatRuleSet {
// Note: the order of rules is important in autocorrect mode. For example, all rules that add new code should be invoked before rules that fix formatting.
// We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible,
// cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule).
val rules = listOf(
val rules = sequenceOf(
// comments & documentation
::CommentsRule,
::SingleConstructorRule, // this rule can add properties to a primary constructor, so should be before KdocComments
Expand Down Expand Up @@ -228,12 +246,10 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS

)
.map {
it.invoke(configRules)
it(configRules)
}
return RuleSet(
DIKTAT_RULE_SET_ID,
rules = rules.toTypedArray()
).ordered()
.toList()
return DiktatRuleSet(rules)
}

private fun validate(config: RulesConfig) =
Expand All @@ -248,8 +264,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
// for some aggregators of static analyzers we need to provide configuration for cli
// in this case diktat would take the configuration from the directory where jar file is stored
val ruleSetProviderPath =
DiktatRuleSetProvider::class
.java
javaClass
.protectionDomain
.codeSource
.location
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.ktlint.KtLintRuleSetProviderWrapper.Companion.toKtLint
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider

/**
* [RuleSetProvider] that provides diKTat ruleset.
*
* By default, it is expected to have `diktat-analysis.yml` configuration in the root folder where 'ktlint' is run
* otherwise it will use default configuration where some rules are disabled.
*
* This class is registered in [resources/META-INF/services/com.pinterest.ktlint.core.RuleSetProvider]
*/
class DiktatRuleSetProviderSpi : RuleSetProvider {
override fun get(): RuleSet = DiktatRuleSetProvider().toKtLint().get()
}
Loading

0 comments on commit 08f3e2a

Please sign in to comment.