Skip to content

Commit

Permalink
Run rules during unit tests in correct order
Browse files Browse the repository at this point in the history
With KtLintAssertThat an AssertJ style `assertThat` is created for a specific rule. In addition to that rule, it is possible to run additional rules. In case the rule which is to be tested has defined a VisitorModifier which requires one or more additional rules to be loaded and to be enabled, it is mandatory that those rules are added to the unit test.

The goal is to execute the rules during unit tests in the same order as when running the CLI version of KtLint. During each unit test a dynamic RuleSet is being created for a limited set of rules (e.g. the rule for which the `assertThat` is created plus the additional rules specified in the unit tests). The rules in this minimized ruleSet are executed in the order as defined by the VisitorModifier as defined in the rules.

In order to achieve above, following is changed as well:
* Split naming policy of rule id and rule set id. The naming policy of the latter is not changed. The naming policy of the ruleId is changed so that the ruleSetId prefix can be specified optionally. If the ruleId is not prefixed with a ruleSetId then it is assumed to be equal to "standard". For all experimental rules, the ruleSetId has been added.
* Remove obsolete parameter "isUnitTestContext" from VisitorProvider
* As the VisitorModifiers are now also used and checked during unit tests, it is required to run the additional rules during the lint phase as well. Some code examples in tests in which the IndentationRule is added as additional rule are changed to comply with that rule.

Closes pinterest#1457
  • Loading branch information
= committed May 19, 2022
1 parent e480944 commit 9a42e17
Show file tree
Hide file tree
Showing 46 changed files with 259 additions and 237 deletions.
4 changes: 2 additions & 2 deletions ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/Rule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
* Implementation **doesn't** have to be thread-safe or stateless
* (provided [RuleSetProvider] creates a new instance on each `get()` call).
*
* @param id must be unique within the ruleset
* @param id: For non-standard rules, it is expected that this id consist of the ruleSetId and ruleId, e.g. "some-rule-set-id:some-rule-id"
* @param visitorModifiers: set of modifiers of the visitor. Preferably a rule has no modifiers at all, meaning that it
* is completely independent of all other rules.
*
Expand All @@ -20,7 +20,7 @@ abstract class Rule(
public val visitorModifiers: Set<VisitorModifier> = emptySet()
) {
init {
IdNamingPolicy.enforceNaming(id)
IdNamingPolicy.enforceRuleIdNaming(id)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ open class RuleSet(
) : Iterable<Rule> {

init {
IdNamingPolicy.enforceNaming(id)
IdNamingPolicy.enforceRuleSetIdNaming(id)
require(rules.isNotEmpty()) { "At least one rule must be provided" }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode

public class VisitorProvider(
ruleSets: Iterable<RuleSet>,
private val debug: Boolean,
isUnitTestContext: Boolean = false
private val debug: Boolean
) {
private val ruleReferences: List<RuleReference> =
VisitorProviderInitializer(ruleSets, debug, isUnitTestContext).getRulePreferences()
VisitorProviderInitializer(ruleSets, debug).getRulePreferences()

internal fun visitor(
ruleSets: Iterable<RuleSet>,
Expand All @@ -27,7 +26,7 @@ public class VisitorProvider(
.rules
.filter { rule -> toQualifiedRuleId(ruleSet.id, rule.id) in enabledQualifiedRuleIds }
.filter { rule -> isNotDisabled(rootNode, toQualifiedRuleId(ruleSet.id, rule.id)) }
.map { rule -> "${ruleSet.id}:${rule.id}" to rule }
.map { rule -> "${toQualifiedRuleId(ruleSet.id, rule.id)}" to rule }
}.toMap()
if (debug && enabledRules.isEmpty()) {
println(
Expand Down Expand Up @@ -130,7 +129,11 @@ private fun toQualifiedRuleId(
ruleSetId: String,
ruleId: String
) =
"$ruleSetId:$ruleId"
if (ruleId.startsWith("$ruleSetId:")) {
ruleId
} else {
"$ruleSetId:$ruleId"
}

private fun String.toQualifiedRuleId() =
if (contains(":")) {
Expand All @@ -141,8 +144,7 @@ private fun String.toQualifiedRuleId() =

private class VisitorProviderInitializer(
val ruleSets: Iterable<RuleSet>,
val debug: Boolean,
val isUnitTestContext: Boolean = false
val debug: Boolean
) {
fun getRulePreferences(): List<RuleReference> {
return ruleSets
Expand Down Expand Up @@ -275,13 +277,7 @@ private class VisitorProviderInitializer(
while (ruleReferencesIterator.hasNext()) {
val ruleReference = ruleReferencesIterator.next()

if (ruleReference.runAfterRule != null && isUnitTestContext) {
// When running unit tests,the RunAfterRule annotation is ignored. The code provided in the unit should
// be formatted as if the rule on which it depends was already applied. In this way the unit test can be
// restricted to one single rule instead of having to take into account all other rules on which it
// might depend.
newRuleReferences.add(ruleReference.copy(runAfterRule = null))
} else if (ruleReference.runAfterRule != null && newRuleReferences.none { rule -> rule.runsAfter(ruleReference) }) {
if (ruleReference.runAfterRule != null && newRuleReferences.none { rule -> rule.runsAfter(ruleReference) }) {
// The RunAfterRule refers to a rule which is not yet added to the new list of rule references.
if (this.none { it.runsAfter(ruleReference) }) {
// The RunAfterRule refers to a rule which is not loaded at all.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@ package com.pinterest.ktlint.core.internal
* Provides policy to have consistent and restricted `id` field naming style.
*/
internal object IdNamingPolicy {
private const val ID_REGEX = "[a-z]+([-][a-z]+)*"
private val idRegex = ID_REGEX.toRegex()
private const val SIMPLE_ID_REGEX = "[a-z]+([-][a-z]+)*"
private val ruleIdRegex = "($SIMPLE_ID_REGEX:)?($SIMPLE_ID_REGEX)".toRegex()
private val ruleSetIdRegex = "($SIMPLE_ID_REGEX)".toRegex()

/**
* Checks provided [id] is valid.
* Checks provided [ruleId] is valid.
*
* Will throw [IllegalArgumentException] on invalid [id] name.
* Will throw [IllegalArgumentException] on invalid [ruleId] name.
*/
internal fun enforceNaming(id: String) =
require(id.matches(idRegex)) { "id $id must match $ID_REGEX" }
internal fun enforceRuleIdNaming(ruleId: String) =
require(ruleId.matches(ruleIdRegex)) { "Rule id '$ruleId' must match '${ruleIdRegex.pattern}'" }

/**
* Checks provided [ruleSetId] is valid.
*
* Will throw [IllegalArgumentException] on invalid [ruleSetId] name.
*/
internal fun enforceRuleSetIdNaming(ruleSetId: String) =
require(ruleSetId.matches(ruleSetIdRegex)) { "RuleSet id '$ruleSetId' must match '${ruleSetIdRegex.pattern}'" }
}
Original file line number Diff line number Diff line change
Expand Up @@ -580,10 +580,7 @@ class VisitorProviderTest {
return VisitorProvider(
ruleSets = ruleSetList,
// Enable debug mode as it is helpful when a test fails
debug = true,
// Although this is a unit test, the isUnitTestContext is disabled by default so that rules marked with
// RunAfterRule can be tested as well.
isUnitTestContext = isUnitTestContext ?: false
debug = true
).run {
var visits: MutableList<Visit>? = null
visitor(ruleSetList, SOME_ROOT_AST_NODE, concurrent ?: false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
*
* @see [AnnotationSpacingRule] for white space rules. Moved since
*/
class AnnotationRule : Rule("annotation") {
class AnnotationRule : Rule("$experimentalRulesetId:annotation") {

companion object {
const val multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.psi.psiUtil.leaves
*
* https://kotlinlang.org/docs/reference/coding-conventions.html#annotation-formatting
*/
class AnnotationSpacingRule : Rule("annotation-spacing") {
class AnnotationSpacingRule : Rule("$experimentalRulesetId:annotation-spacing") {

companion object {
const val ERROR_MESSAGE = "Annotations should occur immediately before the annotated construct"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
*/
@OptIn(FeatureInAlphaState::class)
class ArgumentListWrappingRule :
Rule("argument-list-wrapping"),
Rule("$experimentalRulesetId:argument-list-wrapping"),
UsesEditorConfigProperties {
private var editorConfigIndent = IndentConfig.DEFAULT_INDENT_CONFIG
private var maxLineLength = -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
*/
class BlockCommentInitialStarAlignmentRule :
Rule(
"block-comment-initial-star-alignment",
"$experimentalRulesetId:block-comment-initial-star-alignment",
visitorModifiers = setOf(
// The block comment is a node which can contain multiple lines. The indent of the second and later line
// should be determined based on the indent of the block comment node. This indent is determined by the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiCommentImpl
*/
@OptIn(FeatureInAlphaState::class)
public class CommentWrappingRule :
Rule("comment-wrapping"),
Rule("$experimentalRulesetId:comment-wrapping"),
UsesEditorConfigProperties {
override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> =
listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
* foo(t: T) = "some-result"
* ```
*/
public class DiscouragedCommentLocationRule : Rule("discouraged-comment-location") {
public class DiscouragedCommentLocationRule : Rule("$experimentalRulesetId:discouraged-comment-location") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
import org.jetbrains.kotlin.psi.KtEnumEntry

public class EnumEntryNameCaseRule : Rule("enum-entry-name-case") {

public class EnumEntryNameCaseRule : Rule("$experimentalRulesetId:enum-entry-name-case") {
internal companion object {
val regex = Regex("[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule

public class ExperimentalRuleSetProvider : RuleSetProvider {
public const val experimentalRulesetId = "experimental"

public class ExperimentalRuleSetProvider : RuleSetProvider {
override fun get(): RuleSet = RuleSet(
"experimental",
experimentalRulesetId,
AnnotationRule(),
ArgumentListWrappingRule(),
MultiLineIfElseRule(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
/**
* Lints and formats the spacing after the fun keyword
*/
public class FunKeywordSpacingRule : Rule("fun-keyword-spacing") {
public class FunKeywordSpacingRule : Rule("$experimentalRulesetId:fun-keyword-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement

public class FunctionReturnTypeSpacingRule : Rule("function-return-type-spacing") {
public class FunctionReturnTypeSpacingRule : Rule("$experimentalRulesetId:function-return-type-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
/**
* Lints and formats the spacing after the fun keyword
*/
public class FunctionStartOfBodySpacingRule : Rule("function-start-of-body-spacing") {
public class FunctionStartOfBodySpacingRule : Rule("$experimentalRulesetId:function-start-of-body-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.nextSibling
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

public class FunctionTypeReferenceSpacingRule : Rule("function-type-reference-spacing") {
public class FunctionTypeReferenceSpacingRule : Rule("$experimentalRulesetId:function-type-reference-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
*/
@OptIn(FeatureInAlphaState::class)
public class KdocWrappingRule :
Rule("kdoc-wrapping"),
Rule("$experimentalRulesetId:kdoc-wrapping"),
UsesEditorConfigProperties {
override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> =
listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
/**
* Lint and format the spacing between the modifiers in and after the last modifier in a modifier list.
*/
public class ModifierListSpacingRule : Rule("modifier-list-spacing") {
public class ModifierListSpacingRule : Rule("$experimentalRulesetId:modifier-list-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.leaves
*
* TODO: if, for, when branch, do, while
*/
class MultiLineIfElseRule : Rule("multiline-if-else") {
class MultiLineIfElseRule : Rule("$experimentalRulesetId:multiline-if-else") {

override fun visit(
node: ASTNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

class NoEmptyFirstLineInMethodBlockRule : Rule("no-empty-first-line-in-method-block") {

class NoEmptyFirstLineInMethodBlockRule : Rule("$experimentalRulesetId:no-empty-first-line-in-method-block") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.pinterest.ktlint.core.ast.prevLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

public class NullableTypeSpacingRule : Rule("nullable-type-spacing") {
public class NullableTypeSpacingRule : Rule("$experimentalRulesetId:nullable-type-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtPackageDirective

class PackageNameRule : Rule("package-name") {

class PackageNameRule : Rule("$experimentalRulesetId:package-name") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
* comma's and colons. However, it does have a more complete view on the higher concept of the parameter-list without
* interfering of the parameter-list-wrapping rule.
*/
public class ParameterListSpacingRule : Rule("parameter-list-spacing") {
public class ParameterListSpacingRule : Rule("$experimentalRulesetId:parameter-list-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.pinterest.ktlint.core.ast.prevLeaf
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement

class SpacingAroundAngleBracketsRule : Rule("spacing-around-angle-brackets") {
class SpacingAroundAngleBracketsRule : Rule("$experimentalRulesetId:spacing-around-angle-brackets") {
private fun String.trimBeforeLastLine() = this.substring(this.lastIndexOf('\n'))

override fun visit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement

class SpacingAroundDoubleColonRule : Rule("double-colon-spacing") {

class SpacingAroundDoubleColonRule : Rule("$experimentalRulesetId:double-colon-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
*
* @see [Kotlin Style Guide](https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace)
*/
class SpacingAroundUnaryOperatorRule : Rule("unary-op-spacing") {

class SpacingAroundUnaryOperatorRule : Rule("$experimentalRulesetId:unary-op-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import org.jetbrains.kotlin.psi.psiUtil.prevLeafs
/**
* @see https://youtrack.jetbrains.com/issue/KT-35106
*/
class SpacingBetweenDeclarationsWithAnnotationsRule : Rule("spacing-between-declarations-with-annotations") {

class SpacingBetweenDeclarationsWithAnnotationsRule : Rule("$experimentalRulesetId:spacing-between-declarations-with-annotations") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
/**
* @see https://youtrack.jetbrains.com/issue/KT-35088
*/
class SpacingBetweenDeclarationsWithCommentsRule : Rule("spacing-between-declarations-with-comments") {

class SpacingBetweenDeclarationsWithCommentsRule : Rule("$experimentalRulesetId:spacing-between-declarations-with-comments") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.nextSibling
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

public class SpacingBetweenFunctionNameAndOpeningParenthesisRule : Rule("spacing-between-function-name-and-opening-parenthesis") {
public class SpacingBetweenFunctionNameAndOpeningParenthesisRule : Rule("$experimentalRulesetId:spacing-between-function-name-and-opening-parenthesis") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
/**
* Lints and formats the spacing before and after the angle brackets of a type argument list.
*/
public class TypeArgumentListSpacingRule : Rule("type-argument-list-spacing") {
public class TypeArgumentListSpacingRule : Rule("$experimentalRulesetId:type-argument-list-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
/**
* Lints and formats the spacing before and after the angle brackets of a type parameter list.
*/
public class TypeParameterListSpacingRule : Rule("type-parameter-list-spacing") {
public class TypeParameterListSpacingRule : Rule("$experimentalRulesetId:type-parameter-list-spacing") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
/**
* Ensures there are no unnecessary parentheses before a trailing lambda.
*/
class UnnecessaryParenthesesBeforeTrailingLambdaRule : Rule("unnecessary-parentheses-before-trailing-lambda") {
class UnnecessaryParenthesesBeforeTrailingLambdaRule : Rule("$experimentalRulesetId:unnecessary-parentheses-before-trailing-lambda") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
Expand Down
Loading

0 comments on commit 9a42e17

Please sign in to comment.