Skip to content

Commit

Permalink
Migrate to the new rule set API
Browse files Browse the repository at this point in the history
### What's done:

 - Deprecated API usages removed (except for `DiktatBaseMojo`).
 - @Suppress("Deprecation") annotations removed (except for `DiktatBaseMojo`).
 - A part of #1543.
  • Loading branch information
0x6675636b796f75676974687562 committed Nov 18, 2022
1 parent b8f4350 commit 113fbe5
Show file tree
Hide file tree
Showing 30 changed files with 412 additions and 282 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ open class DiktatExtension(
* @param action configuration lambda for `PatternFilterable`
*/
fun inputs(action: PatternFilterable.() -> Unit) {
action.invoke(patternSet)
action(patternSet)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ abstract class DiktatBaseMojo : AbstractMojo() {
)
reporterImpl.after(file.absolutePath)
} catch (e: RuleExecutionException) {
/*
* https://github.com/pinterest/ktlint/issues/1710:
* no alternative in KtLint 0.47;
* may get changed to `KtLintRuleExecutionException` in KtLint 0.48.
*/
log.error("Unhandled exception during rule execution: ", e)
throw MojoExecutionException("Unhandled exception during rule execution", e)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
package org.cqfn.diktat.plugin.maven

import org.cqfn.diktat.DiktatProcessCommand
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProviderV2

import org.apache.maven.plugins.annotations.Mojo
import kotlin.io.path.absolutePathString
import kotlin.io.path.writeText

/**
* Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files
* Main [Mojo] that call [DiktatRuleSetProviderV2]'s rules on [inputs] files
*/
@Mojo(name = "check")
@Suppress("unused")
Expand All @@ -26,7 +26,7 @@ class DiktatCheckMojo : DiktatBaseMojo() {
}

/**
* Main [Mojo] that call [DiktatRuleSetProvider]'s rules on [inputs] files
* Main [Mojo] that call [DiktatRuleSetProviderV2]'s rules on [inputs] files
* and fixes discovered errors
*/
@Mojo(name = "fix")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.Path
import kotlin.io.path.createTempFile
import kotlin.io.path.div

/**
* Tests for mojo configuration. NB: this tests are using Junit4, because maven-plugin-testing-harness doesn't support 5.
*/
@OptIn(ExperimentalPathApi::class)
@Suppress("LongMethod", "TOO_LONG_FUNCTION")
class DiktatBaseMojoTest {
@get:Rule val mojoRule = MojoRule()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cqfn.diktat.ruleset.rules

import com.pinterest.ktlint.core.RuleProvider
import com.pinterest.ktlint.core.RuleSetProviderV2

/**
* A group of [RuleProvider]'s discoverable through [RuleSetProviderV2].
*/
interface DiktatRuleSet {
/**
* The rule providers.
*/
val ruleProviders: Set<RuleProvider>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import kotlin.Function0

/**
* _KtLint_-agnostic factory which creates a [DiktatRuleSet].
*/
fun interface DiktatRuleSetFactory : Function0<DiktatRuleSet> {
/**
* 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).
*
* For each invocation of [KtLint.lint] and [KtLint.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, [KtLint.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.
*/
override fun invoke(): DiktatRuleSet

companion object {
/**
* @param diktatConfigFile the configuration file where all configurations for
* inspections and rules are stored.
* @return a new instance of [DiktatRuleSetFactory].
*/
operator fun invoke(diktatConfigFile: String = DIKTAT_ANALYSIS_CONF): DiktatRuleSetFactory =
DiktatRuleSetProvider(diktatConfigFile)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
@file:Suppress(
"Deprecation"
)

package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.DIKTAT_ANALYSIS_CONF
Expand All @@ -12,7 +8,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 @@ -88,26 +83,19 @@ 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.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
* 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.
*
* The no-argument constructor is used by the Java SPI interface; that's why
* it's explicitly annotated with [JvmOverloads].
*
* @param diktatConfigFile - configuration file where all configurations for inspections and rules are stored
* @param diktatConfigFile the configuration file where all configurations for
* inspections and rules are stored.
*/
class DiktatRuleSetProvider
@JvmOverloads
constructor(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : RuleSetProvider {
internal class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : DiktatRuleSetFactory {
private val possibleConfigs: Sequence<String?> = sequence {
yield(resolveDefaultConfig())
yield(resolveConfigFileFromJarLocation())
Expand Down Expand Up @@ -143,14 +131,11 @@ constructor(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : RuleS
"LongMethod",
"TOO_LONG_FUNCTION",
)
@Deprecated(
"Marked for removal in KtLint 0.48. See changelog or KDoc for more information.",
)
override fun get(): RuleSet {
override 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 @@ -233,12 +218,13 @@ constructor(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : RuleS

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

private fun validate(config: RulesConfig) =
Expand All @@ -253,8 +239,7 @@ constructor(private var diktatConfigFile: String = DIKTAT_ANALYSIS_CONF) : RuleS
// 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,34 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID
import com.pinterest.ktlint.core.RuleProvider
import com.pinterest.ktlint.core.RuleSetProviderV2

/**
* [RuleSetProviderV2] that provides diKTat ruleset.
*
* The no-argument constructor is used by the Java SPI interface; that's why
* it's explicitly annotated with [JvmOverloads].
*/
@Suppress("serial")
class DiktatRuleSetProviderV2
@JvmOverloads
constructor(private val factory: DiktatRuleSetFactory = DiktatRuleSetFactory()) : RuleSetProviderV2(
id = DIKTAT_RULE_SET_ID,
about = About(
maintainer = "Diktat",
description = "Strict coding standard for Kotlin and a custom set of rules for detecting code smells, code style issues, and bugs",
license = "https://github.com/saveourtool/diktat/blob/master/LICENSE",
repositoryUrl = "https://github.com/saveourtool/diktat",
issueTrackerUrl = "https://github.com/saveourtool/diktat/issues",
),
) {
/**
* @param diktatConfigFile the configuration file where all configurations for
* inspections and rules are stored.
*/
constructor(diktatConfigFile: String) : this(DiktatRuleSetFactory(diktatConfigFile))

override fun getRuleProviders(): Set<RuleProvider> =
factory().ruleProviders
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,52 @@
@file:Suppress(
"Deprecation"
)

package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.qualifiedWithRuleSetId
import org.cqfn.diktat.ruleset.constants.EmitType
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleProvider
import com.pinterest.ktlint.core.api.EditorConfigProperties
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

/**
* This is a wrapper around Ktlint 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 id ID of RuleSet
* @param rules rules which belongs to current RuleSet
* @param rules the rules which belong to the current [DiktatRuleSet].
*/
class OrderedRuleSet(id: String, vararg rules: Rule) : RuleSet(id, rules = adjustRules(id, rules = rules)) {
class OrderedRuleSet private constructor(
private val rules: List<Rule>
) : DiktatRuleSet {
@Suppress("CUSTOM_GETTERS_SETTERS")
override val ruleProviders: Set<RuleProvider>
get() =
rules.map { rule ->
RuleProvider {
rule
}
}.toSet()

/**
* @param id the ID of the [DiktatRuleSet].
* @param rules the rules which belong to the current [DiktatRuleSet].
*/
constructor(
id: String,
rules: List<Rule>
) : this(id, rules = rules.toTypedArray())

/**
* @param id the ID of the [DiktatRuleSet].
* @param rules the rules which belong to the current [DiktatRuleSet].
*/
constructor(
id: String,
vararg rules: Rule
) : this(adjustRules(id, rules))

companion object {
private fun adjustRules(ruleSetId: String, vararg rules: Rule): Array<out Rule> {
private fun adjustRules(ruleSetId: String, rules: Array<out Rule>): List<Rule> {
if (rules.isEmpty()) {
return emptyArray()
return emptyList()
}
return rules.mapIndexed { index, rule ->
if (index == 0) {
Expand All @@ -31,7 +55,7 @@ class OrderedRuleSet(id: String, vararg rules: Rule) : RuleSet(id, rules = adjus
} else {
OrderedRule(ruleSetId, rule, rules[index - 1])
}
}.toTypedArray()
}.toList()
}

private fun adjustVisitorModifiers(
Expand Down Expand Up @@ -64,15 +88,6 @@ class OrderedRuleSet(id: String, vararg rules: Rule) : RuleSet(id, rules = adjus
*/
internal fun Rule.delegatee(): Rule = if (this is OrderedRule) this.rule else this

/**
* @return RuleSet with ordered rules
*/
fun RuleSet.ordered(): RuleSet =
when (this) {
is OrderedRuleSet -> this
else -> OrderedRuleSet(id = id, rules = rules)
}

/**
* @property rule wraps this rule to keep order
*/
Expand All @@ -91,17 +106,6 @@ class OrderedRuleSet(id: String, vararg rules: Rule) : RuleSet(id, rules = adjus
) =
rule.beforeVisitChildNodes(node, autoCorrect, emit)

@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)

override fun afterVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter3
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.Warnings.ENUMS_SEPARATED
import org.cqfn.diktat.ruleset.rules.DiktatRule
import org.cqfn.diktat.ruleset.utils.AstNodePredicate
import org.cqfn.diktat.ruleset.utils.allSiblings
import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace
import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
Expand Down Expand Up @@ -106,7 +107,7 @@ class EnumsSeparated(configRules: List<RulesConfig>) : DiktatRule(
}
}

private fun ASTNode.findLatestTreePrevMatching(predicate: (ASTNode) -> Boolean): ASTNode {
private fun ASTNode.findLatestTreePrevMatching(predicate: AstNodePredicate): ASTNode {
val result = this.treePrev
return if (predicate(result)) result else result.findLatestTreePrevMatching(predicate)
}
Expand Down
Loading

0 comments on commit 113fbe5

Please sign in to comment.