From 9b57c2309ae6b18a3fb3476765b00bbf4fd34f42 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Wed, 15 May 2024 18:25:03 +0200 Subject: [PATCH 01/33] Add RuleAutocorrectApproveHandler interface If a rule implements this interface, and changes the methods beforeVisitChildNodes and/or afterVisitChildNodes in compliance with this interface then the rule is able to support fixing of individual violations by requesting approval to autocorrect each violation found. For this the handler needs to be implemented by the API Consumer that is calling the format function of the KtLintRuleEngine. For now this is implemented in a separate interface to prevent breaking changes in the Rule 1.x API. --- .../ktlint/rule/engine/core/api/Rule.kt | 25 ++++++++- .../core/api/RuleAutocorrectApproveHandler.kt | 51 ++++++++++++++++++ .../rule/engine/api/KtLintRuleEngine.kt | 7 +++ .../engine/internal/AutoCorrectHandler.kt | 10 ++-- .../engine/internal/RuleExecutionContext.kt | 52 +++++++++++-------- .../rule/engine/internal/SuppressHandler.kt | 40 +++++++++++--- .../rules/BlankLineBetweenWhenConditions.kt | 31 +++++------ 7 files changed, 164 insertions(+), 52 deletions(-) create mode 100644 ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt index 57b67e8f99..86b84bf92a 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.rule.engine.core.api +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty import com.pinterest.ktlint.rule.engine.core.internal.IdNamingPolicy @@ -61,6 +62,11 @@ public class RuleSetId( * When wrapping a rule from the ktlint project and modifying its behavior, please change the [ruleId] and [about] fields, so that it is * clear to users whenever they used the original rule provided by KtLint versus a modified version which is not maintained by the KtLint * project. + * + * Note: + * In Ktlint 2.0 the methods from interface [RuleAutocorrectApproveHandler] interface will be merged into the [Rule] class. Consider to + * implement this interface on your Ktlint 1.x compatible rules in order to make your rules suitable for API Consumers, like the + * ktlint-intellij-plugin, that allow their users to fix violations that can be autocorrected on an interactive 1-by-1 basis. */ public open class Rule( /** @@ -96,10 +102,17 @@ public open class Rule( * This method is called on a node in AST before visiting the child nodes. This is repeated recursively for the * child nodes resulting in a depth first traversal of the AST. * + * When a rule overrides this method, it only supports linting and formatting of an entire file. All violations which are found are + * being emitted. The rule can not support manual autocorrect of a specific single violation in the intellij-ktlint-plugin in case that + * files contains multiple violations caused by the same, or by different rules. + * + * IMPORTANT: This method will *not* be called in case the rule implements the [RuleAutocorrectApproveHandler] interface! + * * @param node AST node * @param autoCorrect indicates whether rule should attempt autocorrection * @param emit a way for rule to notify about a violation (lint error) */ + @Deprecated(message = "Marked for removal in Ktlint 2.0. Please implement interface RuleAutocorrectApproveHandler.") public open fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, @@ -109,8 +122,18 @@ public open class Rule( /** * This method is called on a node in AST after all its child nodes have been visited. + * + * When a rule overrides this method, it only supports linting and formatting of an entire file. All violations which are found are + * being emitted. The rule can not support manual autocorrect of a specific single violation in the intellij-ktlint-plugin in case that + * files contains multiple violations caused by the same, or by different rules. + * + * IMPORTANT: This method will *not* be called in case the rule implements the [RuleAutocorrectApproveHandler] interface! + * + * @param node AST node + * @param autoCorrect indicates whether rule should attempt autocorrection + * @param emit a way for rule to notify about a violation (lint error) */ - @Suppress("unused") + @Deprecated(message = "Marked for removal in Ktlint 2.0. Please implement interface RuleAutocorrectApproveHandler.") public open fun afterVisitChildNodes( node: ASTNode, autoCorrect: Boolean, diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt new file mode 100644 index 0000000000..dae4bede87 --- /dev/null +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt @@ -0,0 +1,51 @@ +package com.pinterest.ktlint.rule.engine.core.api + +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * In Ktlint 2.0 the methods from this interface will be merged into the [Rule] class. Consider to implement this interface on your Ktlint + * 1.x compatible rules in order to make your rules suitable for API Consumers, like the ktlint-intellij-plugin, that allow their users to + * fix violations that can be autocorrected on an interactive 1-by-1 basis. + * + * Whenever a rule implements this interface, the [beforeVisitChildNodes] and [afterVisitChildNodes] methods of this interface take + * precedence above the methods with same name in the [Rule] class. As of that the rule should not only implement the interface, but also + * change the implementation by replacing the implementation of [Rule.beforeVisitChildNodes] and/or [Rule.afterVisitChildNodes] in the rule + * class with the methods of this class. + */ +public interface RuleAutocorrectApproveHandler { + /** + * This method is called on a node in AST before visiting the child nodes. This is repeated recursively for the + * child nodes resulting in a depth first traversal of the AST. + * + * When a rule overrides this method, the API Consumer can decide per violation whether the violation needs to be autocorrected. For + * this the [emitAndApprove] function is called, and its result can be used to determine whether the violation is to be corrected. In + * lint mode the [emitAndApprove] should always return false. + * + * @param node AST node + * @param emitAndApprove a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation + * if that is supported by the rule + */ + public fun beforeVisitChildNodes( + node: ASTNode, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + ) { + } + + /** + * This method is called on a node in AST after all its child nodes have been visited. + * + * When a rule overrides this method, the API Consumer can decide per violation whether the violation needs to be autocorrected. For + * this the [emitAndApprove] function is called, and its result can be used to determine whether the violation is to be corrected. In + * lint mode the [emitAndApprove] should always return false. + * + * @param node AST node + * @param emitAndApprove a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation + * if that is supported by the rule + */ + @Suppress("unused") + public open fun afterVisitChildNodes( + node: ASTNode, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + ) { + } +} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 2d32c5f0e4..7a9f5b08c5 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -111,6 +111,8 @@ public class KtLintRuleEngine( // exact order in which violations are being solved. LOGGER.trace { "Lint violation: ${lintError.logMessage(code)}" } } + // No need to ask for approval in lint mode + false } } @@ -196,6 +198,8 @@ public class KtLintRuleEngine( .invoke { rule -> ruleExecutionContext.executeRule(rule, autoCorrectHandler) { offset, errorMessage, canBeAutoCorrected -> if (canBeAutoCorrected) { + // TODO: send LintError to external approve handler + mutated = true /* * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might @@ -218,6 +222,7 @@ public class KtLintRuleEngine( // exact order in which violations are being solved. LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } } + canBeAutoCorrected } } } while (mutated && formatRunCount < MAX_FORMAT_RUNS_PER_FILE) @@ -233,6 +238,8 @@ public class KtLintRuleEngine( if (canBeAutoCorrected) { hasErrorsWhichCanBeAutocorrected = true } + // No need to ask for approval in lint mode + false } } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt index 66e99fa95a..fd052e37e2 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt @@ -1,24 +1,22 @@ package com.pinterest.ktlint.rule.engine.internal -import org.jetbrains.kotlin.com.intellij.lang.ASTNode - /** * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutoCorrectHandler { - fun autoCorrect(node: ASTNode): Boolean + fun autoCorrect(offset: Int): Boolean } internal data object AutoCorrectDisabledHandler : AutoCorrectHandler { - override fun autoCorrect(node: ASTNode) = false + override fun autoCorrect(offset: Int) = false } internal data object AutoCorrectEnabledHandler : AutoCorrectHandler { - override fun autoCorrect(node: ASTNode) = true + override fun autoCorrect(offset: Int) = true } internal class AutoCorrectOffsetRangeHandler( private val autoCorrectOffsetRange: IntRange, ) : AutoCorrectHandler { - override fun autoCorrect(node: ASTNode) = node.startOffset in autoCorrectOffsetRange + override fun autoCorrect(offset: Int) = offset in autoCorrectOffsetRange } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 802b9c03e6..e191ddb624 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -7,6 +7,7 @@ import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.Companion.UTF8_BOM import com.pinterest.ktlint.rule.engine.api.KtLintRuleException import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig @@ -47,7 +48,7 @@ internal class RuleExecutionContext private constructor( fun executeRule( rule: Rule, autoCorrectHandler: AutoCorrectHandler, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { try { rule.startTraversalOfAST() @@ -59,7 +60,7 @@ internal class RuleExecutionContext private constructor( // EditorConfigProperty that is not explicitly defined. editorConfig.filterBy(rule.usesEditorConfigProperties.plus(CODE_STYLE_PROPERTY)), ) - this.executeRuleOnNodeRecursively(rootNode, rule, autoCorrectHandler, emit) + this.executeRuleOnNodeRecursively(rootNode, rule, autoCorrectHandler, emitAndApprove) rule.afterLastNode() } catch (e: RuleExecutionException) { throw KtLintRuleException( @@ -81,29 +82,18 @@ internal class RuleExecutionContext private constructor( node: ASTNode, rule: Rule, autoCorrectHandler: AutoCorrectHandler, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { - // TODO: In Ktlint V2 the autocorrect handler should be passed down to the rules, so that the autocorrect handler can check the - // offset at which the violation is found is in the autocorrect range or not. Currently it is checked whether the offset of the - // node that is triggering the violation is in the range. This has following side effects: - // * if the offset of the node which triggers the violation is inside the range, but the offset of the violation itself it outside - // the autocorrect range than a change is made to code outside the selected range - // * if the offset of the node which triggers the violation is outside the range, but the offset of the violation itself it inside - // the autocorrect range than *not* change is made to code which is in the selected range while the user would have expected it - // to be changed. - // Passing down the autocorrectHandler to the rules is a breaking change as the Rule signature needs to be changed. - val autoCorrect = autoCorrectHandler.autoCorrect(node) - /** * The [suppressionLocator] can be changed during each visit of node when running [KtLintRuleEngine.format]. So a new handler is to * be built before visiting the nodes. */ - val suppressHandler = SuppressHandler(suppressionLocator, autoCorrect, emit) + val suppressHandler = SuppressHandler(suppressionLocator, autoCorrectHandler, emitAndApprove) if (rule.shouldContinueTraversalOfAST()) { try { executeRuleOnNodeRecursively(node, rule, autoCorrectHandler, suppressHandler) } catch (e: Exception) { - if (autoCorrect) { + if (autoCorrectHandler.autoCorrect(node.startOffset)) { // line/col cannot be reliably mapped as exception might originate from a node not present in the // original AST throw RuleExecutionException( @@ -133,25 +123,43 @@ internal class RuleExecutionContext private constructor( autoCorrectHandler: AutoCorrectHandler, suppressHandler: SuppressHandler, ) { - suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> - rule.beforeVisitChildNodes(node, autoCorrect, emit) + if (rule is RuleAutocorrectApproveHandler) { + suppressHandler.handle(node, rule.ruleId) { emitAndApprove -> + rule.beforeVisitChildNodes( + node = node, + emitAndApprove = emitAndApprove, + ) + } + } else { + suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> + rule.beforeVisitChildNodes(node, autoCorrect, emit) + } } if (rule.shouldContinueTraversalOfAST()) { node .getChildren(null) .forEach { childNode -> - suppressHandler.handle(childNode, rule.ruleId) { _, emit -> + suppressHandler.handle(childNode, rule.ruleId) { emitAndApprove -> this.executeRuleOnNodeRecursively( childNode, rule, autoCorrectHandler, - emit, + emitAndApprove, ) } } } - suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> - rule.afterVisitChildNodes(node, autoCorrect, emit) + if (rule is RuleAutocorrectApproveHandler) { + suppressHandler.handle(node, rule.ruleId) { emitAndApprove -> + rule.afterVisitChildNodes( + node = node, + emitAndApprove = emitAndApprove, + ) + } + } else { + suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> + rule.afterVisitChildNodes(node, autoCorrect, emit) + } } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt index 80ccbdec49..49dbd6176d 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt @@ -5,8 +5,8 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode internal class SuppressHandler( private val suppressionLocator: SuppressionLocator, - private val autoCorrect: Boolean, - private val emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + private val autoCorrectHandler: AutoCorrectHandler, + private val emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { fun handle( node: ASTNode, @@ -18,18 +18,46 @@ internal class SuppressHandler( node.startOffset, ruleId, ) - val autoCorrect = this.autoCorrect && !suppress + val autoCorrect = this.autoCorrectHandler.autoCorrect(node.startOffset) && !suppress val emit = if (suppress) { SUPPRESS_EMIT } else { - this.emit + this.emitAndApprove } - function(autoCorrect, emit) + function(autoCorrect, emit.onlyEmit()) } + fun handle( + node: ASTNode, + ruleId: RuleId, + function: ( + // emitAndApprove + (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + ) -> Unit, + ) { + val suppress = suppressionLocator(node.startOffset, ruleId) + if (suppress) { + function(SUPPRESS_EMIT) + } else { + function(emitAndApprove) + } + } + + // Simplify the emitAndApprove to an emit only lambda which can be used in the legacy (deprecated) functions + @Deprecated(message = "Remove in Ktlint 2.0") + private fun ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean).onlyEmit() = + { + offset: Int, + errorMessage: String, + canBeAutoCorrected: Boolean, + -> + this(offset, errorMessage, canBeAutoCorrected) + Unit + } + private companion object { // Swallow violation so that it is not emitted - val SUPPRESS_EMIT: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit = { _, _, _ -> } + val SUPPRESS_EMIT: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean = { _, _, _ -> false } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt index 34a79aba34..32cfce8fee 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt @@ -4,6 +4,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler 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.SinceKtlint.Status.EXPERIMENTAL @@ -21,6 +22,7 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue /** * The Kotlin Coding Conventions suggest to consider using a blank line after a multiline when-condition @@ -36,6 +38,7 @@ public class BlankLineBetweenWhenConditions : id = "blank-line-between-when-conditions", usesEditorConfigProperties = setOf(LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY), ), + RuleAutocorrectApproveHandler, Rule.Experimental { private var lineBreakAfterWhenCondition = LINE_BREAK_AFTER_WHEN_CONDITION_PROPERTY.defaultValue @@ -45,31 +48,28 @@ public class BlankLineBetweenWhenConditions : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (node.elementType == ElementType.WHEN) { - visitWhenStatement(node, autoCorrect, emit) + visitWhenStatement(node, emitAndApprove) } } private fun visitWhenStatement( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { val hasMultilineWhenCondition = node.hasAnyMultilineWhenCondition() if (hasMultilineWhenCondition && lineBreakAfterWhenCondition) { - addBlankLinesBetweenWhenConditions(node, autoCorrect, emit) + addBlankLinesBetweenWhenConditions(node, emitAndApprove) } else { - removeBlankLinesBetweenWhenConditions(node, autoCorrect, emit) + removeBlankLinesBetweenWhenConditions(node, emitAndApprove) } } private fun addBlankLinesBetweenWhenConditions( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { node .children() @@ -81,13 +81,12 @@ public class BlankLineBetweenWhenConditions : .findWhitespaceAfterPreviousCodeSibling() ?.takeUnless { it.containsBlankLine() } ?.let { whitespaceBeforeWhenEntry -> - emit( + emitAndApprove( whitespaceBeforeWhenEntry.startOffset + 1, "Add a blank line between all when-condition in case at least one multiline when-condition is found in the " + "statement", true, - ) - if (autoCorrect) { + ).ifTrue { whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent()}") } } @@ -109,8 +108,7 @@ public class BlankLineBetweenWhenConditions : private fun removeBlankLinesBetweenWhenConditions( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { node .children() @@ -122,12 +120,11 @@ public class BlankLineBetweenWhenConditions : .findWhitespaceAfterPreviousCodeSibling() ?.takeIf { it.containsBlankLine() } ?.let { whitespaceBeforeWhenEntry -> - emit( + emitAndApprove( whitespaceBeforeWhenEntry.startOffset + 1, "Unexpected blank lines between when-condition if all when-conditions are single lines", true, - ) - if (autoCorrect) { + ).ifTrue { whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent(includeNewline = false)}") } } From dbb21c5bd49488ab443f5033166e88a73191b53c Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 18 May 2024 16:09:42 +0200 Subject: [PATCH 02/33] Extract CodeLinter and CodeFormatter from KtLintRuleEngine --- .../rule/engine/api/KtLintRuleEngine.kt | 195 ++++-------------- .../rule/engine/internal/CodeFormatter.kt | 154 ++++++++++++++ .../ktlint/rule/engine/internal/CodeLinter.kt | 56 +++++ .../LintErrorLineAndColumnComparator.kt | 7 + .../engine/internal/LintErrorLogMessage.kt | 12 ++ .../rule/engine/internal/VisitorProvider.kt | 17 +- .../engine/internal/VisitorProviderTest.kt | 11 +- 7 files changed, 282 insertions(+), 170 deletions(-) create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 7a9f5b08c5..12376b24d3 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -2,40 +2,31 @@ package com.pinterest.ktlint.rule.engine.api -import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults.Companion.EMPTY_EDITOR_CONFIG_DEFAULTS import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.EMPTY_EDITOR_CONFIG_OVERRIDE import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.propertyTypes import com.pinterest.ktlint.rule.engine.core.util.safeAs -import com.pinterest.ktlint.rule.engine.internal.AutoCorrectDisabledHandler import com.pinterest.ktlint.rule.engine.internal.AutoCorrectEnabledHandler -import com.pinterest.ktlint.rule.engine.internal.AutoCorrectHandler import com.pinterest.ktlint.rule.engine.internal.AutoCorrectOffsetRangeHandler +import com.pinterest.ktlint.rule.engine.internal.CodeFormatter +import com.pinterest.ktlint.rule.engine.internal.CodeLinter import com.pinterest.ktlint.rule.engine.internal.EditorConfigFinder import com.pinterest.ktlint.rule.engine.internal.EditorConfigGenerator import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoader import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoaderEc4j -import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE -import com.pinterest.ktlint.rule.engine.internal.VisitorProvider -import io.github.oshai.kotlinlogging.KotlinLogging import org.ec4j.core.Resource -import org.ec4j.core.model.PropertyType.EndOfLineValue.crlf -import org.ec4j.core.model.PropertyType.EndOfLineValue.lf import org.jetbrains.kotlin.com.intellij.lang.FileASTNode import java.nio.charset.StandardCharsets import java.nio.file.FileSystem import java.nio.file.FileSystems import java.nio.file.Path -private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() - public class KtLintRuleEngine( /** * The set of [RuleProvider]s to be invoked by the [KtLintRuleEngine]. A [RuleProvider] is able to create a new instance of a [Rule] so @@ -82,6 +73,9 @@ public class KtLintRuleEngine( editorConfigOverride, ) + private val codeLinter = CodeLinter(this) + private val codeFormatter = CodeFormatter(this) + /** * Check the [code] for lint errors. If [code] is path as file reference then the '.editorconfig' files on the path to file are taken * into account. For each lint violation found, the [callback] is invoked. @@ -92,54 +86,31 @@ public class KtLintRuleEngine( public fun lint( code: Code, callback: (LintError) -> Unit = { }, - ) { - LOGGER.debug { "Starting with linting file '${code.fileNameOrStdin()}'" } - - val ruleExecutionContext = createRuleExecutionContext(this, code) - val errors = mutableListOf() - - VisitorProvider(ruleExecutionContext.ruleProviders) - .visitor() - .invoke { rule -> - ruleExecutionContext.executeRule(rule, AutoCorrectDisabledHandler) { offset, errorMessage, canBeAutoCorrected -> - val (line, col) = ruleExecutionContext.positionInTextLocator(offset) - LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - .let { lintError -> - errors.add(lintError) - // In trace mode report the violation immediately. The order in which violations are actually found might be - // different from the order in which they are reported. For debugging purposes it can be helpful to know the - // exact order in which violations are being solved. - LOGGER.trace { "Lint violation: ${lintError.logMessage(code)}" } - } - // No need to ask for approval in lint mode - false - } - } - - errors - .sortedWith { l, r -> if (l.line != r.line) l.line - r.line else l.col - r.col } - .forEach { e -> callback(e) } - - LOGGER.debug { "Finished with linting file '${code.fileNameOrStdin()}'" } - } + ): Unit = codeLinter.lint(code, callback) /** * Fix style violations in [code] for lint errors when possible. If [code] is passed as file reference then the '.editorconfig' files on * the path are taken into account. For each lint violation found, the [callback] is invoked. * + * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until + * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. + * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation */ public fun format( code: Code, callback: (LintError, Boolean) -> Unit = { _, _ -> }, - ): String = format(code, AutoCorrectEnabledHandler, callback) + ): String = codeFormatter.format(code, AutoCorrectEnabledHandler, callback, MAX_FORMAT_RUNS_PER_FILE) /** * Fix style violations in [code] for lint errors found in the [autoCorrectOffsetRange] when possible. If [code] is passed as file * reference then the '.editorconfig' files on the path are taken into account. For each lint violation found, the [callback] is * invoked. * + * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until + * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. + * * IMPORTANT: Partial formatting not always works as expected. The offset of the node which is triggering the violation does not * necessarily to be close to the offset at which the violation is reported. Counter-intuitively the offset of the trigger node must be * located inside the [autoCorrectOffsetRange] instead of the offset at which the violation is reported. @@ -174,112 +145,34 @@ public class KtLintRuleEngine( code: Code, autoCorrectOffsetRange: IntRange, callback: (LintError, Boolean) -> Unit = { _, _ -> }, - ): String = format(code, AutoCorrectOffsetRangeHandler(autoCorrectOffsetRange), callback) + ): String = + codeFormatter.format( + code, + AutoCorrectOffsetRangeHandler(autoCorrectOffsetRange), + callback, + MAX_FORMAT_RUNS_PER_FILE, + ) - private fun format( + /** + * Formats style violations in [code]. Whenever a [LintError] is found which can be autocorrected by the rule that detected the + * violation, the [callback] is invoked to let the calling API Consumer to decide whether the [LintError] is actually to be fixed. + * + * Important: [callback] is only invoked if the rule that found that the violation has implemented the [RuleAutocorrectApproveHandler] + * and the violation can be autocorrected. + * + * @throws KtLintParseException if text is not a valid Kotlin code + * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation + */ + public fun interactiveFormat( code: Code, - autoCorrectHandler: AutoCorrectHandler, - callback: (LintError, Boolean) -> Unit = { _, _ -> }, - ): String { - LOGGER.debug { "Starting with formatting file '${code.fileNameOrStdin()}'" } - - val hasUTF8BOM = code.content.startsWith(UTF8_BOM) - val ruleExecutionContext = createRuleExecutionContext(this, code) - - val visitorProvider = VisitorProvider(ruleExecutionContext.ruleProviders) - var formatRunCount = 0 - var mutated: Boolean - val errors = mutableSetOf>() - do { - mutated = false - formatRunCount++ - visitorProvider - .visitor() - .invoke { rule -> - ruleExecutionContext.executeRule(rule, autoCorrectHandler) { offset, errorMessage, canBeAutoCorrected -> - if (canBeAutoCorrected) { - // TODO: send LintError to external approve handler - - mutated = true - /* - * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might - * have changed. - */ - ruleExecutionContext.rebuildSuppressionLocator() - } - val (line, col) = ruleExecutionContext.positionInTextLocator(offset) - LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - .let { lintError -> - errors.add( - Pair( - lintError, - // It is assumed that a rule that emits that an error can be autocorrected, also does correct the error. - canBeAutoCorrected, - ), - ) - // In trace mode report the violation immediately. The order in which violations are actually found might be - // different from the order in which they are reported. For debugging purposes it can be helpful to know the - // exact order in which violations are being solved. - LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } - } - canBeAutoCorrected - } - } - } while (mutated && formatRunCount < MAX_FORMAT_RUNS_PER_FILE) - if (formatRunCount == MAX_FORMAT_RUNS_PER_FILE && mutated) { - // It is unknown if the last format run introduces new lint violations which can be autocorrected. So run lint once more so that - // the user can be informed about this correctly. - var hasErrorsWhichCanBeAutocorrected = false - visitorProvider - .visitor() - .invoke { rule -> - if (!hasErrorsWhichCanBeAutocorrected) { - ruleExecutionContext.executeRule(rule, AutoCorrectDisabledHandler) { _, _, canBeAutoCorrected -> - if (canBeAutoCorrected) { - hasErrorsWhichCanBeAutocorrected = true - } - // No need to ask for approval in lint mode - false - } - } - } - if (hasErrorsWhichCanBeAutocorrected) { - LOGGER.warn { - "Format was not able to resolve all violations which (theoretically) can be autocorrected in file " + - "${code.filePathOrStdin()} in $MAX_FORMAT_RUNS_PER_FILE consecutive runs of format." - } - } - } - - errors - .sortedWith { (l), (r) -> if (l.line != r.line) l.line - r.line else l.col - r.col } - .forEach { (e, corrected) -> callback(e, corrected) } - - if (!mutated && formatRunCount == 1) { - return code.content - } - - val formattedCode = - ruleExecutionContext - .rootNode - .text - .replace("\n", ruleExecutionContext.determineLineSeparator(code.content)) - return if (hasUTF8BOM) { - UTF8_BOM + formattedCode - } else { - formattedCode - }.also { - LOGGER.debug { "Finished with formatting file '${code.fileNameOrStdin()}'" } - } - } - - private fun RuleExecutionContext.determineLineSeparator(fileContent: String): String { - val eol = editorConfig[END_OF_LINE_PROPERTY] - return when { - eol == crlf || eol != lf && fileContent.lastIndexOf('\r') != -1 -> "\r\n" - else -> "\n" - } - } + callback: (LintError) -> Boolean, + ): String = + codeFormatter.format( + code = code, + autoCorrectHandler = AutoCorrectEnabledHandler, + callback = { lintError, _ -> callback(lintError) }, + maxFormatRunsPerFile = 1, + ) /** * Generates Kotlin `.editorconfig` file section content based on a path to a file or directory. Given that path, all '.editorconfig' @@ -330,7 +223,7 @@ public class KtLintRuleEngine( /** * Reloads an '.editorconfig' file given that it is currently loaded into the KtLint cache. This method is intended * to be called by the API consumer when it is aware of changes in the '.editorconfig' file that should be taken - * into account with next calls to [lint] and/or [format]. See [editorConfigFilePaths] to get the list of + * into account with next calls to [lint] and/or [formatBatch]. See [editorConfigFilePaths] to get the list of * '.editorconfig' files which need to be observed. */ public fun reloadEditorConfigFile(path: Path) { @@ -341,14 +234,6 @@ public class KtLintRuleEngine( public fun transformToAst(code: Code): FileASTNode = createRuleExecutionContext(this, code).rootNode - private fun LintError.logMessage(code: Code) = - "${code.fileNameOrStdin()}:$line:$col: $detail ($ruleId)" + - if (canBeAutoCorrected) { - "" - } else { - " [cannot be autocorrected]" - } - public companion object { internal const val UTF8_BOM = "\uFEFF" diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt new file mode 100644 index 0000000000..a966f7feb8 --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -0,0 +1,154 @@ +package com.pinterest.ktlint.rule.engine.internal + +import com.pinterest.ktlint.logger.api.initKtLintKLogger +import com.pinterest.ktlint.rule.engine.api.Code +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY +import io.github.oshai.kotlinlogging.KotlinLogging +import org.ec4j.core.model.PropertyType + +private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() + +internal class CodeFormatter( + val ktLintRuleEngine: KtLintRuleEngine, +) { + fun format( + code: Code, + autoCorrectHandler: AutoCorrectHandler, + callback: (LintError, Boolean) -> Unit = { _, _ -> }, + maxFormatRunsPerFile: Int, + ): String { + LOGGER.debug { "Starting with formatting file '${code.fileNameOrStdin()}'" } + + val (formattedCode, errors) = format(code, autoCorrectHandler, maxFormatRunsPerFile) + + errors + .sortedWith(lintErrorLineAndColumnComparator { it.first }) + .forEach { (e, corrected) -> callback(e, corrected) } + + return (code.utf8Bom() + formattedCode).also { + LOGGER.debug { "Finished with formatting file '${code.fileNameOrStdin()}'" } + } + } + + private fun Code.utf8Bom() = + if (content.startsWith(KtLintRuleEngine.UTF8_BOM)) { + KtLintRuleEngine.UTF8_BOM + } else { + "" + } + + private fun format( + code: Code, + autoCorrectHandler: AutoCorrectHandler, + maxFormatRunsPerFile: Int, + ): Pair>> { + with(RuleExecutionContext.createRuleExecutionContext(ktLintRuleEngine, code)) { + val errors = mutableSetOf>() + var formatRunCount = 0 + var mutated: Boolean + do { + mutated = + format(autoCorrectHandler, code).let { ruleErrors -> + errors.addAll(ruleErrors) + ruleErrors.any { it.first.canBeAutoCorrected } + } + formatRunCount++ + } while (mutated && formatRunCount < maxFormatRunsPerFile) + if (mutated && formatRunCount == maxFormatRunsPerFile) { + // It is unknown if the last format run introduces new lint violations which can be autocorrected. So run lint once more + // so that the user can be informed about this correctly. + lintAfterFormat().also { + LOGGER.warn { + "Format was not able to resolve all violations which (theoretically) can be autocorrected in file " + + "${code.filePathOrStdin()} in $maxFormatRunsPerFile consecutive runs of format." + } + } + } + val lineSeparator = code.determineLineSeparator(editorConfig[END_OF_LINE_PROPERTY]) + return Pair(formattedCode(lineSeparator), errors) + } + } + + private fun RuleExecutionContext.formattedCode(lineSeparator: String) = rootNode.text.replace("\n", lineSeparator) + + private fun RuleExecutionContext.format( + autoCorrectHandler: AutoCorrectHandler, + code: Code, + ): Set> { + val errors: MutableSet> = mutableSetOf() + VisitorProvider(ruleProviders) + .rules + .forEach { rule -> + executeRule(rule, autoCorrectHandler, code).let { ruleErrors -> errors.addAll(ruleErrors) } + } + return errors + } + + private fun RuleExecutionContext.lintAfterFormat(): Boolean { + var hasErrorsWhichCanBeAutocorrected = false + VisitorProvider(ruleProviders) + .rules + .forEach { rule -> + if (!hasErrorsWhichCanBeAutocorrected) { + executeRule(rule, AutoCorrectDisabledHandler) { _, _, canBeAutoCorrected -> + if (canBeAutoCorrected) { + hasErrorsWhichCanBeAutocorrected = true + } + // No need to ask for approval in lint mode + false + } + } + } + return hasErrorsWhichCanBeAutocorrected + } + + private fun RuleExecutionContext.executeRule( + rule: Rule, + autoCorrectHandler: AutoCorrectHandler, + code: Code, + ): Set> { + val errors = mutableSetOf>() + executeRule(rule, autoCorrectHandler) { offset, errorMessage, canBeAutoCorrected -> + if (canBeAutoCorrected) { + // TODO: send LintError to external approve handler + /* + * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might + * have changed. + */ + rebuildSuppressionLocator() + } + val (line, col) = positionInTextLocator(offset) + LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) + .let { lintError -> + errors.add( + Pair( + lintError, + // It is assumed that a rule that emits that an error can be autocorrected, also does correct the error. + canBeAutoCorrected, + ), + ) + // In trace mode report the violation immediately. The order in which violations are actually found might be + // different from the order in which they are reported. For debugging purposes it can be helpful to know the + // exact order in which violations are being solved. + LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } + } + canBeAutoCorrected + } + return errors + } + + private fun Code.determineLineSeparator(eolEditorConfigProperty: PropertyType.EndOfLineValue): String = + when { + eolEditorConfigProperty == PropertyType.EndOfLineValue.crlf || + eolEditorConfigProperty != PropertyType.EndOfLineValue.lf && + doesNotContain('\r') -> + "\r\n" + + else -> "\n" + } + + private fun Code.doesNotContain(char: Char) = content.lastIndexOf(char) != -1 +} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt new file mode 100644 index 0000000000..ff5ef3ea17 --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt @@ -0,0 +1,56 @@ +package com.pinterest.ktlint.rule.engine.internal + +import com.pinterest.ktlint.logger.api.initKtLintKLogger +import com.pinterest.ktlint.rule.engine.api.Code +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext +import io.github.oshai.kotlinlogging.KotlinLogging + +private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() + +internal class CodeLinter( + val ktLintRuleEngine: KtLintRuleEngine, +) { + fun lint( + code: Code, + callback: (LintError) -> Unit = { }, + ) { + LOGGER.debug { "Starting with linting file '${code.fileNameOrStdin()}'" } + executeLint(code) + .sortedWith(lintErrorLineAndColumnComparator { it }) + .forEach { e -> callback(e) } + LOGGER.debug { "Finished with linting file '${code.fileNameOrStdin()}'" } + } + + private fun executeLint(code: Code): MutableList = + with(createRuleExecutionContext(ktLintRuleEngine, code)) { + val errors = mutableListOf() + VisitorProvider(ruleProviders) + .rules + .forEach { rule -> executeRule(rule, code).let { errors.addAll(it) } } + errors + } + + private fun RuleExecutionContext.executeRule( + rule: Rule, + code: Code, + ): List { + val errors = mutableListOf() + executeRule(rule, AutoCorrectDisabledHandler) { offset, errorMessage, canBeAutoCorrected -> + val (line, col) = this.positionInTextLocator(offset) + LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) + .let { lintError -> + errors.add(lintError) + // In trace mode report the violation immediately. The order in which violations are actually found might be + // different from the order in which they are reported. For debugging purposes it can be helpful to know the + // exact order in which violations are being solved. + LOGGER.trace { "Lint violation: ${lintError.logMessage(code)}" } + } + // No need to ask for approval in lint mode + false + } + return errors + } +} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt new file mode 100644 index 0000000000..bb4d238f1e --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt @@ -0,0 +1,7 @@ +package com.pinterest.ktlint.rule.engine.internal + +import com.pinterest.ktlint.rule.engine.api.LintError + +internal fun lintErrorLineAndColumnComparator(transformer: (T) -> LintError) = + compareBy { transformer(it).line } + .then(compareBy { transformer(it).col }) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt new file mode 100644 index 0000000000..f534cc1858 --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt @@ -0,0 +1,12 @@ +package com.pinterest.ktlint.rule.engine.internal + +import com.pinterest.ktlint.rule.engine.api.Code +import com.pinterest.ktlint.rule.engine.api.LintError + +internal fun LintError.logMessage(code: Code) = + "${code.fileNameOrStdin()}:$line:$col: $detail ($ruleId)" + + if (canBeAutoCorrected) { + "" + } else { + " [cannot be autocorrected]" + } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProvider.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProvider.kt index 37f35b45e3..5540b1c929 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProvider.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProvider.kt @@ -36,15 +36,14 @@ internal class VisitorProvider( RULE_PROVIDER_SORTER }.getSortedRuleProviders(ruleProviders) - internal fun visitor(): ((rule: Rule) -> Unit) -> Unit { - if (ruleProvidersSorted.isEmpty()) { - LOGGER.debug { "Skipping file as no enabled rules are found to be executed" } - return { _ -> } - } - return { visit -> - ruleProvidersSorted.forEach { - visit(it.createNewRuleInstance()) + internal val rules: List + get() { + if (ruleProvidersSorted.isEmpty()) { + LOGGER.debug { "Skipping file as no enabled rules are found to be executed" } + return emptyList() + } + return ruleProvidersSorted.map { + it.createNewRuleInstance() } } - } } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProviderTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProviderTest.kt index 2aa6abf490..2af4c667b4 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProviderTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/VisitorProviderTest.kt @@ -12,12 +12,11 @@ class VisitorProviderTest { VisitorProvider( ruleProviders = emptySet(), recreateRuleSorter = true, - ).visitor() - .invoke { _ -> - assertThat(false) - .withFailMessage("The visitor provider should not have called this lambda in case it has no rule providers") - .isTrue - } + ).rules.forEach { _ -> + assertThat(false) + .withFailMessage("The visitor provider should not have called this lambda in case it has no rule providers") + .isTrue + } } } } From 8710723372d963faca7a2e29e55c003cceff9d85 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 19 May 2024 13:31:15 +0200 Subject: [PATCH 03/33] Make interactiveFormat actually work --- .../api/consumer/KtLintRuleEngineTest.kt | 43 +++++++- .../rule/engine/api/KtLintRuleEngine.kt | 7 +- .../engine/internal/AutoCorrectHandler.kt | 19 ++++ .../rule/engine/internal/CodeFormatter.kt | 43 +++++--- .../engine/internal/RuleExecutionContext.kt | 33 +++--- .../rule/engine/internal/SuppressHandler.kt | 2 +- .../internal/SuppressionLocatorBuilder.kt | 37 +++---- .../ruleset/standard/rules/IndentationRule.kt | 103 +++++++++--------- .../standard/rules/IndentationRuleTest.kt | 12 +- 9 files changed, 187 insertions(+), 112 deletions(-) diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 902043080e..2a658e15a0 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -7,6 +7,7 @@ import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EXPERIMENTAL_RULES_EXECUTION_PROPERTY @@ -313,6 +314,42 @@ class KtLintRuleEngineTest { """.trimIndent(), ) } + + @Test + fun `Given a kotlin code snippet that does contain multiple indentation errors then only format the lint error at specific offset and message`() { + val ktLintRuleEngine = + KtLintRuleEngine( + ruleProviders = + setOf( + RuleProvider { IndentationRule() }, + ), + fileSystem = ktlintTestFileSystem.fileSystem, + ) + + val originalCode = + """ + fun main() { + println("Hello world!") + println("Hello world!") + println("Hello world!") + } + """.trimIndent() + val actual = + ktLintRuleEngine + .interactiveFormat(Code.fromSnippet(originalCode)) { lintError -> + lintError.line == 3 && lintError.detail == "Unexpected indentation (0) (should be 4)" + } + + assertThat(actual).isEqualTo( + """ + fun main() { + println("Hello world!") + println("Hello world!") + println("Hello world!") + } + """.trimIndent(), + ) + } } @Test @@ -358,14 +395,14 @@ class KtLintRuleEngineTest { ruleId = NO_VAR_RULE_ID, about = About(), ), + RuleAutocorrectApproveHandler, Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (node.elementType == ElementType.VAR_KEYWORD) { - emit(node.startOffset, "Unexpected var, use val instead", false) + emitAndApprove(node.startOffset, "Unexpected var, use val instead", false) } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 12376b24d3..55d25afc8d 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -18,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.internal.EditorConfigFinder import com.pinterest.ktlint.rule.engine.internal.EditorConfigGenerator import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoader import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoaderEc4j +import com.pinterest.ktlint.rule.engine.internal.LintErrorAutoCorrectHandler import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE import org.ec4j.core.Resource @@ -95,6 +96,9 @@ public class KtLintRuleEngine( * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. * + * [callback] is invoked once for each unique [LintError] found during all runs. Note that [callback] is only invoked once all format + * runs have been completed. + * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation */ @@ -169,8 +173,7 @@ public class KtLintRuleEngine( ): String = codeFormatter.format( code = code, - autoCorrectHandler = AutoCorrectEnabledHandler, - callback = { lintError, _ -> callback(lintError) }, + autoCorrectHandler = LintErrorAutoCorrectHandler(callback), maxFormatRunsPerFile = 1, ) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt index fd052e37e2..ce60d05620 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt @@ -1,22 +1,41 @@ package com.pinterest.ktlint.rule.engine.internal +import com.pinterest.ktlint.rule.engine.api.LintError + /** * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutoCorrectHandler { fun autoCorrect(offset: Int): Boolean + + fun autoCorrect(lintError: LintError): Boolean } internal data object AutoCorrectDisabledHandler : AutoCorrectHandler { override fun autoCorrect(offset: Int) = false + + override fun autoCorrect(lintError: LintError) = false } internal data object AutoCorrectEnabledHandler : AutoCorrectHandler { override fun autoCorrect(offset: Int) = true + + override fun autoCorrect(lintError: LintError) = true } +@Deprecated(message = "Replace with LintErrorAutocorrectHandler") internal class AutoCorrectOffsetRangeHandler( private val autoCorrectOffsetRange: IntRange, ) : AutoCorrectHandler { override fun autoCorrect(offset: Int) = offset in autoCorrectOffsetRange + + override fun autoCorrect(lintError: LintError) = throw IllegalStateException() +} + +internal class LintErrorAutoCorrectHandler( + private val callback: (LintError) -> Boolean, +) : AutoCorrectHandler { + override fun autoCorrect(offset: Int) = true + + override fun autoCorrect(lintError: LintError) = callback(lintError) } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index a966f7feb8..448281cba7 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -112,30 +112,37 @@ internal class CodeFormatter( ): Set> { val errors = mutableSetOf>() executeRule(rule, autoCorrectHandler) { offset, errorMessage, canBeAutoCorrected -> - if (canBeAutoCorrected) { - // TODO: send LintError to external approve handler + val (line, col) = positionInTextLocator(offset) + val lintError = LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) + val autoCorrect = + if (canBeAutoCorrected) { + if (autoCorrectHandler is AutoCorrectOffsetRangeHandler) { + autoCorrectHandler.autoCorrect(offset) + } else { + autoCorrectHandler.autoCorrect(lintError) + } + } else { + false + } + if (autoCorrect) { /* * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might * have changed. */ rebuildSuppressionLocator() } - val (line, col) = positionInTextLocator(offset) - LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - .let { lintError -> - errors.add( - Pair( - lintError, - // It is assumed that a rule that emits that an error can be autocorrected, also does correct the error. - canBeAutoCorrected, - ), - ) - // In trace mode report the violation immediately. The order in which violations are actually found might be - // different from the order in which they are reported. For debugging purposes it can be helpful to know the - // exact order in which violations are being solved. - LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } - } - canBeAutoCorrected + errors.add( + Pair( + lintError, + // It is assumed that a rule that asks for autocorrect approval, also does correct the error. + autoCorrect, + ), + ) + // In trace mode report the violation immediately. The order in which violations are actually found might be + // different from the order in which they are reported. For debugging purposes it can be helpful to know the + // exact order in which violations are being solved. + LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } + autoCorrect } return errors } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index e191ddb624..8c71ee9801 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -91,24 +91,24 @@ internal class RuleExecutionContext private constructor( val suppressHandler = SuppressHandler(suppressionLocator, autoCorrectHandler, emitAndApprove) if (rule.shouldContinueTraversalOfAST()) { try { - executeRuleOnNodeRecursively(node, rule, autoCorrectHandler, suppressHandler) + executeRuleOnNodeRecursively(node, rule, autoCorrectHandler, suppressHandler, emitAndApprove) } catch (e: Exception) { - if (autoCorrectHandler.autoCorrect(node.startOffset)) { - // line/col cannot be reliably mapped as exception might originate from a node not present in the - // original AST + if (autoCorrectHandler is AutoCorrectDisabledHandler) { + val (line, col) = positionInTextLocator(node.startOffset) throw RuleExecutionException( rule, - 0, - 0, + line, + col, // Prevent extreme long stack trace caused by recursive call and only pass root cause e.cause ?: e, ) } else { - val (line, col) = positionInTextLocator(node.startOffset) + // line/col cannot be reliably mapped as exception might originate from a node not present in the + // original AST throw RuleExecutionException( rule, - line, - col, + 0, + 0, // Prevent extreme long stack trace caused by recursive call and only pass root cause e.cause ?: e, ) @@ -122,6 +122,7 @@ internal class RuleExecutionContext private constructor( rule: Rule, autoCorrectHandler: AutoCorrectHandler, suppressHandler: SuppressHandler, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (rule is RuleAutocorrectApproveHandler) { suppressHandler.handle(node, rule.ruleId) { emitAndApprove -> @@ -139,14 +140,12 @@ internal class RuleExecutionContext private constructor( node .getChildren(null) .forEach { childNode -> - suppressHandler.handle(childNode, rule.ruleId) { emitAndApprove -> - this.executeRuleOnNodeRecursively( - childNode, - rule, - autoCorrectHandler, - emitAndApprove, - ) - } + this.executeRuleOnNodeRecursively( + childNode, + rule, + autoCorrectHandler, + emitAndApprove, + ) } } if (rule is RuleAutocorrectApproveHandler) { diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt index 49dbd6176d..ed84902610 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt @@ -18,7 +18,7 @@ internal class SuppressHandler( node.startOffset, ruleId, ) - val autoCorrect = this.autoCorrectHandler.autoCorrect(node.startOffset) && !suppress + val autoCorrect = this.autoCorrectHandler !is AutoCorrectDisabledHandler && !suppress val emit = if (suppress) { SUPPRESS_EMIT diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt index 9b3fb9e5ce..1369d33c93 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt @@ -120,42 +120,41 @@ internal object SuppressionLocatorBuilder { private fun ASTNode.createSuppressionHintFromEolComment(formatterTags: FormatterTags): CommentSuppressionHint? = text .removePrefix("//") - .trim() - .split(" ") - .takeIf { it.isNotEmpty() } - ?.takeIf { it[0] == formatterTags.formatterTagOff } - ?.let { parts -> - CommentSuppressionHint( - this, - HashSet(parts.tail()), - EOL, - ) - } + .let { createCommentSuppressionHint(it, formatterTags) } private fun ASTNode.createSuppressionHintFromBlockComment(formatterTags: FormatterTags): CommentSuppressionHint? = text .removePrefix("/*") .removeSuffix("*/") - .trim() - .split(" ") - .takeIf { it.isNotEmpty() } - ?.let { parts -> - if (parts[0] == formatterTags.formatterTagOff) { + .let { createCommentSuppressionHint(it, formatterTags) } + + private fun ASTNode.createCommentSuppressionHint( + comment: String, + formatterTags: FormatterTags, + ) = comment + .trim() + .split(" ") + .takeIf { it.isNotEmpty() } + ?.let { parts -> + when (parts[0]) { + formatterTags.formatterTagOff -> CommentSuppressionHint( this, HashSet(parts.tail()), BLOCK_START, ) - } else if (parts[0] == formatterTags.formatterTagOn) { + + formatterTags.formatterTagOn -> CommentSuppressionHint( this, HashSet(parts.tail()), BLOCK_END, ) - } else { + + else -> null - } } + } private fun MutableList.toSuppressionHints(rootNode: ASTNode): MutableList { val suppressionHints = mutableListOf() diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt index 7c75e77c7f..0f2f5bb398 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt @@ -85,6 +85,7 @@ import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.IndentStyle.SPACE import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.IndentStyle.TAB import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler 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.SinceKtlint.Status.STABLE @@ -122,6 +123,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.psiUtil.leaves import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import java.util.Deque import java.util.LinkedList @@ -157,7 +159,8 @@ public class IndentationRule : INDENT_SIZE_PROPERTY, INDENT_STYLE_PROPERTY, ), - ) { + ), + RuleAutocorrectApproveHandler { private var codeStyle = CODE_STYLE_PROPERTY.defaultValue private var indentConfig = IndentConfig.DEFAULT_INDENT_CONFIG @@ -181,8 +184,7 @@ public class IndentationRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (node.isRoot()) { // File should not start with a whitespace @@ -190,10 +192,8 @@ public class IndentationRule : .nextLeaf() ?.takeIf { it.isWhiteSpaceWithoutNewline() } ?.let { whitespaceWithoutNewline -> - emit(node.startOffset, "Unexpected indentation", true) - if (autoCorrect) { - whitespaceWithoutNewline.treeParent.removeChild(whitespaceWithoutNewline) - } + emitAndApprove(node.startOffset, "Unexpected indentation", true) + .ifTrue { whitespaceWithoutNewline.treeParent.removeChild(whitespaceWithoutNewline) } } indentContextStack.addLast(startNoIndentZone(node)) } @@ -207,7 +207,7 @@ public class IndentationRule : lastIndentContext.copy(activated = true), ) } - visitNewLineIndentation(node, autoCorrect, emit) + visitNewLineIndentation(node, emitAndApprove) } node.elementType == CONTEXT_RECEIVER_LIST || @@ -355,7 +355,7 @@ public class IndentationRule : node.elementType == LITERAL_STRING_TEMPLATE_ENTRY && node.nextCodeSibling()?.elementType == CLOSING_QUOTE -> - visitWhiteSpaceBeforeClosingQuote(node, autoCorrect, emit) + visitWhiteSpaceBeforeClosingQuote(node, emitAndApprove) node.elementType == WHEN -> visitWhen(node) @@ -1036,8 +1036,7 @@ public class IndentationRule : override fun afterVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { while (indentContextStack.peekLast()?.toASTNode == node) { LOGGER.trace { @@ -1070,28 +1069,29 @@ public class IndentationRule : private fun visitNewLineIndentation( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (node.ignoreIndent()) { return } - val normalizedNodeIndent = node.normalizedIndent(emit) + val normalizedNodeIndent = node.normalizedIndent(emitAndApprove) val expectedIndentation = node.expectedIndent() val text = node.text val nodeIndent = text.substringAfterLast("\n") if (nodeIndent != normalizedNodeIndent || normalizedNodeIndent != expectedIndentation) { - if (normalizedNodeIndent != expectedIndentation) { - emit( - node.startOffset + text.length - nodeIndent.length, - "Unexpected indentation (${normalizedNodeIndent.length}) (should be ${expectedIndentation.length})", - true, - ) - } else { - // Indentation was at correct level but contained invalid indent characters. This violation has already - // been emitted. - } + val autoCorrect = + if (normalizedNodeIndent != expectedIndentation) { + emitAndApprove( + node.startOffset + text.length - nodeIndent.length, + "Unexpected indentation (${normalizedNodeIndent.length}) (should be ${expectedIndentation.length})", + true, + ) + } else { + // Indentation was at correct level but contained invalid indent characters. This violation has already + // been emitted. + true + } LOGGER.trace { "Line $line: " + (if (!autoCorrect) "would have " else "") + "changed indentation to ${expectedIndentation.length} " + "(from ${normalizedNodeIndent.length}) for ${node.elementType}: ${node.textWithEscapedTabAndNewline()}" @@ -1153,17 +1153,21 @@ public class IndentationRule : return lastIndexContext.nodeIndent + adjustedChildIndent } - private fun ASTNode.normalizedIndent(emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit): String { + private fun ASTNode.normalizedIndent( + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + ): String { val nodeIndent = text.substringAfterLast("\n") return when (indentConfig.indentStyle) { SPACE -> { if ('\t' in nodeIndent) { - emit( + emitAndApprove( startOffset + text.length - nodeIndent.length, "Unexpected tab character(s)", true, - ) - indentConfig.toNormalizedIndent(nodeIndent) + ).ifTrue { + // Ignore approval and fix invalid indent character always + indentConfig.toNormalizedIndent(nodeIndent) + } ?: nodeIndent } else { nodeIndent } @@ -1173,14 +1177,15 @@ public class IndentationRule : val acceptableTrailingSpaces = acceptableTrailingSpaces() val nodeIndentWithoutAcceptableTrailingSpaces = nodeIndent.removeSuffix(acceptableTrailingSpaces) if (' ' in nodeIndentWithoutAcceptableTrailingSpaces) { - emit( + emitAndApprove( startOffset + text.length - nodeIndent.length, "Unexpected space character(s)", true, - ) - indentConfig - .toNormalizedIndent(nodeIndentWithoutAcceptableTrailingSpaces) - .plus(acceptableTrailingSpaces) + ).ifTrue { + indentConfig + .toNormalizedIndent(nodeIndentWithoutAcceptableTrailingSpaces) + .plus(acceptableTrailingSpaces) + } ?: nodeIndent } else { nodeIndent } @@ -1190,13 +1195,12 @@ public class IndentationRule : private fun visitWhiteSpaceBeforeClosingQuote( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { if (!this::stringTemplateIndenter.isInitialized) { stringTemplateIndenter = StringTemplateIndenter(codeStyle, indentConfig) } - stringTemplateIndenter.visitClosingQuotes(currentIndent(), node.treeParent, autoCorrect, emit) + stringTemplateIndenter.visitClosingQuotes(currentIndent(), node.treeParent, emitAndApprove) } private fun ASTNode?.isElvisOperator() = @@ -1334,8 +1338,7 @@ private class StringTemplateIndenter( fun visitClosingQuotes( expectedIndent: String, node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { require(node.elementType == STRING_TEMPLATE) node @@ -1347,7 +1350,7 @@ private class StringTemplateIndenter( // It can not be determined with certainty how mixed indentation characters should be interpreted. // The trimIndent function handles tabs and spaces equally (one tabs equals one space) while the user // might expect that the tab size in the indentation is more than one space. - emit( + emitAndApprove( node.startOffset, "Indentation of multiline string should not contain both tab(s) and space(s)", false, @@ -1394,18 +1397,18 @@ private class StringTemplateIndenter( // It is a deliberate choice not to fix the indents inside the string literal except the line which only // contains the closing quotes. See 'string-template-indent` rule for fixing the content of the string // template itself - emit(it.startOffset, "Unexpected indent of multiline string closing quotes", true) - if (autoCorrect) { - if (it.firstChildNode == null) { - (it as LeafPsiElement).rawInsertBeforeMe( - LeafPsiElement(REGULAR_STRING_PART, correctedExpectedIndent), - ) - } else { - (it.firstChildNode as LeafPsiElement).rawReplaceWithText( - correctedExpectedIndent + actualContent, - ) + emitAndApprove(it.startOffset, "Unexpected indent of multiline string closing quotes", true) + .ifTrue { + if (it.firstChildNode == null) { + (it as LeafPsiElement).rawInsertBeforeMe( + LeafPsiElement(REGULAR_STRING_PART, correctedExpectedIndent), + ) + } else { + (it.firstChildNode as LeafPsiElement).rawReplaceWithText( + correctedExpectedIndent + actualContent, + ) + } } - } } } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt index 395bab814d..240139ffe6 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt @@ -2804,7 +2804,9 @@ internal class IndentationRuleTest { .withEditorConfigOverride(INDENT_STYLE_TAB) .hasLintViolations( LintViolation(2, 1, "Unexpected space character(s)"), + LintViolation(2, 1, "Unexpected indentation (4) (should be 1)"), LintViolation(3, 1, "Unexpected space character(s)"), + LintViolation(3, 1, "Unexpected indentation (2) (should be 0)"), ).isFormattedAs(formattedCode) } @@ -2825,9 +2827,9 @@ internal class IndentationRuleTest { indentationRuleAssertThat(code) .hasLintViolations( LintViolation(2, 1, "Unexpected tab character(s)"), - LintViolation(2, 1, "Unexpected indentation (8) (should be 4)"), + LintViolation(2, 1, "Unexpected indentation (2) (should be 4)"), LintViolation(3, 1, "Unexpected tab character(s)"), - LintViolation(3, 1, "Unexpected indentation (4) (should be 0)"), + LintViolation(3, 1, "Unexpected indentation (1) (should be 0)"), ).isFormattedAs(formattedCode) } @@ -2852,8 +2854,11 @@ internal class IndentationRuleTest { indentationRuleAssertThat(code) .hasLintViolations( LintViolation(2, 1, "Unexpected tab character(s)"), + LintViolation(2, 1, "Unexpected indentation (1) (should be 4)"), LintViolation(3, 1, "Unexpected tab character(s)"), + LintViolation(3, 1, "Unexpected indentation (2) (should be 8)"), LintViolation(4, 1, "Unexpected tab character(s)"), + LintViolation(4, 1, "Unexpected indentation (1) (should be 4)"), ).isFormattedAs(formattedCode) } @@ -2879,8 +2884,11 @@ internal class IndentationRuleTest { .withEditorConfigOverride(INDENT_SIZE_PROPERTY to 2) .hasLintViolations( LintViolation(2, 1, "Unexpected tab character(s)"), + LintViolation(2, 1, "Unexpected indentation (1) (should be 2)"), LintViolation(3, 1, "Unexpected tab character(s)"), + LintViolation(3, 1, "Unexpected indentation (2) (should be 4)"), LintViolation(4, 1, "Unexpected tab character(s)"), + LintViolation(4, 1, "Unexpected indentation (1) (should be 2)"), ).isFormattedAs(formattedCode) } From faef0a42271c18c6bca764b6d6ec31c89f640a04 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 19 May 2024 13:42:07 +0200 Subject: [PATCH 04/33] Simplifly SuppressHandler --- .../rule/engine/internal/SuppressHandler.kt | 22 ++----- .../internal/SuppressionLocatorBuilder.kt | 57 +++++++------------ 2 files changed, 26 insertions(+), 53 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt index ed84902610..a69c53bee9 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt @@ -13,19 +13,11 @@ internal class SuppressHandler( ruleId: RuleId, function: (Boolean, (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) -> Unit, ) { - val suppress = - suppressionLocator( - node.startOffset, - ruleId, - ) - val autoCorrect = this.autoCorrectHandler !is AutoCorrectDisabledHandler && !suppress - val emit = - if (suppress) { - SUPPRESS_EMIT - } else { - this.emitAndApprove - } - function(autoCorrect, emit.onlyEmit()) + val suppress = suppressionLocator(node.startOffset, ruleId) + if (!suppress) { + val autoCorrect = this.autoCorrectHandler !is AutoCorrectDisabledHandler + function(autoCorrect, this.emitAndApprove.onlyEmit()) + } } fun handle( @@ -37,9 +29,7 @@ internal class SuppressHandler( ) -> Unit, ) { val suppress = suppressionLocator(node.startOffset, ruleId) - if (suppress) { - function(SUPPRESS_EMIT) - } else { + if (!suppress) { function(emitAndApprove) } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt index 1369d33c93..572467b64d 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilder.kt @@ -111,50 +111,33 @@ internal object SuppressionLocatorBuilder { } private fun ASTNode.createSuppressionHintFromComment(formatterTags: FormatterTags): CommentSuppressionHint? = - if (text.startsWith("//")) { - createSuppressionHintFromEolComment(formatterTags) - } else { - createSuppressionHintFromBlockComment(formatterTags) - } - - private fun ASTNode.createSuppressionHintFromEolComment(formatterTags: FormatterTags): CommentSuppressionHint? = text .removePrefix("//") - .let { createCommentSuppressionHint(it, formatterTags) } - - private fun ASTNode.createSuppressionHintFromBlockComment(formatterTags: FormatterTags): CommentSuppressionHint? = - text .removePrefix("/*") .removeSuffix("*/") - .let { createCommentSuppressionHint(it, formatterTags) } - - private fun ASTNode.createCommentSuppressionHint( - comment: String, - formatterTags: FormatterTags, - ) = comment - .trim() - .split(" ") - .takeIf { it.isNotEmpty() } - ?.let { parts -> - when (parts[0]) { - formatterTags.formatterTagOff -> - CommentSuppressionHint( - this, - HashSet(parts.tail()), - BLOCK_START, - ) + .trim() + .split(" ") + .takeIf { it.isNotEmpty() } + ?.let { parts -> + when (parts[0]) { + formatterTags.formatterTagOff -> + CommentSuppressionHint( + this, + HashSet(parts.tail()), + BLOCK_START, + ) - formatterTags.formatterTagOn -> - CommentSuppressionHint( - this, - HashSet(parts.tail()), - BLOCK_END, - ) + formatterTags.formatterTagOn -> + CommentSuppressionHint( + this, + HashSet(parts.tail()), + BLOCK_END, + ) - else -> - null + else -> + null + } } - } private fun MutableList.toSuppressionHints(rootNode: ASTNode): MutableList { val suppressionHints = mutableListOf() From a930246daa14d7356fb618e38ecf2619bdeeeee9 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 19 May 2024 14:24:09 +0200 Subject: [PATCH 05/33] Update api contract --- .../api/ktlint-rule-engine-core.api | 10 ++++++++++ ktlint-rule-engine/api/ktlint-rule-engine.api | 1 + .../api/ktlint-ruleset-standard.api | 11 ++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api index 2395ff17f6..098f564170 100644 --- a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api +++ b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api @@ -438,6 +438,16 @@ public final class com/pinterest/ktlint/rule/engine/core/api/Rule$VisitorModifie public static final field INSTANCE Lcom/pinterest/ktlint/rule/engine/core/api/Rule$VisitorModifier$RunAsLateAsPossible; } +public abstract interface class com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { + public abstract fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V + public abstract fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler$DefaultImpls { + public static fun afterVisitChildNodes (Lcom/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler;Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V + public static fun beforeVisitChildNodes (Lcom/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler;Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V +} + public final class com/pinterest/ktlint/rule/engine/core/api/RuleId { public static final field Companion Lcom/pinterest/ktlint/rule/engine/core/api/RuleId$Companion; public fun (Ljava/lang/String;)V diff --git a/ktlint-rule-engine/api/ktlint-rule-engine.api b/ktlint-rule-engine/api/ktlint-rule-engine.api index 85560f94f4..bd9cdc8ce2 100644 --- a/ktlint-rule-engine/api/ktlint-rule-engine.api +++ b/ktlint-rule-engine/api/ktlint-rule-engine.api @@ -77,6 +77,7 @@ public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine { public final fun getEditorConfigOverride ()Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride; public final fun getFileSystem ()Ljava/nio/file/FileSystem; public final fun getRuleProviders ()Ljava/util/Set; + public final fun interactiveFormat (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; public final fun isInvokedFromCli ()Z public final fun lint (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;)V public static synthetic fun lint$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V diff --git a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api index a3455bf324..33fe298f74 100644 --- a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api +++ b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api @@ -72,11 +72,12 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDe public static final fun getBLANK_LINE_BEFORE_DECLARATION_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } -public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { +public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions$Companion; public fun ()V + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions$Companion { @@ -371,14 +372,14 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRul public static final fun getIMPORT_ORDERING_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } -public final class com/pinterest/ktlint/ruleset/standard/rules/IndentationRule : com/pinterest/ktlint/ruleset/standard/StandardRule { +public final class com/pinterest/ktlint/ruleset/standard/rules/IndentationRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { public static final field KDOC_CONTINUATION_INDENT Ljava/lang/String; public static final field TYPE_CONSTRAINT_CONTINUATION_INDENT Ljava/lang/String; public fun ()V public fun afterLastNode ()V - public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleKt { From 8187494e4bf3bae637d207dca1c0f47b78e01791 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 19 May 2024 14:34:10 +0200 Subject: [PATCH 06/33] Refactor and remove SuppressHandler --- .../engine/internal/RuleExecutionContext.kt | 81 +++++++++++++------ .../rule/engine/internal/SuppressHandler.kt | 53 ------------ 2 files changed, 55 insertions(+), 79 deletions(-) delete mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 8c71ee9801..a7bc737c2f 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -88,10 +88,14 @@ internal class RuleExecutionContext private constructor( * The [suppressionLocator] can be changed during each visit of node when running [KtLintRuleEngine.format]. So a new handler is to * be built before visiting the nodes. */ - val suppressHandler = SuppressHandler(suppressionLocator, autoCorrectHandler, emitAndApprove) + val suppress = suppressionLocator(node.startOffset, rule.ruleId) if (rule.shouldContinueTraversalOfAST()) { try { - executeRuleOnNodeRecursively(node, rule, autoCorrectHandler, suppressHandler, emitAndApprove) + if (rule is RuleAutocorrectApproveHandler) { + executeRuleWithApproveHandlerOnNodeRecursivelyKtlint(node, rule, autoCorrectHandler, suppress, emitAndApprove) + } else { + executeRuleOnNodeRecursivelyKtlint(node, rule, autoCorrectHandler, suppress, emitAndApprove) + } } catch (e: Exception) { if (autoCorrectHandler is AutoCorrectDisabledHandler) { val (line, col) = positionInTextLocator(node.startOffset) @@ -117,24 +121,18 @@ internal class RuleExecutionContext private constructor( } } - private fun executeRuleOnNodeRecursively( + @Deprecated(message = "Remove in Ktlint 2.0") + private fun executeRuleOnNodeRecursivelyKtlint( node: ASTNode, rule: Rule, autoCorrectHandler: AutoCorrectHandler, - suppressHandler: SuppressHandler, + suppress: Boolean, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { - if (rule is RuleAutocorrectApproveHandler) { - suppressHandler.handle(node, rule.ruleId) { emitAndApprove -> - rule.beforeVisitChildNodes( - node = node, - emitAndApprove = emitAndApprove, - ) - } - } else { - suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> - rule.beforeVisitChildNodes(node, autoCorrect, emit) - } + val autoCorrect = autoCorrectHandler !is AutoCorrectDisabledHandler + val emitOnly = emitAndApprove.onlyEmit() + if (!suppress) { + rule.beforeVisitChildNodes(node, autoCorrect, emitOnly) } if (rule.shouldContinueTraversalOfAST()) { node @@ -148,20 +146,51 @@ internal class RuleExecutionContext private constructor( ) } } - if (rule is RuleAutocorrectApproveHandler) { - suppressHandler.handle(node, rule.ruleId) { emitAndApprove -> - rule.afterVisitChildNodes( - node = node, - emitAndApprove = emitAndApprove, - ) - } - } else { - suppressHandler.handle(node, rule.ruleId) { autoCorrect, emit -> - rule.afterVisitChildNodes(node, autoCorrect, emit) - } + if (!suppress) { + rule.afterVisitChildNodes(node, autoCorrect, emitOnly) + } + } + + private fun executeRuleWithApproveHandlerOnNodeRecursivelyKtlint( + node: ASTNode, + rule: Rule, + autoCorrectHandler: AutoCorrectHandler, + suppress: Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + ) { + require(rule is RuleAutocorrectApproveHandler) + if (!suppress) { + rule.beforeVisitChildNodes(node, emitAndApprove) + } + if (rule.shouldContinueTraversalOfAST()) { + node + .getChildren(null) + .forEach { childNode -> + this.executeRuleOnNodeRecursively( + childNode, + rule, + autoCorrectHandler, + emitAndApprove, + ) + } + } + if (!suppress) { + rule.afterVisitChildNodes(node, emitAndApprove) } } + // Simplify the emitAndApprove to an emit only lambda which can be used in the legacy (deprecated) functions + @Deprecated(message = "Remove in Ktlint 2.0") + private fun ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean).onlyEmit() = + { + offset: Int, + errorMessage: String, + canBeAutoCorrected: Boolean, + -> + this(offset, errorMessage, canBeAutoCorrected) + Unit + } + companion object { internal fun createRuleExecutionContext( ktLintRuleEngine: KtLintRuleEngine, diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt deleted file mode 100644 index a69c53bee9..0000000000 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.pinterest.ktlint.rule.engine.internal - -import com.pinterest.ktlint.rule.engine.core.api.RuleId -import org.jetbrains.kotlin.com.intellij.lang.ASTNode - -internal class SuppressHandler( - private val suppressionLocator: SuppressionLocator, - private val autoCorrectHandler: AutoCorrectHandler, - private val emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, -) { - fun handle( - node: ASTNode, - ruleId: RuleId, - function: (Boolean, (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) -> Unit, - ) { - val suppress = suppressionLocator(node.startOffset, ruleId) - if (!suppress) { - val autoCorrect = this.autoCorrectHandler !is AutoCorrectDisabledHandler - function(autoCorrect, this.emitAndApprove.onlyEmit()) - } - } - - fun handle( - node: ASTNode, - ruleId: RuleId, - function: ( - // emitAndApprove - (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, - ) -> Unit, - ) { - val suppress = suppressionLocator(node.startOffset, ruleId) - if (!suppress) { - function(emitAndApprove) - } - } - - // Simplify the emitAndApprove to an emit only lambda which can be used in the legacy (deprecated) functions - @Deprecated(message = "Remove in Ktlint 2.0") - private fun ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean).onlyEmit() = - { - offset: Int, - errorMessage: String, - canBeAutoCorrected: Boolean, - -> - this(offset, errorMessage, canBeAutoCorrected) - Unit - } - - private companion object { - // Swallow violation so that it is not emitted - val SUPPRESS_EMIT: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean = { _, _, _ -> false } - } -} From 358087dca107dfb18c24b905f3c8bef9e7845b65 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 19 May 2024 18:39:34 +0200 Subject: [PATCH 07/33] Refactor AutoCorrectOffsetRangeHandler to RangeAutoCorrectHandler so that all lint errors which are found in the autocorrect offset range will be fixed --- .../rule/engine/api/KtLintRuleEngine.kt | 31 ++---------- .../engine/internal/AutoCorrectHandler.kt | 49 ++++++++++++------- .../rule/engine/internal/CodeFormatter.kt | 6 +-- 3 files changed, 36 insertions(+), 50 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 55d25afc8d..018c188ed7 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -11,7 +11,6 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.propertyTypes import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.rule.engine.internal.AutoCorrectEnabledHandler -import com.pinterest.ktlint.rule.engine.internal.AutoCorrectOffsetRangeHandler import com.pinterest.ktlint.rule.engine.internal.CodeFormatter import com.pinterest.ktlint.rule.engine.internal.CodeLinter import com.pinterest.ktlint.rule.engine.internal.EditorConfigFinder @@ -19,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.internal.EditorConfigGenerator import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoader import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoaderEc4j import com.pinterest.ktlint.rule.engine.internal.LintErrorAutoCorrectHandler +import com.pinterest.ktlint.rule.engine.internal.RangeAutoCorrectHandler import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE import org.ec4j.core.Resource @@ -115,32 +115,7 @@ public class KtLintRuleEngine( * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. * - * IMPORTANT: Partial formatting not always works as expected. The offset of the node which is triggering the violation does not - * necessarily to be close to the offset at which the violation is reported. Counter-intuitively the offset of the trigger node must be - * located inside the [autoCorrectOffsetRange] instead of the offset at which the violation is reported. - * - * For example, the given code might contain the when-statement below: - * ``` - * // code with lint violations - * - * when(foobar) { - * FOO -> "Single line" - * BAR -> - * """ - * Multi line - * """.trimIndent() - * else -> null - * } - * - * // more code with lint violations - * ``` - * The `blank-line-between-when-conditions` rule requires blank lines to be added between the conditions. If the when-keyword above is - * included in the range which is to be formatted then the blank lines before the conditions are added. If only the when-conditions - * itself are selected, but not the when-keyword, then the blank lines are not added. - * - * This unexpected behavior is a side effect of the way the partial formatting is implemented currently. The side effects can be - * prevented by delaying the decision to autocorrect as late as possible and the exact offset of the error is known. This however would - * cause a breaking change, and needs to wait until Ktlint V2.x. + * [callback] is invoked once for each unique [LintError] found. Note that [callback] is only invoked once format is completed. * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation @@ -152,7 +127,7 @@ public class KtLintRuleEngine( ): String = codeFormatter.format( code, - AutoCorrectOffsetRangeHandler(autoCorrectOffsetRange), + RangeAutoCorrectHandler(code, autoCorrectOffsetRange), callback, MAX_FORMAT_RUNS_PER_FILE, ) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt index ce60d05620..6d299e6bee 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt @@ -1,41 +1,56 @@ package com.pinterest.ktlint.rule.engine.internal +import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.LintError /** * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutoCorrectHandler { - fun autoCorrect(offset: Int): Boolean - fun autoCorrect(lintError: LintError): Boolean } internal data object AutoCorrectDisabledHandler : AutoCorrectHandler { - override fun autoCorrect(offset: Int) = false - override fun autoCorrect(lintError: LintError) = false } internal data object AutoCorrectEnabledHandler : AutoCorrectHandler { - override fun autoCorrect(offset: Int) = true - override fun autoCorrect(lintError: LintError) = true } -@Deprecated(message = "Replace with LintErrorAutocorrectHandler") -internal class AutoCorrectOffsetRangeHandler( - private val autoCorrectOffsetRange: IntRange, -) : AutoCorrectHandler { - override fun autoCorrect(offset: Int) = offset in autoCorrectOffsetRange - - override fun autoCorrect(lintError: LintError) = throw IllegalStateException() -} - internal class LintErrorAutoCorrectHandler( private val callback: (LintError) -> Boolean, ) : AutoCorrectHandler { - override fun autoCorrect(offset: Int) = true - override fun autoCorrect(lintError: LintError) = callback(lintError) } + +internal class RangeAutoCorrectHandler( + private val code: Code, + private val autoCorrectOffsetRange: IntRange, +) : AutoCorrectHandler { + override fun autoCorrect(lintError: LintError) = lintError.isInRange(code, autoCorrectOffsetRange) + + private fun LintError.isInRange( + code: Code, + intRange: IntRange, + ): Boolean { + val (startLine, startCol) = code.lineAndColumnFrom(intRange.first) + val (endLine, endCol) = code.lineAndColumnFrom(intRange.last) + return !( + line < startLine || + (line == startLine && col < startCol) || + (line == endLine && col > endCol) || + line > endLine + ) + } + + private fun Code.lineAndColumnFrom(offset: Int) = + content + .substring(0, offset) + .let { text -> + Pair( + text.count { it == '\n' } + 1, + text.length - text.lastIndexOf('\n'), + ) + } +} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 448281cba7..545bcda858 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -116,11 +116,7 @@ internal class CodeFormatter( val lintError = LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) val autoCorrect = if (canBeAutoCorrected) { - if (autoCorrectHandler is AutoCorrectOffsetRangeHandler) { - autoCorrectHandler.autoCorrect(offset) - } else { - autoCorrectHandler.autoCorrect(lintError) - } + autoCorrectHandler.autoCorrect(lintError) } else { false } From f27e4ff39e428f953e597a5c348f77a2f462031c Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Wed, 22 May 2024 16:45:40 +0200 Subject: [PATCH 08/33] Disable autocorrect in rules that do not implement the RuleApproveAutocorrectHandler / rename AutocorrectHandlers --- .../api/consumer/KtLintRuleEngineTest.kt | 41 -------------- .../rule/engine/api/KtLintRuleEngine.kt | 34 ++--------- .../engine/internal/AutoCorrectHandler.kt | 56 ------------------- .../engine/internal/AutocorrectHandler.kt | 38 +++++++++++++ .../rule/engine/internal/CodeFormatter.kt | 20 +++---- .../ktlint/rule/engine/internal/CodeLinter.kt | 2 +- .../engine/internal/RuleExecutionContext.kt | 22 ++++---- 7 files changed, 64 insertions(+), 149 deletions(-) delete mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 2a658e15a0..4cccba7ab7 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -274,47 +274,6 @@ class KtLintRuleEngineTest { ) } - @Test - fun `Given a kotlin code snippet that does contain multiple indentation errors then only format errors found in given range`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) - - val originalCode = - """ - fun main() { - println("Hello world!") - println("Hello world!") - println("Hello world!") - } - """.trimIndent() - val newlineIndexes = - Regex("\n") - .findAll(originalCode) - .map { it.range.first } - .toList() - val actual = - ktLintRuleEngine.format( - code = Code.fromSnippet(originalCode), - autoCorrectOffsetRange = IntRange(newlineIndexes[1], newlineIndexes[2]), - ) - - assertThat(actual).isEqualTo( - """ - fun main() { - println("Hello world!") - println("Hello world!") - println("Hello world!") - } - """.trimIndent(), - ) - } - @Test fun `Given a kotlin code snippet that does contain multiple indentation errors then only format the lint error at specific offset and message`() { val ktLintRuleEngine = diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 018c188ed7..6b45796ed5 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -10,15 +10,14 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERT import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.propertyTypes import com.pinterest.ktlint.rule.engine.core.util.safeAs -import com.pinterest.ktlint.rule.engine.internal.AutoCorrectEnabledHandler +import com.pinterest.ktlint.rule.engine.internal.AllAutocorrectHandler import com.pinterest.ktlint.rule.engine.internal.CodeFormatter import com.pinterest.ktlint.rule.engine.internal.CodeLinter import com.pinterest.ktlint.rule.engine.internal.EditorConfigFinder import com.pinterest.ktlint.rule.engine.internal.EditorConfigGenerator import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoader import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoaderEc4j -import com.pinterest.ktlint.rule.engine.internal.LintErrorAutoCorrectHandler -import com.pinterest.ktlint.rule.engine.internal.RangeAutoCorrectHandler +import com.pinterest.ktlint.rule.engine.internal.LintErrorAutocorrectHandler import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE import org.ec4j.core.Resource @@ -105,32 +104,7 @@ public class KtLintRuleEngine( public fun format( code: Code, callback: (LintError, Boolean) -> Unit = { _, _ -> }, - ): String = codeFormatter.format(code, AutoCorrectEnabledHandler, callback, MAX_FORMAT_RUNS_PER_FILE) - - /** - * Fix style violations in [code] for lint errors found in the [autoCorrectOffsetRange] when possible. If [code] is passed as file - * reference then the '.editorconfig' files on the path are taken into account. For each lint violation found, the [callback] is - * invoked. - * - * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until - * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. - * - * [callback] is invoked once for each unique [LintError] found. Note that [callback] is only invoked once format is completed. - * - * @throws KtLintParseException if text is not a valid Kotlin code - * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation - */ - public fun format( - code: Code, - autoCorrectOffsetRange: IntRange, - callback: (LintError, Boolean) -> Unit = { _, _ -> }, - ): String = - codeFormatter.format( - code, - RangeAutoCorrectHandler(code, autoCorrectOffsetRange), - callback, - MAX_FORMAT_RUNS_PER_FILE, - ) + ): String = codeFormatter.format(code, AllAutocorrectHandler, callback, MAX_FORMAT_RUNS_PER_FILE) /** * Formats style violations in [code]. Whenever a [LintError] is found which can be autocorrected by the rule that detected the @@ -148,7 +122,7 @@ public class KtLintRuleEngine( ): String = codeFormatter.format( code = code, - autoCorrectHandler = LintErrorAutoCorrectHandler(callback), + autocorrectHandler = LintErrorAutocorrectHandler(callback), maxFormatRunsPerFile = 1, ) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt deleted file mode 100644 index 6d299e6bee..0000000000 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutoCorrectHandler.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.pinterest.ktlint.rule.engine.internal - -import com.pinterest.ktlint.rule.engine.api.Code -import com.pinterest.ktlint.rule.engine.api.LintError - -/** - * Handler which determines whether autocorrect should be enabled or disabled for the given offset. - */ -internal sealed interface AutoCorrectHandler { - fun autoCorrect(lintError: LintError): Boolean -} - -internal data object AutoCorrectDisabledHandler : AutoCorrectHandler { - override fun autoCorrect(lintError: LintError) = false -} - -internal data object AutoCorrectEnabledHandler : AutoCorrectHandler { - override fun autoCorrect(lintError: LintError) = true -} - -internal class LintErrorAutoCorrectHandler( - private val callback: (LintError) -> Boolean, -) : AutoCorrectHandler { - override fun autoCorrect(lintError: LintError) = callback(lintError) -} - -internal class RangeAutoCorrectHandler( - private val code: Code, - private val autoCorrectOffsetRange: IntRange, -) : AutoCorrectHandler { - override fun autoCorrect(lintError: LintError) = lintError.isInRange(code, autoCorrectOffsetRange) - - private fun LintError.isInRange( - code: Code, - intRange: IntRange, - ): Boolean { - val (startLine, startCol) = code.lineAndColumnFrom(intRange.first) - val (endLine, endCol) = code.lineAndColumnFrom(intRange.last) - return !( - line < startLine || - (line == startLine && col < startCol) || - (line == endLine && col > endCol) || - line > endLine - ) - } - - private fun Code.lineAndColumnFrom(offset: Int) = - content - .substring(0, offset) - .let { text -> - Pair( - text.count { it == '\n' } + 1, - text.length - text.lastIndexOf('\n'), - ) - } -} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt new file mode 100644 index 0000000000..ac22e0048b --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt @@ -0,0 +1,38 @@ +package com.pinterest.ktlint.rule.engine.internal + +import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler + +/** + * Handler which determines whether autocorrect should be enabled or disabled for the given offset. + */ +internal sealed interface AutocorrectHandler { + fun autoCorrect(lintError: LintError): Boolean +} + +/** + * Autocorrect no [LintError]s. This handler is used for backward compatability with rules that have not implemented + * [RuleAutocorrectApproveHandler]. + */ +internal data object NoneAutocorrectHandler : AutocorrectHandler { + override fun autoCorrect(lintError: LintError) = false +} + +/** + * Autocorrect all [LintError]s. This handler is used for backward compatability with rules that have not implemented + * [RuleAutocorrectApproveHandler]. + */ +internal data object AllAutocorrectHandler : AutocorrectHandler { + override fun autoCorrect(lintError: LintError) = true +} + +/** + * The [LintErrorAutocorrectHandler] only works for rules that implement [RuleAutocorrectApproveHandler]. For rules that do not implement + * that interface, no autocorrections will be made even though the rule is capable doing so. Reason for this is that the API consumer should + * be able to control whether a specific [LintError] is to be corrected or not. + */ +internal class LintErrorAutocorrectHandler( + private val callback: (LintError) -> Boolean, +) : AutocorrectHandler { + override fun autoCorrect(lintError: LintError) = callback(lintError) +} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 545bcda858..4ad470dd89 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -16,13 +16,13 @@ internal class CodeFormatter( ) { fun format( code: Code, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, callback: (LintError, Boolean) -> Unit = { _, _ -> }, maxFormatRunsPerFile: Int, ): String { LOGGER.debug { "Starting with formatting file '${code.fileNameOrStdin()}'" } - val (formattedCode, errors) = format(code, autoCorrectHandler, maxFormatRunsPerFile) + val (formattedCode, errors) = format(code, autocorrectHandler, maxFormatRunsPerFile) errors .sortedWith(lintErrorLineAndColumnComparator { it.first }) @@ -42,7 +42,7 @@ internal class CodeFormatter( private fun format( code: Code, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, maxFormatRunsPerFile: Int, ): Pair>> { with(RuleExecutionContext.createRuleExecutionContext(ktLintRuleEngine, code)) { @@ -51,7 +51,7 @@ internal class CodeFormatter( var mutated: Boolean do { mutated = - format(autoCorrectHandler, code).let { ruleErrors -> + format(autocorrectHandler, code).let { ruleErrors -> errors.addAll(ruleErrors) ruleErrors.any { it.first.canBeAutoCorrected } } @@ -75,14 +75,14 @@ internal class CodeFormatter( private fun RuleExecutionContext.formattedCode(lineSeparator: String) = rootNode.text.replace("\n", lineSeparator) private fun RuleExecutionContext.format( - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, code: Code, ): Set> { val errors: MutableSet> = mutableSetOf() VisitorProvider(ruleProviders) .rules .forEach { rule -> - executeRule(rule, autoCorrectHandler, code).let { ruleErrors -> errors.addAll(ruleErrors) } + executeRule(rule, autocorrectHandler, code).let { ruleErrors -> errors.addAll(ruleErrors) } } return errors } @@ -93,7 +93,7 @@ internal class CodeFormatter( .rules .forEach { rule -> if (!hasErrorsWhichCanBeAutocorrected) { - executeRule(rule, AutoCorrectDisabledHandler) { _, _, canBeAutoCorrected -> + executeRule(rule, NoneAutocorrectHandler) { _, _, canBeAutoCorrected -> if (canBeAutoCorrected) { hasErrorsWhichCanBeAutocorrected = true } @@ -107,16 +107,16 @@ internal class CodeFormatter( private fun RuleExecutionContext.executeRule( rule: Rule, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, code: Code, ): Set> { val errors = mutableSetOf>() - executeRule(rule, autoCorrectHandler) { offset, errorMessage, canBeAutoCorrected -> + executeRule(rule, autocorrectHandler) { offset, errorMessage, canBeAutoCorrected -> val (line, col) = positionInTextLocator(offset) val lintError = LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) val autoCorrect = if (canBeAutoCorrected) { - autoCorrectHandler.autoCorrect(lintError) + autocorrectHandler.autoCorrect(lintError) } else { false } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt index ff5ef3ea17..0c10a71045 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt @@ -38,7 +38,7 @@ internal class CodeLinter( code: Code, ): List { val errors = mutableListOf() - executeRule(rule, AutoCorrectDisabledHandler) { offset, errorMessage, canBeAutoCorrected -> + executeRule(rule, NoneAutocorrectHandler) { offset, errorMessage, canBeAutoCorrected -> val (line, col) = this.positionInTextLocator(offset) LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) .let { lintError -> diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index a7bc737c2f..6d4370948f 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -47,7 +47,7 @@ internal class RuleExecutionContext private constructor( fun executeRule( rule: Rule, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { try { @@ -60,7 +60,7 @@ internal class RuleExecutionContext private constructor( // EditorConfigProperty that is not explicitly defined. editorConfig.filterBy(rule.usesEditorConfigProperties.plus(CODE_STYLE_PROPERTY)), ) - this.executeRuleOnNodeRecursively(rootNode, rule, autoCorrectHandler, emitAndApprove) + this.executeRuleOnNodeRecursively(rootNode, rule, autocorrectHandler, emitAndApprove) rule.afterLastNode() } catch (e: RuleExecutionException) { throw KtLintRuleException( @@ -81,7 +81,7 @@ internal class RuleExecutionContext private constructor( private fun executeRuleOnNodeRecursively( node: ASTNode, rule: Rule, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { /** @@ -92,12 +92,12 @@ internal class RuleExecutionContext private constructor( if (rule.shouldContinueTraversalOfAST()) { try { if (rule is RuleAutocorrectApproveHandler) { - executeRuleWithApproveHandlerOnNodeRecursivelyKtlint(node, rule, autoCorrectHandler, suppress, emitAndApprove) + executeRuleWithApproveHandlerOnNodeRecursivelyKtlint(node, rule, autocorrectHandler, suppress, emitAndApprove) } else { - executeRuleOnNodeRecursivelyKtlint(node, rule, autoCorrectHandler, suppress, emitAndApprove) + executeRuleOnNodeRecursivelyKtlint(node, rule, autocorrectHandler, suppress, emitAndApprove) } } catch (e: Exception) { - if (autoCorrectHandler is AutoCorrectDisabledHandler) { + if (autocorrectHandler is NoneAutocorrectHandler) { val (line, col) = positionInTextLocator(node.startOffset) throw RuleExecutionException( rule, @@ -125,11 +125,11 @@ internal class RuleExecutionContext private constructor( private fun executeRuleOnNodeRecursivelyKtlint( node: ASTNode, rule: Rule, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, suppress: Boolean, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { - val autoCorrect = autoCorrectHandler !is AutoCorrectDisabledHandler + val autoCorrect = autocorrectHandler is AllAutocorrectHandler val emitOnly = emitAndApprove.onlyEmit() if (!suppress) { rule.beforeVisitChildNodes(node, autoCorrect, emitOnly) @@ -141,7 +141,7 @@ internal class RuleExecutionContext private constructor( this.executeRuleOnNodeRecursively( childNode, rule, - autoCorrectHandler, + autocorrectHandler, emitAndApprove, ) } @@ -154,7 +154,7 @@ internal class RuleExecutionContext private constructor( private fun executeRuleWithApproveHandlerOnNodeRecursivelyKtlint( node: ASTNode, rule: Rule, - autoCorrectHandler: AutoCorrectHandler, + autocorrectHandler: AutocorrectHandler, suppress: Boolean, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { @@ -169,7 +169,7 @@ internal class RuleExecutionContext private constructor( this.executeRuleOnNodeRecursively( childNode, rule, - autoCorrectHandler, + autocorrectHandler, emitAndApprove, ) } From b5cc08b1817c925bd5686e0f06680bf51f52cc5a Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Thu, 23 May 2024 21:02:06 +0200 Subject: [PATCH 09/33] Ensure backward compatibility by adding parameter used to set the default autocorrect value for rules that do not implement the RuleAutocorrectApproveHandler --- .../ktlint/api/consumer/ApiTestRunner.kt | 59 -- .../api/consumer/KtLintRuleEngineTest.kt | 697 +++++++++++++----- .../resources/api/no-code-style-error/Main.kt | 3 - .../rule/engine/api/KtLintRuleEngine.kt | 20 +- .../engine/internal/AutocorrectHandler.kt | 9 +- .../rule/engine/internal/CodeFormatter.kt | 23 +- .../engine/internal/RuleExecutionContext.kt | 7 +- 7 files changed, 560 insertions(+), 258 deletions(-) delete mode 100644 ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/ApiTestRunner.kt delete mode 100644 ktlint-api-consumer/src/test/resources/api/no-code-style-error/Main.kt diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/ApiTestRunner.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/ApiTestRunner.kt deleted file mode 100644 index f4d03dc12c..0000000000 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/ApiTestRunner.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.pinterest.ktlint.api.consumer - -import com.pinterest.ktlint.logger.api.initKtLintKLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import java.nio.file.FileVisitResult -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.SimpleFileVisitor -import java.nio.file.attribute.BasicFileAttributes -import kotlin.io.path.Path -import kotlin.io.path.copyTo -import kotlin.io.path.createDirectories -import kotlin.io.path.relativeToOrSelf - -private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() - -class ApiTestRunner( - private val tempDir: Path, -) { - fun prepareTestProject(testProjectName: String): Path { - val testProjectPath = TEST_PROJECTS_PATHS.resolve(testProjectName) - assert(Files.exists(testProjectPath)) { - "Test project $testProjectName does not exist!" - } - - return tempDir.resolve(testProjectName).also { testProjectPath.copyRecursively(it) } - } - - private fun Path.copyRecursively(dest: Path) { - Files.walkFileTree( - this, - object : SimpleFileVisitor() { - override fun preVisitDirectory( - dir: Path, - attrs: BasicFileAttributes, - ): FileVisitResult { - val relativeDir = dir.relativeToOrSelf(this@copyRecursively) - dest.resolve(relativeDir).createDirectories() - return FileVisitResult.CONTINUE - } - - override fun visitFile( - file: Path, - attrs: BasicFileAttributes, - ): FileVisitResult { - val relativeFile = file.relativeToOrSelf(this@copyRecursively) - val destinationFile = dest.resolve(relativeFile) - LOGGER.trace { "Copy '$relativeFile' to '$destinationFile'" } - file.copyTo(destinationFile) - return FileVisitResult.CONTINUE - } - }, - ) - } - - companion object { - private val TEST_PROJECTS_PATHS: Path = Path("src", "test", "resources", "api") - } -} diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 4cccba7ab7..46c32bd17a 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -1,7 +1,8 @@ package com.pinterest.ktlint.api.consumer +import com.pinterest.ktlint.api.consumer.KtLintRuleEngineTest.RuleWithAutocorrectApproveHandler.Companion.RULE_WITH_AUTOCORRECT_APPROVE_HANDLER +import com.pinterest.ktlint.api.consumer.KtLintRuleEngineTest.RuleWithoutAutocorrectApproveHandler.Companion.RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER import com.pinterest.ktlint.rule.engine.api.Code -import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError @@ -13,20 +14,21 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EXPERIMENTAL_RULES_EXECUTION_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution import com.pinterest.ktlint.rule.engine.core.api.editorconfig.createRuleExecutionEditorConfigProperty -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.ec4j.toPropertyBuilderWithValue +import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.rules.FilenameRule +import com.pinterest.ktlint.ruleset.standard.rules.INDENTATION_RULE_ID import com.pinterest.ktlint.ruleset.standard.rules.IndentationRule import com.pinterest.ktlint.test.KtlintTestFileSystem import org.assertj.core.api.Assertions.assertThat -import org.ec4j.core.model.EditorConfig -import org.ec4j.core.model.Glob -import org.ec4j.core.model.Section import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File +import java.io.FileWriter import java.nio.file.Path /** @@ -36,6 +38,21 @@ import java.nio.file.Path */ class KtLintRuleEngineTest { private val ktlintTestFileSystem = KtlintTestFileSystem() + private val ktLintRuleEngine = + KtLintRuleEngine( + ruleProviders = + setOf( + RuleProvider { IndentationRule() }, + RuleProvider { RuleWithAutocorrectApproveHandler() }, + RuleProvider { RuleWithoutAutocorrectApproveHandler() }, + ), + editorConfigOverride = + EditorConfigOverride.from( + RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, + RULE_WITH_AUTOCORRECT_APPROVE_HANDLER.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, + ), + fileSystem = ktlintTestFileSystem.fileSystem, + ) @AfterEach fun tearDown() { @@ -45,113 +62,77 @@ class KtLintRuleEngineTest { @Nested inner class `Lint with KtLintRuleEngine` { @Test - fun `Given a file that does not contain an error`( + fun `Given a file containing errors found by standard and custom rules`( @TempDir tempDir: Path, ) { - val dir = ApiTestRunner(tempDir).prepareTestProject("no-code-style-error") - - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), ) + } val lintErrors = mutableListOf() ktLintRuleEngine.lint( - code = Code.fromFile(File("$dir/Main.kt")), - callback = { lintErrors.add(it) }, - ) + code = Code.fromFile(File(filePath)), + ) { lintErrors.add(it) } - assertThat(lintErrors).isEmpty() + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) } @Test - fun `Given a kotlin code snippet that does not contain an error`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) - + fun `Given a kotlin code snippet containing errors found by standard and custom rules`() { val lintErrors = mutableListOf() ktLintRuleEngine.lint( code = Code.fromSnippet( """ - fun main() { - println("Hello world!") - } + fun bar() { + // foo + // bar + } """.trimIndent(), ), - callback = { lintErrors.add(it) }, - ) + ) { lintErrors.add(it) } - assertThat(lintErrors).isEmpty() + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) } @Test - fun `Given a kotlin script code snippet that does not contain an error`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) - + fun `Given a kotlin script code snippet containing errors found by standard and custom rules`() { val lintErrors = mutableListOf() ktLintRuleEngine.lint( code = Code.fromSnippet( """ plugins { - id("foo") - id("bar") - } + // foo + // bar + } """.trimIndent(), script = true, ), - callback = { lintErrors.add(it) }, - ) - - assertThat(lintErrors).isEmpty() - } - - @Test - fun `Given a code snippet that violates a custom rule prefixed by a rule set id`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { NoVarRule() }, - ), - editorConfigOverride = - EditorConfigOverride.from( - NoVarRule.NO_VAR_RULE_ID.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) + ) { lintErrors.add(it) } - val lintErrors = mutableListOf() - ktLintRuleEngine.lint( - code = - Code.fromSnippet( - """ - var foo = "foo" - """.trimIndent(), - ), - callback = { lintErrors.add(it) }, + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), ) - - assertThat(lintErrors).isNotEmpty } @Test @@ -172,68 +153,78 @@ class KtLintRuleEngineTest { var foo = "foo" """.trimIndent(), ), - callback = { lintErrors.add(it) }, - ) + ) { lintErrors.add(it) } assertThat(lintErrors).isEmpty() } } @Nested - inner class `Format with KtLintRuleEngine` { + inner class `Format (legacy) with KtLintRuleEngine` { @Test fun `Given a file that does not contain an error`( @TempDir tempDir: Path, ) { - val dir = ApiTestRunner(tempDir).prepareTestProject("no-code-style-error") - - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), ) + } - val original = File("$dir/Main.kt").readText() - + val lintErrors = mutableListOf() val actual = ktLintRuleEngine.format( - code = Code.fromFile(File("$dir/Main.kt")), - ) + code = Code.fromFile(File(filePath)), + ) { lintError, _ -> lintErrors.add(lintError) } - assertThat(actual).isEqualTo(original) + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent(), + ) } @Test fun `Given a kotlin code snippet that does contain an indentation error`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) - + val lintErrors = mutableListOf() val actual = ktLintRuleEngine.format( code = Code.fromSnippet( """ - fun main() { - println("Hello world!") - } + fun bar() { + // foo + // bar + } """.trimIndent(), ), - ) + ) { lintError, _ -> lintErrors.add(lintError) } + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) assertThat(actual).isEqualTo( """ - fun main() { - println("Hello world!") + fun bar() { + // FOO + // BAR } """.trimIndent(), ) @@ -241,71 +232,398 @@ class KtLintRuleEngineTest { @Test fun `Given a kotlin script code snippet that does contain an indentation error`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, - ) - + val lintErrors = mutableListOf() val actual = ktLintRuleEngine.format( code = Code.fromSnippet( """ plugins { - id("foo") - id("bar") - } + // foo + // bar + } """.trimIndent(), script = true, ), - ) + ) { lintError, _ -> lintErrors.add(lintError) } + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) assertThat(actual).isEqualTo( """ plugins { - id("foo") - id("bar") + // FOO + // BAR } """.trimIndent(), ) } + } - @Test - fun `Given a kotlin code snippet that does contain multiple indentation errors then only format the lint error at specific offset and message`() { - val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = - setOf( - RuleProvider { IndentationRule() }, - ), - fileSystem = ktlintTestFileSystem.fileSystem, + @Nested + inner class `Format with KtLintRuleEngine` { + @Nested + inner class `Given a file that does not contain an error` { + @Test + fun `Given defaultAutocorrect is not set`( + @TempDir + tempDir: Path, + ) { + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ) + } + + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = Code.fromFile(File(filePath)), + ) { lintError -> + lintErrors.add(lintError) + true + } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), ) + assertThat(actual).isEqualTo( + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent(), + ) + } - val originalCode = - """ - fun main() { - println("Hello world!") - println("Hello world!") - println("Hello world!") + @Test + fun `Given defaultAutocorrect is enabled`( + @TempDir + tempDir: Path, + ) { + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ) } - """.trimIndent() + + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = Code.fromFile(File(filePath)), + defaultAutocorrect = true, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent(), + ) + } + + @Test + fun `Given defaultAutocorrect is disabled`( + @TempDir + tempDir: Path, + ) { + val filePath = "$tempDir/Code.kt" + FileWriter(filePath).use { + it.write( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ) + } + + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = Code.fromFile(File(filePath)), + defaultAutocorrect = false, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + // Note that "foo" is not transformed to "FOO" as the defaultAutocorrect for rules without AutocorrectApproveHandler is + // not set + """ + fun bar() { + // foo + // BAR + } + """.trimIndent(), + ) + } + } + + @Nested + inner class `Given a kotlin code snippet that does contain an indentation error` { + @Test + fun `Given defaultAutocorrect is not set`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ), + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent(), + ) + } + + @Test + fun `Given defaultAutocorrect is enabled`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ), + defaultAutocorrect = true, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + fun bar() { + // FOO + // BAR + } + """.trimIndent(), + ) + } + + @Test + fun `Given defaultAutocorrect is disabled`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + fun bar() { + // foo + // bar + } + """.trimIndent(), + ), + defaultAutocorrect = false, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + // Note that "foo" is not transformed to "FOO" as the defaultAutocorrect for rules without AutocorrectApproveHandler is + // not set + """ + fun bar() { + // foo + // BAR + } + """.trimIndent(), + ) + } + } + + @Nested + inner class `Given a kotlin script code snippet that does contain an indentation error` { + @Test + fun `Given defaultAutocorrect is not set`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + plugins { + // foo + // bar + } + """.trimIndent(), + script = true, + ), + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + plugins { + // FOO + // BAR + } + """.trimIndent(), + ) + } + + @Test + fun `Given defaultAutocorrect is enabled`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + plugins { + // foo + // bar + } + """.trimIndent(), + script = true, + ), + defaultAutocorrect = true, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + """ + plugins { + // FOO + // BAR + } + """.trimIndent(), + ) + } + + @Test + fun `Given defaultAutocorrect is disabled`() { + val lintErrors = mutableListOf() + val actual = + ktLintRuleEngine.format( + code = + Code.fromSnippet( + """ + plugins { + // foo + // bar + } + """.trimIndent(), + script = true, + ), + defaultAutocorrect = false, + ) { lintError -> lintErrors.add(lintError) } + + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + assertThat(actual).isEqualTo( + // Note that "foo" is not transformed to "FOO" as the defaultAutocorrect for rules without AutocorrectApproveHandler is + // not set + """ + plugins { + // foo + // BAR + } + """.trimIndent(), + ) + } + } + + @Test + fun `Given a kotlin code snippet that does contain multiple indentation errors then only format the lint error at specific offset and message`() { + val lintErrors = mutableListOf() val actual = ktLintRuleEngine - .interactiveFormat(Code.fromSnippet(originalCode)) { lintError -> - lintError.line == 3 && lintError.detail == "Unexpected indentation (0) (should be 4)" + .format( + code = + Code.fromSnippet( + """ + // bar + // bar + // bar + """.trimIndent(), + ), + ) { lintError -> + if (lintError.line == 3 && lintError.detail == "Unexpected indentation (0) (should be 4)") { + ALLOW_AUTOCORRECT + } else { + NO_AUTOCORRECT + } } + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(1, 1, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(2, 1, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(3, 1, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + ) assertThat(actual).isEqualTo( """ - fun main() { - println("Hello world!") - println("Hello world!") - println("Hello world!") - } + // bar + // BAR + // bar """.trimIndent(), ) } @@ -313,45 +631,71 @@ class KtLintRuleEngineTest { @Test fun `Given that all experimental rules are enabled`() { - val ktLintEngine = + val ktLintRuleEngine = KtLintRuleEngine( ruleProviders = setOf( - RuleProvider { NoVarRule() }, + RuleProvider { IndentationRule() }, + RuleProvider { RuleWithAutocorrectApproveHandler() }, + RuleProvider { RuleWithoutAutocorrectApproveHandler() }, ), - editorConfigDefaults = - EditorConfigDefaults( - EditorConfig - .builder() - .section( - Section - .builder() - .glob(Glob("*.{kt,kts}")) - .properties( - EXPERIMENTAL_RULES_EXECUTION_PROPERTY.toPropertyBuilderWithValue("enabled"), - ), - ).build(), + editorConfigOverride = + EditorConfigOverride.from( + EXPERIMENTAL_RULES_EXECUTION_PROPERTY to RuleExecution.enabled, ), fileSystem = ktlintTestFileSystem.fileSystem, ) - val errors = mutableListOf() - ktLintEngine.lint( + + val lintErrors = mutableListOf() + ktLintRuleEngine.lint( code = Code.fromSnippet( """ - var foo = "foo" + fun bar() { + // foo + // bar + } """.trimIndent(), ), - callback = errors::add, + callback = lintErrors::add, ) - val failedRules = errors.map { it.ruleId } - check(failedRules.contains(NoVarRule.NO_VAR_RULE_ID)) + assertThat(lintErrors).containsExactlyInAnyOrder( + LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), + LintError(3, 5, RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, "Bar comment with autocorrect approve handler", true), + LintError(4, 1, INDENTATION_RULE_ID, "Unexpected indentation (4) (should be 0)", true), + ) + } + + private class RuleWithoutAutocorrectApproveHandler : + Rule( + ruleId = RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, + about = About(), + ), + Rule.Experimental { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.elementType == ElementType.EOL_COMMENT && node.text == "// foo") { + emit(node.startOffset, "Foo comment without autocorrect approve handler", true) + if (autoCorrect) { + node + .safeAs() + ?.rawReplaceWithText("// FOO") + } + } + } + + companion object { + val RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER = RuleId("custom:rule-without-autocorrect-approval-handler") + } } - private class NoVarRule : + private class RuleWithAutocorrectApproveHandler : Rule( - ruleId = NO_VAR_RULE_ID, + ruleId = RULE_WITH_AUTOCORRECT_APPROVE_HANDLER, about = About(), ), RuleAutocorrectApproveHandler, @@ -360,13 +704,18 @@ class KtLintRuleEngineTest { node: ASTNode, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { - if (node.elementType == ElementType.VAR_KEYWORD) { - emitAndApprove(node.startOffset, "Unexpected var, use val instead", false) + if (node.elementType == ElementType.EOL_COMMENT && node.text == "// bar") { + emitAndApprove(node.startOffset, "Bar comment with autocorrect approve handler", true) + .ifTrue { + node + .safeAs() + ?.rawReplaceWithText("// BAR") + } } } companion object { - val NO_VAR_RULE_ID = RuleId("test:no-var-rule") + val RULE_WITH_AUTOCORRECT_APPROVE_HANDLER = RuleId("custom:rule-with-autocorrect-approval-handler") } } } diff --git a/ktlint-api-consumer/src/test/resources/api/no-code-style-error/Main.kt b/ktlint-api-consumer/src/test/resources/api/no-code-style-error/Main.kt deleted file mode 100644 index acf03f8cf9..0000000000 --- a/ktlint-api-consumer/src/test/resources/api/no-code-style-error/Main.kt +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - println("Hello world!") -} diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 6b45796ed5..6a1845d8c7 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -5,6 +5,7 @@ package com.pinterest.ktlint.rule.engine.api import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults.Companion.EMPTY_EDITOR_CONFIG_DEFAULTS import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.EMPTY_EDITOR_CONFIG_OVERRIDE import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue @@ -89,8 +90,8 @@ public class KtLintRuleEngine( ): Unit = codeLinter.lint(code, callback) /** - * Fix style violations in [code] for lint errors when possible. If [code] is passed as file reference then the '.editorconfig' files on - * the path are taken into account. For each lint violation found, the [callback] is invoked. + * Fix all style violations in [code] for lint errors when possible. If [code] is passed as file reference then the '.editorconfig' + * files on the path are taken into account. For each lint violation found, the [callback] is invoked. * * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. @@ -107,22 +108,25 @@ public class KtLintRuleEngine( ): String = codeFormatter.format(code, AllAutocorrectHandler, callback, MAX_FORMAT_RUNS_PER_FILE) /** - * Formats style violations in [code]. Whenever a [LintError] is found which can be autocorrected by the rule that detected the - * violation, the [callback] is invoked to let the calling API Consumer to decide whether the [LintError] is actually to be fixed. + * Formats style violations in [code]. Whenever a [LintError] is found the [callback] is invoked. If the [LintError] can be + * autocorrected *and* the rule that found that the violation has implemented the [RuleAutocorrectApproveHandler] interface, the API + * Consumer determines whether that [LintError] is to autocorrected, or not. * - * Important: [callback] is only invoked if the rule that found that the violation has implemented the [RuleAutocorrectApproveHandler] - * and the violation can be autocorrected. + * In case the rule has not implemented the [RuleAutocorrectApproveHandler] interface, then the result of the [callback] is ignored as + * the rule is not able to process it. For such rules the [defaultAutocorrect] determines whether autocorrect for this rule is to be + * applied, or not. By default, the autocorrect will be applied (backwards compatability). * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation */ - public fun interactiveFormat( + public fun format( code: Code, + defaultAutocorrect: Boolean = true, callback: (LintError) -> Boolean, ): String = codeFormatter.format( code = code, - autocorrectHandler = LintErrorAutocorrectHandler(callback), + autocorrectHandler = LintErrorAutocorrectHandler(defaultAutocorrect, callback), maxFormatRunsPerFile = 1, ) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt index ac22e0048b..77795de25a 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt @@ -7,7 +7,7 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutocorrectHandler { - fun autoCorrect(lintError: LintError): Boolean + fun autocorrect(lintError: LintError): Boolean } /** @@ -15,7 +15,7 @@ internal sealed interface AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object NoneAutocorrectHandler : AutocorrectHandler { - override fun autoCorrect(lintError: LintError) = false + override fun autocorrect(lintError: LintError) = false } /** @@ -23,7 +23,7 @@ internal data object NoneAutocorrectHandler : AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object AllAutocorrectHandler : AutocorrectHandler { - override fun autoCorrect(lintError: LintError) = true + override fun autocorrect(lintError: LintError) = true } /** @@ -32,7 +32,8 @@ internal data object AllAutocorrectHandler : AutocorrectHandler { * be able to control whether a specific [LintError] is to be corrected or not. */ internal class LintErrorAutocorrectHandler( + val autocorrectRuleWithoutAutocorrectApproveHandler: Boolean, private val callback: (LintError) -> Boolean, ) : AutocorrectHandler { - override fun autoCorrect(lintError: LintError) = callback(lintError) + override fun autocorrect(lintError: LintError) = callback(lintError) } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 4ad470dd89..7a68bd811f 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -114,13 +114,18 @@ internal class CodeFormatter( executeRule(rule, autocorrectHandler) { offset, errorMessage, canBeAutoCorrected -> val (line, col) = positionInTextLocator(offset) val lintError = LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - val autoCorrect = - if (canBeAutoCorrected) { - autocorrectHandler.autoCorrect(lintError) - } else { - false - } - if (autoCorrect) { + val autocorrect = + autocorrectHandler + // The autocorrect is always to be called (even when the error can not be autocorrected) as it informs the API Consumer + // about the LintError. + // TODO: rename... "LintErrorHandler.emit" and let this function return an enum value as ALLOW_AUTOCORRECT and + // NO_AUTOCORRECT + .autocorrect(lintError) + .let { autocorrect -> + // Ignore decision of the API Consumer in case the error can not be autocorrected + autocorrect && canBeAutoCorrected + } + if (autocorrect) { /* * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might * have changed. @@ -131,14 +136,14 @@ internal class CodeFormatter( Pair( lintError, // It is assumed that a rule that asks for autocorrect approval, also does correct the error. - autoCorrect, + autocorrect, ), ) // In trace mode report the violation immediately. The order in which violations are actually found might be // different from the order in which they are reported. For debugging purposes it can be helpful to know the // exact order in which violations are being solved. LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } - autoCorrect + autocorrect } return errors } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 6d4370948f..0585c32943 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -129,7 +129,12 @@ internal class RuleExecutionContext private constructor( suppress: Boolean, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, ) { - val autoCorrect = autocorrectHandler is AllAutocorrectHandler + val autoCorrect = + autocorrectHandler is AllAutocorrectHandler || + ( + autocorrectHandler is LintErrorAutocorrectHandler && + autocorrectHandler.autocorrectRuleWithoutAutocorrectApproveHandler + ) val emitOnly = emitAndApprove.onlyEmit() if (!suppress) { rule.beforeVisitChildNodes(node, autoCorrect, emitOnly) From 8084c01d127782121c2d81d29a86c3f8515c1147 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Thu, 23 May 2024 21:26:34 +0200 Subject: [PATCH 10/33] Add FormatDecision as return result from callback used in format function --- .../api/consumer/KtLintRuleEngineTest.kt | 44 +++++++++++++++---- ktlint-rule-engine/api/ktlint-rule-engine.api | 13 ++++-- .../rule/engine/api/KtLintRuleEngine.kt | 14 +++++- .../engine/internal/AutocorrectHandler.kt | 11 +++-- .../rule/engine/internal/CodeFormatter.kt | 5 ++- 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 46c32bd17a..99f5a52103 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -5,6 +5,8 @@ import com.pinterest.ktlint.api.consumer.KtLintRuleEngineTest.RuleWithoutAutocor import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.Rule @@ -290,7 +292,7 @@ class KtLintRuleEngineTest { code = Code.fromFile(File(filePath)), ) { lintError -> lintErrors.add(lintError) - true + ALLOW_AUTOCORRECT } assertThat(lintErrors).containsExactlyInAnyOrder( @@ -330,7 +332,10 @@ class KtLintRuleEngineTest { ktLintRuleEngine.format( code = Code.fromFile(File(filePath)), defaultAutocorrect = true, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -369,7 +374,10 @@ class KtLintRuleEngineTest { ktLintRuleEngine.format( code = Code.fromFile(File(filePath)), defaultAutocorrect = false, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -405,7 +413,10 @@ class KtLintRuleEngineTest { } """.trimIndent(), ), - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -437,7 +448,10 @@ class KtLintRuleEngineTest { """.trimIndent(), ), defaultAutocorrect = true, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -469,7 +483,10 @@ class KtLintRuleEngineTest { """.trimIndent(), ), defaultAutocorrect = false, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -506,7 +523,10 @@ class KtLintRuleEngineTest { """.trimIndent(), script = true, ), - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -539,7 +559,10 @@ class KtLintRuleEngineTest { script = true, ), defaultAutocorrect = true, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), @@ -572,7 +595,10 @@ class KtLintRuleEngineTest { script = true, ), defaultAutocorrect = false, - ) { lintError -> lintErrors.add(lintError) } + ) { lintError -> + lintErrors.add(lintError) + ALLOW_AUTOCORRECT + } assertThat(lintErrors).containsExactlyInAnyOrder( LintError(2, 5, RULE_WITHOUT_AUTOCORRECT_APPROVE_HANDLER, "Foo comment without autocorrect approve handler", true), diff --git a/ktlint-rule-engine/api/ktlint-rule-engine.api b/ktlint-rule-engine/api/ktlint-rule-engine.api index bd9cdc8ce2..946c66b532 100644 --- a/ktlint-rule-engine/api/ktlint-rule-engine.api +++ b/ktlint-rule-engine/api/ktlint-rule-engine.api @@ -69,15 +69,14 @@ public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine { public synthetic fun (Ljava/util/Set;Lcom/pinterest/ktlint/rule/engine/api/EditorConfigDefaults;Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride;ZLjava/nio/file/FileSystem;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun editorConfigFilePaths (Ljava/nio/file/Path;)Ljava/util/List; public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; + public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;ZLkotlin/jvm/functions/Function1;)Ljava/lang/String; public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/String; - public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/String; + public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/String; public final fun generateKotlinEditorConfigSection (Ljava/nio/file/Path;)Ljava/lang/String; public final fun getEditorConfigDefaults ()Lcom/pinterest/ktlint/rule/engine/api/EditorConfigDefaults; public final fun getEditorConfigOverride ()Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride; public final fun getFileSystem ()Ljava/nio/file/FileSystem; public final fun getRuleProviders ()Ljava/util/Set; - public final fun interactiveFormat (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; public final fun isInvokedFromCli ()Z public final fun lint (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;)V public static synthetic fun lint$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V @@ -89,6 +88,14 @@ public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine { public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$Companion { } +public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision : java/lang/Enum { + public static final field ALLOW_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; + public static final field NO_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; + public static fun values ()[Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; +} + public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleException : java/lang/RuntimeException { public fun (IILjava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V public fun getCause ()Ljava/lang/Throwable; diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 6a1845d8c7..968e3278e6 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -122,7 +122,7 @@ public class KtLintRuleEngine( public fun format( code: Code, defaultAutocorrect: Boolean = true, - callback: (LintError) -> Boolean, + callback: (LintError) -> FormatDecision, ): String = codeFormatter.format( code = code, @@ -130,6 +130,18 @@ public class KtLintRuleEngine( maxFormatRunsPerFile = 1, ) + public enum class FormatDecision { + /** + * Autocorrect the [LintError] if supported by the rule. + */ + ALLOW_AUTOCORRECT, + + /** + * Do not autocorrect the [LintError] even when this is supported by the rule. + */ + NO_AUTOCORRECT, + } + /** * Generates Kotlin `.editorconfig` file section content based on a path to a file or directory. Given that path, all '.editorconfig' * files on that path are taken into account to determine the values of properties which are already used. diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt index 77795de25a..ce3f0a36ff 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt @@ -1,5 +1,8 @@ package com.pinterest.ktlint.rule.engine.internal +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler @@ -7,7 +10,7 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutocorrectHandler { - fun autocorrect(lintError: LintError): Boolean + fun autocorrect(lintError: LintError): FormatDecision } /** @@ -15,7 +18,7 @@ internal sealed interface AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object NoneAutocorrectHandler : AutocorrectHandler { - override fun autocorrect(lintError: LintError) = false + override fun autocorrect(lintError: LintError) = NO_AUTOCORRECT } /** @@ -23,7 +26,7 @@ internal data object NoneAutocorrectHandler : AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object AllAutocorrectHandler : AutocorrectHandler { - override fun autocorrect(lintError: LintError) = true + override fun autocorrect(lintError: LintError) = ALLOW_AUTOCORRECT } /** @@ -33,7 +36,7 @@ internal data object AllAutocorrectHandler : AutocorrectHandler { */ internal class LintErrorAutocorrectHandler( val autocorrectRuleWithoutAutocorrectApproveHandler: Boolean, - private val callback: (LintError) -> Boolean, + private val callback: (LintError) -> FormatDecision, ) : AutocorrectHandler { override fun autocorrect(lintError: LintError) = callback(lintError) } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 7a68bd811f..a268bdc481 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -3,6 +3,7 @@ package com.pinterest.ktlint.rule.engine.internal import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY @@ -121,9 +122,9 @@ internal class CodeFormatter( // TODO: rename... "LintErrorHandler.emit" and let this function return an enum value as ALLOW_AUTOCORRECT and // NO_AUTOCORRECT .autocorrect(lintError) - .let { autocorrect -> + .let { autocorrectDecision -> // Ignore decision of the API Consumer in case the error can not be autocorrected - autocorrect && canBeAutoCorrected + autocorrectDecision == ALLOW_AUTOCORRECT && canBeAutoCorrected } if (autocorrect) { /* From 38f27d2d607097d7fed95a5f87fa034b2dea158e Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Fri, 24 May 2024 16:05:14 +0200 Subject: [PATCH 11/33] Update documentation --- .../snapshot/docs/api/custom-integration.md | 109 +++++++++++------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/documentation/snapshot/docs/api/custom-integration.md b/documentation/snapshot/docs/api/custom-integration.md index 51049a1578..c1a103f526 100644 --- a/documentation/snapshot/docs/api/custom-integration.md +++ b/documentation/snapshot/docs/api/custom-integration.md @@ -4,13 +4,13 @@ The `Ktlint Rule Engine` is the central entry point for custom integrations with the `Ktlint API`. See [basic API Consumer](https://github.com/pinterest/ktlint/blob/master/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt) for a basic example on how to invoke the `Ktlint Rule Engine`. This example also explains how the logging of the `Ktlint Rule Engine` can be configured to your needs. -The `KtLintRuleEngine` instance only needs to be created once for the entire lifetime of your application. Reusing the same instance results in better performance due to caching. +The `KtLintRuleEngine` instance only needs to be created once for the entire lifetime of your application. Reusing the same instance results in better performance due to caching. ```kotlin title="Creating the KtLintRuleEngine" val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, - ) + KtLintRuleEngine( + ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, + ) ``` ### Rule provider @@ -18,12 +18,12 @@ val ktLintRuleEngine = The `KtLintRuleEngine` must be configured with at least one `RuleProvider`. A `RuleProvider` is a lambda which upon request of the `KtLintRuleEngine` provides a new instance of a specific rule. You can either provide any of the standard rules provided by KtLint or with your own custom rules, or with a combination of both. ```kotlin title="Creating a set of RuleProviders" val KTLINT_API_CONSUMER_RULE_PROVIDERS = - setOf( - // Can provide custom rules - RuleProvider { NoVarRule() }, - // but also reuse rules from KtLint rulesets - RuleProvider { IndentationRule() }, - ) + setOf( + // Can provide custom rules + RuleProvider { NoVarRule() }, + // but also reuse rules from KtLint rulesets + RuleProvider { IndentationRule() }, + ) ``` ### Editor config: defaults & overrides @@ -32,29 +32,29 @@ When linting and formatting files, the `KtlintRuleEngine` takes the `.editorconf ```kotlin title="Specifying the editorConfigOverride" val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, - editorConfigOverride = EditorConfigOverride.from( - INDENT_STYLE_PROPERTY to IndentConfig.IndentStyle.SPACE, - INDENT_SIZE_PROPERTY to 4 - ) + KtLintRuleEngine( + ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, + editorConfigOverride = EditorConfigOverride.from( + INDENT_STYLE_PROPERTY to IndentConfig.IndentStyle.SPACE, + INDENT_SIZE_PROPERTY to 4 ) + ) ``` The `editorConfigOverride` property takes an `EditorConfigProperty` as key. KtLint defines several such properties, but they can also be defined as part of a custom rule. The `editorConfigDefaults` property is more cumbersome to define as it is based directly on the data format of the `ec4j` library which is used for parsing the `.editorconfig` file. -The defaults can be loaded from a path or a directory. If a path to a file is specified, the name of the file does not necessarily have to end with `.editorconfig`. If a path to a directory is specified, the directory should contain a file with name `.editorconfig`. Note that the `propertyTypes` have to be derived from the same collection of rule providers that are specified in the `ruleProviders` property of the `KtLintRuleEngine`. +The defaults can be loaded from a path or a directory. If a path to a file is specified, the name of the file does not necessarily have to end with `.editorconfig`. If a path to a directory is specified, the directory should contain a file with name `.editorconfig`. Note that the `propertyTypes` have to be derived from the same collection of rule providers that are specified in the `ruleProviders` property of the `KtLintRuleEngine`. ```kotlin title="Specifying the editorConfigDefaults using an '.editorconfig' file" val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, - editorConfigDefaults = EditorConfigDefaults.load( - path = Paths.get("/some/path/to/editorconfig/file/or/directory"), - propertyTypes = KTLINT_API_CONSUMER_RULE_PROVIDERS.propertyTypes(), - ) + KtLintRuleEngine( + ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, + editorConfigDefaults = EditorConfigDefaults.load( + path = Paths.get("/some/path/to/editorconfig/file/or/directory"), + propertyTypes = KTLINT_API_CONSUMER_RULE_PROVIDERS.propertyTypes(), + ) ) ``` If you want to include all RuleProviders of the Ktlint project than you can easily retrieve the collection using `StandardRuleSetProvider().getRuleProviders()`. @@ -63,20 +63,20 @@ The `EditorConfigDefaults` property can also be specified programmatically as is ```kotlin title="Specifying the editorConfigDefaults programmatically" val ktLintRuleEngine = - KtLintRuleEngine( - ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, - editorConfigDefaults = EditorConfigDefaults( - org.ec4j.core.model.EditorConfig - .builder() - // .. add relevant properties - .build() - ) + KtLintRuleEngine( + ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, + editorConfigDefaults = EditorConfigDefaults( + org.ec4j.core.model.EditorConfig + .builder() + // .. add relevant properties + .build() ) + ) ``` ### Lint & format -Once the `KtLintRuleEngine` has been defined, it is ready to be invoked for each file or code snippet that has to be linted or formatted. The the `lint` and `format` functions take a `Code` instance as parameter. Such an instance can either be created from a file +Once the `KtLintRuleEngine` has been defined, it is ready to be invoked for code that has to be linted or formatted. The `lint` and `format` functions take a `Code` instance as parameter. Such an instance can either be created from a file ```kotlin title="Code from file" val code = Code.fromFile( File("/some/path/to/file") @@ -91,21 +91,48 @@ val code = Code.fromSnippet( ) ``` -The `lint` function is invoked with a lambda which is called each time a `LintError` is found and does not return a result. -```kotlin title="Specifying the editorConfigDefaults programmatically" +The `lint` function is invoked with an optional lambda. Once linting is complete, the lambda will be called for each `LintError` which is found. +```kotlin title="Invoking lint" ktLintRuleEngine - .lint(codeFile) { lintError -> + .lint(code) { lintError -> // handle } ``` -The `format` function is invoked with a lambda which is called each time a `LintError` is found and returns the formatted code as result. Note that the `LintError` should be inspected for errors that could not be autocorrected. -```kotlin title="Specifying the editorConfigDefaults programmatically" +The `format` function is invoked with a lambda. The lambda is called for each `LintError` which is found. If the `LintError` can be autocorrected, the return value of the lambda instructs the rule whether this specific `LintError` is to be autocorrect, or not. If the `LintError` can not be autocorrected, the return result of the lambda is ignored. The formatted code is returned as result of the function. + +The new `format` function allows the API Consumer to decided which LintError is to be autocorrected, or not. This is most interesting for API Consumers that let their user interactively decide per LintError how it has to be handled. For example see the `ktlint-intellij-plugin` which in 'manual' mode displays all lint violations, which that on a case by case basis can be autocorrected. + +!!! note + The lambda of the legacy version of the `format` just takes a single parameter and does not return a value. + +```kotlin title="Invoke format (preferred starting from Ktlint 1.3)" +val formattedCode = + ktLintRuleEngine + .format(code) { lintError -> + if (lintError.canBeAutocorrected) { + // Return FormatDecision.ALLOW_AUTOCORRECT to execute the autocorrect of this lintError if this is supported by the rule. + // Return FormatDecision.NO_AUTOCORRECT if the LintError should not be corrected even if is supported by the rule. + } else { + // In case the LintError can not be autocorrected, the return value of the lambda will be ignored. + // For clarity reasons it is advised to return FormatDecision.NO_AUTOCORRECT in case the LintError can not be autocorrected. + FormatDecision.NO_AUTOCORRECT + } + } +``` + +!!! warning + Rules need to implement the interface `RuleAutocorrectApproveHandler` in order to let the API Consumer to decide whether a `LintError` is to be autocorrected, or not. This interface is implemented for all rules provided via the Ktlint project starting from version 1.3. However, external rulesets may not have implemented this interface on their rulesets though. Contact the maintainer of such ruleset to implement this. + +The (legacy) `format` function is invoked with an optional lambda. Once formatting is complete, the lambda will be called for each `LintError` which is found. The (legacy) `format` function fixes all `LintErrors` for which an autocorrect is available. The formatted code is returned as result of the function. + +```kotlin title="Invoke format (deprecated as of Ktlint 1.3, will be removed in Ktlint 2.0)" +// Up until Ktlint 1.2.1 the format was invoked with a lambda having two parameters and not returning a result. This function will be removed in Ktlint 2.0 val formattedCode = - ktLintRuleEngine - .format(codeFile) { lintError -> - // handle - } + ktLintRuleEngine + .format(code) { lintError, corrected -> + // handle + } ``` ## Logging From 998bc313de5bfc9dd5d649641e05059f90ec7500 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Fri, 24 May 2024 17:06:54 +0200 Subject: [PATCH 12/33] Refactor FormatDecision to AutocorrectDecision --- .../snapshot/docs/api/custom-integration.md | 8 +-- .../api/consumer/KtLintRuleEngineTest.kt | 21 ++++--- .../api/ktlint-rule-engine-core.api | 12 ++++ .../engine/core/api/AutocorrectDecision.kt | 19 +++++++ .../core/api/RuleAutocorrectApproveHandler.kt | 4 +- ktlint-rule-engine/api/ktlint-rule-engine.api | 8 --- .../rule/engine/api/KtLintRuleEngine.kt | 15 +---- .../engine/internal/AutocorrectHandler.kt | 16 +++--- .../rule/engine/internal/CodeFormatter.kt | 55 +++++++++---------- .../ktlint/rule/engine/internal/CodeLinter.kt | 3 +- .../engine/internal/RuleExecutionContext.kt | 11 ++-- .../rules/BlankLineBetweenWhenConditions.kt | 15 ++--- .../ruleset/standard/rules/IndentationRule.kt | 25 +++++---- 13 files changed, 117 insertions(+), 95 deletions(-) create mode 100644 ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt diff --git a/documentation/snapshot/docs/api/custom-integration.md b/documentation/snapshot/docs/api/custom-integration.md index c1a103f526..8d9cb45a54 100644 --- a/documentation/snapshot/docs/api/custom-integration.md +++ b/documentation/snapshot/docs/api/custom-integration.md @@ -111,12 +111,12 @@ val formattedCode = ktLintRuleEngine .format(code) { lintError -> if (lintError.canBeAutocorrected) { - // Return FormatDecision.ALLOW_AUTOCORRECT to execute the autocorrect of this lintError if this is supported by the rule. - // Return FormatDecision.NO_AUTOCORRECT if the LintError should not be corrected even if is supported by the rule. + // Return AutocorrectDecision.ALLOW_AUTOCORRECT to execute the autocorrect of this lintError if this is supported by the rule. + // Return AutocorrectDecision.NO_AUTOCORRECT if the LintError should not be corrected even if is supported by the rule. } else { // In case the LintError can not be autocorrected, the return value of the lambda will be ignored. - // For clarity reasons it is advised to return FormatDecision.NO_AUTOCORRECT in case the LintError can not be autocorrected. - FormatDecision.NO_AUTOCORRECT + // For clarity reasons it is advised to return AutocorrectDecision.NO_AUTOCORRECT in case the LintError can not be autocorrected. + AutocorrectDecision.NO_AUTOCORRECT } } ``` diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 99f5a52103..8841406775 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -5,9 +5,10 @@ import com.pinterest.ktlint.api.consumer.KtLintRuleEngineTest.RuleWithoutAutocor import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOCORRECT +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.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler @@ -16,6 +17,7 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EXPERIMENTAL_RULES_EXECUTION_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution import com.pinterest.ktlint.rule.engine.core.api.editorconfig.createRuleExecutionEditorConfigProperty +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.rules.FilenameRule import com.pinterest.ktlint.ruleset.standard.rules.INDENTATION_RULE_ID @@ -24,7 +26,6 @@ import com.pinterest.ktlint.test.KtlintTestFileSystem import org.assertj.core.api.Assertions.assertThat import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement -import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -619,7 +620,7 @@ class KtLintRuleEngineTest { } @Test - fun `Given a kotlin code snippet that does contain multiple indentation errors then only format the lint error at specific offset and message`() { + fun `Given a kotlin code snippet that does contain multiple errors then only format the lint error at specific offset and message`() { val lintErrors = mutableListOf() val actual = ktLintRuleEngine @@ -633,7 +634,12 @@ class KtLintRuleEngineTest { """.trimIndent(), ), ) { lintError -> - if (lintError.line == 3 && lintError.detail == "Unexpected indentation (0) (should be 4)") { + lintErrors.add(lintError) + if (lintError.line == 2 && + lintError.col == 1 && + lintError.ruleId == RULE_WITH_AUTOCORRECT_APPROVE_HANDLER && + lintError.detail == "Bar comment with autocorrect approve handler" + ) { ALLOW_AUTOCORRECT } else { NO_AUTOCORRECT @@ -699,6 +705,7 @@ class KtLintRuleEngineTest { about = About(), ), Rule.Experimental { + @Deprecated("Marked for removal in Ktlint 2.0. Please implement interface RuleAutocorrectApproveHandler.") override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, @@ -728,11 +735,11 @@ class KtLintRuleEngineTest { Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.EOL_COMMENT && node.text == "// bar") { emitAndApprove(node.startOffset, "Bar comment with autocorrect approve handler", true) - .ifTrue { + .ifAutocorrectAllowed { node .safeAs() ?.rawReplaceWithText("// BAR") diff --git a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api index 098f564170..f04686499e 100644 --- a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api +++ b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api @@ -62,6 +62,18 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt public static final fun upsertWhitespaceBeforeMe (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Ljava/lang/String;)V } +public final class com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision : java/lang/Enum { + public static final field ALLOW_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision; + public static final field NO_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision; + public static fun values ()[Lcom/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision; +} + +public final class com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecisionKt { + public static final fun ifAutocorrectAllowed (Lcom/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; +} + public final class com/pinterest/ktlint/rule/engine/core/api/ElementType { public static final field INSTANCE Lcom/pinterest/ktlint/rule/engine/core/api/ElementType; public final fun getABSTRACT_KEYWORD ()Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType; diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt new file mode 100644 index 0000000000..7a5ffb7d25 --- /dev/null +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt @@ -0,0 +1,19 @@ +package com.pinterest.ktlint.rule.engine.core.api + +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOCORRECT + +public enum class AutocorrectDecision { + /** + * Autocorrect the [LintError] if supported by the rule. + */ + ALLOW_AUTOCORRECT, + + /** + * Do not autocorrect the [LintError] even when this is supported by the rule. + */ + NO_AUTOCORRECT, +} + +public inline fun AutocorrectDecision.ifAutocorrectAllowed(function: () -> T): T? = + takeIf { this == ALLOW_AUTOCORRECT } + ?.let { function() } diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt index dae4bede87..25bc92a3f7 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt @@ -27,7 +27,7 @@ public interface RuleAutocorrectApproveHandler { */ public fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { } @@ -45,7 +45,7 @@ public interface RuleAutocorrectApproveHandler { @Suppress("unused") public open fun afterVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { } } diff --git a/ktlint-rule-engine/api/ktlint-rule-engine.api b/ktlint-rule-engine/api/ktlint-rule-engine.api index 946c66b532..66d4d289ed 100644 --- a/ktlint-rule-engine/api/ktlint-rule-engine.api +++ b/ktlint-rule-engine/api/ktlint-rule-engine.api @@ -88,14 +88,6 @@ public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine { public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$Companion { } -public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision : java/lang/Enum { - public static final field ALLOW_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; - public static final field NO_AUTOCORRECT Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; - public static fun values ()[Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine$FormatDecision; -} - public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleException : java/lang/RuntimeException { public fun (IILjava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V public fun getCause ()Ljava/lang/Throwable; diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 968e3278e6..22decad924 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -4,6 +4,7 @@ package com.pinterest.ktlint.rule.engine.api import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults.Companion.EMPTY_EDITOR_CONFIG_DEFAULTS import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.EMPTY_EDITOR_CONFIG_OVERRIDE +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleProvider @@ -122,7 +123,7 @@ public class KtLintRuleEngine( public fun format( code: Code, defaultAutocorrect: Boolean = true, - callback: (LintError) -> FormatDecision, + callback: (LintError) -> AutocorrectDecision, ): String = codeFormatter.format( code = code, @@ -130,18 +131,6 @@ public class KtLintRuleEngine( maxFormatRunsPerFile = 1, ) - public enum class FormatDecision { - /** - * Autocorrect the [LintError] if supported by the rule. - */ - ALLOW_AUTOCORRECT, - - /** - * Do not autocorrect the [LintError] even when this is supported by the rule. - */ - NO_AUTOCORRECT, - } - /** * Generates Kotlin `.editorconfig` file section content based on a path to a file or directory. Given that path, all '.editorconfig' * files on that path are taken into account to determine the values of properties which are already used. diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt index ce3f0a36ff..fb68a7bbda 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/AutocorrectHandler.kt @@ -1,16 +1,16 @@ package com.pinterest.ktlint.rule.engine.internal -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOCORRECT +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler /** * Handler which determines whether autocorrect should be enabled or disabled for the given offset. */ internal sealed interface AutocorrectHandler { - fun autocorrect(lintError: LintError): FormatDecision + fun autocorrectDecision(lintError: LintError): AutocorrectDecision } /** @@ -18,7 +18,7 @@ internal sealed interface AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object NoneAutocorrectHandler : AutocorrectHandler { - override fun autocorrect(lintError: LintError) = NO_AUTOCORRECT + override fun autocorrectDecision(lintError: LintError) = NO_AUTOCORRECT } /** @@ -26,7 +26,7 @@ internal data object NoneAutocorrectHandler : AutocorrectHandler { * [RuleAutocorrectApproveHandler]. */ internal data object AllAutocorrectHandler : AutocorrectHandler { - override fun autocorrect(lintError: LintError) = ALLOW_AUTOCORRECT + override fun autocorrectDecision(lintError: LintError) = ALLOW_AUTOCORRECT } /** @@ -36,7 +36,7 @@ internal data object AllAutocorrectHandler : AutocorrectHandler { */ internal class LintErrorAutocorrectHandler( val autocorrectRuleWithoutAutocorrectApproveHandler: Boolean, - private val callback: (LintError) -> FormatDecision, + private val callback: (LintError) -> AutocorrectDecision, ) : AutocorrectHandler { - override fun autocorrect(lintError: LintError) = callback(lintError) + override fun autocorrectDecision(lintError: LintError) = callback(lintError) } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index a268bdc481..6fe68c2dec 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -3,8 +3,9 @@ package com.pinterest.ktlint.rule.engine.internal import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.FormatDecision.ALLOW_AUTOCORRECT import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOCORRECT +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY import io.github.oshai.kotlinlogging.KotlinLogging @@ -99,7 +100,7 @@ internal class CodeFormatter( hasErrorsWhichCanBeAutocorrected = true } // No need to ask for approval in lint mode - false + NO_AUTOCORRECT } } } @@ -115,36 +116,34 @@ internal class CodeFormatter( executeRule(rule, autocorrectHandler) { offset, errorMessage, canBeAutoCorrected -> val (line, col) = positionInTextLocator(offset) val lintError = LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - val autocorrect = - autocorrectHandler - // The autocorrect is always to be called (even when the error can not be autocorrected) as it informs the API Consumer - // about the LintError. - // TODO: rename... "LintErrorHandler.emit" and let this function return an enum value as ALLOW_AUTOCORRECT and - // NO_AUTOCORRECT - .autocorrect(lintError) - .let { autocorrectDecision -> - // Ignore decision of the API Consumer in case the error can not be autocorrected - autocorrectDecision == ALLOW_AUTOCORRECT && canBeAutoCorrected - } - if (autocorrect) { - /* - * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might - * have changed. - */ - rebuildSuppressionLocator() - } - errors.add( - Pair( - lintError, - // It is assumed that a rule that asks for autocorrect approval, also does correct the error. - autocorrect, - ), - ) // In trace mode report the violation immediately. The order in which violations are actually found might be // different from the order in which they are reported. For debugging purposes it can be helpful to know the // exact order in which violations are being solved. LOGGER.trace { "Format violation: ${lintError.logMessage(code)}" } - autocorrect + + // Always request the autocorrectDecision, even in case it is already known that the LintError can not be autocorrected. In + // this way the API Consumer can still use data from the LintError for other purposes. + autocorrectHandler + .autocorrectDecision(lintError) + .also { autocorrectDecision -> + // Ignore decision of the API Consumer in case the error can not be autocorrected + val autocorrect = autocorrectDecision == ALLOW_AUTOCORRECT && canBeAutoCorrected + if (autocorrect) { + /* + * Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might + * have changed. + */ + rebuildSuppressionLocator() + } + // TODO: There is no need to collect the errors anymore as the callback containing the lintError is already executed + errors.add( + Pair( + lintError, + // It is assumed that a rule that asks for autocorrect approval, also does correct the error. + autocorrect, + ), + ) + } } return errors } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt index 0c10a71045..1274075dd1 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt @@ -4,6 +4,7 @@ import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import io.github.oshai.kotlinlogging.KotlinLogging @@ -49,7 +50,7 @@ internal class CodeLinter( LOGGER.trace { "Lint violation: ${lintError.logMessage(code)}" } } // No need to ask for approval in lint mode - false + AutocorrectDecision.NO_AUTOCORRECT } return errors } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 0585c32943..711ddad12c 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -6,6 +6,7 @@ import com.pinterest.ktlint.rule.engine.api.KtLintParseException import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.Companion.UTF8_BOM import com.pinterest.ktlint.rule.engine.api.KtLintRuleException +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleProvider @@ -48,7 +49,7 @@ internal class RuleExecutionContext private constructor( fun executeRule( rule: Rule, autocorrectHandler: AutocorrectHandler, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { try { rule.startTraversalOfAST() @@ -82,7 +83,7 @@ internal class RuleExecutionContext private constructor( node: ASTNode, rule: Rule, autocorrectHandler: AutocorrectHandler, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { /** * The [suppressionLocator] can be changed during each visit of node when running [KtLintRuleEngine.format]. So a new handler is to @@ -127,7 +128,7 @@ internal class RuleExecutionContext private constructor( rule: Rule, autocorrectHandler: AutocorrectHandler, suppress: Boolean, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val autoCorrect = autocorrectHandler is AllAutocorrectHandler || @@ -161,7 +162,7 @@ internal class RuleExecutionContext private constructor( rule: Rule, autocorrectHandler: AutocorrectHandler, suppress: Boolean, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(rule is RuleAutocorrectApproveHandler) if (!suppress) { @@ -186,7 +187,7 @@ internal class RuleExecutionContext private constructor( // Simplify the emitAndApprove to an emit only lambda which can be used in the legacy (deprecated) functions @Deprecated(message = "Remove in Ktlint 2.0") - private fun ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean).onlyEmit() = + private fun ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision).onlyEmit() = { offset: Int, errorMessage: String, diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt index 32cfce8fee..78323788e8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE @@ -11,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty +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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -22,7 +24,6 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.utils.addToStdlib.ifTrue /** * The Kotlin Coding Conventions suggest to consider using a blank line after a multiline when-condition @@ -48,7 +49,7 @@ public class BlankLineBetweenWhenConditions : override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.WHEN) { visitWhenStatement(node, emitAndApprove) @@ -57,7 +58,7 @@ public class BlankLineBetweenWhenConditions : private fun visitWhenStatement( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val hasMultilineWhenCondition = node.hasAnyMultilineWhenCondition() if (hasMultilineWhenCondition && lineBreakAfterWhenCondition) { @@ -69,7 +70,7 @@ public class BlankLineBetweenWhenConditions : private fun addBlankLinesBetweenWhenConditions( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() @@ -86,7 +87,7 @@ public class BlankLineBetweenWhenConditions : "Add a blank line between all when-condition in case at least one multiline when-condition is found in the " + "statement", true, - ).ifTrue { + ).ifAutocorrectAllowed { whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent()}") } } @@ -108,7 +109,7 @@ public class BlankLineBetweenWhenConditions : private fun removeBlankLinesBetweenWhenConditions( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() @@ -124,7 +125,7 @@ public class BlankLineBetweenWhenConditions : whitespaceBeforeWhenEntry.startOffset + 1, "Unexpected blank lines between when-condition if all when-conditions are single lines", true, - ).ifTrue { + ).ifAutocorrectAllowed { whitespaceBeforeWhenEntry.upsertWhitespaceBeforeMe("\n${whenEntry.indent(includeNewline = false)}") } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt index 0f2f5bb398..2e1a49debb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt @@ -1,6 +1,7 @@ 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.ElementType.ANNOTATED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -98,6 +99,7 @@ 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.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.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment @@ -123,7 +125,6 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.psiUtil.leaves import org.jetbrains.kotlin.psi.psiUtil.parents -import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import java.util.Deque import java.util.LinkedList @@ -184,7 +185,7 @@ public class IndentationRule : override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isRoot()) { // File should not start with a whitespace @@ -193,7 +194,7 @@ public class IndentationRule : ?.takeIf { it.isWhiteSpaceWithoutNewline() } ?.let { whitespaceWithoutNewline -> emitAndApprove(node.startOffset, "Unexpected indentation", true) - .ifTrue { whitespaceWithoutNewline.treeParent.removeChild(whitespaceWithoutNewline) } + .ifAutocorrectAllowed { whitespaceWithoutNewline.treeParent.removeChild(whitespaceWithoutNewline) } } indentContextStack.addLast(startNoIndentZone(node)) } @@ -1036,7 +1037,7 @@ public class IndentationRule : override fun afterVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { while (indentContextStack.peekLast()?.toASTNode == node) { LOGGER.trace { @@ -1069,7 +1070,7 @@ public class IndentationRule : private fun visitNewLineIndentation( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.ignoreIndent()) { return @@ -1086,7 +1087,7 @@ public class IndentationRule : node.startOffset + text.length - nodeIndent.length, "Unexpected indentation (${normalizedNodeIndent.length}) (should be ${expectedIndentation.length})", true, - ) + ) == AutocorrectDecision.ALLOW_AUTOCORRECT } else { // Indentation was at correct level but contained invalid indent characters. This violation has already // been emitted. @@ -1154,7 +1155,7 @@ public class IndentationRule : } private fun ASTNode.normalizedIndent( - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): String { val nodeIndent = text.substringAfterLast("\n") return when (indentConfig.indentStyle) { @@ -1164,7 +1165,7 @@ public class IndentationRule : startOffset + text.length - nodeIndent.length, "Unexpected tab character(s)", true, - ).ifTrue { + ).ifAutocorrectAllowed { // Ignore approval and fix invalid indent character always indentConfig.toNormalizedIndent(nodeIndent) } ?: nodeIndent @@ -1181,7 +1182,7 @@ public class IndentationRule : startOffset + text.length - nodeIndent.length, "Unexpected space character(s)", true, - ).ifTrue { + ).ifAutocorrectAllowed { indentConfig .toNormalizedIndent(nodeIndentWithoutAcceptableTrailingSpaces) .plus(acceptableTrailingSpaces) @@ -1195,7 +1196,7 @@ public class IndentationRule : private fun visitWhiteSpaceBeforeClosingQuote( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (!this::stringTemplateIndenter.isInitialized) { stringTemplateIndenter = StringTemplateIndenter(codeStyle, indentConfig) @@ -1338,7 +1339,7 @@ private class StringTemplateIndenter( fun visitClosingQuotes( expectedIndent: String, node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Boolean, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == STRING_TEMPLATE) node @@ -1398,7 +1399,7 @@ private class StringTemplateIndenter( // contains the closing quotes. See 'string-template-indent` rule for fixing the content of the string // template itself emitAndApprove(it.startOffset, "Unexpected indent of multiline string closing quotes", true) - .ifTrue { + .ifAutocorrectAllowed { if (it.firstChildNode == null) { (it as LeafPsiElement).rawInsertBeforeMe( LeafPsiElement(REGULAR_STRING_PART, correctedExpectedIndent), From c8c9f518c1b79d0dcff0c6764a9f13508da63543 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 25 May 2024 17:47:42 +0200 Subject: [PATCH 13/33] Update ktlint-api-consumer --- .../example/ktlint/api/consumer/rules/NoVarRule.kt | 14 ++++++++++---- .../ktlint/rule/engine/internal/CodeFormatter.kt | 1 - 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt index 97ff82f22c..549a497cda 100644 --- a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt +++ b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt @@ -1,7 +1,9 @@ package com.example.ktlint.api.consumer.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -9,14 +11,18 @@ public class NoVarRule : Rule( ruleId = RuleId("$CUSTOM_RULE_SET_ID:no-var"), about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.VAR_KEYWORD) { - emit(node.startOffset, "Unexpected var, use val instead", false) + emitAndApprove(node.startOffset, "Unexpected var, use val instead", false) + // In case that LintError can be autocorrected, use syntax below + // .ifAutocorrectAllowed { + // // Fix + // } } } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 6fe68c2dec..c61280af55 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -135,7 +135,6 @@ internal class CodeFormatter( */ rebuildSuppressionLocator() } - // TODO: There is no need to collect the errors anymore as the callback containing the lintError is already executed errors.add( Pair( lintError, From 913d259206dc4457903a370f063d8461427b0ee2 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 25 May 2024 21:05:32 +0200 Subject: [PATCH 14/33] Fix all rules --- .../ktlint/api/consumer/rules/NoVarRule.kt | 4 +- .../api/consumer/KtLintRuleEngineTest.kt | 4 +- .../engine/core/api/AutocorrectDecision.kt | 4 +- .../core/api/RuleAutocorrectApproveHandler.kt | 18 +- .../engine/core/api/ASTNodeExtensionTest.kt | 8 +- .../rule/engine/api/KtLintRuleEngine.kt | 2 +- .../engine/internal/rules/InternalRule.kt | 4 +- .../internal/rules/KtlintSuppressionRule.kt | 63 +++--- .../rule/engine/api/DisabledRulesTest.kt | 8 +- .../ktlint/rule/engine/api/KtLintTest.kt | 38 ++-- .../engine/internal/RuleProviderSorterTest.kt | 8 +- .../internal/SuppressionLocatorBuilderTest.kt | 8 +- .../InternalRuleProvidersFilterTest.kt | 8 +- .../rulefilter/RunAfterRuleFilterTest.kt | 8 +- .../ktlint/ruleset/standard/StandardRule.kt | 4 +- .../ruleset/standard/rules/AnnotationRule.kt | 101 ++++----- .../standard/rules/AnnotationSpacingRule.kt | 59 +++--- .../rules/ArgumentListWrappingRule.kt | 36 ++-- .../rules/BackingPropertyNamingRule.kt | 8 +- .../rules/BinaryExpressionWrappingRule.kt | 47 ++--- .../rules/BlankLineBeforeDeclarationRule.kt | 16 +- .../rules/BlankLineBetweenWhenConditions.kt | 4 +- .../BlockCommentInitialStarAlignmentRule.kt | 9 +- .../rules/ChainMethodContinuationRule.kt | 53 +++-- .../standard/rules/ChainWrappingRule.kt | 106 +++++----- .../ruleset/standard/rules/ClassNamingRule.kt | 4 +- .../standard/rules/ClassSignatureRule.kt | 195 ++++++++--------- .../standard/rules/CommentSpacingRule.kt | 17 +- .../standard/rules/CommentWrappingRule.kt | 11 +- .../standard/rules/ConditionWrappingRule.kt | 17 +- .../rules/ContextReceiverWrappingRule.kt | 39 ++-- .../rules/DiscouragedCommentLocationRule.kt | 6 +- .../standard/rules/EnumEntryNameCaseRule.kt | 4 +- .../standard/rules/EnumWrappingRule.kt | 59 +++--- .../ruleset/standard/rules/FilenameRule.kt | 12 +- .../standard/rules/FinalNewlineRule.kt | 17 +- .../standard/rules/FunKeywordSpacingRule.kt | 8 +- .../rules/FunctionExpressionBodyRule.kt | 64 +++--- .../standard/rules/FunctionLiteralRule.kt | 122 +++++------ .../standard/rules/FunctionNamingRule.kt | 4 +- .../rules/FunctionReturnTypeSpacingRule.kt | 27 ++- .../standard/rules/FunctionSignatureRule.kt | 128 +++++------ .../rules/FunctionStartOfBodySpacingRule.kt | 50 ++--- .../rules/FunctionTypeModifierSpacingRule.kt | 11 +- .../rules/FunctionTypeReferenceSpacingRule.kt | 23 +- .../standard/rules/IfElseBracingRule.kt | 20 +- .../standard/rules/IfElseWrappingRule.kt | 34 ++- .../standard/rules/ImportOrderingRule.kt | 14 +- .../ruleset/standard/rules/IndentationRule.kt | 10 +- .../ktlint/ruleset/standard/rules/KdocRule.kt | 5 +- .../standard/rules/KdocWrappingRule.kt | 11 +- .../standard/rules/MaxLineLengthRule.kt | 4 +- .../rules/MixedConditionOperatorsRule.kt | 6 +- .../standard/rules/ModifierListSpacingRule.kt | 35 ++-- .../standard/rules/ModifierOrderRule.kt | 13 +- .../standard/rules/MultiLineIfElseRule.kt | 11 +- .../rules/MultilineExpressionWrappingRule.kt | 82 ++++---- .../standard/rules/MultilineLoopRule.kt | 12 +- .../rules/NoBlankLineBeforeRbraceRule.kt | 8 +- .../standard/rules/NoBlankLineInListRule.kt | 15 +- .../NoBlankLinesInChainedMethodCallsRule.kt | 12 +- .../rules/NoConsecutiveBlankLinesRule.kt | 25 +-- .../rules/NoConsecutiveCommentsRule.kt | 4 +- .../standard/rules/NoEmptyClassBodyRule.kt | 21 +- .../ruleset/standard/rules/NoEmptyFileRule.kt | 4 +- .../rules/NoEmptyFirstLineInClassBodyRule.kt | 8 +- .../NoEmptyFirstLineInMethodBlockRule.kt | 8 +- .../rules/NoLineBreakAfterElseRule.kt | 11 +- .../rules/NoLineBreakBeforeAssignmentRule.kt | 56 ++--- .../standard/rules/NoMultipleSpacesRule.kt | 13 +- .../standard/rules/NoSemicolonsRule.kt | 23 +- .../rules/NoSingleLineBlockCommentRule.kt | 11 +- .../standard/rules/NoTrailingSpacesRule.kt | 19 +- .../standard/rules/NoUnitReturnRule.kt | 17 +- .../standard/rules/NoUnusedImportsRule.kt | 86 ++++---- .../standard/rules/NoWildcardImportsRule.kt | 4 +- .../standard/rules/NullableTypeSpacingRule.kt | 11 +- .../ruleset/standard/rules/PackageNameRule.kt | 5 +- .../rules/ParameterListSpacingRule.kt | 94 ++++----- .../rules/ParameterListWrappingRule.kt | 52 +++-- .../standard/rules/ParameterWrappingRule.kt | 44 ++-- .../standard/rules/PropertyNamingRule.kt | 10 +- .../standard/rules/PropertyWrappingRule.kt | 44 ++-- .../rules/SpacingAroundAngleBracketsRule.kt | 41 ++-- .../standard/rules/SpacingAroundColonRule.kt | 198 +++++++++--------- .../standard/rules/SpacingAroundCommaRule.kt | 39 ++-- .../standard/rules/SpacingAroundCurlyRule.kt | 147 ++++++------- .../standard/rules/SpacingAroundDotRule.kt | 17 +- .../rules/SpacingAroundDoubleColonRule.kt | 25 +-- .../rules/SpacingAroundKeywordRule.kt | 23 +- .../rules/SpacingAroundOperatorsRule.kt | 25 +-- .../standard/rules/SpacingAroundParensRule.kt | 25 +-- .../rules/SpacingAroundRangeOperatorRule.kt | 25 +-- .../rules/SpacingAroundSquareBracketsRule.kt | 25 +-- .../rules/SpacingAroundUnaryOperatorRule.kt | 12 +- ...gBetweenDeclarationsWithAnnotationsRule.kt | 13 +- ...cingBetweenDeclarationsWithCommentsRule.kt | 8 +- ...enFunctionNameAndOpeningParenthesisRule.kt | 11 +- .../standard/rules/StatementWrappingRule.kt | 43 ++-- .../rules/StringTemplateIndentRule.kt | 69 +++--- .../standard/rules/StringTemplateRule.kt | 14 +- .../rules/TrailingCommaOnCallSiteRule.kt | 41 ++-- .../TrailingCommaOnDeclarationSiteRule.kt | 57 ++--- .../rules/TryCatchFinallySpacingRule.kt | 33 ++- .../standard/rules/TypeArgumentCommentRule.kt | 4 +- .../rules/TypeArgumentListSpacingRule.kt | 43 ++-- .../rules/TypeParameterCommentRule.kt | 4 +- .../rules/TypeParameterListSpacingRule.kt | 74 +++---- ...saryParenthesesBeforeTrailingLambdaRule.kt | 8 +- .../rules/ValueArgumentCommentRule.kt | 4 +- .../rules/ValueParameterCommentRule.kt | 4 +- .../ruleset/standard/rules/WrappingRule.kt | 110 +++++----- .../SpacingAroundAngleBracketsRuleTest.kt | 12 ++ .../src/main/kotlin/yourpkgname/NoVarRule.kt | 8 +- 114 files changed, 1699 insertions(+), 1771 deletions(-) diff --git a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt index 549a497cda..4f198fbcfa 100644 --- a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt +++ b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt @@ -15,10 +15,10 @@ public class NoVarRule : RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.VAR_KEYWORD) { - emitAndApprove(node.startOffset, "Unexpected var, use val instead", false) + emit(node.startOffset, "Unexpected var, use val instead", false) // In case that LintError can be autocorrected, use syntax below // .ifAutocorrectAllowed { // // Fix diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 8841406775..4d52be3aef 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -735,10 +735,10 @@ class KtLintRuleEngineTest { Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.EOL_COMMENT && node.text == "// bar") { - emitAndApprove(node.startOffset, "Bar comment with autocorrect approve handler", true) + emit(node.startOffset, "Bar comment with autocorrect approve handler", true) .ifAutocorrectAllowed { node .safeAs() diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt index 7a5ffb7d25..b39ef9a3e3 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/AutocorrectDecision.kt @@ -4,12 +4,12 @@ import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOC public enum class AutocorrectDecision { /** - * Autocorrect the [LintError] if supported by the rule. + * Autocorrect lint violation if supported by the [Rule]. */ ALLOW_AUTOCORRECT, /** - * Do not autocorrect the [LintError] even when this is supported by the rule. + * Do not autocorrect lint violation even when this is supported by the [Rule]. */ NO_AUTOCORRECT, } diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt index 25bc92a3f7..1a73ca3e6b 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler.kt @@ -18,16 +18,16 @@ public interface RuleAutocorrectApproveHandler { * child nodes resulting in a depth first traversal of the AST. * * When a rule overrides this method, the API Consumer can decide per violation whether the violation needs to be autocorrected. For - * this the [emitAndApprove] function is called, and its result can be used to determine whether the violation is to be corrected. In - * lint mode the [emitAndApprove] should always return false. + * this the [emit] function is called, and its result can be used to determine whether the violation is to be corrected. In + * lint mode the [emit] should always return false. * * @param node AST node - * @param emitAndApprove a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation + * @param emit a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation * if that is supported by the rule */ public fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { } @@ -35,17 +35,17 @@ public interface RuleAutocorrectApproveHandler { * This method is called on a node in AST after all its child nodes have been visited. * * When a rule overrides this method, the API Consumer can decide per violation whether the violation needs to be autocorrected. For - * this the [emitAndApprove] function is called, and its result can be used to determine whether the violation is to be corrected. In - * lint mode the [emitAndApprove] should always return false. + * this the [emit] function is called, and its result can be used to determine whether the violation is to be corrected. In + * lint mode the [emit] should always return false. * * @param node AST node - * @param emitAndApprove a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation + * @param emit a way for rule to notify about a violation (lint error) and get approval to actually autocorrect the violation * if that is supported by the rule */ @Suppress("unused") - public open fun afterVisitChildNodes( + public fun afterVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { } } diff --git a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt index 657fa0f6ff..cf0f753f3f 100644 --- a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt +++ b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt @@ -989,7 +989,7 @@ class ASTNodeExtensionTest { .text // The newline (and indentation spaces) before the word "comment" inside the block comment is entirely ignored. As there are no - // whitespace nodes containing a newline, this piece of code is considered to be a oneliner starting with the word "val". + // whitespace nodes containing a newline, this piece of code is considered to be an oneliner starting with the word "val". assertThat(actual).contains("val") } @@ -1075,11 +1075,11 @@ class ASTNodeExtensionTest { ) : Rule( ruleId = RuleId("test:dummy-rule"), about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { block(node) } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 22decad924..62b739aa13 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -180,7 +180,7 @@ public class KtLintRuleEngine( /** * Reloads an '.editorconfig' file given that it is currently loaded into the KtLint cache. This method is intended * to be called by the API consumer when it is aware of changes in the '.editorconfig' file that should be taken - * into account with next calls to [lint] and/or [formatBatch]. See [editorConfigFilePaths] to get the list of + * into account with next calls to [lint] and/or [format]. See [editorConfigFilePaths] to get the list of * '.editorconfig' files which need to be observed. */ public fun reloadEditorConfigFile(path: Path) { diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt index cfc4f12f1d..5ce5d86fae 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt @@ -1,6 +1,7 @@ package com.pinterest.ktlint.rule.engine.internal.rules import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty @@ -23,4 +24,5 @@ public open class InternalRule internal constructor( visitorModifiers = visitorModifiers, usesEditorConfigProperties = usesEditorConfigProperties, about = INTERNAL_RULE_ABOUT, - ) + ), + RuleAutocorrectApproveHandler diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt index a159a21e70..d0fa821694 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.rule.engine.internal.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -8,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -70,16 +72,15 @@ public class KtlintSuppressionRule( override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { isKtlintRuleSuppressionInAnnotation(it) } - ?.let { visitKtlintSuppressionInAnnotation(node, autoCorrect, emit) } + ?.let { visitKtlintSuppressionInAnnotation(node, emit) } node .ktlintDirectiveOrNull(ruleIdValidator) - ?.visitKtlintDirective(autoCorrect, emit) + ?.visitKtlintDirective(emit) } private fun isKtlintRuleSuppressionInAnnotation(node: ASTNode) = @@ -97,8 +98,7 @@ public class KtlintSuppressionRule( private fun visitKtlintSuppressionInAnnotation( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .psi @@ -113,13 +113,14 @@ public class KtlintSuppressionRule( val offset = literalStringTemplateEntry.startOffset if (prefixedSuppression.isUnknownKtlintSuppression()) { emit(offset, "Ktlint rule with id '$prefixedSuppression' is unknown or not loaded", false) + Unit } else if (prefixedSuppression != literalStringTemplateEntry.text) { emit(offset, "Identifier to suppress ktlint rule must be fully qualified with the rule set id", true) - if (autoCorrect) { - node - .createLiteralStringTemplateEntry(prefixedSuppression) - ?.let { literalStringTemplateEntry.replaceWith(it) } - } + .ifAutocorrectAllowed { + node + .createLiteralStringTemplateEntry(prefixedSuppression) + ?.let { literalStringTemplateEntry.replaceWith(it) } + } } } } @@ -145,38 +146,35 @@ public class KtlintSuppressionRule( ?.node private fun KtLintDirective.visitKtlintDirective( - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (ktlintDirectiveType) { KTLINT_DISABLE -> { if (node.elementType == EOL_COMMENT && node.prevLeaf().isWhiteSpaceWithNewline()) { - removeDanglingEolCommentWithKtlintDisableDirective(autoCorrect, emit) + removeDanglingEolCommentWithKtlintDisableDirective(emit) } else { - visitKtlintDisableDirective(autoCorrect, emit) + visitKtlintDisableDirective(emit) } } KTLINT_ENABLE -> { - removeKtlintEnableDirective(autoCorrect, emit) + removeKtlintEnableDirective(emit) } } } private fun KtLintDirective.removeDanglingEolCommentWithKtlintDisableDirective( - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(offset, "Directive 'ktlint-disable' in EOL comment is ignored as it is not preceded by a code element", true) - if (autoCorrect) { - node.removePrecedingWhitespace() - node.remove() - } + .ifAutocorrectAllowed { + node.removePrecedingWhitespace() + node.remove() + } } private fun KtLintDirective.visitKtlintDisableDirective( - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == BLOCK_COMMENT && hasNoMatchingKtlintEnableDirective(ruleIdValidator)) { emit( @@ -187,7 +185,7 @@ public class KtlintSuppressionRule( ) return } - emit(offset, "Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation", true) + val autocorrectDecision = emit(offset, "Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation", true) suppressionIdChanges .filterIsInstance() .forEach { ktlintDirectiveChange -> @@ -199,7 +197,7 @@ public class KtlintSuppressionRule( false, ) } - if (autoCorrect) { + autocorrectDecision.ifAutocorrectAllowed { val suppressionIds = suppressionIdChanges .filterIsInstance() @@ -225,14 +223,13 @@ public class KtlintSuppressionRule( } private fun KtLintDirective.removeKtlintEnableDirective( - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(offset, "Directive 'ktlint-enable' is obsolete after migrating to suppress annotations", true) - if (autoCorrect) { - node.removePrecedingWhitespace() - node.remove() - } + .ifAutocorrectAllowed { + node.removePrecedingWhitespace() + node.remove() + } } private fun String.isUnknownKtlintSuppression(): Boolean = @@ -298,7 +295,6 @@ private class KtLintDirective( private fun ASTNode.surroundsMultipleListElements(): Boolean { require(ktlintDirectiveType == KTLINT_DISABLE && elementType == BLOCK_COMMENT) return if (treeParent.elementType in listTypeTokenSet) { - val firstElementAfterEnableDirective = nextSibling { it.elementType in listElementTypeTokenSet } findMatchingKtlintEnableDirective(ruleIdValidator) ?.siblings(false) ?.takeWhile { it != this } @@ -389,6 +385,7 @@ private fun String.toKtlintDirectiveTypeOrNull() = private fun String.toSuppressionIdChanges(ruleIdValidator: (String) -> Boolean) = trim() .split(" ") + .asSequence() .map { it.trim() } .filter { it.isNotBlank() } .map { it.qualifiedRuleIdString() } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/DisabledRulesTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/DisabledRulesTest.kt index f5c4d0014b..3825356c07 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/DisabledRulesTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/DisabledRulesTest.kt @@ -1,6 +1,8 @@ package com.pinterest.ktlint.rule.engine.api +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution @@ -61,11 +63,11 @@ class DisabledRulesTest { ) : Rule( ruleId = ruleId, about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(node.startOffset, SOME_NO_VAR_RULE_VIOLATION, false) } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 1fbc943bf1..96063b4ac6 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -12,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.api.RuleExecutionCall.RuleMethod.BEFORE_ import com.pinterest.ktlint.rule.engine.api.RuleExecutionCall.RuleMethod.BEFORE_FIRST import com.pinterest.ktlint.rule.engine.api.RuleExecutionCall.VisitNodeType.CHILD import com.pinterest.ktlint.rule.engine.api.RuleExecutionCall.VisitNodeType.ROOT +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER @@ -20,9 +21,11 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.PACKAGE_DIRECTIVE import com.pinterest.ktlint.rule.engine.core.api.ElementType.REGULAR_STRING_PART import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAsLateAsPossible +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isRoot import org.assertj.core.api.Assertions.assertThat import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -475,11 +478,11 @@ private open class DummyRule( ) : Rule( ruleId = RuleId("test:dummy"), about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { block(node) } @@ -492,19 +495,19 @@ private class AutoCorrectErrorRule : Rule( ruleId = AUTOCORRECT_ERROR_RULE_ID, about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == REGULAR_STRING_PART) { when (node.text) { STRING_VALUE_TO_BE_AUTOCORRECTED -> { emit(node.startOffset, ERROR_MESSAGE_CAN_BE_AUTOCORRECTED, true) - if (autoCorrect) { - (node as LeafElement).rawReplaceWithText(STRING_VALUE_AFTER_AUTOCORRECT) - } + .ifAutocorrectAllowed { + (node as LeafElement).rawReplaceWithText(STRING_VALUE_AFTER_AUTOCORRECT) + } } STRING_VALUE_NOT_TO_BE_CORRECTED -> @@ -541,7 +544,8 @@ private class SimpleTestRule( ruleId = ruleId, about = About(), visitorModifiers, - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeFirstNode(editorConfig: EditorConfig) { ruleExecutionCalls.add(RuleExecutionCall(ruleId, BEFORE_FIRST)) if (stopTraversalInBeforeFirstNode) { @@ -551,8 +555,7 @@ private class SimpleTestRule( override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { ruleExecutionCalls.add(node.toRuleExecutionCall(ruleId, BEFORE_CHILDREN)) if (stopTraversalInBeforeVisitChildNodes(node)) { @@ -562,8 +565,7 @@ private class SimpleTestRule( override fun afterVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { ruleExecutionCalls.add(node.toRuleExecutionCall(ruleId, AFTER_CHILDREN)) if (stopTraversalInAfterVisitChildNodes(node)) { @@ -609,7 +611,7 @@ private data class RuleExecutionCall( val elementType: IElementType? = null, val classIdentifier: String? = null, ) { - enum class RuleMethod { BEFORE_FIRST, BEFORE_CHILDREN, VISIT, AFTER_CHILDREN, AFTER_LAST } + enum class RuleMethod { BEFORE_FIRST, BEFORE_CHILDREN, AFTER_CHILDREN, AFTER_LAST } enum class VisitNodeType { ROOT, CHILD } } @@ -643,7 +645,8 @@ private class WithStateRule : Rule( ruleId = RuleId("test:with-state"), about = About(), - ) { + ), + RuleAutocorrectApproveHandler { private var hasNotBeenVisitedYet = true override fun beforeFirstNode(editorConfig: EditorConfig) { @@ -655,8 +658,7 @@ private class WithStateRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(node.startOffset, "Fake violation which can be autocorrected", true) } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt index acf18b5d66..c73aad4689 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt @@ -1,8 +1,10 @@ package com.pinterest.ktlint.rule.engine.internal +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId @@ -459,13 +461,13 @@ class RuleProviderSorterTest { ruleId = ruleId, about = About(), visitorModifiers, - ) { + ), + RuleAutocorrectApproveHandler { constructor(ruleId: RuleId, visitorModifier: VisitorModifier) : this(ruleId, setOf(visitorModifier)) override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Unit = throw UnsupportedOperationException( "Rule should never be really invoked because that is not the aim of this unit test.", diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt index e55951e3bf..17fd03f6b1 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt @@ -6,8 +6,10 @@ import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.EMPTY import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.plus import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId @@ -374,11 +376,11 @@ class SuppressionLocatorBuilderTest { ) : Rule( ruleId = id, about = About(), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.IDENTIFIER && node.text.startsWith("foo")) { emit(node.startOffset, "Line should not contain a foo identifier", false) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt index 2a6ce8388c..a740cebfcd 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt @@ -1,7 +1,9 @@ package com.pinterest.ktlint.rule.engine.internal.rulefilter import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId @@ -46,11 +48,11 @@ class InternalRuleProvidersFilterTest { ruleId = ruleId, about = About(), visitorModifiers, - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Unit = throw UnsupportedOperationException( "Rule should never be really invoked because that is not the aim of this unit test.", diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt index 60b9fc7687..78b5b9ba4e 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt @@ -1,8 +1,10 @@ package com.pinterest.ktlint.rule.engine.internal.rulefilter +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId @@ -374,13 +376,13 @@ class RunAfterRuleFilterTest { ruleId = ruleId, about = About(), visitorModifiers, - ) { + ), + RuleAutocorrectApproveHandler { constructor(ruleId: RuleId, visitorModifier: VisitorModifier) : this(ruleId, setOf(visitorModifier)) override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Unit = throw UnsupportedOperationException( "Rule should never be really invoked because that is not the aim of this unit test.", diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt index 19b68659ba..77ad24ca56 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt @@ -1,6 +1,7 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleSetId import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty @@ -25,4 +26,5 @@ public open class StandardRule internal constructor( visitorModifiers = visitorModifiers, usesEditorConfigProperties = usesEditorConfigProperties, about = STANDARD_RULE_ABOUT, - ) + ), + RuleAutocorrectApproveHandler diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt index 2bdabde834..2ce641b009 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -29,6 +30,7 @@ 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.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.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -110,39 +112,37 @@ public class AnnotationRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { FILE_ANNOTATION_LIST -> { - visitAnnotationList(node, emit, autoCorrect) - visitFileAnnotationList(node, emit, autoCorrect) + visitAnnotationList(node, emit) + visitFileAnnotationList(node, emit) } ANNOTATED_EXPRESSION, MODIFIER_LIST -> { - visitAnnotationList(node, emit, autoCorrect) + visitAnnotationList(node, emit) } ANNOTATION -> { // Annotation array // @[...] - visitAnnotation(node, emit, autoCorrect) + visitAnnotation(node, emit) } ANNOTATION_ENTRY -> { - visitAnnotationEntry(node, emit, autoCorrect) + visitAnnotationEntry(node, emit) } TYPE_ARGUMENT_LIST -> { - visitTypeArgumentList(node, emit, autoCorrect) + visitTypeArgumentList(node, emit) } } } private fun visitAnnotationList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType in ANNOTATION_CONTAINER) @@ -182,14 +182,14 @@ public class AnnotationRule : // wrapped if (!prevLeaf.textContains('\n')) { emit(prevLeaf.startOffset, "Expected newline before annotation", true) - if (autoCorrect) { - prevLeaf.upsertWhitespaceBeforeMe( - prevLeaf - .text - .substringBeforeLast('\n', "") - .plus(expectedIndent), - ) - } + .ifAutocorrectAllowed { + prevLeaf.upsertWhitespaceBeforeMe( + prevLeaf + .text + .substringBeforeLast('\n', "") + .plus(expectedIndent), + ) + } } } } @@ -204,9 +204,9 @@ public class AnnotationRule : // Let the indentation rule determine the exact indentation and only report and fix when the line needs to be wrapped if (!prevLeaf.textContains('\n')) { emit(prevLeaf.startOffset, "Expected newline after last annotation", true) - if (autoCorrect) { - prevLeaf.upsertWhitespaceAfterMe(expectedIndent) - } + .ifAutocorrectAllowed { + prevLeaf.upsertWhitespaceAfterMe(expectedIndent) + } } } @@ -219,9 +219,9 @@ public class AnnotationRule : // Let the indentation rule determine the exact indentation and only report and fix when the line needs to be wrapped if (!leaf.textContains('\n')) { emit(leaf.startOffset, "Expected newline", true) - if (autoCorrect) { - leaf.upsertWhitespaceBeforeMe(node.indent()) - } + .ifAutocorrectAllowed { + leaf.upsertWhitespaceBeforeMe(node.indent()) + } } } } @@ -269,8 +269,7 @@ public class AnnotationRule : private fun visitTypeArgumentList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() @@ -280,13 +279,12 @@ public class AnnotationRule : .mapNotNull { it.findChildByType(MODIFIER_LIST) } .filter { it.elementType == MODIFIER_LIST } .any { it.shouldWrapAnnotations() } - .ifTrue { wrapTypeArgumentList(node, emit, autoCorrect) } + .ifTrue { wrapTypeArgumentList(node, emit) } } private fun wrapTypeArgumentList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() @@ -295,9 +293,9 @@ public class AnnotationRule : val prevLeaf = typeProjection.prevLeaf().takeIf { it.isWhiteSpace() } if (prevLeaf == null || prevLeaf.isWhiteSpaceWithoutNewline()) { emit(typeProjection.startOffset - 1, "Expected newline", true) - if (autoCorrect) { - typeProjection.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) - } + .ifAutocorrectAllowed { + typeProjection.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) + } } } @@ -307,17 +305,16 @@ public class AnnotationRule : val prevLeaf = gt.prevLeaf().takeIf { it.isWhiteSpace() } if (prevLeaf == null || prevLeaf.isWhiteSpaceWithoutNewline()) { emit(gt.startOffset, "Expected newline", true) - if (autoCorrect) { - gt.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) - } + .ifAutocorrectAllowed { + gt.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) + } } } } private fun visitAnnotationEntry( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == ANNOTATION_ENTRY) if (node.isPrecededByOtherAnnotationEntryOnTheSameLine() && node.isPrecededByAnnotationOnAnotherLine()) { @@ -329,8 +326,7 @@ public class AnnotationRule : node.startOffset, "All annotations should either be on a single line or all annotations should be on a separate line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node .firstChildLeafOrSelf() .upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) @@ -409,8 +405,7 @@ public class AnnotationRule : private fun visitFileAnnotationList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .lastChildLeafOrSelf() @@ -423,8 +418,7 @@ public class AnnotationRule : codeLeaf.startOffset, "File annotations should be separated from file contents with a blank line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { codeLeaf.upsertWhitespaceBeforeMe("\n\n") } } @@ -433,26 +427,21 @@ public class AnnotationRule : private fun visitAnnotation( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == ANNOTATION) if ((node.isFollowedByOtherAnnotationEntry() && node.isOnSameLineAsNextAnnotationEntry()) || (node.isPrecededByOtherAnnotationEntry() && node.isOnSameLineAsPreviousAnnotationEntry()) ) { - emit( - node.startOffset, - "@[...] style annotations should be on a separate line from other annotations.", - true, - ) - if (autoCorrect) { - if (node.isFollowedByOtherAnnotationEntry()) { - node.upsertWhitespaceAfterMe(getNewlineWithIndent(node.treeParent)) - } else if (node.isPrecededByOtherAnnotationEntry()) { - node.upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) + emit(node.startOffset, "@[...] style annotations should be on a separate line from other annotations.", true) + .ifAutocorrectAllowed { + if (node.isFollowedByOtherAnnotationEntry()) { + node.upsertWhitespaceAfterMe(getNewlineWithIndent(node.treeParent)) + } else if (node.isPrecededByOtherAnnotationEntry()) { + node.upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) + } } - } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt index 5e66a2ebe8..634bdc422d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -37,8 +39,7 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != ElementType.MODIFIER_LIST && node.elementType != ElementType.FILE_ANNOTATION_LIST) { return @@ -90,30 +91,30 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { if (node.elementType != ElementType.FILE_ANNOTATION_LIST && next.isPartOfComment()) { val psi = node.psi emit(psi.endOffset, ERROR_MESSAGE, true) - if (autoCorrect) { - // Special-case autocorrection when the annotation is separated from the annotated construct - // by a comment: we need to swap the order of the comment and the annotation - // Remove the annotation and the following whitespace - val eolComment = node.nextSibling { it.isCommentOnSameLineAsPrevLeaf() } - if (eolComment != null) { - eolComment.prevSibling { it.isWhiteSpace() }?.let { it.treeParent.removeChild(it) } - eolComment.nextSibling { it.isWhiteSpace() }?.let { it.treeParent.removeChild(it) } - eolComment.treeParent?.removeChild(eolComment) - } else { - node.nextSibling { it.isWhiteSpace() }?.let { it.treeParent?.removeChild(it) } - } - node.treeParent.removeChild(node) + .ifAutocorrectAllowed { + // Special-case autocorrection when the annotation is separated from the annotated construct + // by a comment: we need to swap the order of the comment and the annotation + // Remove the annotation and the following whitespace + val eolComment = node.nextSibling { it.isCommentOnSameLineAsPrevLeaf() } + if (eolComment != null) { + eolComment.prevSibling { it.isWhiteSpace() }?.let { it.treeParent.removeChild(it) } + eolComment.nextSibling { it.isWhiteSpace() }?.let { it.treeParent.removeChild(it) } + eolComment.treeParent?.removeChild(eolComment) + } else { + node.nextSibling { it.isWhiteSpace() }?.let { it.treeParent?.removeChild(it) } + } + node.treeParent.removeChild(node) - // Insert the annotation prior to the annotated construct - val beforeAnchor = next.nextCodeSibling() - val treeParent = next.treeParent - treeParent.addChild(node, beforeAnchor) - if (eolComment != null) { - treeParent.addChild(PsiWhiteSpaceImpl(" "), beforeAnchor) - treeParent.addChild(eolComment, beforeAnchor) + // Insert the annotation prior to the annotated construct + val beforeAnchor = next.nextCodeSibling() + val treeParent = next.treeParent + treeParent.addChild(node, beforeAnchor) + if (eolComment != null) { + treeParent.addChild(PsiWhiteSpaceImpl(" "), beforeAnchor) + treeParent.addChild(eolComment, beforeAnchor) + } + treeParent.addChild(PsiWhiteSpaceImpl("\n"), beforeAnchor) } - treeParent.addChild(PsiWhiteSpaceImpl("\n"), beforeAnchor) - } } } if (whiteSpaces.isNotEmpty() && node.elementType != ElementType.FILE_ANNOTATION_LIST) { @@ -121,10 +122,10 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { if (whiteSpaces.any { psi -> psi.textToCharArray().count { it == '\n' } > 1 }) { val psi = node.psi emit(psi.endOffset, ERROR_MESSAGE, true) - if (autoCorrect) { - removeIntraLineBreaks(node, annotations.last()) - removeExtraLineBreaks(node) - } + .ifAutocorrectAllowed { + removeIntraLineBreaks(node, annotations.last()) + removeExtraLineBreaks(node) + } } } } @@ -177,7 +178,7 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { last: KtAnnotationEntry, ) { val txt = node.text - // Pull the next before raw replace or it will blow up + // Pull the next before raw replace, or it will blow up val lNext = node.nextLeaf() if (node is PsiWhiteSpaceImpl) { if (txt.toCharArray().count { it == '\n' } > 1) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt index 7bb95cb356..ad47b8c1a0 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLLECTION_LITERAL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION @@ -24,6 +25,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProper 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -96,8 +98,7 @@ public class ArgumentListWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (editorConfigIndent.disabled) { return @@ -107,7 +108,7 @@ public class ArgumentListWrappingRule : if (needToWrapArgumentList(node)) { node .children() - .forEach { child -> wrapArgumentInList(child, emit, autoCorrect) } + .forEach { child -> wrapArgumentInList(child, emit) } } } } @@ -186,17 +187,16 @@ public class ArgumentListWrappingRule : private fun wrapArgumentInList( child: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (child.elementType) { LPAR -> { val prevLeaf = child.prevLeaf() if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) { emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - prevLeaf.delete() - } + .ifAutocorrectAllowed { + prevLeaf.delete() + } } } @@ -217,20 +217,20 @@ public class ArgumentListWrappingRule : } else { // The current child needs to be wrapped to a newline. emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - // The indentation is purely based on the previous leaf only. Note that in - // autoCorrect mode the indent rule, if enabled, runs after this rule and - // determines the final indentation. But if the indent rule is disabled then the - // indent of this rule is kept. - (prevLeaf as LeafPsiElement).rawReplaceWithText(intendedIndent) - } + .ifAutocorrectAllowed { + // The indentation is purely based on the previous leaf only. Note that in + // autoCorrect mode the indent rule, if enabled, runs after this rule and + // determines the final indentation. But if the indent rule is disabled then the + // indent of this rule is kept. + (prevLeaf as LeafPsiElement).rawReplaceWithText(intendedIndent) + } } } else { // Insert a new whitespace element in order to wrap the current child to a new line. emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - child.treeParent.addChild(PsiWhiteSpaceImpl(intendedIndent), child) - } + .ifAutocorrectAllowed { + child.treeParent.addChild(PsiWhiteSpaceImpl(intendedIndent), child) + } } // Indentation of child nodes need to be fixed by the IndentationRule. } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule.kt index 3b8e727a49..709f104dae 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.INTERNAL_KEYWORD @@ -38,8 +39,7 @@ public class BackingPropertyNamingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == PROPERTY } @@ -48,7 +48,7 @@ public class BackingPropertyNamingRule : private fun visitProperty( property: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { property .findChildByType(IDENTIFIER) @@ -60,7 +60,7 @@ public class BackingPropertyNamingRule : private fun visitBackingProperty( identifier: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { identifier .text diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRule.kt index 897eec3dce..523bc933cf 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONDITION @@ -26,6 +27,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -75,18 +77,16 @@ public class BinaryExpressionWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { - BINARY_EXPRESSION -> visitBinaryExpression(node, emit, autoCorrect) + BINARY_EXPRESSION -> visitBinaryExpression(node, emit) } } private fun visitBinaryExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == BINARY_EXPRESSION) @@ -104,8 +104,7 @@ public class BinaryExpressionWrappingRule : expression.startOffset, "Line is exceeding max line length. Break line between assignment and expression", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { expression.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(expression)) } } @@ -133,8 +132,7 @@ public class BinaryExpressionWrappingRule : expression.startOffset, "Line is exceeding max line length. Break line before expression", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { expression.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(expression)) } } @@ -145,13 +143,13 @@ public class BinaryExpressionWrappingRule : .firstOrNull { !it.isCodeLeaf() } ?.takeIf { it.elementType == CALL_EXPRESSION } ?.takeIf { it.causesMaxLineLengthToBeExceeded() } - ?.let { callExpression -> visitCallExpression(callExpression, emit, autoCorrect) } + ?.let { callExpression -> visitCallExpression(callExpression, emit) } // The remainder (operation reference plus right hand side) might still cause the max line length to be exceeded node .takeIf { node.lastChildNode.causesMaxLineLengthToBeExceeded() || node.isPartOfConditionExceedingMaxLineLength() } ?.findChildByType(OPERATION_REFERENCE) - ?.let { operationReference -> visitOperationReference(operationReference, emit, autoCorrect) } + ?.let { operationReference -> visitOperationReference(operationReference, emit) } } private fun ASTNode.isPartOfConditionExceedingMaxLineLength() = @@ -169,8 +167,7 @@ public class BinaryExpressionWrappingRule : private fun visitCallExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == CALL_EXPRESSION } @@ -186,17 +183,17 @@ public class BinaryExpressionWrappingRule : .findChildByType(LBRACE) ?.let { lbrace -> emit(lbrace.startOffset + 1, "Newline expected after '{'", true) - if (autoCorrect) { - lbrace.upsertWhitespaceAfterMe(indentConfig.childIndentOf(lbrace.treeParent)) - } + .ifAutocorrectAllowed { + lbrace.upsertWhitespaceAfterMe(indentConfig.childIndentOf(lbrace.treeParent)) + } } functionLiteral .findChildByType(RBRACE) ?.let { rbrace -> emit(rbrace.startOffset, "Newline expected before '}'", true) - if (autoCorrect) { - rbrace.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node.treeParent)) - } + .ifAutocorrectAllowed { + rbrace.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node.treeParent)) + } } } } @@ -204,8 +201,7 @@ public class BinaryExpressionWrappingRule : private fun visitOperationReference( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == OPERATION_REFERENCE } @@ -227,9 +223,9 @@ public class BinaryExpressionWrappingRule : ?.let { // Wrapping after the elvis operator leads to violating the 'chain-wrapping' rule, so it must wrapped itself emit(operationReference.startOffset, "Line is exceeding max line length. Break line before '?:'", true) - if (autoCorrect) { - operationReference.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(operationReference)) - } + .ifAutocorrectAllowed { + operationReference.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(operationReference)) + } } } else { operationReference @@ -239,8 +235,7 @@ public class BinaryExpressionWrappingRule : nextSibling.startOffset, "Line is exceeding max line length. Break line after '${operationReference.text}' in binary expression", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { nextSibling.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(operationReference)) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt index b43a56ee07..932c980720 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY @@ -20,6 +21,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -44,8 +46,7 @@ public class BlankLineBeforeDeclarationRule : Rule.OfficialCodeStyle { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { CLASS, @@ -55,14 +56,13 @@ public class BlankLineBeforeDeclarationRule : PROPERTY, PROPERTY_ACCESSOR, -> - visitDeclaration(node, autoCorrect, emit) + visitDeclaration(node, emit) } } private fun visitDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isFirstCodeSiblingInClassBody()) { // No blank line between class signature and first declaration in class body: @@ -148,9 +148,9 @@ public class BlankLineBeforeDeclarationRule : prevLeaf != null && (!prevLeaf.isWhiteSpace() || !prevLeaf.text.startsWith("\n\n")) }?.let { insertBeforeNode -> emit(insertBeforeNode.startOffset, "Expected a blank line for this declaration", true) - if (autoCorrect) { - insertBeforeNode.upsertWhitespaceBeforeMe("\n".plus(node.indent())) - } + .ifAutocorrectAllowed { + insertBeforeNode.upsertWhitespaceBeforeMe("\n".plus(node.indent())) + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt index 78323788e8..37767d1ad5 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions.kt @@ -49,10 +49,10 @@ public class BlankLineBetweenWhenConditions : override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.WHEN) { - visitWhenStatement(node, emitAndApprove) + visitWhenStatement(node, emit) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule.kt index 9d9242b431..22717b50fb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -30,14 +32,14 @@ public class BlockCommentInitialStarAlignmentRule : ) { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == BLOCK_COMMENT) { val expectedIndentForLineWithInitialStar = node.indent(false) + " *" val lines = node.text.split("\n") var offset = node.startOffset val modifiedLines = mutableListOf() + var autocorrect = false lines.forEach { line -> val modifiedLine = CONTINUATION_COMMENT_REGEX @@ -46,6 +48,7 @@ public class BlockCommentInitialStarAlignmentRule : val (prefix, content) = matchResult.destructured if (prefix != expectedIndentForLineWithInitialStar) { emit(offset + prefix.length, "Initial star should align with start of block comment", true) + .ifAutocorrectAllowed { autocorrect = true } expectedIndentForLineWithInitialStar + content } else { line @@ -55,7 +58,7 @@ public class BlockCommentInitialStarAlignmentRule : modifiedLines.add(modifiedLine) offset += line.length + 1 } - if (autoCorrect) { + if (autocorrect) { val newText = modifiedLines.joinToString(separator = "\n") if (node.text != newText) { (node as LeafElement).rawReplaceWithText(newText) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt index 5fc7413b3d..4cfa164f6d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARRAY_ACCESS_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION @@ -38,6 +39,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -104,8 +106,7 @@ public class ChainMethodContinuationRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType in chainOperatorTokenSet } @@ -124,17 +125,16 @@ public class ChainMethodContinuationRule : ?.takeUnless { it.rootASTNode.treeParent.elementType == PACKAGE_DIRECTIVE } ?.takeUnless { it.rootASTNode.treeParent.elementType == LONG_STRING_TEMPLATE_ENTRY } ?.let { chainedExpression -> - fixWhitespaceBeforeChainOperators(chainedExpression, emit, autoCorrect) + fixWhitespaceBeforeChainOperators(chainedExpression, emit) disallowCommentBetweenDotAndCallExpression(chainedExpression, emit) - fixWhiteSpaceAfterChainOperators(chainedExpression, emit, autoCorrect) + fixWhiteSpaceAfterChainOperators(chainedExpression, emit) } } } private fun fixWhitespaceBeforeChainOperators( chainedExpression: ChainedExpression, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val wrapBeforeEachChainOperator = chainedExpression.wrapBeforeChainOperator() val exceedsMaxLineLength = chainedExpression.exceedsMaxLineLength() @@ -145,11 +145,11 @@ public class ChainMethodContinuationRule : .forEach { chainOperator -> when { chainOperator.shouldBeOnSameLineAsClosingElementOfPreviousExpressionInMethodChain() -> { - removeWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) + removeWhiteSpaceBeforeChainOperator(chainOperator, emit) } wrapBeforeEachChainOperator || exceedsMaxLineLength || chainOperator.isPrecededByComment() -> { - insertWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) + insertWhiteSpaceBeforeChainOperator(chainOperator, emit) } } } @@ -261,8 +261,7 @@ public class ChainMethodContinuationRule : private fun insertWhiteSpaceBeforeChainOperator( chainOperator: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { chainOperator .prevLeaf() @@ -275,9 +274,10 @@ public class ChainMethodContinuationRule : // fooBar // .bar { ... }.foo() emit(chainOperator.startOffset, "Expected newline before '${chainOperator.text}'", true) - if (autoCorrect) { - chainOperator.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(chainOperator.treeParent)) - } + .ifAutocorrectAllowed { + chainOperator.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(chainOperator.treeParent)) + } + Unit } whiteSpaceOrComment == null || whiteSpaceOrComment.isWhiteSpaceWithoutNewline() -> { @@ -286,9 +286,10 @@ public class ChainMethodContinuationRule : // fooBar // .bar { ... }.foo() emit(chainOperator.startOffset, "Expected newline before '${chainOperator.text}'", true) - if (autoCorrect) { - chainOperator.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(chainOperator.treeParent)) - } + .ifAutocorrectAllowed { + chainOperator.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(chainOperator.treeParent)) + } + Unit } } } @@ -317,8 +318,7 @@ public class ChainMethodContinuationRule : private fun removeWhiteSpaceBeforeChainOperator( chainOperator: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { chainOperator .prevLeaf() @@ -336,16 +336,16 @@ public class ChainMethodContinuationRule : // .trimIndent() if (whiteSpaceOrComment.isWhiteSpaceWithNewline()) { emit(chainOperator.startOffset, "Unexpected newline before '${chainOperator.text}'", true) - if (autoCorrect) { - whiteSpaceOrComment?.treeParent?.removeChild(whiteSpaceOrComment) - } + .ifAutocorrectAllowed { + whiteSpaceOrComment?.treeParent?.removeChild(whiteSpaceOrComment) + } } } } private fun disallowCommentBetweenDotAndCallExpression( chainedExpression: ChainedExpression, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { chainedExpression .chainOperators @@ -359,8 +359,7 @@ public class ChainMethodContinuationRule : private fun fixWhiteSpaceAfterChainOperators( chainedExpression: ChainedExpression, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { chainedExpression .chainOperators @@ -370,9 +369,9 @@ public class ChainMethodContinuationRule : .takeIf { it.isWhiteSpaceWithNewline() } ?.let { whiteSpace -> emit(whiteSpace.startOffset - 1, "Unexpected newline after '${chainOperator.text}'", true) - if (autoCorrect) { - whiteSpace.treeParent.removeChild(whiteSpace) - } + .ifAutocorrectAllowed { + whiteSpace.treeParent.removeChild(whiteSpace) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt index 3183473bad..9b7555c2b3 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANDAND import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.DIV @@ -24,6 +25,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline @@ -56,7 +58,6 @@ public class ChainWrappingRule : private val sameLineTokens = TokenSet.create(MUL, DIV, PERC, ANDAND, OROR) private val prefixTokens = TokenSet.create(PLUS, MINUS) private val nextLineTokens = TokenSet.create(DOT, SAFE_ACCESS, ELVIS) - private val noSpaceAroundTokens = TokenSet.create(DOT, SAFE_ACCESS) private var indentConfig = DEFAULT_INDENT_CONFIG override fun beforeFirstNode(editorConfig: EditorConfig) { @@ -69,8 +70,7 @@ public class ChainWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { /* org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement (DOT) | "." @@ -85,21 +85,21 @@ public class ChainWrappingRule : val nextLeaf = node.nextCodeLeaf()?.prevLeaf() if (nextLeaf.isWhiteSpaceWithNewline() && !node.isElvisOperatorAndComment()) { emit(node.startOffset, "Line must not end with \"${node.text}\"", true) - if (autoCorrect) { - // rewriting - // to - // - // (or) - // to - // - if (node.elementType == ELVIS) { - node.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) - node.upsertWhitespaceAfterMe(" ") - } else { - node.treeParent.removeChild(node) - (nextLeaf as LeafElement).rawInsertAfterMe(node as LeafElement) + .ifAutocorrectAllowed { + // rewriting + // to + // + // (or) + // to + // + if (node.elementType == ELVIS) { + node.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) + node.upsertWhitespaceAfterMe(" ") + } else { + node.treeParent.removeChild(node) + (nextLeaf as LeafElement).rawInsertAfterMe(node as LeafElement) + } } - } } } else if (sameLineTokens.contains(elementType) || prefixTokens.contains(elementType)) { if (node.isPartOfComment()) { @@ -123,46 +123,46 @@ public class ChainWrappingRule : if (prevLeaf != null && prevLeaf.isWhiteSpaceWithNewline()) { emit(node.startOffset, "Line must not begin with \"${node.text}\"", true) - if (autoCorrect) { - // rewriting - // to - // - // (or) - // to - // - val nextLeaf = node.nextLeaf() - val whiteSpaceToBeDeleted = - when { - nextLeaf.isWhiteSpaceWithNewline() -> { - // Node is preceded and followed by whitespace. Prefer to remove the whitespace before the node as this will - // change the indent of the next line - prevLeaf - } + .ifAutocorrectAllowed { + // rewriting + // to + // + // (or) + // to + // + val nextLeaf = node.nextLeaf() + val whiteSpaceToBeDeleted = + when { + nextLeaf.isWhiteSpaceWithNewline() -> { + // Node is preceded and followed by whitespace. Prefer to remove the whitespace before the node as this will + // change the indent of the next line + prevLeaf + } - nextLeaf.isWhiteSpaceWithoutNewline() -> nextLeaf + nextLeaf.isWhiteSpaceWithoutNewline() -> nextLeaf - else -> null - } + else -> null + } - if (node.treeParent.elementType == OPERATION_REFERENCE) { - val operationReference = node.treeParent - val insertBeforeSibling = - operationReference - .prevCodeSibling() - ?.nextSibling() - operationReference.treeParent.removeChild(operationReference) - insertBeforeSibling?.treeParent?.addChild(operationReference, insertBeforeSibling) - node.treeParent.upsertWhitespaceBeforeMe(" ") - } else { - val insertionPoint = prevLeaf.prevCodeLeaf() as LeafPsiElement - (node as LeafPsiElement).treeParent.removeChild(node) - insertionPoint.rawInsertAfterMe(node) - (insertionPoint as ASTNode).upsertWhitespaceAfterMe(" ") + if (node.treeParent.elementType == OPERATION_REFERENCE) { + val operationReference = node.treeParent + val insertBeforeSibling = + operationReference + .prevCodeSibling() + ?.nextSibling() + operationReference.treeParent.removeChild(operationReference) + insertBeforeSibling?.treeParent?.addChild(operationReference, insertBeforeSibling) + node.treeParent.upsertWhitespaceBeforeMe(" ") + } else { + val insertionPoint = prevLeaf.prevCodeLeaf() as LeafPsiElement + (node as LeafPsiElement).treeParent.removeChild(node) + insertionPoint.rawInsertAfterMe(node) + (insertionPoint as ASTNode).upsertWhitespaceAfterMe(" ") + } + whiteSpaceToBeDeleted + ?.treeParent + ?.removeChild(whiteSpaceToBeDeleted) } - whiteSpaceToBeDeleted - ?.treeParent - ?.removeChild(whiteSpaceToBeDeleted) - } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt index 78f4b9a518..80329711ee 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER @@ -26,8 +27,7 @@ public class ClassNamingRule : StandardRule("class-naming") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (!allowBacktickedClassName && node.elementType == IMPORT_DIRECTIVE) { node diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt index 9dc041f4b8..fbef559758 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS @@ -36,6 +37,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPE import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY_OFF import com.pinterest.ktlint.rule.engine.core.api.hasModifier +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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -103,11 +105,10 @@ public class ClassSignatureRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == CLASS) { - visitClass(node, emit, autoCorrect) + visitClass(node, emit) } } @@ -160,8 +161,7 @@ public class ClassSignatureRule : private fun visitClass( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == CLASS) @@ -169,17 +169,16 @@ public class ClassSignatureRule : node.hasTooManyParameters() || node.containsMultilineParameter() || (codeStyle == ktlint_official && node.containsAnnotatedParameter()) || - (isMaxLineLengthSet() && classSignatureExcludingSuperTypesExceedsMaxLineLength(node, emit, autoCorrect)) || + (isMaxLineLengthSet() && classSignatureExcludingSuperTypesExceedsMaxLineLength(node, emit)) || (!isMaxLineLengthSet() && node.classSignatureExcludingSuperTypesIsMultiline()) - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = wrapPrimaryConstructorParameters, dryRun = false) - fixWhitespacesInSuperTypeList(node, emit, autoCorrect, wrappedPrimaryConstructor = wrapPrimaryConstructorParameters) - fixClassBody(node, emit, autoCorrect) + fixWhiteSpacesInValueParameterList(node, emit, multiline = wrapPrimaryConstructorParameters, dryRun = false) + fixWhitespacesInSuperTypeList(node, emit, wrappedPrimaryConstructor = wrapPrimaryConstructorParameters) + fixClassBody(node, emit) } private fun classSignatureExcludingSuperTypesExceedsMaxLineLength( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Boolean { val actualClassSignatureLength = node.getClassSignatureLength(excludeSuperTypes = true) // Calculate the length of the class signature in case it, excluding the super types, would be rewritten as single @@ -188,7 +187,7 @@ public class ClassSignatureRule : val length = actualClassSignatureLength + // Calculate the white space correction in case the signature would be rewritten to a single line - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = false, dryRun = true) + fixWhiteSpacesInValueParameterList(node, emit, multiline = false, dryRun = true) return length > maxLineLength } @@ -226,8 +225,7 @@ public class ClassSignatureRule : private fun fixWhiteSpacesInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -242,11 +240,11 @@ public class ClassSignatureRule : whiteSpaceCorrection += if (hasNoValueParameters) { - fixWhiteSpacesInEmptyValueParameterList(node, emit, autoCorrect, dryRun) + fixWhiteSpacesInEmptyValueParameterList(node, emit, dryRun) } else { - fixWhiteSpacesBeforeFirstParameterInValueParameterList(node, emit, autoCorrect, multiline, dryRun) + - fixWhiteSpacesBetweenParametersInValueParameterList(node, emit, autoCorrect, multiline, dryRun) + - fixWhiteSpaceBeforeClosingParenthesis(node, emit, autoCorrect, multiline, dryRun) + fixWhiteSpacesBeforeFirstParameterInValueParameterList(node, emit, multiline, dryRun) + + fixWhiteSpacesBetweenParametersInValueParameterList(node, emit, multiline, dryRun) + + fixWhiteSpaceBeforeClosingParenthesis(node, emit, multiline, dryRun) } return whiteSpaceCorrection @@ -254,8 +252,7 @@ public class ClassSignatureRule : private fun fixWhiteSpacesInEmptyValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, dryRun: Boolean, ): Int { var whiteSpaceCorrection = 0 @@ -273,9 +270,9 @@ public class ClassSignatureRule : }?.let { parameterList -> if (!dryRun) { emit(parameterList.startOffset, "No parenthesis expected", true) - } - if (autoCorrect && !dryRun) { - parameterList.treeParent.removeChild(parameterList) + .ifAutocorrectAllowed { + parameterList.treeParent.removeChild(parameterList) + } } else { whiteSpaceCorrection -= parameterList.textLength } @@ -288,8 +285,7 @@ public class ClassSignatureRule : private fun fixWhiteSpacesBeforeFirstParameterInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -311,13 +307,13 @@ public class ClassSignatureRule : if (whiteSpaceBeforeIdentifier == null || !whiteSpaceBeforeIdentifier.textContains('\n') ) { - if (!dryRun) { - emit(firstParameterInList.startOffset, "Newline expected after opening parenthesis", true) - } // Let indent rule determine the exact indent val expectedParameterIndent = indentConfig.childIndentOf(node) - if (autoCorrect && !dryRun) { - valueParameterList.firstChildNode.upsertWhitespaceAfterMe(expectedParameterIndent) + if (!dryRun) { + emit(firstParameterInList.startOffset, "Newline expected after opening parenthesis", true) + .ifAutocorrectAllowed { + valueParameterList.firstChildNode.upsertWhitespaceAfterMe(expectedParameterIndent) + } } else { whiteSpaceCorrection += expectedParameterIndent.length - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -329,10 +325,9 @@ public class ClassSignatureRule : firstParameter!!.startOffset, "No whitespace expected between opening parenthesis and first parameter name", true, - ) - } - if (autoCorrect && !dryRun) { - whiteSpaceBeforeIdentifier.treeParent.removeChild(whiteSpaceBeforeIdentifier) + ).ifAutocorrectAllowed { + whiteSpaceBeforeIdentifier.treeParent.removeChild(whiteSpaceBeforeIdentifier) + } } else { whiteSpaceCorrection -= whiteSpaceBeforeIdentifier.textLength } @@ -345,8 +340,7 @@ public class ClassSignatureRule : private fun fixWhiteSpacesBetweenParametersInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -373,13 +367,13 @@ public class ClassSignatureRule : if (whiteSpaceBeforeIdentifier == null || !whiteSpaceBeforeIdentifier.textContains('\n') ) { - if (!dryRun) { - emit(valueParameter.startOffset, "Parameter should start on a newline", true) - } // Let IndentationRule determine the exact indent val expectedParameterIndent = indentConfig.childIndentOf(node) - if (autoCorrect && !dryRun) { - firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(expectedParameterIndent) + if (!dryRun) { + emit(valueParameter.startOffset, "Parameter should start on a newline", true) + .ifAutocorrectAllowed { + firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(expectedParameterIndent) + } } else { whiteSpaceCorrection += expectedParameterIndent.length - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -388,9 +382,9 @@ public class ClassSignatureRule : if (whiteSpaceBeforeIdentifier == null || whiteSpaceBeforeIdentifier.text != " ") { if (!dryRun) { emit(firstChildNodeInValueParameter!!.startOffset, "Single whitespace expected before parameter", true) - } - if (autoCorrect && !dryRun) { - firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(" ") + .ifAutocorrectAllowed { + firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(" ") + } } else { whiteSpaceCorrection += 1 - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -404,8 +398,7 @@ public class ClassSignatureRule : private fun fixWhiteSpaceBeforeClosingParenthesis( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -423,13 +416,13 @@ public class ClassSignatureRule : if (whiteSpaceBeforeClosingParenthesis == null || !whiteSpaceBeforeClosingParenthesis.textContains('\n') ) { - if (!dryRun) { - emit(closingParenthesisPrimaryConstructor!!.startOffset, "Newline expected before closing parenthesis", true) - } // Let IndentationRule determine the exact indent val expectedParameterIndent = node.indent() - if (autoCorrect && !dryRun) { - closingParenthesisPrimaryConstructor!!.upsertWhitespaceBeforeMe(expectedParameterIndent) + if (!dryRun) { + emit(closingParenthesisPrimaryConstructor!!.startOffset, "Newline expected before closing parenthesis", true) + .ifAutocorrectAllowed { + closingParenthesisPrimaryConstructor.upsertWhitespaceBeforeMe(expectedParameterIndent) + } } else { whiteSpaceCorrection += expectedParameterIndent.length - (whiteSpaceBeforeClosingParenthesis?.textLength ?: 0) } @@ -443,10 +436,9 @@ public class ClassSignatureRule : whiteSpaceBeforeClosingParenthesis.startOffset, "No whitespace expected between last parameter and closing parenthesis", true, - ) - } - if (autoCorrect && !dryRun) { - whiteSpaceBeforeClosingParenthesis.treeParent.removeChild(whiteSpaceBeforeClosingParenthesis) + ).ifAutocorrectAllowed { + whiteSpaceBeforeClosingParenthesis.treeParent.removeChild(whiteSpaceBeforeClosingParenthesis) + } } else { whiteSpaceCorrection -= whiteSpaceBeforeClosingParenthesis.textLength } @@ -458,8 +450,7 @@ public class ClassSignatureRule : private fun fixWhitespacesInSuperTypeList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, wrappedPrimaryConstructor: Boolean, ): Int { var whiteSpaceCorrection = 0 @@ -471,22 +462,22 @@ public class ClassSignatureRule : .firstOrNull { it.elementType == SUPER_TYPE_CALL_ENTRY } ?.let { superTypeCallEntry -> emit(superTypeCallEntry.startOffset, "Super type call must be first super type", true) - if (autoCorrect) { - val superTypeList = node.findChildByType(SUPER_TYPE_LIST) ?: return 0 - val originalFirstSuperType = superTypes.first() - val commaBeforeSuperTypeCall = requireNotNull(superTypeCallEntry.prevSibling { it.elementType == COMMA }) - - // Remove the whitespace before the super type call and do not insert a new whitespace as it will be fixed later - superTypeCallEntry - .prevSibling() - ?.takeIf { it.elementType == WHITE_SPACE } - ?.let { whitespaceBeforeSuperTypeCallEntry -> - superTypeList.removeChild(whitespaceBeforeSuperTypeCallEntry) - } + .ifAutocorrectAllowed { + val superTypeList = node.findChildByType(SUPER_TYPE_LIST) ?: return 0 + val originalFirstSuperType = superTypes.first() + val commaBeforeSuperTypeCall = requireNotNull(superTypeCallEntry.prevSibling { it.elementType == COMMA }) + + // Remove the whitespace before the super type call and do not insert a new whitespace as it will be fixed later + superTypeCallEntry + .prevSibling() + ?.takeIf { it.elementType == WHITE_SPACE } + ?.let { whitespaceBeforeSuperTypeCallEntry -> + superTypeList.removeChild(whitespaceBeforeSuperTypeCallEntry) + } - superTypeList.addChild(superTypeCallEntry, superTypes.first()) - superTypeList.addChild(commaBeforeSuperTypeCall, originalFirstSuperType) - } + superTypeList.addChild(superTypeCallEntry, superTypes.first()) + superTypeList.addChild(commaBeforeSuperTypeCall, originalFirstSuperType) + } } } @@ -510,9 +501,9 @@ public class ClassSignatureRule : val expectedWhitespace = " " if (whiteSpaceBeforeSuperType.text != expectedWhitespace) { emit(firstSuperType.startOffset, "Expected single space before the super type", true) - if (autoCorrect) { - firstSuperType.upsertWhitespaceBeforeMe(expectedWhitespace) - } + .ifAutocorrectAllowed { + firstSuperType.upsertWhitespaceBeforeMe(expectedWhitespace) + } } } } @@ -526,16 +517,16 @@ public class ClassSignatureRule : ?.takeIf { it.elementType == WHITE_SPACE } .let { whiteSpaceBeforeIdentifier -> if (node.hasMultilineSuperTypeList() || - classSignaturesIncludingFirstSuperTypeExceedsMaxLineLength(node, emit, autoCorrect) + classSignaturesIncludingFirstSuperTypeExceedsMaxLineLength(node, emit) ) { if (whiteSpaceBeforeIdentifier == null || !whiteSpaceBeforeIdentifier.textContains('\n') ) { emit(superTypeFirstChildNode.startOffset, "Super type should start on a newline", true) - if (autoCorrect) { - // Let IndentationRule determine the exact indent - superTypeFirstChildNode.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) - } + .ifAutocorrectAllowed { + // Let IndentationRule determine the exact indent + superTypeFirstChildNode.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) + } } } else { val expectedWhitespace = " " @@ -543,9 +534,9 @@ public class ClassSignatureRule : whiteSpaceBeforeIdentifier.text != expectedWhitespace ) { emit(superTypeFirstChildNode.startOffset, "Expected single space before the super type", true) - if (autoCorrect) { - superTypeFirstChildNode.upsertWhitespaceBeforeMe(expectedWhitespace) - } + .ifAutocorrectAllowed { + superTypeFirstChildNode.upsertWhitespaceBeforeMe(expectedWhitespace) + } } } } @@ -565,19 +556,19 @@ public class ClassSignatureRule : (whiteSpaceBeforeIdentifier == null || whiteSpaceBeforeIdentifier.text != expectedWhitespace) ) { emit(firstChildNodeInSuperType.startOffset, "Expected single space before the first super type", true) - if (autoCorrect) { - firstChildNodeInSuperType.upsertWhitespaceBeforeMe(expectedWhitespace) - } + .ifAutocorrectAllowed { + firstChildNodeInSuperType.upsertWhitespaceBeforeMe(expectedWhitespace) + } } } else { if (whiteSpaceBeforeIdentifier == null || !whiteSpaceBeforeIdentifier.textContains('\n') ) { emit(firstChildNodeInSuperType.startOffset, "Super type should start on a newline", true) - if (autoCorrect) { - // Let IndentationRule determine the exact indent - firstChildNodeInSuperType.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) - } + .ifAutocorrectAllowed { + // Let IndentationRule determine the exact indent + firstChildNodeInSuperType.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) + } } } } @@ -593,9 +584,9 @@ public class ClassSignatureRule : .findChildByType(WHITE_SPACE) ?.let { whitespace -> emit(whitespace.startOffset, "No whitespace expected", true) - if (autoCorrect) { - whitespace.treeParent.removeChild(whitespace) - } + .ifAutocorrectAllowed { + whitespace.treeParent.removeChild(whitespace) + } } } @@ -604,8 +595,7 @@ public class ClassSignatureRule : private fun classSignaturesIncludingFirstSuperTypeExceedsMaxLineLength( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Boolean { val actualClassSignatureLength = node.getClassSignatureLength(excludeSuperTypes = false) // Calculate the length of the class signature in case it, including the super types, would be rewritten as single @@ -614,7 +604,7 @@ public class ClassSignatureRule : val length = actualClassSignatureLength + // Calculate the white space correction in case the signature would be rewritten to a single line - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = false, dryRun = true) + fixWhiteSpacesInValueParameterList(node, emit, multiline = false, dryRun = true) return length > maxLineLength } @@ -627,19 +617,18 @@ public class ClassSignatureRule : private fun fixClassBody( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(CLASS_BODY) ?.let { classBody -> if (classBody.prevLeaf()?.text != " ") { emit(classBody.startOffset, "Expected a single space before class body", true) - if (autoCorrect) { - classBody - .prevLeaf(true) - ?.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + classBody + .prevLeaf(true) + ?.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRule.kt index 04368b85d1..d0aa25c9ef 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRule.kt @@ -1,8 +1,10 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -14,16 +16,15 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class CommentSpacingRule : StandardRule("comment-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == EOL_COMMENT) { val prevLeaf = node.prevLeaf() if (prevLeaf !is PsiWhiteSpace && prevLeaf is LeafPsiElement) { emit(node.startOffset, "Missing space before //", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + } } val text = node.text if (text.length != 2 && @@ -34,9 +35,9 @@ public class CommentSpacingRule : StandardRule("comment-spacing") { !text.startsWith("//language=") ) { emit(node.startOffset, "Missing space after //", true) - if (autoCorrect) { - (node as LeafPsiElement).rawReplaceWithText("// " + text.removePrefix("//")) - } + .ifAutocorrectAllowed { + (node as LeafPsiElement).rawReplaceWithText("// " + text.removePrefix("//")) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRule.kt index 8f56f111ea..59e1763e77 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE @@ -11,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.hasNewLineInClosedRange +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.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -38,8 +40,7 @@ public class CommentWrappingRule : ) { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == BLOCK_COMMENT) { val beforeBlockComment = @@ -99,8 +100,7 @@ public class CommentWrappingRule : node.startOffset, "A single line block comment after a code element on the same line must be replaced with an EOL comment", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node.upsertWhitespaceBeforeMe(" ") } } @@ -114,8 +114,7 @@ public class CommentWrappingRule : nextLeaf.startOffset, "A block comment may not be followed by any other element on that same line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { nextLeaf.upsertWhitespaceBeforeMe(node.indent()) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRule.kt index f0db48210f..8a68041cdc 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANDAND import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.OPERATION_REFERENCE @@ -14,13 +15,13 @@ 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.firstChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace 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.leavesInClosedRange import com.pinterest.ktlint.rule.engine.core.api.nextSibling -import com.pinterest.ktlint.rule.engine.core.api.parent import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -53,14 +54,13 @@ public class ConditionWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.isLogicalBinaryExpression() } ?.takeIf { it.isMultiline() } ?.let { - visitLogicalExpression(it, emit, autoCorrect) + visitLogicalExpression(it, emit) } } @@ -72,8 +72,7 @@ public class ConditionWrappingRule : private fun visitLogicalExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(OPERATION_REFERENCE) @@ -89,9 +88,9 @@ public class ConditionWrappingRule : ?: 0, ) emit(startOffset, "Newline expected before operand in multiline condition", true) - if (autoCorrect) { - operationReference.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + operationReference.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt index b08338807f..75d81c1425 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONTEXT_RECEIVER import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONTEXT_RECEIVER_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.GT @@ -18,6 +19,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment @@ -61,22 +63,20 @@ public class ContextReceiverWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when { node.elementType == CONTEXT_RECEIVER_LIST -> - visitContextReceiverList(node, autoCorrect, emit) + visitContextReceiverList(node, emit) node.elementType == TYPE_ARGUMENT_LIST && node.isPartOf(CONTEXT_RECEIVER) -> - visitContextReceiverTypeArgumentList(node, autoCorrect, emit) + visitContextReceiverTypeArgumentList(node, emit) } } private fun visitContextReceiverList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // Context receiver must be followed by new line or comment node @@ -85,11 +85,11 @@ public class ContextReceiverWrappingRule : ?.takeIf { !it.isWhiteSpaceWithNewline() } ?.let { nodeAfterContextReceiver -> emit(nodeAfterContextReceiver.startOffset, "Expected a newline after the context receiver", true) - if (autoCorrect) { - nodeAfterContextReceiver - .firstChildLeafOrSelf() - .upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(node)) - } + .ifAutocorrectAllowed { + nodeAfterContextReceiver + .firstChildLeafOrSelf() + .upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(node)) + } } // Check line length assuming that the context receiver is indented correctly. Wrapping rule must however run before indenting. @@ -104,8 +104,7 @@ public class ContextReceiverWrappingRule : it.startOffset, "Newline expected before context receiver as max line length is violated", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { it .prevLeaf(includeEmpty = true) ?.upsertWhitespaceAfterMe(indentConfig.childIndentOf(node)) @@ -118,8 +117,7 @@ public class ContextReceiverWrappingRule : rpar.startOffset, "Newline expected before closing parenthesis as max line length is violated", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { rpar.upsertWhitespaceBeforeMe(node.indent()) } } @@ -128,8 +126,7 @@ public class ContextReceiverWrappingRule : private fun visitContextReceiverTypeArgumentList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val contextReceiver = node.treeParent.text // Check line length assuming that the context receiver is indented correctly. Wrapping rule must however run @@ -145,8 +142,7 @@ public class ContextReceiverWrappingRule : it.startOffset, "Newline expected before context receiver type projection as max line length is violated", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { it.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) } } @@ -157,10 +153,9 @@ public class ContextReceiverWrappingRule : gt.startOffset, "Newline expected before closing angle bracket as max line length is violated", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { // Ideally, the closing angle bracket should be de-indented to make it consistent with - // de-intentation of closing ")", "}" and "]". This however would be inconsistent with how the + // de-indentation of closing ")", "}" and "]". This however would be inconsistent with how the // type argument lists are formatted by IntelliJ IDEA default formatter. gt.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index bd9b948976..c693da3324 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision 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.SinceKtlint.Status.EXPERIMENTAL @@ -26,10 +27,9 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- @Suppress("RedundantOverride") override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - super.beforeVisitChildNodes(node, autoCorrect, emit) + super.beforeVisitChildNodes(node, emit) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule.kt index bddce64322..7bc0012b9f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision 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.SinceKtlint.Status.EXPERIMENTAL @@ -22,8 +23,7 @@ public class EnumEntryNameCaseRule : StandardRule("enum-entry-name-case") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node !is CompositeElement) { return diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRule.kt index 8b197d48ec..63d9ba4080 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY @@ -16,6 +17,7 @@ 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.firstChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline @@ -55,37 +57,34 @@ public class EnumWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == CLASS } ?.takeIf { (node.psi as KtClass).isEnum() } ?.findChildByType(CLASS_BODY) ?.let { classBody -> - visitEnumClass(classBody, autoCorrect, emit) + visitEnumClass(classBody, emit) } } private fun visitEnumClass( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == CLASS_BODY) - val commentBeforeFirstEnumEntry = wrapCommentBeforeFirstEnumEntry(node, emit, autoCorrect) + val commentBeforeFirstEnumEntry = wrapCommentBeforeFirstEnumEntry(node, emit) if (commentBeforeFirstEnumEntry || node.isMultiline() || node.hasAnnotatedEnumEntry() || node.hasCommentedEnumEntry()) { - wrapEnumEntries(node, autoCorrect, emit) - wrapClosingBrace(node, emit, autoCorrect) + wrapEnumEntries(node, emit) + wrapClosingBrace(node, emit) } - addBlankLineBetweenEnumEntriesAndOtherDeclarations(node, emit, autoCorrect) + addBlankLineBetweenEnumEntriesAndOtherDeclarations(node, emit) } private fun wrapCommentBeforeFirstEnumEntry( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Boolean { val firstEnumEntry = node.findChildByType(ENUM_ENTRY)?.firstChildLeafOrSelf() if (firstEnumEntry != null) { @@ -98,9 +97,9 @@ public class EnumWrappingRule : val expectedIndent = indentConfig.childIndentOf(node) if (commentBeforeFirstEnumEntry.prevLeaf()?.text != expectedIndent) { emit(node.startOffset, "Expected a (single) newline before comment", true) - if (autoCorrect) { - commentBeforeFirstEnumEntry.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + commentBeforeFirstEnumEntry.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + } return true } } @@ -127,37 +126,34 @@ public class EnumWrappingRule : private fun wrapEnumEntries( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() .filter { it.elementType == ENUM_ENTRY } .forEach { enumEntry -> - wrapEnumEntry(enumEntry, autoCorrect, emit) + wrapEnumEntry(enumEntry, emit) } } private fun wrapEnumEntry( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .prevLeaf { !it.isPartOfComment() && !it.isWhiteSpaceWithoutNewline() } ?.takeUnless { it.isWhiteSpaceWithNewline() } ?.let { prevLeaf -> emit(node.startOffset, "Enum entry should start on a separate line", true) - if (autoCorrect) { - prevLeaf.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + prevLeaf.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) + } } } private fun wrapClosingBrace( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(RBRACE) @@ -166,17 +162,16 @@ public class EnumWrappingRule : val expectedIndent = indentConfig.parentIndentOf(node) if (prevLeaf?.text != expectedIndent) { emit(rbrace.startOffset, "Expected newline before '}'", true) - if (autoCorrect) { - rbrace.upsertWhitespaceBeforeMe(expectedIndent) - } + .ifAutocorrectAllowed { + rbrace.upsertWhitespaceBeforeMe(expectedIndent) + } } } } private fun addBlankLineBetweenEnumEntriesAndOtherDeclarations( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .children() @@ -187,9 +182,9 @@ public class EnumWrappingRule : val expectedIndent = "\n".plus(indentConfig.siblingIndentOf(node)) if (nextSibling.text != expectedIndent) { emit(nextSibling.startOffset + 1, "Expected blank line between enum entries and other declaration(s)", true) - if (autoCorrect) { - nextSibling.upsertWhitespaceBeforeMe(expectedIndent) - } + .ifAutocorrectAllowed { + nextSibling.upsertWhitespaceBeforeMe(expectedIndent) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FilenameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FilenameRule.kt index c92b0f103d..bc17e8886b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FilenameRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FilenameRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER @@ -46,8 +47,7 @@ import org.jetbrains.kotlin.psi.KtFile public class FilenameRule : StandardRule("filename") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isRoot()) { node as FileASTNode? ?: error("node is not ${FileASTNode::class} but ${node::class}") @@ -125,7 +125,7 @@ public class FilenameRule : StandardRule("filename") { private fun String.shouldMatchClassName( className: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (this != className) { emit( @@ -141,7 +141,7 @@ public class FilenameRule : StandardRule("filename") { private fun String.shouldMatchFileName( filename: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (this != filename) { emit( @@ -152,7 +152,9 @@ public class FilenameRule : StandardRule("filename") { } } - private fun String.shouldMatchPascalCase(emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + private fun String.shouldMatchPascalCase( + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + ) { if (!this.matches(PASCAL_CASE_REGEX)) { emit(0, "File name '$this.kt' should conform PascalCase", false) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRule.kt index f68717d1c2..f0881263da 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision 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.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INSERT_FINAL_NEWLINE_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isRoot import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -25,8 +27,7 @@ public class FinalNewlineRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isRoot()) { if (node.textLength == 0) { @@ -37,16 +38,16 @@ public class FinalNewlineRule : if (insertFinalNewline) { if (lastNode !is PsiWhiteSpace || !lastNode.textContains('\n')) { emit(node.textLength - 1, "File must end with a newline (\\n)", true) - if (autoCorrect) { - node.addChild(PsiWhiteSpaceImpl("\n"), null) - } + .ifAutocorrectAllowed { + node.addChild(PsiWhiteSpaceImpl("\n"), null) + } } } else { if (lastNode is PsiWhiteSpace && lastNode.textContains('\n')) { emit(lastNode.startOffset, "Redundant newline (\\n) at the end of file", true) - if (autoCorrect) { - lastNode.node.treeParent.removeChild(lastNode.node) - } + .ifAutocorrectAllowed { + lastNode.node.treeParent.removeChild(lastNode.node) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRule.kt index 57b280d40b..b37377246b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN_KEYWORD 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -19,8 +21,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class FunKeywordSpacingRule : StandardRule("fun-keyword-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == FUN_KEYWORD } @@ -31,8 +32,7 @@ public class FunKeywordSpacingRule : StandardRule("fun-keyword-spacing") { whiteSpaceAfterFunKeyword.startOffset, "Single space expected after the fun keyword", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (whiteSpaceAfterFunKeyword as LeafPsiElement).rawReplaceWithText(" ") } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRule.kt index 4397149dc9..176ba5f1bb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ @@ -22,6 +23,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.leavesInClosedRange @@ -94,18 +96,16 @@ public class FunctionExpressionBodyRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == BLOCK && it.treeParent.elementType == FUN } - ?.let { visitFunctionBody(node, autoCorrect, emit) } + ?.let { visitFunctionBody(node, emit) } } private fun visitFunctionBody( block: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(block.elementType == BLOCK) block @@ -116,43 +116,43 @@ public class FunctionExpressionBodyRule : ?.nextSibling { !it.isWhiteSpace() } ?.let { codeSibling -> emit(block.startOffset, "Function body should be replaced with body expression", true) - if (autoCorrect) { - with(block.treeParent) { - // Insert the code sibling before the block - addChild(LeafPsiElement(EQ, "="), block) - addChild(PsiWhiteSpaceImpl(" "), block) - addChild(codeSibling, block) - // Remove old (and now empty block) - removeChild(block) + .ifAutocorrectAllowed { + with(block.treeParent) { + // Insert the code sibling before the block + addChild(LeafPsiElement(EQ, "="), block) + addChild(PsiWhiteSpaceImpl(" "), block) + addChild(codeSibling, block) + // Remove old (and now empty block) + removeChild(block) + } } - } } block .takeIf { it.containingOnly(THROW) } ?.findChildByType(THROW) ?.let { throwNode -> emit(block.startOffset, "Function body should be replaced with body expression", true) - if (autoCorrect) { - with(block.treeParent) { - // Remove whitespace before block - block - .prevSibling() - .takeIf { it.isWhiteSpace() } - ?.let { removeChild(it) } - if (findChildByType(TYPE_REFERENCE) == null) { - // Insert Unit as return type as otherwise a compilation error results - addChild(LeafPsiElement(COLON, ":"), block) + .ifAutocorrectAllowed { + with(block.treeParent) { + // Remove whitespace before block + block + .prevSibling() + .takeIf { it.isWhiteSpace() } + ?.let { removeChild(it) } + if (findChildByType(TYPE_REFERENCE) == null) { + // Insert Unit as return type as otherwise a compilation error results + addChild(LeafPsiElement(COLON, ":"), block) + addChild(PsiWhiteSpaceImpl(" "), block) + addChild(createUnitTypeReference(), block) + } + addChild(PsiWhiteSpaceImpl(" "), block) + addChild(LeafPsiElement(EQ, "="), block) addChild(PsiWhiteSpaceImpl(" "), block) - addChild(createUnitTypeReference(), block) + addChild(throwNode, block) + // Remove old (and now empty block) + removeChild(block) } - addChild(PsiWhiteSpaceImpl(" "), block) - addChild(LeafPsiElement(EQ, "="), block) - addChild(PsiWhiteSpaceImpl(" "), block) - addChild(throwNode, block) - // Remove old (and now empty block) - removeChild(block) } - } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt index d66b4b3e02..97d8f08a24 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt @@ -1,6 +1,7 @@ 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.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_LITERAL @@ -23,6 +24,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -105,26 +107,24 @@ public class FunctionLiteralRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == FUNCTION_LITERAL) { node .findChildByType(VALUE_PARAMETER_LIST) - ?.let { visitValueParameterList(it, autoCorrect, emit) } + ?.let { visitValueParameterList(it, emit) } node .findChildByType(ARROW) - ?.let { visitArrow(it, autoCorrect, emit) } + ?.let { visitArrow(it, emit) } node .findChildByType(BLOCK) - ?.let { visitBlock(it, autoCorrect, emit) } + ?.let { visitBlock(it, emit) } } } private fun visitValueParameterList( parameterList: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val valueParameters = parameterList @@ -132,9 +132,9 @@ public class FunctionLiteralRule : .filter { it.elementType == VALUE_PARAMETER } if (valueParameters.count() > 1 || parameterList.wrapFirstParameterToNewline()) { if (parameterList.textContains('\n') || parameterList.doesNotFitOnSameLineAsStartOfFunctionLiteral()) { - rewriteToMultilineParameterList(parameterList, autoCorrect, emit) + rewriteToMultilineParameterList(parameterList, emit) } else { - rewriteToSingleLineFunctionLiteral(parameterList, emit, autoCorrect) + rewriteToSingleLineFunctionLiteral(parameterList, emit) } } else { if (parameterList.textContains('\n')) { @@ -157,7 +157,7 @@ public class FunctionLiteralRule : // -> // bar() // } - rewriteToSingleLineFunctionLiteral(parameterList, emit, autoCorrect) + rewriteToSingleLineFunctionLiteral(parameterList, emit) } } } @@ -230,28 +230,26 @@ public class FunctionLiteralRule : private fun rewriteToMultilineParameterList( parameterList: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(parameterList.elementType == VALUE_PARAMETER_LIST) parameterList .children() .filter { it.elementType == VALUE_PARAMETER } - .forEach { wrapValueParameter(it, autoCorrect, emit) } + .forEach { wrapValueParameter(it, emit) } parameterList .treeParent .findChildByType(ARROW) - ?.let { arrow -> wrapArrow(arrow, autoCorrect, emit) } + ?.let { arrow -> wrapArrow(arrow, emit) } parameterList .treeParent .findChildByType(RBRACE) - ?.let { rbrace -> wrapBeforeRbrace(rbrace, autoCorrect, emit) } + ?.let { rbrace -> wrapBeforeRbrace(rbrace, emit) } } private fun wrapValueParameter( valueParameter: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(valueParameter.elementType == VALUE_PARAMETER) valueParameter @@ -262,26 +260,24 @@ public class FunctionLiteralRule : !whitespaceBeforeValueParameter.textContains('\n') ) { emit(valueParameter.startOffset, "Newline expected before parameter", true) - if (autoCorrect) { - valueParameter.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(valueParameter.parent(FUNCTION_LITERAL)!!)) - } + .ifAutocorrectAllowed { + valueParameter.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(valueParameter.parent(FUNCTION_LITERAL)!!)) + } } } } private fun wrapArrow( arrow: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - wrapBeforeArrow(arrow, emit, autoCorrect) - wrapAfterArrow(arrow, emit, autoCorrect) + wrapBeforeArrow(arrow, emit) + wrapAfterArrow(arrow, emit) } private fun wrapBeforeArrow( arrow: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(arrow.elementType == ARROW) arrow @@ -292,17 +288,16 @@ public class FunctionLiteralRule : !whitespaceBeforeArrow.textContains('\n') ) { emit(arrow.startOffset, "Newline expected before arrow", true) - if (autoCorrect) { - arrow.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(arrow.treeParent)) - } + .ifAutocorrectAllowed { + arrow.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(arrow.treeParent)) + } } } } private fun wrapAfterArrow( arrow: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(arrow.elementType == ARROW) arrow @@ -313,17 +308,16 @@ public class FunctionLiteralRule : !whitespaceAfterArrow.textContains('\n') ) { emit(arrow.startOffset + arrow.textLength - 1, "Newline expected after arrow", true) - if (autoCorrect) { - arrow.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(arrow)) - } + .ifAutocorrectAllowed { + arrow.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(arrow)) + } } } } private fun wrapBeforeRbrace( rbrace: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(rbrace.elementType == RBRACE) rbrace @@ -334,17 +328,16 @@ public class FunctionLiteralRule : !whitespaceBeforeRbrace.textContains('\n') ) { emit(rbrace.startOffset, "Newline expected before closing brace", true) - if (autoCorrect) { - rbrace.upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(rbrace)) - } + .ifAutocorrectAllowed { + rbrace.upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(rbrace)) + } } } } private fun rewriteToSingleLineFunctionLiteral( parameterList: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(parameterList.elementType == VALUE_PARAMETER_LIST) parameterList @@ -352,25 +345,24 @@ public class FunctionLiteralRule : ?.takeIf { it.isWhiteSpaceWithNewline() } ?.let { whitespaceBeforeParameterList -> emit(parameterList.startOffset, "No newline expected before parameter", true) - if (autoCorrect) { - whitespaceBeforeParameterList.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + whitespaceBeforeParameterList.upsertWhitespaceBeforeMe(" ") + } } parameterList .nextSibling { it.isWhiteSpace() } ?.takeIf { it.isWhiteSpaceWithNewline() } ?.let { whitespaceAfterParameterList -> emit(parameterList.startOffset + parameterList.textLength, "No newline expected after parameter", true) - if (autoCorrect) { - whitespaceAfterParameterList.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + whitespaceAfterParameterList.upsertWhitespaceAfterMe(" ") + } } } private fun visitArrow( arrow: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(arrow.elementType == ARROW) arrow @@ -378,13 +370,13 @@ public class FunctionLiteralRule : ?.takeIf { it.findChildByType(VALUE_PARAMETER) == null && arrow.isFollowedByNonEmptyBlock() } ?.let { emit(arrow.startOffset, "Arrow is redundant when parameter list is empty", true) - if (autoCorrect) { - arrow - .nextSibling() - .takeIf { it.isWhiteSpace() } - ?.let { it.treeParent.removeChild(it) } - arrow.treeParent.removeChild(arrow) - } + .ifAutocorrectAllowed { + arrow + .nextSibling() + .takeIf { it.isWhiteSpace() } + ?.let { it.treeParent.removeChild(it) } + arrow.treeParent.removeChild(arrow) + } } } @@ -395,8 +387,7 @@ public class FunctionLiteralRule : private fun visitBlock( block: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(block.elementType == BLOCK) if (block.textContains('\n') || block.exceedsMaxLineLength()) { @@ -404,8 +395,8 @@ public class FunctionLiteralRule : .prevCodeSibling() ?.let { prevCodeSibling -> when (prevCodeSibling.elementType) { - ARROW -> wrapAfterArrow(prevCodeSibling, emit, autoCorrect) - LBRACE -> wrapAfterLbrace(prevCodeSibling, emit, autoCorrect) + ARROW -> wrapAfterArrow(prevCodeSibling, emit) + LBRACE -> wrapAfterLbrace(prevCodeSibling, emit) else -> LOGGER.debug { "Unexpected type of element ${prevCodeSibling.elementType}" } } } @@ -413,14 +404,13 @@ public class FunctionLiteralRule : block .nextCodeSibling() ?.takeIf { it.elementType == RBRACE } - ?.let { rbrace -> wrapBeforeRbrace(rbrace, autoCorrect, emit) } + ?.let { rbrace -> wrapBeforeRbrace(rbrace, emit) } } } private fun wrapAfterLbrace( lbrace: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(lbrace.elementType == LBRACE) lbrace @@ -431,9 +421,9 @@ public class FunctionLiteralRule : !whitespaceAfterLbrace.textContains('\n') ) { emit(lbrace.startOffset, "Newline expected after opening brace", true) - if (autoCorrect) { - lbrace.upsertWhitespaceAfterMe(indentConfig.childIndentOf(lbrace)) - } + .ifAutocorrectAllowed { + lbrace.upsertWhitespaceAfterMe(indentConfig.childIndentOf(lbrace)) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt index 19dbf6fe87..ea15c9ce25 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -50,8 +51,7 @@ public class FunctionNamingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (!isTestClass && node.elementType == IMPORT_DIRECTIVE) { (node.psi as KtImportDirective) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRule.kt index ef91c6a5c0..9ce9a83313 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE @@ -9,6 +10,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -32,23 +34,21 @@ public class FunctionReturnTypeSpacingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node.firstChildNode node .takeIf { node.elementType == FUN } ?.let { node.findChildByType(COLON) } ?.let { colonNode -> - removeWhiteSpaceBetweenClosingParenthesisAndColon(colonNode, emit, autoCorrect) - fixWhiteSpaceBetweenColonAndReturnType(colonNode, emit, autoCorrect) + removeWhiteSpaceBetweenClosingParenthesisAndColon(colonNode, emit) + fixWhiteSpaceBetweenColonAndReturnType(colonNode, emit) } } private fun removeWhiteSpaceBetweenClosingParenthesisAndColon( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == COLON) node @@ -56,16 +56,15 @@ public class FunctionReturnTypeSpacingRule : ?.takeIf { it.elementType == WHITE_SPACE } ?.let { whitespaceBeforeColonNode -> emit(whitespaceBeforeColonNode.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - whitespaceBeforeColonNode.treeParent?.removeChild(whitespaceBeforeColonNode) - } + .ifAutocorrectAllowed { + whitespaceBeforeColonNode.treeParent?.removeChild(whitespaceBeforeColonNode) + } } } private fun fixWhiteSpaceBetweenColonAndReturnType( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == COLON) node @@ -89,9 +88,9 @@ public class FunctionReturnTypeSpacingRule : whiteSpaceAfterColon.lengthUntilNewline(true) // Length of the line after but excluding the whitespace if (newLineLength <= maxLineLength) { emit(node.startOffset, "Single space expected between colon and return type", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt index b8f09641c1..79961fc8b8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION @@ -33,6 +34,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.editorconfig.MAX_LINE_LENGTH_PROPERTY_OFF +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.isCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -106,8 +108,7 @@ public class FunctionSignatureRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == FUN) { node @@ -122,7 +123,7 @@ public class FunctionSignatureRule : return } - visitFunctionSignature(node, emit, autoCorrect) + visitFunctionSignature(node, emit) } } @@ -172,8 +173,7 @@ public class FunctionSignatureRule : private fun visitFunctionSignature( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == FUN) @@ -182,7 +182,7 @@ public class FunctionSignatureRule : node.containsMultilineParameter() || (codeStyle == ktlint_official && node.containsAnnotatedParameter()) if (isMaxLineLengthSet()) { - val singleLineFunctionSignatureLength = calculateFunctionSignatureLengthAsSingleLineSignature(node, emit, autoCorrect) + val singleLineFunctionSignatureLength = calculateFunctionSignatureLengthAsSingleLineSignature(node, emit) // Function signatures not having parameters, should not be reformatted automatically. It would result in function signatures // like below, which are not acceptable: // fun aVeryLongFunctionName( @@ -194,21 +194,21 @@ public class FunctionSignatureRule : // Leave it up to the max-line-length rule to detect those violations so that the developer can handle it manually. val rewriteFunctionSignatureWithParameters = node.countParameters() > 0 && singleLineFunctionSignatureLength > maxLineLength if (forceMultilineSignature || rewriteFunctionSignatureWithParameters) { - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = true, dryRun = false) + fixWhiteSpacesInValueParameterList(node, emit, multiline = true, dryRun = false) if (node.findChildByType(EQ) == null) { - fixWhitespaceBeforeFunctionBodyBlock(node, emit, autoCorrect, dryRun = false) + fixWhitespaceBeforeFunctionBodyBlock(node, emit, dryRun = false) } else { // Due to rewriting the function signature, the remaining length on the last line of the multiline signature needs to be // recalculated val lengthOfLastLine = recalculateRemainingLengthForFirstLineOfBodyExpression(node) - fixFunctionBodyExpression(node, emit, autoCorrect, maxLineLength - lengthOfLastLine) + fixFunctionBodyExpression(node, emit, maxLineLength - lengthOfLastLine) } } else { - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = false, dryRun = false) + fixWhiteSpacesInValueParameterList(node, emit, multiline = false, dryRun = false) if (node.findChildByType(EQ) == null) { - fixWhitespaceBeforeFunctionBodyBlock(node, emit, autoCorrect, dryRun = false) + fixWhitespaceBeforeFunctionBodyBlock(node, emit, dryRun = false) } else { - fixFunctionBodyExpression(node, emit, autoCorrect, maxLineLength - singleLineFunctionSignatureLength) + fixFunctionBodyExpression(node, emit, maxLineLength - singleLineFunctionSignatureLength) } } } else { @@ -220,9 +220,9 @@ public class FunctionSignatureRule : .functionSignatureNodes() .none { it.textContains('\n') } if (!forceMultilineSignature && rewriteToSingleLineFunctionSignature) { - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = false, dryRun = false) + fixWhiteSpacesInValueParameterList(node, emit, multiline = false, dryRun = false) } else { - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = true, dryRun = false) + fixWhiteSpacesInValueParameterList(node, emit, multiline = true, dryRun = false) } } } @@ -266,8 +266,7 @@ public class FunctionSignatureRule : private fun calculateFunctionSignatureLengthAsSingleLineSignature( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Int { val actualFunctionSignatureLength = node.getFunctionSignatureLength() @@ -275,9 +274,9 @@ public class FunctionSignatureRule : // maximum line length). The white space correction will be calculated via a dry run of the actual fix. return actualFunctionSignatureLength + // Calculate the white space correction in case the signature would be rewritten to a single line - fixWhiteSpacesInValueParameterList(node, emit, autoCorrect, multiline = false, dryRun = true) + + fixWhiteSpacesInValueParameterList(node, emit, multiline = false, dryRun = true) + if (node.findChildByType(EQ) == null) { - fixWhitespaceBeforeFunctionBodyBlock(node, emit, autoCorrect, dryRun = true) + fixWhitespaceBeforeFunctionBodyBlock(node, emit, dryRun = true) } else { 0 } @@ -292,8 +291,7 @@ public class FunctionSignatureRule : private fun fixWhiteSpacesInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -308,11 +306,11 @@ public class FunctionSignatureRule : whiteSpaceCorrection += if (firstParameterInList == null) { // handle empty parameter list - fixWhiteSpacesInEmptyValueParameterList(node, emit, autoCorrect, dryRun) + fixWhiteSpacesInEmptyValueParameterList(node, emit, dryRun) } else { - fixWhiteSpacesBeforeFirstParameterInValueParameterList(node, emit, autoCorrect, multiline, dryRun) + - fixWhiteSpacesBetweenParametersInValueParameterList(node, emit, autoCorrect, multiline, dryRun) + - fixWhiteSpaceBeforeClosingParenthesis(node, emit, autoCorrect, multiline, dryRun) + fixWhiteSpacesBeforeFirstParameterInValueParameterList(node, emit, multiline, dryRun) + + fixWhiteSpacesBetweenParametersInValueParameterList(node, emit, multiline, dryRun) + + fixWhiteSpaceBeforeClosingParenthesis(node, emit, multiline, dryRun) } return whiteSpaceCorrection @@ -320,8 +318,7 @@ public class FunctionSignatureRule : private fun fixWhiteSpacesInEmptyValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, dryRun: Boolean, ): Int { var whiteSpaceCorrection = 0 @@ -342,10 +339,9 @@ public class FunctionSignatureRule : whiteSpace.startOffset, "No whitespace expected in empty parameter list", true, - ) - } - if (autoCorrect && !dryRun) { - whiteSpace.treeParent.removeChild(whiteSpace) + ).ifAutocorrectAllowed { + whiteSpace.treeParent.removeChild(whiteSpace) + } } else { whiteSpaceCorrection -= whiteSpace.textLength } @@ -356,8 +352,7 @@ public class FunctionSignatureRule : private fun fixWhiteSpacesBeforeFirstParameterInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -384,10 +379,9 @@ public class FunctionSignatureRule : firstParameterInList.startOffset, "Newline expected after opening parenthesis", true, - ) - } - if (autoCorrect && !dryRun) { - valueParameterList.firstChildNode.upsertWhitespaceAfterMe(expectedParameterIndent) + ).ifAutocorrectAllowed { + valueParameterList.firstChildNode.upsertWhitespaceAfterMe(expectedParameterIndent) + } } else { whiteSpaceCorrection += expectedParameterIndent.length - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -399,10 +393,9 @@ public class FunctionSignatureRule : firstParameter!!.startOffset, "No whitespace expected between opening parenthesis and first parameter name", true, - ) - } - if (autoCorrect && !dryRun) { - whiteSpaceBeforeIdentifier.treeParent.removeChild(whiteSpaceBeforeIdentifier) + ).ifAutocorrectAllowed { + whiteSpaceBeforeIdentifier.treeParent.removeChild(whiteSpaceBeforeIdentifier) + } } else { whiteSpaceCorrection -= whiteSpaceBeforeIdentifier.textLength } @@ -415,8 +408,7 @@ public class FunctionSignatureRule : private fun fixWhiteSpacesBetweenParametersInValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -448,10 +440,9 @@ public class FunctionSignatureRule : valueParameter.startOffset, "Parameter should start on a newline", true, - ) - } - if (autoCorrect && !dryRun) { - firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(expectedParameterIndent) + ).ifAutocorrectAllowed { + firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(expectedParameterIndent) + } } else { whiteSpaceCorrection += expectedParameterIndent.length - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -463,10 +454,9 @@ public class FunctionSignatureRule : firstChildNodeInValueParameter!!.startOffset, "Single whitespace expected before parameter", true, - ) - } - if (autoCorrect && !dryRun) { - firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(" ") + ).ifAutocorrectAllowed { + firstChildNodeInValueParameter.upsertWhitespaceBeforeMe(" ") + } } else { whiteSpaceCorrection += 1 - (whiteSpaceBeforeIdentifier?.textLength ?: 0) } @@ -480,8 +470,7 @@ public class FunctionSignatureRule : private fun fixWhiteSpaceBeforeClosingParenthesis( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multiline: Boolean, dryRun: Boolean, ): Int { @@ -504,10 +493,9 @@ public class FunctionSignatureRule : closingParenthesis!!.startOffset, "Newline expected before closing parenthesis", true, - ) - } - if (autoCorrect && !dryRun) { - closingParenthesis!!.upsertWhitespaceBeforeMe(newlineAndIndent) + ).ifAutocorrectAllowed { + closingParenthesis.upsertWhitespaceBeforeMe(newlineAndIndent) + } } else { whiteSpaceCorrection += newlineAndIndent.length - (whiteSpaceBeforeClosingParenthesis?.textLength ?: 0) } @@ -521,10 +509,9 @@ public class FunctionSignatureRule : whiteSpaceBeforeClosingParenthesis.startOffset, "No whitespace expected between last parameter and closing parenthesis", true, - ) - } - if (autoCorrect && !dryRun) { - whiteSpaceBeforeClosingParenthesis.treeParent.removeChild(whiteSpaceBeforeClosingParenthesis) + ).ifAutocorrectAllowed { + whiteSpaceBeforeClosingParenthesis.treeParent.removeChild(whiteSpaceBeforeClosingParenthesis) + } } else { whiteSpaceCorrection -= whiteSpaceBeforeClosingParenthesis.textLength } @@ -536,8 +523,7 @@ public class FunctionSignatureRule : private fun fixFunctionBodyExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, maxLengthRemainingForFirstLineOfBodyExpression: Int, ) { val lastNodeOfFunctionSignatureWithBodyExpression = @@ -581,8 +567,7 @@ public class FunctionSignatureRule : whiteSpaceBeforeFunctionBodyExpression!!.startOffset, "First line of body expression fits on same line as function signature", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (whiteSpaceBeforeFunctionBodyExpression as LeafPsiElement).rawReplaceWithText(" ") } } @@ -599,8 +584,7 @@ public class FunctionSignatureRule : functionBodyExpressionNodes.first().startOffset, "Single whitespace expected before expression body", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { functionBodyExpressionNodes .first() .upsertWhitespaceBeforeMe(" ") @@ -614,8 +598,7 @@ public class FunctionSignatureRule : functionBodyExpressionNodes.first().startOffset, "Newline expected before expression body", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { functionBodyExpressionNodes .first() .upsertWhitespaceBeforeMe(indentConfig.childIndentOf(node)) @@ -649,8 +632,7 @@ public class FunctionSignatureRule : private fun fixWhitespaceBeforeFunctionBodyBlock( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, dryRun: Boolean, ): Int { var whiteSpaceCorrection = 0 @@ -666,9 +648,9 @@ public class FunctionSignatureRule : if (whiteSpaceBeforeBlock == null || whiteSpaceBeforeBlock.text != " ") { if (!dryRun) { emit(block.startOffset, "Expected a single space before body block", true) - } - if (autoCorrect && !dryRun) { - block.upsertWhitespaceBeforeMe(" ") + .ifAutocorrectAllowed { + block.upsertWhitespaceBeforeMe(" ") + } } else { whiteSpaceCorrection += 1 - (whiteSpaceBeforeBlock?.textLength ?: 0) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRule.kt index e703cacb7c..df14d51330 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe @@ -21,33 +23,30 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-body-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == FUN) { node .findChildByType(ElementType.EQ) - ?.let { visitFunctionFollowedByBodyExpression(node, emit, autoCorrect) } + ?.let { visitFunctionFollowedByBodyExpression(node, emit) } node .findChildByType(ElementType.BLOCK) - ?.let { visitFunctionFollowedByBodyBlock(node, emit, autoCorrect) } + ?.let { visitFunctionFollowedByBodyBlock(node, emit) } } } private fun visitFunctionFollowedByBodyExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - fixWhiteSpaceBeforeAssignmentOfBodyExpression(node, emit, autoCorrect) - fixWhiteSpaceBetweenAssignmentAndBodyExpression(node, emit, autoCorrect) + fixWhiteSpaceBeforeAssignmentOfBodyExpression(node, emit) + fixWhiteSpaceBetweenAssignmentAndBodyExpression(node, emit) } private fun fixWhiteSpaceBeforeAssignmentOfBodyExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(ElementType.EQ) @@ -61,15 +60,15 @@ public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-bo assignmentExpression.startOffset, "Expected a single white space before assignment of expression body", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { assignmentExpression.upsertWhitespaceBeforeMe(" ") } + Unit } else if (whiteSpaceBeforeAssignment.text != " ") { emit(whiteSpaceBeforeAssignment.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - assignmentExpression.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + assignmentExpression.upsertWhitespaceBeforeMe(" ") + } } } } @@ -77,8 +76,7 @@ public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-bo private fun fixWhiteSpaceBetweenAssignmentAndBodyExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(ElementType.EQ) @@ -92,8 +90,7 @@ public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-bo assignmentExpression.startOffset, "Expected a single white space between assignment and expression body on same line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { assignmentExpression.upsertWhitespaceAfterMe(" ") } } @@ -103,8 +100,7 @@ public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-bo private fun visitFunctionFollowedByBodyBlock( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(ElementType.BLOCK) @@ -115,12 +111,12 @@ public class FunctionStartOfBodySpacingRule : StandardRule("function-start-of-bo .let { whiteSpaceBeforeExpressionBlock -> if (whiteSpaceBeforeExpressionBlock?.text != " ") { emit(block.startOffset, "Expected a single white space before start of function body", true) - if (autoCorrect) { - block - .firstChildNode - .prevLeaf(true) - ?.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + block + .firstChildNode + .prevLeaf(true) + ?.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule.kt index c14f798fc1..a67760e135 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE @@ -7,6 +8,7 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule 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.SinceKtlint.Status.EXPERIMENTAL +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevSibling import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe @@ -22,8 +24,7 @@ public class FunctionTypeModifierSpacingRule : Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == MODIFIER_LIST } @@ -32,9 +33,9 @@ public class FunctionTypeModifierSpacingRule : ?.takeUnless { it.isPrecededBySingleSpace() } ?.let { functionTypeNode -> emit(functionTypeNode.startOffset, "Expected a single space between the modifier list and the function type", true) - if (autoCorrect) { - functionTypeNode.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + functionTypeNode.upsertWhitespaceBeforeMe(" ") + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt index 5f83c99f3a..2792dd0450 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.NULLABLE_TYPE import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_REFERENCE @@ -8,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.EXPERIMENTAL +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextSibling import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -17,8 +19,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class FunctionTypeReferenceSpacingRule : StandardRule("function-type-reference-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == FUN) { node @@ -28,11 +29,11 @@ public class FunctionTypeReferenceSpacingRule : StandardRule("function-type-refe .firstChildNode .takeIf { it.elementType == NULLABLE_TYPE } ?.let { nullableTypeElement -> - visitNodesUntilStartOfValueParameterList(nullableTypeElement.firstChildNode, emit, autoCorrect) + visitNodesUntilStartOfValueParameterList(nullableTypeElement.firstChildNode, emit) } if (typeReference.elementType != NULLABLE_TYPE) { - visitNodesUntilStartOfValueParameterList(typeReference, emit, autoCorrect) + visitNodesUntilStartOfValueParameterList(typeReference, emit) } } } @@ -52,27 +53,25 @@ public class FunctionTypeReferenceSpacingRule : StandardRule("function-type-refe private fun visitNodesUntilStartOfValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { var currentNode: ASTNode? = node while (currentNode != null && currentNode.elementType != VALUE_PARAMETER_LIST) { val nextNode = currentNode.nextSibling() - removeIfNonEmptyWhiteSpace(currentNode, emit, autoCorrect) + removeIfNonEmptyWhiteSpace(currentNode, emit) currentNode = nextNode } } private fun removeIfNonEmptyWhiteSpace( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == WHITE_SPACE && node.text.isNotEmpty()) { emit(node.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - node.treeParent.removeChild(node) - } + .ifAutocorrectAllowed { + node.treeParent.removeChild(node) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt index d5e8ac318d..22ed6c7a98 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE_KEYWORD @@ -17,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -58,18 +60,16 @@ public class IfElseBracingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == IF) { - visitIfStatement(node, autoCorrect, emit) + visitIfStatement(node, emit) } } private fun visitIfStatement( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val thenNode = requireNotNull(node.findChildByType(THEN)) { @@ -87,11 +87,11 @@ public class IfElseBracingRule : val elseBracing = elseNode.hasBracing() if (parentIfBracing || thenBracing || elseBracing) { if (!thenBracing) { - visitBranchWithoutBraces(thenNode, autoCorrect, emit) + visitBranchWithoutBraces(thenNode, emit) } if (!elseBracing) { if (elseNode.firstChildNode?.elementType != IF) { - visitBranchWithoutBraces(elseNode, autoCorrect, emit) + visitBranchWithoutBraces(elseNode, emit) } else { // Postpone changing the else-if until that node is being processed } @@ -120,15 +120,13 @@ public class IfElseBracingRule : private fun visitBranchWithoutBraces( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Boolean { emit( node.firstChildNode?.startOffset ?: node.startOffset, "All branches of the if statement should be wrapped between braces if at least one branch is wrapped between braces", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { autocorrect(node) } return true diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt index ddeb59210b..477dbf6344 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE @@ -20,6 +21,7 @@ import com.pinterest.ktlint.rule.engine.core.api.children 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -63,37 +65,34 @@ public class IfElseWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when { - node.elementType == IF -> visitIf(node, autoCorrect, emit) + node.elementType == IF -> visitIf(node, emit) node.isPartOfComment() && node.treeParent.elementType == IF -> visitComment(node, emit) } } private fun visitIf( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val outerIf = node.outerIf() val multilineIf = outerIf.textContains('\n') val nestedIf = outerIf.isNestedIf() with(node) { findChildByType(THEN) - ?.let { visitElement(it, autoCorrect, emit, multilineIf, nestedIf) } + ?.let { visitElement(it, emit, multilineIf, nestedIf) } findChildByType(ELSE_KEYWORD) - ?.let { visitElement(it, autoCorrect, emit, multilineIf, nestedIf) } + ?.let { visitElement(it, emit, multilineIf, nestedIf) } findChildByType(ELSE) - ?.let { visitElement(it, autoCorrect, emit, multilineIf, nestedIf) } + ?.let { visitElement(it, emit, multilineIf, nestedIf) } } } private fun visitElement( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multilineIf: Boolean, nestedIf: Boolean, ) { @@ -101,13 +100,13 @@ public class IfElseWrappingRule : visitBranchSingleLineIf(node, emit) } if (multilineIf || nestedIf) { - visitBranch(node, autoCorrect, emit, multilineIf) + visitBranch(node, emit, multilineIf) } } private fun visitBranchSingleLineIf( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(BLOCK) @@ -124,8 +123,7 @@ public class IfElseWrappingRule : private fun visitBranch( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, multilineIf: Boolean, ) { if (multilineIf) { @@ -158,9 +156,9 @@ public class IfElseWrappingRule : ?.let { // Expected a newline with indent. Leave it up to the IndentationRule to determine exact indent emit(startOffset, "Expected a newline", true) - if (autoCorrect) { - upsertWhitespaceBeforeMe(expectedIndent) - } + .ifAutocorrectAllowed { + upsertWhitespaceBeforeMe(expectedIndent) + } } } } @@ -199,7 +197,7 @@ public class IfElseWrappingRule : private fun visitComment( comment: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(comment.isPartOfComment()) if (comment.betweenCodeSiblings(ElementType.RPAR, THEN) || diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt index 6b7be1659e..667ce3f08f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt @@ -1,12 +1,14 @@ 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.ElementType 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.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.ruleset.standard.StandardRule import com.pinterest.ktlint.ruleset.standard.rules.ImportOrderingRule.Companion.ASCII_PATTERN @@ -56,8 +58,7 @@ public class ImportOrderingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.IMPORT_LIST) { val children = node.getChildren(null) @@ -109,14 +110,15 @@ public class ImportOrderingRule : } else { val autoCorrectWhitespace = hasTooMuchWhitespace(children) && !isCustomLayout() val autoCorrectSortOrder = !importsAreEqual(imports, sortedImportsWithSpaces) + var autocorrect = autoCorrectDuplicateImports if (autoCorrectSortOrder || autoCorrectWhitespace) { emit( node.startOffset, ERROR_MESSAGES.getOrDefault(importsLayout, CUSTOM_ERROR_MESSAGE), true, - ) + ).ifAutocorrectAllowed { autocorrect = true } } - if (autoCorrect && (autoCorrectDuplicateImports || autoCorrectSortOrder || autoCorrectWhitespace)) { + if (autocorrect) { node.removeRange(node.firstChildNode, node.lastChildNode.treeNext) sortedImportsWithSpaces.reduce { current, next -> node.addChild(current, null) @@ -134,7 +136,7 @@ public class ImportOrderingRule : private fun getUniqueImportsAndBlankLines( children: Array, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ): Pair> { var autoCorrectDuplicateImports = false val imports = mutableListOf() @@ -150,7 +152,7 @@ public class ImportOrderingRule : imports += current } else { emit(current.startOffset, "Duplicate '${current.text}' found", true) - autoCorrectDuplicateImports = true + .ifAutocorrectAllowed { autoCorrectDuplicateImports = true } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt index 2e1a49debb..57d77f30f3 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt @@ -185,7 +185,7 @@ public class IndentationRule : override fun beforeVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isRoot()) { // File should not start with a whitespace @@ -193,7 +193,7 @@ public class IndentationRule : .nextLeaf() ?.takeIf { it.isWhiteSpaceWithoutNewline() } ?.let { whitespaceWithoutNewline -> - emitAndApprove(node.startOffset, "Unexpected indentation", true) + emit(node.startOffset, "Unexpected indentation", true) .ifAutocorrectAllowed { whitespaceWithoutNewline.treeParent.removeChild(whitespaceWithoutNewline) } } indentContextStack.addLast(startNoIndentZone(node)) @@ -208,7 +208,7 @@ public class IndentationRule : lastIndentContext.copy(activated = true), ) } - visitNewLineIndentation(node, emitAndApprove) + visitNewLineIndentation(node, emit) } node.elementType == CONTEXT_RECEIVER_LIST || @@ -356,7 +356,7 @@ public class IndentationRule : node.elementType == LITERAL_STRING_TEMPLATE_ENTRY && node.nextCodeSibling()?.elementType == CLOSING_QUOTE -> - visitWhiteSpaceBeforeClosingQuote(node, emitAndApprove) + visitWhiteSpaceBeforeClosingQuote(node, emit) node.elementType == WHEN -> visitWhen(node) @@ -1037,7 +1037,7 @@ public class IndentationRule : override fun afterVisitChildNodes( node: ASTNode, - emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { while (indentContextStack.peekLast()?.toASTNode == node) { LOGGER.trace { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocRule.kt index 1c52ff8445..be0a58db15 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE @@ -36,8 +37,7 @@ public class KdocRule : Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == KDOC } @@ -50,6 +50,7 @@ public class KdocRule : false, ) } + Unit } else { if (it.treeParent.elementType == FILE) { emit(node.startOffset, "A dangling toplevel KDoc is not allowed", false) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule.kt index 3aa6005e63..2198a57068 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_END import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_START @@ -10,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -33,8 +35,7 @@ public class KdocWrappingRule : ) { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == KDOC) { node @@ -57,9 +58,9 @@ public class KdocWrappingRule : ?.takeIf { isNonIndentLeafOnSameLine(it) } ?.let { nextLeaf -> emit(nextLeaf.startOffset, "A KDoc comment may not be followed by any other element on that same line", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(node.indent()) - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(node.indent()) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt index fdcbf3a776..c3c3ad5595 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE @@ -69,8 +70,7 @@ public class MaxLineLengthRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isWhiteSpace()) { return diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRule.kt index c3318a5fab..69b8c28e52 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANDAND import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.OPERATION_REFERENCE @@ -23,8 +24,7 @@ public class MixedConditionOperatorsRule : Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.isLogicalBinaryExpression() } @@ -42,7 +42,7 @@ public class MixedConditionOperatorsRule : private fun visitLogicalExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .parent { it.elementType == BINARY_EXPRESSION && it.treeParent.elementType != BINARY_EXPRESSION } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt index 84492bcd70..a3f1b6ab95 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST @@ -9,6 +10,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.nextSibling @@ -25,22 +27,20 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class ModifierListSpacingRule : StandardRule("modifier-list-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == MODIFIER_LIST) { node .children() - .forEach { visitModifierChild(it, autoCorrect, emit) } + .forEach { visitModifierChild(it, emit) } // The whitespace of the last entry of the modifier list is actually placed outside the modifier list - visitModifierChild(node, autoCorrect, emit) + visitModifierChild(node, emit) } } private fun visitModifierChild( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == WHITE_SPACE) { return @@ -60,22 +60,23 @@ public class ModifierListSpacingRule : StandardRule("modifier-list-spacing") { ) { if (whitespace.text.contains("\n\n")) { emit(whitespace.startOffset, "Single newline expected after annotation", true) - if (autoCorrect) { - (whitespace as LeafPsiElement).rawReplaceWithText( - "\n".plus(whitespace.text.substringAfterLast("\n")), - ) - } + .ifAutocorrectAllowed { + (whitespace as LeafPsiElement).rawReplaceWithText( + "\n".plus(whitespace.text.substringAfterLast("\n")), + ) + } } else if (!whitespace.text.contains('\n') && whitespace.text != " ") { emit(whitespace.startOffset, "Single whitespace or newline expected after annotation", true) - if (autoCorrect) { - (whitespace as LeafPsiElement).rawReplaceWithText(" ") - } + .ifAutocorrectAllowed { + (whitespace as LeafPsiElement).rawReplaceWithText(" ") + } } + Unit } else { emit(whitespace.startOffset, "Single whitespace expected after modifier", true) - if (autoCorrect) { - (whitespace as LeafPsiElement).rawReplaceWithText(" ") - } + .ifAutocorrectAllowed { + (whitespace as LeafPsiElement).rawReplaceWithText(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule.kt index dfdafa27b6..fdb2907e54 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ABSTRACT_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ACTUAL_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -29,26 +30,26 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VARARG_KEYWORD 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtDeclarationModifierList -import java.util.Arrays @SinceKtlint("0.7", STABLE) public class ModifierOrderRule : StandardRule("modifier-order") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.psi is KtDeclarationModifierList) { val modifierArr = node.getChildren(tokenSet) val sorted = modifierArr.copyOf().apply { sortWith(compareBy { ORDERED_MODIFIERS.indexOf(it.elementType) }) } - if (!Arrays.equals(modifierArr, sorted)) { + if (!modifierArr.contentEquals(sorted)) { // Since annotations can be fairly lengthy and/or span multiple lines we are // squashing them into a single placeholder text to guarantee a single line output + var autocorrect = false squashAnnotations(sorted) .joinToString(" ") .let { squashedAnnotations -> @@ -56,9 +57,9 @@ public class ModifierOrderRule : StandardRule("modifier-order") { node.startOffset, "Incorrect modifier order (should be \"$squashedAnnotations\")", true, - ) + ).ifAutocorrectAllowed { autocorrect = true } } - if (autoCorrect) { + if (autocorrect) { modifierArr.forEachIndexed { i, n -> node.replaceChild(n, sorted[i].clone() as ASTNode) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRule.kt index c5a1417dcc..c50b5ab687 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION @@ -17,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -55,8 +57,7 @@ public class MultiLineIfElseRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != THEN && node.elementType != ELSE) { return @@ -117,9 +118,9 @@ public class MultiLineIfElseRule : } emit(node.firstChildNode.startOffset, "Missing { ... }", true) - if (autoCorrect) { - autocorrect(node) - } + .ifAutocorrectAllowed { + autocorrect(node) + } } private fun autocorrect(node: ASTNode) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt index 4bd8d07398..79756519e6 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARRAY_ACCESS_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW @@ -37,6 +38,7 @@ 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.firstChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline @@ -77,19 +79,18 @@ public class MultilineExpressionWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType in CHAINABLE_EXPRESSION && !node.isPartOfSpreadOperatorExpression() && (node.treeParent.elementType !in CHAINABLE_EXPRESSION || node.isRightHandSideOfBinaryExpression()) ) { - visitExpression(node, emit, autoCorrect) + visitExpression(node, emit) } if (node.elementType == BINARY_EXPRESSION && node.treeParent.elementType != BINARY_EXPRESSION ) { - visitExpression(node, emit, autoCorrect) + visitExpression(node, emit) } } @@ -99,8 +100,7 @@ public class MultilineExpressionWrappingRule : private fun visitExpression( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.containsWhitespaceWithNewline() && node.needToWrapMultilineExpression()) { node @@ -108,43 +108,43 @@ public class MultilineExpressionWrappingRule : .let { prevLeaf -> if (prevLeaf != null && !prevLeaf.textContains('\n')) { emit(node.startOffset, "A multiline expression should start on a new line", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) - val leafOnSameLineAfterMultilineExpression = - node - .lastChildLeafOrSelf() - .nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() } - ?.takeIf { !it.isWhiteSpaceWithNewline() } - when { - leafOnSameLineAfterMultilineExpression == null -> Unit - - leafOnSameLineAfterMultilineExpression.treeParent.elementType == OPERATION_REFERENCE -> { - // When binary expressions are wrapped, each binary expression for itself is checked whether it is a - // multiline expression. So there is no need to check whether wrapping after the operation reference is - // needed - Unit - } - - leafOnSameLineAfterMultilineExpression.elementType == COMMA && - ( - leafOnSameLineAfterMultilineExpression.treeParent.elementType == VALUE_ARGUMENT_LIST || - leafOnSameLineAfterMultilineExpression.treeParent.elementType == VALUE_PARAMETER_LIST - ) -> { - // Keep comma on same line as multiline expression: - // foo( - // fooBar - // .filter { it.bar }, - // ) - leafOnSameLineAfterMultilineExpression - .nextLeaf() - ?.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) - } - - else -> { - leafOnSameLineAfterMultilineExpression.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + val leafOnSameLineAfterMultilineExpression = + node + .lastChildLeafOrSelf() + .nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() } + ?.takeIf { !it.isWhiteSpaceWithNewline() } + when { + leafOnSameLineAfterMultilineExpression == null -> Unit + + leafOnSameLineAfterMultilineExpression.treeParent.elementType == OPERATION_REFERENCE -> { + // When binary expressions are wrapped, each binary expression for itself is checked whether it is a + // multiline expression. So there is no need to check whether wrapping after the operation reference is + // needed + Unit + } + + leafOnSameLineAfterMultilineExpression.elementType == COMMA && + ( + leafOnSameLineAfterMultilineExpression.treeParent.elementType == VALUE_ARGUMENT_LIST || + leafOnSameLineAfterMultilineExpression.treeParent.elementType == VALUE_PARAMETER_LIST + ) -> { + // Keep comma on same line as multiline expression: + // foo( + // fooBar + // .filter { it.bar }, + // ) + leafOnSameLineAfterMultilineExpression + .nextLeaf() + ?.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + } + + else -> { + leafOnSameLineAfterMultilineExpression.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + } } } - } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule.kt index 2826f77cd6..a99684531f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.DO_KEYWORD @@ -14,6 +15,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -53,8 +55,7 @@ public class MultilineLoopRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == BODY } @@ -70,10 +71,9 @@ public class MultilineLoopRule : !it.treeParent.textContains('\n') } ?: return emit(node.firstChildNode.startOffset, "Missing { ... }", true) - - if (autoCorrect) { - autocorrect(node) - } + .ifAutocorrectAllowed { + autocorrect(node) + } } private fun autocorrect(node: ASTNode) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRule.kt index 15405905fe..f5c0dc58b5 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRule.kt @@ -1,9 +1,11 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -14,8 +16,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoBlankLineBeforeRbraceRule : StandardRule("no-blank-line-before-rbrace") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is PsiWhiteSpace && node.textContains('\n') && @@ -27,8 +28,7 @@ public class NoBlankLineBeforeRbraceRule : StandardRule("no-blank-line-before-rb node.startOffset + split[0].length + split[1].length + 1, "Unexpected blank line(s) before \"}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}") } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRule.kt index 9adfc0aa3b..75a020e810 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.SUPER_TYPE_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST @@ -13,6 +14,7 @@ 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextSibling import com.pinterest.ktlint.rule.engine.core.api.prevSibling import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -27,8 +29,7 @@ public class NoBlankLineInListRule : Rule.OfficialCodeStyle { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != WHITE_SPACE) { return @@ -39,7 +40,7 @@ public class NoBlankLineInListRule : .elementType .takeIf { it in LIST_TYPES } ?.let { treeParentElementType -> - visitWhiteSpace(node, emit, autoCorrect, treeParentElementType) + visitWhiteSpace(node, emit, treeParentElementType) } // Note: depending on the implementation of the list type in the Kotlin language, the whitespace before the first and after the last @@ -55,7 +56,6 @@ public class NoBlankLineInListRule : visitWhiteSpace( node = node, emit = emit, - autoCorrect = autoCorrect, partOfElementType = treeParentElementType, replaceWithSingeSpace = treeParentElementType == TYPE_CONSTRAINT_LIST, ) @@ -74,7 +74,6 @@ public class NoBlankLineInListRule : visitWhiteSpace( node = node, emit = emit, - autoCorrect = autoCorrect, partOfElementType = treeParentElementType, replaceWithSingeSpace = node.nextSibling()?.elementType == CLASS_BODY, ) @@ -83,8 +82,7 @@ public class NoBlankLineInListRule : private fun visitWhiteSpace( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, partOfElementType: IElementType, replaceWithSingeSpace: Boolean = false, ) { @@ -97,8 +95,7 @@ public class NoBlankLineInListRule : node.startOffset + 1, "Unexpected blank line(s) in ${partOfElementType.elementTypeDescription()}", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { if (replaceWithSingeSpace) { (node as LeafPsiElement).rawReplaceWithText(" ") } else { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRule.kt index 3b8cc863b7..67b1ae2419 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRule.kt @@ -1,8 +1,10 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION 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.ifAutocorrectAllowed import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace @@ -12,16 +14,14 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoBlankLinesInChainedMethodCallsRule : StandardRule("no-blank-lines-in-chained-method-calls") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val isBlankLine = node is PsiWhiteSpace && node.getText().contains("\n\n") if (isBlankLine && node.treeParent.elementType == DOT_QUALIFIED_EXPRESSION) { emit(node.startOffset + 1, "Needless blank line(s)", true) - - if (autoCorrect) { - (node as LeafPsiElement).rawReplaceWithText("\n" + node.getText().split("\n\n")[1]) - } + .ifAutocorrectAllowed { + (node as LeafPsiElement).rawReplaceWithText("\n" + node.getText().split("\n\n")[1]) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRule.kt index 1b31726008..5de5953a76 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.PRIMARY_CONSTRUCTOR 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -17,8 +19,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoConsecutiveBlankLinesRule : StandardRule("no-consecutive-blank-lines") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is PsiWhiteSpace && node.prevSibling != null @@ -44,18 +45,18 @@ public class NoConsecutiveBlankLinesRule : StandardRule("no-consecutive-blank-li 2 } emit(offset, "Needless blank line(s)", true) - if (autoCorrect) { - val newText = - buildString { - append(split.first()) - append("\n") - if (!eof && !betweenClassAndPrimaryConstructor) { + .ifAutocorrectAllowed { + val newText = + buildString { + append(split.first()) append("\n") + if (!eof && !betweenClassAndPrimaryConstructor) { + append("\n") + } + append(split.last()) } - append(split.last()) - } - (node as LeafPsiElement).rawReplaceWithText(newText) - } + (node as LeafPsiElement).rawReplaceWithText(newText) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt index 503d04a567..e3816dde27 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_END @@ -29,8 +30,7 @@ public class NoConsecutiveCommentsRule : Rule.OfficialCodeStyle { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.isStartOfComment() } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRule.kt index fe43aca326..3fbabcd793 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE @@ -8,6 +9,7 @@ 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.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -18,8 +20,7 @@ import org.jetbrains.kotlin.psi.KtObjectLiteralExpression public class NoEmptyClassBodyRule : StandardRule("no-empty-class-body") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == CLASS_BODY && node.firstChildNode?.let { n -> @@ -34,15 +35,15 @@ public class NoEmptyClassBodyRule : StandardRule("no-empty-class-body") { .none { it.text == "companion" } ) { emit(node.startOffset, "Unnecessary block (\"{}\")", true) - if (autoCorrect) { - val prevNode = node.treePrev - if (prevNode.elementType == WHITE_SPACE) { - // remove space between declaration and block - prevNode.treeParent.removeChild(prevNode) + .ifAutocorrectAllowed { + val prevNode = node.treePrev + if (prevNode.elementType == WHITE_SPACE) { + // remove space between declaration and block + prevNode.treeParent.removeChild(prevNode) + } + // remove block + node.treeParent.removeChild(node) } - // remove block - node.treeParent.removeChild(node) - } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRule.kt index 4da4869c08..b76ac6b8fd 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint @@ -17,8 +18,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class NoEmptyFileRule : StandardRule(id = "no-empty-file") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.isRoot() } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRule.kt index 9a280e1e79..ecf1ff3b03 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.Rule @@ -10,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -40,8 +42,7 @@ public class NoEmptyFirstLineInClassBodyRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == CLASS_BODY) { node @@ -58,8 +59,7 @@ public class NoEmptyFirstLineInClassBodyRule : whitespace.startOffset + 1, "Class body should not start with blank line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (whitespace as LeafPsiElement).rawReplaceWithText(indentConfig.childIndentOf(node)) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRule.kt index b7f3ffb6ce..fd317e95b5 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE @@ -7,6 +8,7 @@ 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -19,8 +21,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoEmptyFirstLineInMethodBlockRule : StandardRule("no-empty-first-line-in-method-block") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isWhiteSpaceWithNewline() && node.prevLeaf()?.elementType == LBRACE && @@ -35,8 +36,7 @@ public class NoEmptyFirstLineInMethodBlockRule : StandardRule("no-empty-first-li node.startOffset + 1, "First line in a method block should not be empty", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}") } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRule.kt index ea83c56504..387f89ab04 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.IF_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -17,8 +19,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoLineBreakAfterElseRule : StandardRule("no-line-break-after-else") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is PsiWhiteSpace && node.textContains('\n') @@ -27,9 +28,9 @@ public class NoLineBreakAfterElseRule : StandardRule("no-line-break-after-else") node.nextLeaf()?.elementType.let { it == IF_KEYWORD || it == LBRACE } ) { emit(node.startOffset + 1, "Unexpected line break after \"else\"", true) - if (autoCorrect) { - (node as LeafPsiElement).rawReplaceWithText(" ") - } + .ifAutocorrectAllowed { + (node as LeafPsiElement).rawReplaceWithText(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt index 6d4b463c8d..a155a95283 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt @@ -1,9 +1,11 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -19,49 +21,47 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings public class NoLineBreakBeforeAssignmentRule : StandardRule("no-line-break-before-assignment") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == EQ) { - visitEquals(node, emit, autoCorrect) + visitEquals(node, emit) } } private fun visitEquals( assignmentNode: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { assignmentNode .prevSibling() .takeIf { it.isWhiteSpaceWithNewline() } ?.let { unexpectedNewlineBeforeAssignment -> emit(unexpectedNewlineBeforeAssignment.startOffset, "Line break before assignment is not allowed", true) - if (autoCorrect) { - val parent = assignmentNode.treeParent - // Insert assignment surrounded by whitespaces at new position - assignmentNode - .siblings(false) - .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } - .last() - .let { before -> - if (!before.prevSibling().isWhiteSpace()) { - parent.addChild(PsiWhiteSpaceImpl(" "), before) + .ifAutocorrectAllowed { + val parent = assignmentNode.treeParent + // Insert assignment surrounded by whitespaces at new position + assignmentNode + .siblings(false) + .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } + .last() + .let { before -> + if (!before.prevSibling().isWhiteSpace()) { + parent.addChild(PsiWhiteSpaceImpl(" "), before) + } + parent.addChild(LeafPsiElement(EQ, "="), before) + if (!before.isWhiteSpace()) { + parent.addChild(PsiWhiteSpaceImpl(" "), before) + } } - parent.addChild(LeafPsiElement(EQ, "="), before) - if (!before.isWhiteSpace()) { - parent.addChild(PsiWhiteSpaceImpl(" "), before) + // Cleanup old assignment and whitespace after it. The indent before the old assignment is kept unchanged + assignmentNode + .nextSibling() + .takeIf { it.isWhiteSpace() } + ?.let { whiteSpaceAfterEquals -> + parent.removeChild(whiteSpaceAfterEquals) } - } - // Cleanup old assignment and whitespace after it. The indent before the old assignment is kept unchanged - assignmentNode - .nextSibling() - .takeIf { it.isWhiteSpace() } - ?.let { whiteSpaceAfterEquals -> - parent.removeChild(whiteSpaceAfterEquals) - } - parent.removeChild(assignmentNode) - } + parent.removeChild(assignmentNode) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRule.kt index a6ceb95499..60b9fab25b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_MARKDOWN_LINK import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_TAG 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace @@ -14,8 +16,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NoMultipleSpacesRule : StandardRule("no-multi-spaces") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node is PsiWhiteSpace } @@ -24,10 +25,10 @@ public class NoMultipleSpacesRule : StandardRule("no-multi-spaces") { val beforeIndentation = node.removeIndentation() if (beforeIndentation.length > 1) { emit(node.startOffset + 1, "Unnecessary long whitespace", true) - if (autoCorrect) { - val remainder = node.text.substring(beforeIndentation.length) - (node as LeafPsiElement).rawReplaceWithText(" $remainder") - } + .ifAutocorrectAllowed { + val remainder = node.text.substring(beforeIndentation.length) + (node as LeafPsiElement).rawReplaceWithText(" $remainder") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt index 007ea1423b..1f82840129 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_KEYWORD @@ -8,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRu 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -41,8 +43,7 @@ public class NoSemicolonsRule : ) { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != SEMICOLON) { return @@ -50,13 +51,13 @@ public class NoSemicolonsRule : val nextLeaf = node.nextLeaf() if (nextLeaf.doesNotRequirePreSemi() && isNoSemicolonRequiredAfter(node)) { emit(node.startOffset, "Unnecessary semicolon", true) - if (autoCorrect) { - val prevLeaf = node.prevLeaf(true) - node.treeParent.removeChild(node) - if (prevLeaf.isWhiteSpace() && (nextLeaf == null || nextLeaf.isWhiteSpace())) { - node.treeParent.removeChild(prevLeaf!!) + .ifAutocorrectAllowed { + val prevLeaf = node.prevLeaf(true) + node.treeParent.removeChild(node) + if (prevLeaf.isWhiteSpace() && (nextLeaf == null || nextLeaf.isWhiteSpace())) { + node.treeParent.removeChild(prevLeaf!!) + } } - } } else if (nextLeaf !is PsiWhiteSpace) { val prevLeaf = node.prevLeaf() if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) { @@ -64,9 +65,9 @@ public class NoSemicolonsRule : } // todo: move to a separate rule emit(node.startOffset + 1, "Missing spacing after \";\"", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRule.kt index 6095a27386..f067ce2be1 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.Rule @@ -11,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf @@ -39,8 +41,7 @@ public class NoSingleLineBlockCommentRule : Rule.OfficialCodeStyle { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == BLOCK_COMMENT) { val afterBlockComment = @@ -54,9 +55,9 @@ public class NoSingleLineBlockCommentRule : afterBlockComment.nextLeaf().isWhitespaceWithNewlineOrNull() ) { emit(node.startOffset, "Replace the block comment with an EOL comment", true) - if (autoCorrect) { - node.replaceWithEndOfLineComment() - } + .ifAutocorrectAllowed { + node.replaceWithEndOfLineComment() + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt index 77886232a0..d0b209e09d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.parent @@ -17,8 +19,7 @@ import org.jetbrains.kotlin.kdoc.psi.api.KDoc public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfKDoc()) { if (node.elementType == WHITE_SPACE && node.hasTrailingSpacesBeforeNewline()) { @@ -30,13 +31,13 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { .dropLastWhile { it == ' ' } .length emit(node.startOffset + offsetOfFirstSpaceBeforeNewlineInText, "Trailing space(s)", true) - if (autoCorrect) { - node.removeTrailingSpacesBeforeNewline() - } + .ifAutocorrectAllowed { + node.removeTrailingSpacesBeforeNewline() + } } } else if (node.elementType == WHITE_SPACE || node.isPartOfComment()) { val lines = node.text.split("\n") - var violated = false + var autocorrect = false var violationOffset = node.startOffset val modifiedLines = @@ -53,7 +54,9 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { val modifiedLine = line.trimEnd() val firstTrailingSpaceOffset = violationOffset + modifiedLine.length emit(firstTrailingSpaceOffset, "Trailing space(s)", true) - violated = true + .ifAutocorrectAllowed { + autocorrect = true + } modifiedLine } @@ -62,7 +65,7 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { violationOffset += line.length + 1 modifiedLine } - if (violated && autoCorrect) { + if (autocorrect) { (node as LeafPsiElement).rawReplaceWithText(modifiedLines.joinToString(separator = "\n")) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRule.kt index d059ad119f..714d425ce2 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_REFERENCE @@ -7,6 +8,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIS 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -15,8 +17,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class NoUnitReturnRule : StandardRule("no-unit-return") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == TYPE_REFERENCE && node.treeParent.elementType == FUN && @@ -24,13 +25,13 @@ public class NoUnitReturnRule : StandardRule("no-unit-return") { node.nextCodeLeaf(skipSubtree = true)?.elementType == LBRACE ) { emit(node.startOffset, "Unnecessary \"Unit\" return type", true) - if (autoCorrect) { - var prevNode = node - while (prevNode.treePrev.elementType != VALUE_PARAMETER_LIST) { - prevNode = prevNode.treePrev + .ifAutocorrectAllowed { + var prevNode = node + while (prevNode.treePrev.elementType != VALUE_PARAMETER_LIST) { + prevNode = prevNode.treePrev + } + node.treeParent.removeRange(prevNode, node.treeNext) } - node.treeParent.removeRange(prevNode, node.treeNext) - } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt index 83ffd8135d..9409ecb331 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BY_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE @@ -11,6 +12,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.REFERENCE_EXPRESSIO 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isRoot import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -47,8 +49,7 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isRoot()) { rootNode = node @@ -64,9 +65,9 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { if (imports.containsKey(importPath)) { // Emit directly when same import occurs more than once emit(node.startOffset, "Unused import", true) - if (autoCorrect) { - node.psi.delete() - } + .ifAutocorrectAllowed { + node.psi.delete() + } } else { imports[importPath] = node } @@ -117,8 +118,7 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { override fun afterVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == FILE) { val directCalls = ref.filter { !it.inDotQualifiedExpression }.map { it.text } @@ -129,10 +129,10 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { importPath.endsWith(".$parent") && directCalls.none { importPath.endsWith(".$it") } }.forEach { (importPath, importNode) -> emit(importNode.startOffset, "Unused import", true) - if (autoCorrect) { - imports.remove(importPath, importNode) - importNode.removeImportDirective() - } + .ifAutocorrectAllowed { + imports.remove(importPath, importNode) + importNode.removeImportDirective() + } } } @@ -150,9 +150,9 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { importPath.substring(packageName.length + 1).indexOf('.') == -1 ) { emit(node.startOffset, "Unnecessary import", true) - if (autoCorrect) { - importDirective.delete() - } + .ifAutocorrectAllowed { + importDirective.delete() + } } else if (name != null && (!ref.map { it.text }.contains(name) || !isAValidImport(importPath)) && !OPERATOR_SET.contains(name) && @@ -160,38 +160,38 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { !importPath.ignoreProvideDelegate() ) { emit(node.startOffset, "Unused import", true) - if (autoCorrect) { - val nextSibling = node.nextSibling() - if (nextSibling == null) { - // Last import - node - .lastChildLeafOrSelf() - .nextLeaf() - ?.takeIf { it.isWhiteSpaceWithNewline() } - ?.let { whitespace -> - if (node.prevLeaf() == null) { - // Also it was the first import, and it is not preceded by any other node containing some text. So - // all whitespace until the next is redundant - whitespace.treeParent.removeChild(whitespace) - } else { - val textAfterFirstNewline = - whitespace - .text - .substringAfter("\n") - if (textAfterFirstNewline.isNotBlank()) { - (whitespace as LeafElement).rawReplaceWithText(textAfterFirstNewline) + .ifAutocorrectAllowed { + val nextSibling = node.nextSibling() + if (nextSibling == null) { + // Last import + node + .lastChildLeafOrSelf() + .nextLeaf() + ?.takeIf { it.isWhiteSpaceWithNewline() } + ?.let { whitespace -> + if (node.prevLeaf() == null) { + // Also it was the first import, and it is not preceded by any other node containing some text. So + // all whitespace until the next is redundant + whitespace.treeParent.removeChild(whitespace) + } else { + val textAfterFirstNewline = + whitespace + .text + .substringAfter("\n") + if (textAfterFirstNewline.isNotBlank()) { + (whitespace as LeafElement).rawReplaceWithText(textAfterFirstNewline) + } } } - } - } else { - nextSibling - .takeIf { it.isWhiteSpaceWithNewline() } - ?.let { whitespace -> - whitespace.treeParent.removeChild(whitespace) - } + } else { + nextSibling + .takeIf { it.isWhiteSpaceWithNewline() } + ?.let { whitespace -> + whitespace.treeParent.removeChild(whitespace) + } + } + importDirective.delete() } - importDirective.delete() - } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt index 153df4f75f..d21a825530 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.IMPORT_DIRECTIVE import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint @@ -29,8 +30,7 @@ public class NoWildcardImportsRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == IMPORT_DIRECTIVE) { val importDirective = node.psi as KtImportDirective diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRule.kt index c93222ec61..5e34601898 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.QUEST import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -16,8 +18,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class NullableTypeSpacingRule : StandardRule("nullable-type-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == QUEST } @@ -25,9 +26,9 @@ public class NullableTypeSpacingRule : StandardRule("nullable-type-spacing") { ?.takeIf { it.elementType == WHITE_SPACE } ?.let { whiteSpaceBeforeQuest -> emit(whiteSpaceBeforeQuest.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - (whiteSpaceBeforeQuest as LeafPsiElement).rawRemove() - } + .ifAutocorrectAllowed { + (whiteSpaceBeforeQuest as LeafPsiElement).rawRemove() + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageNameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageNameRule.kt index 49b9033226..1a9fa01e38 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageNameRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PackageNameRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.PACKAGE_DIRECTIVE import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -17,8 +18,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class PackageNameRule : StandardRule("package-name") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == PACKAGE_DIRECTIVE } @@ -31,6 +31,7 @@ public class PackageNameRule : StandardRule("package-name") { // underscores as well. But as this has been forbidden by KtLint since early versions, this is still // prohibited. emit(expression.startOffset, "Package name must not contain underscore", false) + Unit } else if (!expression.text.matches(VALID_PACKAGE_NAME_REGEXP)) { emit(expression.startOffset, "Package name contains a disallowed character", false) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt index 3e0a0610dc..2d631c4a4a 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA @@ -15,6 +16,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.lineLength @@ -50,18 +52,16 @@ public class ParameterListSpacingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == VALUE_PARAMETER_LIST) { - visitValueParameterList(node, emit, autoCorrect) + visitValueParameterList(node, emit) } } private fun visitValueParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == VALUE_PARAMETER_LIST) val countValueParameters = @@ -80,11 +80,11 @@ public class ParameterListSpacingRule : when (el.elementType) { WHITE_SPACE -> { if (countValueParameters == 0 && node.containsNoComments()) { - removeUnexpectedWhiteSpace(el, emit, autoCorrect) + removeUnexpectedWhiteSpace(el, emit) } else if (valueParameterCount == 0 && el.isNotIndent()) { if (node.containsNoComments()) { // whitespace before first parameter - removeUnexpectedWhiteSpace(el, emit, autoCorrect) + removeUnexpectedWhiteSpace(el, emit) } else { // Avoid conflict with comment spacing rule which requires a whitespace before the // EOL-comment @@ -92,17 +92,17 @@ public class ParameterListSpacingRule : } else if (valueParameterCount == countValueParameters && el.isNotIndent()) { if (node.containsNoComments()) { // whitespace after the last parameter - removeUnexpectedWhiteSpace(el, emit, autoCorrect) + removeUnexpectedWhiteSpace(el, emit) } else { // Avoid conflict with comment spacing rule which requires a whitespace before the // EOL-comment } } else if (el.nextCodeSibling()?.elementType == COMMA) { // No whitespace between parameter name and comma allowed - removeUnexpectedWhiteSpace(el, emit, autoCorrect) + removeUnexpectedWhiteSpace(el, emit) } else if (el.elementType == WHITE_SPACE && el.isNotIndent() && el.isNotSingleSpace()) { require(el.prevCodeSibling()?.elementType == COMMA) - replaceWithSingleSpace(el, emit, autoCorrect) + replaceWithSingleSpace(el, emit) } } @@ -111,12 +111,12 @@ public class ParameterListSpacingRule : el .nextLeaf() ?.takeIf { it.elementType != WHITE_SPACE } - ?.let { addMissingWhiteSpaceAfterMe(el, emit, autoCorrect) } + ?.let { addMissingWhiteSpaceAfterMe(el, emit) } } VALUE_PARAMETER -> { valueParameterCount += 1 - visitValueParameter(el, emit, autoCorrect) + visitValueParameter(el, emit) } } } @@ -126,28 +126,25 @@ public class ParameterListSpacingRule : private fun visitValueParameter( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - visitModifierList(node, emit, autoCorrect) - removeWhiteSpaceBetweenParameterIdentifierAndColon(node, emit, autoCorrect) - fixWhiteSpaceAfterColonInParameter(node, emit, autoCorrect) + visitModifierList(node, emit) + removeWhiteSpaceBetweenParameterIdentifierAndColon(node, emit) + fixWhiteSpaceAfterColonInParameter(node, emit) } private fun visitModifierList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val modifierList = node.findChildByType(MODIFIER_LIST) ?: return - removeWhiteSpaceBetweenModifiersInList(modifierList, emit, autoCorrect) - removeWhiteSpaceBetweenModifierListAndParameterIdentifier(modifierList, emit, autoCorrect) + removeWhiteSpaceBetweenModifiersInList(modifierList, emit) + removeWhiteSpaceBetweenModifierListAndParameterIdentifier(modifierList, emit) } private fun removeWhiteSpaceBetweenModifiersInList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == MODIFIER_LIST) node @@ -155,52 +152,48 @@ public class ParameterListSpacingRule : .filter { it.elementType == WHITE_SPACE } // Store elements in list before changing them as otherwise only the first whitespace is being changed .toList() - .forEach { visitWhiteSpaceAfterModifier(it, emit, autoCorrect) } + .forEach { visitWhiteSpaceAfterModifier(it, emit) } } private fun visitWhiteSpaceAfterModifier( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeUnless { // Ignore when the modifier is an annotation which is placed on a separate line it.isIndent() && it.getPrecedingModifier()?.elementType == ANNOTATION_ENTRY }?.takeIf { it.isNotSingleSpace() } - ?.let { replaceWithSingleSpace(it, emit, autoCorrect) } + ?.let { replaceWithSingleSpace(it, emit) } } private fun removeWhiteSpaceBetweenModifierListAndParameterIdentifier( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == MODIFIER_LIST) node .nextSibling() ?.takeIf { it.elementType == WHITE_SPACE } - ?.let { visitWhiteSpaceAfterModifier(it, emit, autoCorrect) } + ?.let { visitWhiteSpaceAfterModifier(it, emit) } } private fun removeWhiteSpaceBetweenParameterIdentifierAndColon( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(COLON) ?.prevLeaf() ?.takeIf { it.elementType == WHITE_SPACE } ?.let { whiteSpaceBeforeColon -> - removeUnexpectedWhiteSpace(whiteSpaceBeforeColon, emit, autoCorrect) + removeUnexpectedWhiteSpace(whiteSpaceBeforeColon, emit) } } private fun fixWhiteSpaceAfterColonInParameter( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val colonNode = node.findChildByType(COLON) ?: return colonNode @@ -208,7 +201,7 @@ public class ParameterListSpacingRule : ?.takeIf { it.elementType == WHITE_SPACE } .let { whiteSpaceAfterColon -> if (whiteSpaceAfterColon == null) { - addMissingWhiteSpaceAfterMe(colonNode, emit, autoCorrect) + addMissingWhiteSpaceAfterMe(colonNode, emit) } else { if (node.isTypeReferenceWithModifierList() && whiteSpaceAfterColon.isIndent()) { // Allow the type to be wrapped to the next line when it has a modifier: @@ -226,7 +219,7 @@ public class ParameterListSpacingRule : // ) Unit } else if (whiteSpaceAfterColon.isNotSingleSpace()) { - replaceWithSingleSpace(whiteSpaceAfterColon, emit, autoCorrect) + replaceWithSingleSpace(whiteSpaceAfterColon, emit) } } } @@ -234,14 +227,13 @@ public class ParameterListSpacingRule : private fun addMissingWhiteSpaceAfterMe( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == COLON || node.elementType == COMMA) emit(node.startOffset, "Whitespace after '${node.text}' is missing", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } private fun ASTNode.isNotIndent(): Boolean = !isIndent() @@ -258,24 +250,22 @@ public class ParameterListSpacingRule : private fun removeUnexpectedWhiteSpace( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(node.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - (node as LeafElement).rawRemove() - } + .ifAutocorrectAllowed { + (node as LeafElement).rawRemove() + } } private fun replaceWithSingleSpace( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(node.startOffset, "Expected a single space", true) - if (autoCorrect) { - (node as LeafPsiElement).rawReplaceWithText(" ") - } + .ifAutocorrectAllowed { + (node as LeafPsiElement).rawReplaceWithText(" ") + } } private fun ASTNode.getPrecedingModifier(): ASTNode? = diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt index 3ff2a962e0..00cb9efd1c 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_LITERAL @@ -22,6 +23,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -71,19 +73,17 @@ public class ParameterListWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { - NULLABLE_TYPE -> visitNullableType(node, emit, autoCorrect) - VALUE_PARAMETER_LIST -> visitParameterList(node, emit, autoCorrect) + NULLABLE_TYPE -> visitNullableType(node, emit) + VALUE_PARAMETER_LIST -> visitParameterList(node, emit) } } private fun visitNullableType( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == NULLABLE_TYPE) node @@ -101,8 +101,7 @@ public class ParameterListWrappingRule : lpar.startOffset + 1, "Expected new line before function type as it does not fit on a single line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { lpar.upsertWhitespaceAfterMe(indentConfig.childIndentOf(node)) } } @@ -114,8 +113,7 @@ public class ParameterListWrappingRule : rpar.startOffset, "Expected new line after function type as it does not fit on a single line", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { rpar.upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(node)) } } @@ -188,15 +186,14 @@ public class ParameterListWrappingRule : private fun visitParameterList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (isPrecededByComment(node)) { emit(node.startOffset, "Parameter list should not be preceded by a comment", false) } else if (node.needToWrapParameterList()) { node .children() - .forEach { child -> wrapParameterInList(child, emit, autoCorrect) } + .forEach { child -> wrapParameterInList(child, emit) } } } @@ -242,8 +239,7 @@ public class ParameterListWrappingRule : private fun wrapParameterInList( child: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (child.elementType) { LPAR -> { @@ -252,9 +248,9 @@ public class ParameterListWrappingRule : prevLeaf.isWhiteSpaceWithNewline() ) { emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - (prevLeaf as PsiWhiteSpace).delete() - } + .ifAutocorrectAllowed { + (prevLeaf as PsiWhiteSpace).delete() + } } } @@ -275,20 +271,20 @@ public class ParameterListWrappingRule : } else { // The current child needs to be wrapped to a newline. emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - // The indentation is purely based on the previous leaf only. Note that in - // autoCorrect mode the indent rule, if enabled, runs after this rule and - // determines the final indentation. But if the indent rule is disabled then the - // indent of this rule is kept. - (prevLeaf as LeafPsiElement).rawReplaceWithText(intendedIndent) - } + .ifAutocorrectAllowed { + // The indentation is purely based on the previous leaf only. Note that in + // autoCorrect mode the indent rule, if enabled, runs after this rule and + // determines the final indentation. But if the indent rule is disabled then the + // indent of this rule is kept. + (prevLeaf as LeafPsiElement).rawReplaceWithText(intendedIndent) + } } } else { // Insert a new whitespace element in order to wrap the current child to a new line. emit(child.startOffset, errorMessage(child), true) - if (autoCorrect) { - child.treeParent.addChild(PsiWhiteSpaceImpl(intendedIndent), child) - } + .ifAutocorrectAllowed { + child.treeParent.addChild(PsiWhiteSpaceImpl(intendedIndent), child) + } } // Indentation of child nodes need to be fixed by the IndentationRule. } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRule.kt index 35f2dfa9be..2975241c27 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRule.kt @@ -1,6 +1,7 @@ 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.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON @@ -18,6 +19,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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 @@ -68,18 +70,16 @@ public class ParameterWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == VALUE_PARAMETER) { - rearrangeValueParameter(node, autoCorrect, emit) + rearrangeValueParameter(node, emit) } } private fun rearrangeValueParameter( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == VALUE_PARAMETER) @@ -99,7 +99,7 @@ public class ParameterWrappingRule : ?.let { colon -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(colon) > maxLineLength) { fromNode.sumOfTextLengthUntil(colon) - requireNewlineAfterLeaf(colon, autoCorrect, emit) + requireNewlineAfterLeaf(colon, emit) return } } @@ -108,7 +108,7 @@ public class ParameterWrappingRule : .findChildByType(TYPE_REFERENCE) ?.let { typeReference -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(typeReference.orTrailingComma()) > maxLineLength) { - requireNewlineBeforeLeaf(typeReference, autoCorrect, emit) + requireNewlineBeforeLeaf(typeReference, emit) return } } @@ -117,7 +117,7 @@ public class ParameterWrappingRule : .findChildByType(EQ) ?.let { equal -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(equal.orTrailingComma()) > maxLineLength) { - requireNewlineAfterLeaf(equal, autoCorrect, emit) + requireNewlineAfterLeaf(equal, emit) return } } @@ -126,7 +126,7 @@ public class ParameterWrappingRule : .findChildByType(CALL_EXPRESSION) ?.let { callExpression -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(callExpression.orTrailingComma()) > maxLineLength) { - requireNewlineBeforeLeaf(callExpression, autoCorrect, emit) + requireNewlineBeforeLeaf(callExpression, emit) return } } @@ -147,35 +147,37 @@ public class ParameterWrappingRule : private fun requireNewlineBeforeLeaf( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit( node.startOffset - 1, """Missing newline before "${node.text}"""", true, - ) - LOGGER.trace { "$line: " + ((if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}") } - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == AutocorrectDecision.NO_AUTOCORRECT) "would have " else "") + + "inserted newline before ${node.text}" + } + }.ifAutocorrectAllowed { node.upsertWhitespaceBeforeMe(node.indent()) } } private fun requireNewlineAfterLeaf( nodeAfterWhichNewlineIsRequired: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, nodeToFix: ASTNode = nodeAfterWhichNewlineIsRequired, ) { emit( nodeAfterWhichNewlineIsRequired.startOffset + 1, """Missing newline after "${nodeAfterWhichNewlineIsRequired.text}"""", true, - ) - LOGGER.trace { - "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" - } - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == AutocorrectDecision.NO_AUTOCORRECT) "would have " else "") + + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" + } + }.ifAutocorrectAllowed { nodeToFix.upsertWhitespaceAfterMe(indentConfig.childIndentOf(nodeToFix)) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt index 2a0da216ef..2139e0b1fb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONST_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE @@ -30,8 +31,7 @@ import org.jetbrains.kotlin.lexer.KtTokens public class PropertyNamingRule : StandardRule("property-naming") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == PROPERTY } @@ -40,7 +40,7 @@ public class PropertyNamingRule : StandardRule("property-naming") { private fun visitProperty( property: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { property .findChildByType(IDENTIFIER) @@ -64,7 +64,7 @@ public class PropertyNamingRule : StandardRule("property-naming") { private fun visitConstProperty( identifier: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { identifier .text @@ -86,7 +86,7 @@ public class PropertyNamingRule : StandardRule("property-naming") { private fun visitNonConstProperty( identifier: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { identifier .text diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRule.kt index 6e6764c768..b31d69d359 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRule.kt @@ -1,6 +1,8 @@ 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 @@ -16,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPER 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 @@ -65,18 +68,16 @@ public class PropertyWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == PROPERTY) { - rearrangeProperty(node, autoCorrect, emit) + rearrangeProperty(node, emit) } } private fun rearrangeProperty( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == PROPERTY) @@ -96,7 +97,7 @@ public class PropertyWrappingRule : ?.let { colon -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(colon) > maxLineLength) { fromNode.sumOfTextLengthUntil(colon) - requireNewlineAfterLeaf(colon, autoCorrect, emit) + requireNewlineAfterLeaf(colon, emit) return } } @@ -105,7 +106,7 @@ public class PropertyWrappingRule : .findChildByType(TYPE_REFERENCE) ?.let { typeReference -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(typeReference) > maxLineLength) { - requireNewlineBeforeLeaf(typeReference, autoCorrect, emit) + requireNewlineBeforeLeaf(typeReference, emit) return } } @@ -114,7 +115,7 @@ public class PropertyWrappingRule : .findChildByType(EQ) ?.let { equal -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(equal) > maxLineLength) { - requireNewlineAfterLeaf(equal, autoCorrect, emit) + requireNewlineAfterLeaf(equal, emit) return } } @@ -123,7 +124,7 @@ public class PropertyWrappingRule : .findChildByType(CALL_EXPRESSION) ?.let { callExpression -> if (baseIndentLength + fromNode.sumOfTextLengthUntil(callExpression) > maxLineLength) { - requireNewlineBeforeLeaf(callExpression, autoCorrect, emit) + requireNewlineBeforeLeaf(callExpression, emit) return } } @@ -138,35 +139,36 @@ public class PropertyWrappingRule : private fun requireNewlineBeforeLeaf( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit( node.startOffset - 1, """Missing newline before "${node.text}"""", true, - ) - LOGGER.trace { "$line: " + ((if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}") } - if (autoCorrect) { + ).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, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, nodeToFix: ASTNode = nodeAfterWhichNewlineIsRequired, ) { emit( nodeAfterWhichNewlineIsRequired.startOffset + 1, """Missing newline after "${nodeAfterWhichNewlineIsRequired.text}"""", true, - ) - LOGGER.trace { - "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" - } - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") + + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" + } + }.ifAutocorrectAllowed { nodeToFix.upsertWhitespaceAfterMe(indentConfig.childIndentOf(nodeToFix)) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRule.kt index d016ea680d..e49113f200 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER_LIST @@ -9,6 +10,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -20,8 +22,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement public class SpacingAroundAngleBracketsRule : StandardRule("spacing-around-angle-brackets") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != TYPE_PARAMETER_LIST && node.elementType != TYPE_ARGUMENT_LIST) { return @@ -35,9 +36,9 @@ public class SpacingAroundAngleBracketsRule : StandardRule("spacing-around-angle // Ignore when the whitespace is preceded by certain keywords, e.g. fun func(arg: T) {} if (!ELEMENT_TYPES_ALLOWING_PRECEDING_WHITESPACE.contains(beforeLeftAngle.prevLeaf()?.elementType)) { emit(beforeLeftAngle.startOffset, "Unexpected spacing before \"<\"", true) - if (autoCorrect) { - beforeLeftAngle.treeParent.removeChild(beforeLeftAngle) - } + .ifAutocorrectAllowed { + beforeLeftAngle.treeParent.removeChild(beforeLeftAngle) + } } } @@ -45,11 +46,11 @@ public class SpacingAroundAngleBracketsRule : StandardRule("spacing-around-angle val afterLeftAngle = openingBracket.nextLeaf() if (afterLeftAngle?.elementType == WHITE_SPACE) { if (afterLeftAngle.isWhiteSpaceWithoutNewline()) { - // when spacing does not include any new lines, e.g. Map< String, Int> emit(afterLeftAngle.startOffset, "Unexpected spacing after \"<\"", true) - if (autoCorrect) { - afterLeftAngle.treeParent.removeChild(afterLeftAngle) - } + .ifAutocorrectAllowed { + // when spacing does not include any new lines, e.g. Map< String, Int> + afterLeftAngle.treeParent.removeChild(afterLeftAngle) + } } else { // when spacing contains at least one new line, e.g. // SomeGenericType<[whitespace] @@ -59,8 +60,11 @@ public class SpacingAroundAngleBracketsRule : StandardRule("spacing-around-angle // SomeGenericType< // String, Int, String> val newLineWithIndent = afterLeftAngle.text.trimBeforeLastLine() - if (autoCorrect) { - (afterLeftAngle as LeafElement).rawReplaceWithText(newLineWithIndent) + if (newLineWithIndent != afterLeftAngle.text) { + emit(afterLeftAngle.startOffset, "Single newline expected after \"<\"", true) + .ifAutocorrectAllowed { + (afterLeftAngle as LeafElement).rawReplaceWithText(newLineWithIndent) + } } } } @@ -72,11 +76,11 @@ public class SpacingAroundAngleBracketsRule : StandardRule("spacing-around-angle // Check for rogue spacing before a closing bracket if (beforeRightAngle?.elementType == WHITE_SPACE) { if (beforeRightAngle.isWhiteSpaceWithoutNewline()) { - // when spacing does not include any new lines, e.g. Map emit(beforeRightAngle.startOffset, "Unexpected spacing before \">\"", true) - if (autoCorrect) { - beforeRightAngle.treeParent.removeChild(beforeRightAngle) - } + .ifAutocorrectAllowed { + // when spacing does not include any new lines, e.g. Map + beforeRightAngle.treeParent.removeChild(beforeRightAngle) + } } else { // when spacing contains at least one new line, e.g. // SomeGenericType val newLineWithIndent = beforeRightAngle.text.trimBeforeLastLine() - if (autoCorrect) { - (beforeRightAngle as LeafElement).rawReplaceWithText(newLineWithIndent) + if (newLineWithIndent != beforeRightAngle.text) { + emit(beforeRightAngle.startOffset, "Single newline expected before \">\"", true) + .ifAutocorrectAllowed { + (beforeRightAngle as LeafElement).rawReplaceWithText(newLineWithIndent) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt index 5dd34b8650..c79581ec9e 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision 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.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline @@ -34,157 +36,153 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull public class SpacingAroundColonRule : StandardRule("colon-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == COLON) { - removeUnexpectedNewlineBefore(node, emit, autoCorrect) - removeUnexpectedSpacingAround(node, emit, autoCorrect) - addMissingSpacingAround(node, emit, autoCorrect) + removeUnexpectedNewlineBefore(node, emit) + removeUnexpectedSpacingAround(node, emit) + addMissingSpacingAround(node, emit) } } private fun removeUnexpectedNewlineBefore( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val psiParent = node.psi.parent val prevLeaf = node.prevLeaf() if (prevLeaf != null && prevLeaf.isWhiteSpaceWithNewline()) { emit(prevLeaf.startOffset, "Unexpected newline before \":\"", true) - if (autoCorrect) { - val prevNonCodeElements = - node - .siblings(forward = false) - .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } - .toList() - .reversed() - when { - psiParent is KtProperty || psiParent is KtNamedFunction -> { - val equalsSignElement = - node - .siblings(forward = true) - .firstOrNull { it.elementType == EQ } - if (equalsSignElement != null) { - equalsSignElement - .treeNext - ?.let { treeNext -> - prevNonCodeElements.forEach { - node.treeParent.addChild(it, treeNext) - } - if (treeNext.isWhiteSpace()) { - equalsSignElement.treeParent.removeChild(treeNext) - } - Unit - } - } - val blockElement = - node - .siblings(forward = true) - .firstIsInstanceOrNull() - if (blockElement != null) { - val before = - blockElement - .firstChildNode - .nextSibling() - prevNonCodeElements - .let { - if (it.first().isWhiteSpace()) { - blockElement.treeParent.removeChild(it.first()) - it.drop(1) + .ifAutocorrectAllowed { + val prevNonCodeElements = + node + .siblings(forward = false) + .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } + .toList() + .reversed() + when { + psiParent is KtProperty || psiParent is KtNamedFunction -> { + val equalsSignElement = + node + .siblings(forward = true) + .firstOrNull { it.elementType == EQ } + if (equalsSignElement != null) { + equalsSignElement + .treeNext + ?.let { treeNext -> + prevNonCodeElements.forEach { + node.treeParent.addChild(it, treeNext) + } + if (treeNext.isWhiteSpace()) { + equalsSignElement.treeParent.removeChild(treeNext) + } + Unit } - if (it.last().isWhiteSpaceWithNewline()) { - blockElement.treeParent.removeChild(it.last()) - it.dropLast(1) - } else { - it + } + val blockElement = + node + .siblings(forward = true) + .firstIsInstanceOrNull() + if (blockElement != null) { + val before = + blockElement + .firstChildNode + .nextSibling() + prevNonCodeElements + .let { + if (it.first().isWhiteSpace()) { + blockElement.treeParent.removeChild(it.first()) + it.drop(1) + } + if (it.last().isWhiteSpaceWithNewline()) { + blockElement.treeParent.removeChild(it.last()) + it.dropLast(1) + } else { + it + } + }.forEach { + blockElement.addChild(it, before) } - }.forEach { - blockElement.addChild(it, before) - } + } } - } - prevLeaf.prevLeaf()?.isPartOfComment() == true -> { - val nextLeaf = node.nextLeaf() - prevNonCodeElements.forEach { - node.treeParent.addChild(it, nextLeaf) - } - if (nextLeaf != null && nextLeaf.isWhiteSpace()) { - node.treeParent.removeChild(nextLeaf) + prevLeaf.prevLeaf()?.isPartOfComment() == true -> { + val nextLeaf = node.nextLeaf() + prevNonCodeElements.forEach { + node.treeParent.addChild(it, nextLeaf) + } + if (nextLeaf != null && nextLeaf.isWhiteSpace()) { + node.treeParent.removeChild(nextLeaf) + } } - } - else -> { - val text = prevLeaf.text - if (node.spacingBefore) { - (prevLeaf as LeafPsiElement).rawReplaceWithText(" ") - } else { - prevLeaf.treeParent.removeChild(prevLeaf) + else -> { + val text = prevLeaf.text + if (node.spacingBefore) { + (prevLeaf as LeafPsiElement).rawReplaceWithText(" ") + } else { + prevLeaf.treeParent.removeChild(prevLeaf) + } + node.upsertWhitespaceAfterMe(text) } - node.upsertWhitespaceAfterMe(text) } } - } } } private fun removeUnexpectedSpacingAround( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.prevSibling().isWhiteSpaceWithoutNewline() && node.noSpacingBefore) { emit(node.startOffset, "Unexpected spacing before \":\"", true) - if (autoCorrect) { - node - .prevSibling() - ?.let { prevSibling -> - prevSibling.treeParent.removeChild(prevSibling) - } - } + .ifAutocorrectAllowed { + node + .prevSibling() + ?.let { prevSibling -> + prevSibling.treeParent.removeChild(prevSibling) + } + } } if (node.nextSibling().isWhiteSpaceWithoutNewline() && node.spacingAfter) { emit(node.startOffset, "Unexpected spacing after \":\"", true) - if (autoCorrect) { - node - .nextSibling() - ?.let { nextSibling -> - nextSibling.treeParent.removeChild(nextSibling) - } - } + .ifAutocorrectAllowed { + node + .nextSibling() + ?.let { nextSibling -> + nextSibling.treeParent.removeChild(nextSibling) + } + } } } private fun addMissingSpacingAround( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val missingSpacingBefore = !node.prevSibling().isWhiteSpace() && node.spacingBefore val missingSpacingAfter = !node.nextSibling().isWhiteSpace() && node.noSpacingAfter when { missingSpacingBefore && missingSpacingAfter -> { emit(node.startOffset, "Missing spacing around \":\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + node.upsertWhitespaceAfterMe(" ") + } } missingSpacingBefore -> { emit(node.startOffset, "Missing spacing before \":\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + } } missingSpacingAfter -> { emit(node.startOffset + 1, "Missing spacing after \":\"", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRule.kt index efd6a89cc1..a3c59d55df 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.GT import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACKET import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfString import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -26,36 +28,35 @@ public class SpacingAroundCommaRule : StandardRule("comma-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString()) { val prevLeaf = node.prevLeaf() if (prevLeaf is PsiWhiteSpace) { emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true) - if (autoCorrect) { - val isPrecededByComment = prevLeaf.prevLeaf { it !is PsiWhiteSpace } is PsiComment - if (isPrecededByComment && prevLeaf.isWhiteSpaceWithNewline()) { - // If comma is on new line and preceded by a comment, it should be moved before this comment - // https://github.com/pinterest/ktlint/issues/367 - val previousStatement = node.prevCodeLeaf()!! - previousStatement.treeParent.addChild(node.clone(), previousStatement.nextSibling()) - val nextLeaf = node.nextLeaf() - if (nextLeaf is PsiWhiteSpace) { - nextLeaf.treeParent.removeChild(nextLeaf) + .ifAutocorrectAllowed { + val isPrecededByComment = prevLeaf.prevLeaf { it !is PsiWhiteSpace } is PsiComment + if (isPrecededByComment && prevLeaf.isWhiteSpaceWithNewline()) { + // If comma is on new line and preceded by a comment, it should be moved before this comment + // https://github.com/pinterest/ktlint/issues/367 + val previousStatement = node.prevCodeLeaf()!! + previousStatement.treeParent.addChild(node.clone(), previousStatement.nextSibling()) + val nextLeaf = node.nextLeaf() + if (nextLeaf is PsiWhiteSpace) { + nextLeaf.treeParent.removeChild(nextLeaf) + } + node.treeParent.removeChild(node) + } else { + prevLeaf.treeParent.removeChild(prevLeaf) } - node.treeParent.removeChild(node) - } else { - prevLeaf.treeParent.removeChild(prevLeaf) } - } } val nextLeaf = node.nextLeaf() if (nextLeaf !is PsiWhiteSpace && nextLeaf?.elementType !in rTokenSet) { emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true) - if (autoCorrect) { - (node as ASTNode).upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + (node as ASTNode).upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt index 5a9e620dc3..05131d4eb6 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.AT import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLONCOLON @@ -26,6 +27,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERT 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isLeaf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isPartOfString @@ -72,98 +74,103 @@ public class SpacingAroundCurlyRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isLeaf() && !node.isPartOfString()) { val prevLeaf = node.prevLeaf() val nextLeaf = node.nextLeaf() val spacingBefore: Boolean val spacingAfter: Boolean - if (node.elementType == LBRACE) { - spacingBefore = - prevLeaf is PsiWhiteSpace || - prevLeaf?.elementType == AT || - ( - prevLeaf?.elementType == LPAR && - ((node as LeafPsiElement).parent is KtLambdaExpression || node.parent.parent is KtLambdaExpression) - ) - spacingAfter = nextLeaf is PsiWhiteSpace || nextLeaf?.elementType == RBRACE - if (prevLeaf is PsiWhiteSpace && - !prevLeaf.textContains('\n') && - prevLeaf.prevLeaf()?.let { - it.elementType == LPAR || it.elementType == AT - } == true - ) { - emit(node.startOffset, "Unexpected space before \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf.node.treeParent.removeChild(prevLeaf.node) + when (node.elementType) { + LBRACE -> { + spacingBefore = + prevLeaf is PsiWhiteSpace || + prevLeaf?.elementType == AT || + ( + prevLeaf?.elementType == LPAR && + ((node as LeafPsiElement).parent is KtLambdaExpression || node.parent.parent is KtLambdaExpression) + ) + spacingAfter = nextLeaf is PsiWhiteSpace || nextLeaf?.elementType == RBRACE + if (prevLeaf is PsiWhiteSpace && + !prevLeaf.textContains('\n') && + prevLeaf.prevLeaf()?.let { + it.elementType == LPAR || it.elementType == AT + } == true + ) { + emit(node.startOffset, "Unexpected space before \"${node.text}\"", true) + .ifAutocorrectAllowed { + prevLeaf.node.treeParent.removeChild(prevLeaf.node) + } } - } - prevLeaf - ?.takeIf { it.isWhiteSpaceWithNewline() } - ?.takeIf { - prevLeaf.prevLeaf()?.let { it.elementType == RPAR || KtTokens.KEYWORDS.contains(it.elementType) } == true || - node.treeParent.elementType == CLASS_BODY || - // allow newline for lambda return type - (prevLeaf.treeParent.elementType == FUN && prevLeaf.treeNext.elementType != LAMBDA_EXPRESSION) - }?.run { - emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true) - if (autoCorrect) { - if (isPrecededByEolComment()) { - // All consecutive whitespaces and comments preceding the curly have to be moved after the curly brace - leavesIncludingSelf(forward = false) - .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } - .toList() - .reversed() - .takeIf { it.isNotEmpty() } - ?.let { leavesToMoveAfterCurly -> - node.treeParent.addChildren( - leavesToMoveAfterCurly.first(), - leavesToMoveAfterCurly.last(), - node.treeNext, - ) + prevLeaf + ?.takeIf { it.isWhiteSpaceWithNewline() } + ?.takeIf { + prevLeaf.prevLeaf()?.let { it.elementType == RPAR || KtTokens.KEYWORDS.contains(it.elementType) } == true || + node.treeParent.elementType == CLASS_BODY || + // allow newline for lambda return type + (prevLeaf.treeParent.elementType == FUN && prevLeaf.treeNext.elementType != LAMBDA_EXPRESSION) + }?.run { + emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true) + .ifAutocorrectAllowed { + if (isPrecededByEolComment()) { + // All consecutive whitespaces and comments preceding the curly have to be moved after the curly brace + leavesIncludingSelf(forward = false) + .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } + .toList() + .reversed() + .takeIf { it.isNotEmpty() } + ?.let { leavesToMoveAfterCurly -> + node.treeParent.addChildren( + leavesToMoveAfterCurly.first(), + leavesToMoveAfterCurly.last(), + node.treeNext, + ) + } } - } - (this as LeafPsiElement).rawReplaceWithText(" ") + (this as LeafPsiElement).rawReplaceWithText(" ") + } } - } - } else if (node.elementType == RBRACE) { - spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.elementType == LBRACE - spacingAfter = nextLeaf == null || nextLeaf is PsiWhiteSpace || shouldNotToBeSeparatedBySpace(nextLeaf) - nextLeaf - .takeIf { it.isWhiteSpaceWithoutNewline() } - ?.takeIf { shouldNotToBeSeparatedBySpace(it.nextLeaf()) } - ?.let { leaf -> - emit(node.startOffset, "Unexpected space after \"${node.text}\"", true) - if (autoCorrect) { - leaf.treeParent.removeChild(leaf) + } + + RBRACE -> { + spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.elementType == LBRACE + spacingAfter = nextLeaf == null || nextLeaf is PsiWhiteSpace || shouldNotToBeSeparatedBySpace(nextLeaf) + nextLeaf + .takeIf { it.isWhiteSpaceWithoutNewline() } + ?.takeIf { shouldNotToBeSeparatedBySpace(it.nextLeaf()) } + ?.let { leaf -> + emit(node.startOffset, "Unexpected space after \"${node.text}\"", true) + .ifAutocorrectAllowed { + leaf.treeParent.removeChild(leaf) + } } - } - } else { - return + } + + else -> { + return + } } when { !spacingBefore && !spacingAfter -> { emit(node.startOffset, "Missing spacing around \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + node.upsertWhitespaceAfterMe(" ") + } } !spacingBefore -> { emit(node.startOffset, "Missing spacing before \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + } } !spacingAfter -> { emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRule.kt index ac2d3f15b3..33a7278713 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRule.kt @@ -1,8 +1,10 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isPartOfString import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -16,23 +18,22 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class SpacingAroundDotRule : StandardRule("dot-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is LeafPsiElement && node.textMatches(".") && !node.isPartOfString() && !node.isPartOfComment()) { val prevLeaf = node.prevLeaf() if (prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n')) { emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf.node.treeParent.removeChild(prevLeaf.node) - } + .ifAutocorrectAllowed { + prevLeaf.node.treeParent.removeChild(prevLeaf.node) + } } val nextLeaf = node.nextLeaf() if (nextLeaf is PsiWhiteSpace) { emit(nextLeaf.startOffset, "Unexpected spacing after \"${node.text}\"", true) - if (autoCorrect) { - nextLeaf.node.treeParent.removeChild(nextLeaf.node) - } + .ifAutocorrectAllowed { + nextLeaf.node.treeParent.removeChild(nextLeaf.node) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt index 1462c3d322..a1c4d6dfe7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALLABLE_REFERENCE_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_LITERAL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLONCOLON 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -18,8 +20,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == COLONCOLON) { val prevLeaf = node.prevLeaf() @@ -45,24 +46,24 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing") when { spacingBefore && spacingAfter -> { emit(node.startOffset, "Unexpected spacing around \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf!!.removeSelf(removeSingleWhiteSpace) - nextLeaf!!.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + prevLeaf!!.removeSelf(removeSingleWhiteSpace) + nextLeaf!!.treeParent.removeChild(nextLeaf) + } } spacingBefore -> { emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf.removeSelf(removeSingleWhiteSpace) - } + .ifAutocorrectAllowed { + prevLeaf.removeSelf(removeSingleWhiteSpace) + } } spacingAfter -> { emit(nextLeaf!!.startOffset, "Unexpected spacing after \"${node.text}\"", true) - if (autoCorrect) { - nextLeaf.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + nextLeaf.treeParent.removeChild(nextLeaf) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRule.kt index fa773504d7..eb6fd39197 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CATCH_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.DO_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE_KEYWORD @@ -16,6 +17,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe @@ -50,23 +52,22 @@ public class SpacingAroundKeywordRule : StandardRule("keyword-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is LeafPsiElement) { if (tokenSet.contains(node.elementType) && node.parent !is KDocName && node.nextLeaf() !is PsiWhiteSpace) { emit(node.startOffset + node.text.length, "Missing spacing after \"${node.text}\"", true) - if (autoCorrect) { - (node as ASTNode).upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + (node as ASTNode).upsertWhitespaceAfterMe(" ") + } } else if (keywordsWithoutSpaces.contains(node.elementType) && node.nextLeaf() is PsiWhiteSpace) { val parent = node.parent val nextLeaf = node.nextLeaf() if (parent is KtPropertyAccessor && parent.hasBody() && nextLeaf != null) { emit(node.startOffset, "Unexpected spacing after \"${node.text}\"", true) - if (autoCorrect) { - nextLeaf.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + nextLeaf.treeParent.removeChild(nextLeaf) + } } } if (noLFBeforeSet.contains(node.elementType)) { @@ -84,9 +85,9 @@ public class SpacingAroundKeywordRule : StandardRule("keyword-spacing") { (!isElseKeyword || parentOfRBrace.treeParent?.treeParent == node.treeParent) ) { emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true) - if (autoCorrect) { - (prevLeaf as LeafElement).rawReplaceWithText(" ") - } + .ifAutocorrectAllowed { + (prevLeaf as LeafElement).rawReplaceWithText(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt index a50826b942..c05cb938a6 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANDAND import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.DIV @@ -29,6 +30,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.parent @@ -47,8 +49,7 @@ import org.jetbrains.kotlin.psi.KtPrefixExpression public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isUnaryOperator()) { // Allow: @@ -86,24 +87,24 @@ public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { when { !spacingBefore && !spacingAfter -> { emit(node.startOffset, "Missing spacing around \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + node.upsertWhitespaceAfterMe(" ") + } } !spacingBefore -> { emit(node.startOffset, "Missing spacing before \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + } } !spacingAfter -> { emit(node.startOffset + node.textLength, "Missing spacing after \"${node.text}\"", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(" ") + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt index b86eac62c9..171007e505 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE @@ -15,6 +16,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIS 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -31,8 +33,7 @@ import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace public class SpacingAroundParensRule : StandardRule("paren-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == LPAR || node.elementType == RPAR) { val prevLeaf = node.prevLeaf() @@ -68,24 +69,24 @@ public class SpacingAroundParensRule : StandardRule("paren-spacing") { when { spacingBefore && spacingAfter -> { emit(node.startOffset, "Unexpected spacing around \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf!!.treeParent.removeChild(prevLeaf) - nextLeaf!!.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + prevLeaf!!.treeParent.removeChild(prevLeaf) + nextLeaf!!.treeParent.removeChild(nextLeaf) + } } spacingBefore -> { emit(prevLeaf!!.startOffset, "Unexpected spacing before \"${node.text}\"", true) - if (autoCorrect) { - prevLeaf.treeParent.removeChild(prevLeaf) - } + .ifAutocorrectAllowed { + prevLeaf.treeParent.removeChild(prevLeaf) + } } spacingAfter -> { emit(node.startOffset + 1, "Unexpected spacing after \"${node.text}\"", true) - if (autoCorrect) { - nextLeaf!!.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + nextLeaf!!.treeParent.removeChild(nextLeaf) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt index d4293263e5..090e5fc2a7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule.kt @@ -1,10 +1,12 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.RANGE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RANGE_UNTIL 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -16,8 +18,7 @@ import org.jetbrains.kotlin.lexer.KtSingleValueToken public class SpacingAroundRangeOperatorRule : StandardRule("range-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == RANGE || node.elementType == RANGE_UNTIL) { val prevLeaf = node.prevLeaf() @@ -25,24 +26,24 @@ public class SpacingAroundRangeOperatorRule : StandardRule("range-spacing") { when { prevLeaf is PsiWhiteSpace && nextLeaf is PsiWhiteSpace -> { emit(node.startOffset, "Unexpected spacing around \"${node.elementTypeDescription()}\"", true) - if (autoCorrect) { - prevLeaf.node.treeParent.removeChild(prevLeaf.node) - nextLeaf.node.treeParent.removeChild(nextLeaf.node) - } + .ifAutocorrectAllowed { + prevLeaf.node.treeParent.removeChild(prevLeaf.node) + nextLeaf.node.treeParent.removeChild(nextLeaf.node) + } } prevLeaf is PsiWhiteSpace -> { emit(prevLeaf.node.startOffset, "Unexpected spacing before \"${node.elementTypeDescription()}\"", true) - if (autoCorrect) { - prevLeaf.node.treeParent.removeChild(prevLeaf.node) - } + .ifAutocorrectAllowed { + prevLeaf.node.treeParent.removeChild(prevLeaf.node) + } } nextLeaf is PsiWhiteSpace -> { emit(nextLeaf.node.startOffset, "Unexpected spacing after \"${node.elementTypeDescription()}\"", true) - if (autoCorrect) { - nextLeaf.node.treeParent.removeChild(nextLeaf.node) - } + .ifAutocorrectAllowed { + nextLeaf.node.treeParent.removeChild(nextLeaf.node) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt index c7d0dea9f7..28f536d187 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLLECTION_LITERAL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC_MARKDOWN_LINK import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACKET @@ -8,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule 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.SinceKtlint.Status.EXPERIMENTAL +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -25,8 +27,7 @@ public class SpacingAroundSquareBracketsRule : Rule.Experimental { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == LBRACKET || node.elementType == RBRACKET) { val prevLeaf = node.prevLeaf() @@ -76,24 +77,24 @@ public class SpacingAroundSquareBracketsRule : when { spacingBefore && spacingAfter -> { emit(node.startOffset, "Unexpected spacing around '${node.text}'", true) - if (autoCorrect) { - prevLeaf!!.treeParent.removeChild(prevLeaf) - nextLeaf!!.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + prevLeaf!!.treeParent.removeChild(prevLeaf) + nextLeaf!!.treeParent.removeChild(nextLeaf) + } } spacingBefore -> { emit(prevLeaf!!.startOffset, "Unexpected spacing before '${node.text}'", true) - if (autoCorrect) { - prevLeaf.treeParent.removeChild(prevLeaf) - } + .ifAutocorrectAllowed { + prevLeaf.treeParent.removeChild(prevLeaf) + } } spacingAfter -> { emit(node.startOffset + 1, "Unexpected spacing after '${node.text}'", true) - if (autoCorrect) { - nextLeaf!!.treeParent.removeChild(nextLeaf) - } + .ifAutocorrectAllowed { + nextLeaf!!.treeParent.removeChild(nextLeaf) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRule.kt index a8fa5f048d..2d3972042e 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -21,8 +23,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class SpacingAroundUnaryOperatorRule : StandardRule("unary-op-spacing") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.PREFIX_EXPRESSION || node.elementType == ElementType.POSTFIX_EXPRESSION @@ -39,10 +40,9 @@ public class SpacingAroundUnaryOperatorRule : StandardRule("unary-op-spacing") { .firstOrNull { it.isWhiteSpace() } ?: return emit(whiteSpace.startOffset, "Unexpected spacing in ${node.text.replace("\n", "\\n")}", true) - - if (autoCorrect) { - node.removeChild(whiteSpace) - } + .ifAutocorrectAllowed { + node.removeChild(whiteSpace) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRule.kt index 703a0b131e..bb0e59c8c7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +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.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace @@ -28,18 +30,16 @@ import org.jetbrains.kotlin.psi.psiUtil.leaves public class SpacingBetweenDeclarationsWithAnnotationsRule : StandardRule("spacing-between-declarations-with-annotations") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.psi is KtDeclaration && node.isAnnotated()) { - visitDeclaration(node, emit, autoCorrect) + visitDeclaration(node, emit) } } private fun visitDeclaration( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .psi @@ -52,8 +52,7 @@ public class SpacingBetweenDeclarationsWithAnnotationsRule : StandardRule("spaci prevLeaf.startOffset + 1, "Declarations and declarations with annotations should have an empty space between.", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { prevLeaf.upsertWhitespaceBeforeMe("\n".plus(node.indent())) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRule.kt index 960c21c7b3..8663d50e5e 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.prevSibling import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -25,8 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset public class SpacingBetweenDeclarationsWithCommentsRule : StandardRule("spacing-between-declarations-with-comments") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node is PsiComment) { val declaration = node.parent as? KtDeclaration ?: return @@ -43,8 +44,7 @@ public class SpacingBetweenDeclarationsWithCommentsRule : StandardRule("spacing- node.startOffset, "Declarations and declarations with comments should have an empty space between.", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { val indent = node.prevLeaf()?.text?.trim('\n') ?: "" (declaration.prevSibling.node as LeafPsiElement).rawReplaceWithText("\n\n$indent") } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt index cd038ba79f..e4b7c37df7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt @@ -1,11 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE 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.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.nextSibling import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -15,8 +17,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class SpacingBetweenFunctionNameAndOpeningParenthesisRule : StandardRule("spacing-between-function-name-and-opening-parenthesis") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { node.elementType == ElementType.FUN } @@ -25,9 +26,9 @@ public class SpacingBetweenFunctionNameAndOpeningParenthesisRule : StandardRule( ?.takeIf { it.elementType == WHITE_SPACE } ?.let { whiteSpace -> emit(whiteSpace.startOffset, "Unexpected whitespace", true) - if (autoCorrect) { - whiteSpace.treeParent.removeChild(whiteSpace) - } + .ifAutocorrectAllowed { + whiteSpace.treeParent.removeChild(whiteSpace) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt index c96ee71e5d..4c91958844 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW @@ -22,6 +23,7 @@ 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.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.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf @@ -59,30 +61,28 @@ public class StatementWrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { BLOCK -> if (node.treeParent.elementType == FUNCTION_LITERAL) { // LBRACE and RBRACE are outside of BLOCK - visitBlock(node.treeParent, emit, autoCorrect) + visitBlock(node.treeParent, emit) } else { - visitBlock(node, emit, autoCorrect) + visitBlock(node, emit) } CLASS_BODY, WHEN -> - visitBlock(node, emit, autoCorrect) + visitBlock(node, emit) SEMICOLON -> - visitSemiColon(node, autoCorrect, emit) + visitSemiColon(node, emit) } } private fun visitBlock( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeUnless { @@ -113,13 +113,13 @@ public class StatementWrappingRule : val nextCodeLeaf = lbraceOrArrow.nextCodeLeaf() if (nextCodeLeaf != null && noNewLineInClosedRange(lbraceOrArrow, nextCodeLeaf)) { emit(nextCodeLeaf.startOffset, "Missing newline after '${lbraceOrArrow.text}'", true) - if (autoCorrect) { - if (node.elementType == WHEN) { - lbraceOrArrow.upsertWhitespaceAfterMe(lbraceOrArrow.indentAsChild) - } else { - lbraceOrArrow.upsertWhitespaceAfterMe(lbraceOrArrow.indentAsSibling) + .ifAutocorrectAllowed { + if (node.elementType == WHEN) { + lbraceOrArrow.upsertWhitespaceAfterMe(lbraceOrArrow.indentAsChild) + } else { + lbraceOrArrow.upsertWhitespaceAfterMe(lbraceOrArrow.indentAsSibling) + } } - } } node @@ -128,9 +128,9 @@ public class StatementWrappingRule : val prevCodeLeaf = rbrace.prevCodeLeaf() if (prevCodeLeaf != null && noNewLineInClosedRange(prevCodeLeaf, rbrace)) { emit(rbrace.startOffset, "Missing newline before '}'", true) - if (autoCorrect) { - rbrace.upsertWhitespaceBeforeMe(rbrace.indentAsParent) - } + .ifAutocorrectAllowed { + rbrace.upsertWhitespaceBeforeMe(rbrace.indentAsParent) + } } } } @@ -193,8 +193,7 @@ public class StatementWrappingRule : private fun visitSemiColon( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val previousCodeLeaf = node.prevCodeLeaf()?.lastChildLeafOrSelf() ?: return val nextCodeLeaf = node.nextCodeLeaf()?.firstChildLeafOrSelf() ?: return @@ -205,9 +204,9 @@ public class StatementWrappingRule : } if (noNewLineInClosedRange(previousCodeLeaf, nextCodeLeaf)) { emit(node.startOffset + 1, """Missing newline after '${node.text}'""", true) - if (autoCorrect) { - node.upsertWhitespaceAfterMe(previousCodeLeaf.indent()) - } + .ifAutocorrectAllowed { + node.upsertWhitespaceAfterMe(previousCodeLeaf.indent()) + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt index b4d10ee441..0989fdea13 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION @@ -26,6 +27,7 @@ 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.firstChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed 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.nextCodeSibling @@ -89,8 +91,7 @@ public class StringTemplateIndentRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .takeIf { it.elementType == STRING_TEMPLATE } @@ -103,9 +104,9 @@ public class StringTemplateIndentRule : ?.takeUnless { it.isFunctionBodyExpressionOnSameLine() } ?.let { whiteSpace -> emit(stringTemplate.startOffset, "Expected newline before multiline string template", true) - if (autoCorrect) { - whiteSpace.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(whiteSpace.treeParent)) - } + .ifAutocorrectAllowed { + whiteSpace.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(whiteSpace.treeParent)) + } } stringTemplate .getFirstLeafAfterTrimIndent() @@ -116,9 +117,9 @@ public class StringTemplateIndentRule : it.treeParent.elementType == BINARY_EXPRESSION && it.nextSibling()?.elementType == OPERATION_REFERENCE }?.let { nextLeaf -> emit(nextLeaf.startOffset, "Expected newline after multiline string template", true) - if (autoCorrect) { - nextLeaf.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(stringTemplate.treeParent)) - } + .ifAutocorrectAllowed { + nextLeaf.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(stringTemplate.treeParent)) + } } if (stringTemplate.containsMixedIndentationCharacters()) { @@ -134,7 +135,7 @@ public class StringTemplateIndentRule : } val indent = stringTemplate.getIndent() - indentStringTemplate(node, indent, emit, autoCorrect) + indentStringTemplate(node, indent, emit) } } } @@ -219,8 +220,7 @@ public class StringTemplateIndentRule : private fun indentStringTemplate( node: ASTNode, newIndent: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // Get the max prefix length that all lines in the multiline string have in common. All whitespace characters are counted as // one single position. Note that the way of counting should be in sync with the way this is done by the trimIndent @@ -242,7 +242,7 @@ public class StringTemplateIndentRule : .map { it.indentLength() } .minOrNull() ?: 0 - checkAndFixNewLineAfterOpeningQuotes(node, newIndent, emit, autoCorrect) + checkAndFixNewLineAfterOpeningQuotes(node, newIndent, emit) node .children() @@ -271,7 +271,6 @@ public class StringTemplateIndentRule : newIndent = expectedIndent, newContent = currentContent, emit = emit, - autoCorrect = autoCorrect, ) } else if (currentIndent != expectedIndent) { checkAndFixIndent( @@ -279,21 +278,19 @@ public class StringTemplateIndentRule : oldIndentLength = currentIndent.length, newIndent = expectedIndent, newContent = currentContent, - autoCorrect = autoCorrect, emit = emit, ) } } } - checkAndFixNewLineBeforeClosingQuotes(node, newIndent, emit, autoCorrect) + checkAndFixNewLineBeforeClosingQuotes(node, newIndent, emit) } private fun checkAndFixNewLineAfterOpeningQuotes( node: ASTNode, indent: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val firstNodeAfterOpeningQuotes = node.firstChildNode.nextLeaf() ?: return if (firstNodeAfterOpeningQuotes.text.isNotBlank()) { @@ -301,8 +298,7 @@ public class StringTemplateIndentRule : firstNodeAfterOpeningQuotes.startOffset + firstNodeAfterOpeningQuotes.text.length, "Missing newline after the opening quotes of the raw string literal", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (firstNodeAfterOpeningQuotes as LeafPsiElement).rawInsertBeforeMe( LeafPsiElement(REGULAR_STRING_PART, "\n" + indent), ) @@ -315,15 +311,13 @@ public class StringTemplateIndentRule : oldIndent: String, newIndent: String, newContent: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit( node.startOffset + oldIndent.indexOf(wrongIndentChar), "Unexpected '$wrongIndentDescription' character(s) in margin of multiline string", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node.firstChildNode as LeafPsiElement).rawReplaceWithText( newIndent + newContent, ) @@ -335,28 +329,26 @@ public class StringTemplateIndentRule : oldIndentLength: Int, newIndent: String, newContent: String, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { emit(node.startOffset + oldIndentLength, "Unexpected indent of raw string literal", true) - if (autoCorrect) { - if (node.elementType == CLOSING_QUOTE) { - (node as LeafPsiElement).rawInsertBeforeMe( - LeafPsiElement(REGULAR_STRING_PART, newIndent), - ) - } else { - (node.firstChildLeafOrSelf() as LeafPsiElement).rawReplaceWithText( - newIndent + newContent, - ) + .ifAutocorrectAllowed { + if (node.elementType == CLOSING_QUOTE) { + (node as LeafPsiElement).rawInsertBeforeMe( + LeafPsiElement(REGULAR_STRING_PART, newIndent), + ) + } else { + (node.firstChildLeafOrSelf() as LeafPsiElement).rawReplaceWithText( + newIndent + newContent, + ) + } } - } } private fun checkAndFixNewLineBeforeClosingQuotes( node: ASTNode, indent: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val lastNodeBeforeClosingQuotes = node.lastChildNode.prevLeaf() ?: return if (lastNodeBeforeClosingQuotes.text.isNotBlank()) { @@ -364,8 +356,7 @@ public class StringTemplateIndentRule : lastNodeBeforeClosingQuotes.startOffset + lastNodeBeforeClosingQuotes.text.length, "Missing newline before the closing quotes of the raw string literal", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (lastNodeBeforeClosingQuotes as LeafPsiElement).rawInsertAfterMe( LeafPsiElement(REGULAR_STRING_PART, "\n" + indent), ) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt index 9f66ecf17c..68f3102e09 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLOSING_QUOTE import com.pinterest.ktlint.rule.engine.core.api.ElementType.LITERAL_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_STRING_TEMPLATE_ENTRY @@ -8,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_TEMPLATE_ENTRY 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.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory @@ -28,8 +30,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildOfType public class StringTemplateRule : StandardRule("string-template") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val elementType = node.elementType // code below is commented out because (setting aside potentially dangerous replaceChild part) @@ -50,8 +51,7 @@ public class StringTemplateRule : StandardRule("string-template") { entryExpression.operationTokenNode.startOffset, "Redundant \"toString()\" call in string template", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { entryExpression .node .let { entryExpressionNode -> @@ -75,9 +75,9 @@ public class StringTemplateRule : StandardRule("string-template") { } ) { emit(node.treePrev.startOffset + 2, "Redundant curly braces", true) - if (autoCorrect) { - node.removeCurlyBracesIfRedundant() - } + .ifAutocorrectAllowed { + node.removeCurlyBracesIfRedundant() + } } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt index 85e8b090f6..40e123e356 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLLECTION_LITERAL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA @@ -15,6 +16,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.nextSibling @@ -56,24 +58,22 @@ public class TrailingCommaOnCallSiteRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // Keep processing of element types in sync with Intellij Kotlin formatting settings. // https://github.com/JetBrains/intellij-kotlin/blob/master/formatter/src/org/jetbrains/kotlin/idea/formatter/trailingComma/util.kt when (node.elementType) { - COLLECTION_LITERAL_EXPRESSION -> visitCollectionLiteralExpression(node, autoCorrect, emit) - INDICES -> visitIndices(node, autoCorrect, emit) - TYPE_ARGUMENT_LIST -> visitTypeList(node, autoCorrect, emit) - VALUE_ARGUMENT_LIST -> visitValueList(node, autoCorrect, emit) + COLLECTION_LITERAL_EXPRESSION -> visitCollectionLiteralExpression(node, emit) + INDICES -> visitIndices(node, emit) + TYPE_ARGUMENT_LIST -> visitTypeList(node, emit) + VALUE_ARGUMENT_LIST -> visitValueList(node, emit) else -> Unit } } private fun visitCollectionLiteralExpression( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -83,7 +83,6 @@ public class TrailingCommaOnCallSiteRule : inspectNode = inspectNode, emit = emit, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, ) } @@ -91,8 +90,7 @@ public class TrailingCommaOnCallSiteRule : private fun visitIndices( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -102,14 +100,12 @@ public class TrailingCommaOnCallSiteRule : inspectNode = inspectNode, emit = emit, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, ) } private fun visitValueList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.treeParent.elementType != ElementType.FUNCTION_LITERAL) { node @@ -120,7 +116,6 @@ public class TrailingCommaOnCallSiteRule : inspectNode = inspectNode, emit = emit, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, ) } } @@ -128,8 +123,7 @@ public class TrailingCommaOnCallSiteRule : private fun visitTypeList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -139,15 +133,13 @@ public class TrailingCommaOnCallSiteRule : inspectNode = inspectNode, emit = emit, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, ) } private fun ASTNode.reportAndCorrectTrailingCommaNodeBefore( inspectNode: ASTNode, isTrailingCommaAllowed: Boolean, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val prevLeaf = inspectNode.prevLeaf() val trailingCommaNode = prevLeaf?.findPreviousTrailingCommaNodeOrNull() @@ -163,8 +155,7 @@ public class TrailingCommaOnCallSiteRule : trailingCommaNode!!.startOffset, "Unnecessary trailing comma before \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { this.removeChild(trailingCommaNode) } } @@ -176,8 +167,7 @@ public class TrailingCommaOnCallSiteRule : prevNode.startOffset + prevNode.textLength, "Missing trailing comma before \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { inspectNode .prevCodeSibling() ?.nextSibling() @@ -192,8 +182,7 @@ public class TrailingCommaOnCallSiteRule : trailingCommaNode!!.startOffset, "Unnecessary trailing comma before \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { this.removeChild(trailingCommaNode) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt index cf07130557..31ac49a2c2 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS @@ -21,6 +22,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty +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.isCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -80,26 +82,24 @@ public class TrailingCommaOnDeclarationSiteRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // Keep processing of element types in sync with Intellij Kotlin formatting settings. // https://github.com/JetBrains/intellij-kotlin/blob/master/formatter/src/org/jetbrains/kotlin/idea/formatter/trailingComma/util.kt when (node.elementType) { - CLASS -> visitClass(node, emit, autoCorrect) - DESTRUCTURING_DECLARATION -> visitDestructuringDeclaration(node, autoCorrect, emit) - FUNCTION_LITERAL -> visitFunctionLiteral(node, autoCorrect, emit) - TYPE_PARAMETER_LIST -> visitTypeList(node, autoCorrect, emit) - VALUE_PARAMETER_LIST -> visitValueList(node, autoCorrect, emit) - WHEN_ENTRY -> visitWhenEntry(node, autoCorrect, emit) + CLASS -> visitClass(node, emit) + DESTRUCTURING_DECLARATION -> visitDestructuringDeclaration(node, emit) + FUNCTION_LITERAL -> visitFunctionLiteral(node, emit) + TYPE_PARAMETER_LIST -> visitTypeList(node, emit) + VALUE_PARAMETER_LIST -> visitValueList(node, emit) + WHEN_ENTRY -> visitWhenEntry(node, emit) else -> Unit } } private fun visitDestructuringDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -108,7 +108,6 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = inspectNode, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } @@ -117,8 +116,7 @@ public class TrailingCommaOnDeclarationSiteRule : private fun visitFunctionLiteral( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -129,15 +127,13 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = inspectNode, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } private fun visitValueList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.treeParent.elementType != FUNCTION_LITERAL) { node @@ -147,7 +143,6 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = inspectNode, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } @@ -156,8 +151,7 @@ public class TrailingCommaOnDeclarationSiteRule : private fun visitTypeList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val inspectNode = node @@ -166,15 +160,13 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = inspectNode, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } private fun visitWhenEntry( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val psi = node.psi require(psi is KtWhenEntry) @@ -190,15 +182,13 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = inspectNode, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } private fun visitClass( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val psi = node.psi require(psi is KtClass) @@ -216,7 +206,6 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = nodeAfterLastEnumEntry, isTrailingCommaAllowed = false, - autoCorrect = autoCorrect, emit = emit, ) } @@ -225,7 +214,6 @@ public class TrailingCommaOnDeclarationSiteRule : node.reportAndCorrectTrailingCommaNodeBefore( inspectNode = nodeAfterLastEnumEntry, isTrailingCommaAllowed = node.isTrailingCommaAllowed(), - autoCorrect = autoCorrect, emit = emit, ) } @@ -262,8 +250,7 @@ public class TrailingCommaOnDeclarationSiteRule : private fun ASTNode.reportAndCorrectTrailingCommaNodeBefore( inspectNode: ASTNode, isTrailingCommaAllowed: Boolean, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val prevLeaf = inspectNode.prevLeaf() val trailingCommaNode = prevLeaf?.findPreviousTrailingCommaNodeOrNull() @@ -286,8 +273,7 @@ public class TrailingCommaOnDeclarationSiteRule : trailingCommaNode!!.startOffset, "Expected a newline between the trailing comma and \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { lastNodeBeforeArrow.upsertWhitespaceAfterMe(inspectNode.treeParent.indent()) } } @@ -297,8 +283,7 @@ public class TrailingCommaOnDeclarationSiteRule : trailingCommaNode!!.startOffset, "Unnecessary trailing comma before \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { this.removeChild(trailingCommaNode) } } @@ -323,8 +308,7 @@ public class TrailingCommaOnDeclarationSiteRule : "Missing trailing comma before \"${inspectNode.text}\"", true, ) - } - if (autoCorrect) { + }.ifAutocorrectAllowed { if (addNewLine) { val indent = prevNode @@ -368,8 +352,7 @@ public class TrailingCommaOnDeclarationSiteRule : trailingCommaNode!!.startOffset, "Unnecessary trailing comma before \"${inspectNode.text}\"", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { this.removeChild(trailingCommaNode) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt index 13938a6547..2859e30a5d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.CATCH import com.pinterest.ktlint.rule.engine.core.api.ElementType.FINALLY @@ -16,6 +17,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.nextSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -53,8 +55,7 @@ public class TryCatchFinallySpacingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfComment() && node.treeParent.elementType in TRY_CATCH_FINALLY_TOKEN_SET) { emit(node.startOffset, "No comment expected at this location", false) @@ -62,19 +63,18 @@ public class TryCatchFinallySpacingRule : } when (node.elementType) { BLOCK -> { - visitBlock(node, emit, autoCorrect) + visitBlock(node, emit) } CATCH, FINALLY -> { - visitClause(node, emit, autoCorrect) + visitClause(node, emit) } } } private fun visitBlock( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.treeParent.elementType !in TRY_CATCH_FINALLY_TOKEN_SET) { return @@ -86,9 +86,9 @@ public class TryCatchFinallySpacingRule : val nextSibling = lbrace.nextSibling { !it.isPartOfComment() }!! if (!nextSibling.text.startsWith("\n")) { emit(lbrace.startOffset + 1, "Expected a newline after '{'", true) - if (autoCorrect) { - lbrace.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + lbrace.upsertWhitespaceAfterMe(indentConfig.siblingIndentOf(node)) + } } } @@ -98,24 +98,23 @@ public class TryCatchFinallySpacingRule : val prevSibling = rbrace.prevSibling { !it.isPartOfComment() }!! if (!prevSibling.text.startsWith("\n")) { emit(rbrace.startOffset, "Expected a newline before '}'", true) - if (autoCorrect) { - rbrace.upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(node)) - } + .ifAutocorrectAllowed { + rbrace.upsertWhitespaceBeforeMe(indentConfig.parentIndentOf(node)) + } } } } private fun visitClause( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val prevLeaf = node.prevLeaf { !it.isPartOfComment() }!! if (prevLeaf.text != " ") { emit(node.startOffset, "A single space is required before '${node.elementTypeName()}'", true) - if (autoCorrect) { - node.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + node.upsertWhitespaceBeforeMe(" ") + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt index a6049632c6..3e60ccd8bf 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST @@ -25,8 +26,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet public class TypeArgumentCommentRule : StandardRule("type-argument-comment") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfComment() && node.treeParent.elementType in typeArgumentTokenSet) { when (node.elementType) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt index 3f9c2dbf57..26059894aa 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -10,6 +11,7 @@ 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.findCompositeParentElementOfType +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOfCompositeElementOfType import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline @@ -48,31 +50,29 @@ public class TypeArgumentListSpacingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { ElementType.TYPE_ARGUMENT_LIST -> { - visitFunctionDeclaration(node, autoCorrect, emit) - visitInsideTypeArgumentList(node, autoCorrect, emit) + visitFunctionDeclaration(node, emit) + visitInsideTypeArgumentList(node, emit) } ElementType.SUPER_TYPE_LIST, ElementType.SUPER_EXPRESSION -> - visitInsideTypeArgumentList(node, autoCorrect, emit) + visitInsideTypeArgumentList(node, emit) } } private fun visitFunctionDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // No whitespace expected before type argument list of function call // val list = listOf () node .prevLeaf(includeEmpty = true) ?.takeIf { it.elementType == ElementType.WHITE_SPACE } - ?.let { noWhitespaceExpected(it, autoCorrect, emit) } + ?.let { noWhitespaceExpected(it, emit) } // No whitespace expected after type argument list of function call // val list = listOf () @@ -89,13 +89,12 @@ public class TypeArgumentListSpacingRule : }?.lastChildNode ?.nextLeaf(includeEmpty = true) ?.takeIf { it.elementType == ElementType.WHITE_SPACE } - ?.let { noWhitespaceExpected(it, autoCorrect, emit) } + ?.let { noWhitespaceExpected(it, emit) } } private fun visitInsideTypeArgumentList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val multiline = node.textContains('\n') val expectedIndent = @@ -113,9 +112,9 @@ public class TypeArgumentListSpacingRule : if (nextSibling.text != expectedIndent) { if (nextSibling.isWhiteSpaceWithoutNewline()) { emit(nextSibling.startOffset, "Expected newline", true) - if (autoCorrect) { - nextSibling.upsertWhitespaceAfterMe(expectedIndent) - } + .ifAutocorrectAllowed { + nextSibling.upsertWhitespaceAfterMe(expectedIndent) + } } else { // Let Indentation rule fix the indentation } @@ -124,7 +123,7 @@ public class TypeArgumentListSpacingRule : if (nextSibling.isWhiteSpace()) { // Disallow // val list = listOf< String>() - noWhitespaceExpected(nextSibling, autoCorrect, emit) + noWhitespaceExpected(nextSibling, emit) } } } @@ -137,9 +136,9 @@ public class TypeArgumentListSpacingRule : if (prevSibling.text != expectedIndent) { if (prevSibling.isWhiteSpaceWithoutNewline()) { emit(prevSibling.startOffset, "Expected newline", true) - if (autoCorrect) { - prevSibling.upsertWhitespaceBeforeMe(expectedIndent) - } + .ifAutocorrectAllowed { + prevSibling.upsertWhitespaceBeforeMe(expectedIndent) + } } else { // Let Indentation rule fix the indentation } @@ -148,7 +147,7 @@ public class TypeArgumentListSpacingRule : if (prevSibling.isWhiteSpace()) { // Disallow // val list = listOf() - noWhitespaceExpected(prevSibling, autoCorrect, emit) + noWhitespaceExpected(prevSibling, emit) } } } @@ -156,16 +155,14 @@ public class TypeArgumentListSpacingRule : private fun noWhitespaceExpected( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.text != "") { emit( node.startOffset, "No whitespace expected at this position", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node.treeParent.removeChild(node) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt index 1a9e7a41bd..e8e4999b6e 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER @@ -25,8 +26,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet public class TypeParameterCommentRule : StandardRule("type-parameter-comment") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfComment() && node.treeParent.elementType in typeParameterTokenSet) { when (node.elementType) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt index 250182acd2..568724b72d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONSTRUCTOR_KEYWORD @@ -19,6 +20,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE 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.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithoutNewline import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling @@ -57,32 +59,30 @@ public class TypeParameterListSpacingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType != TYPE_PARAMETER_LIST) { return } when (node.treeParent.elementType) { - CLASS -> visitClassDeclaration(node, autoCorrect, emit) - TYPEALIAS -> visitTypeAliasDeclaration(node, autoCorrect, emit) - FUN -> visitFunctionDeclaration(node, autoCorrect, emit) + CLASS -> visitClassDeclaration(node, emit) + TYPEALIAS -> visitTypeAliasDeclaration(node, emit) + FUN -> visitFunctionDeclaration(node, emit) } - visitInsideTypeParameterList(node, autoCorrect, emit) + visitInsideTypeParameterList(node, emit) } private fun visitClassDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // No white space expected between class name and parameter list // class Bar node .prevSibling() ?.takeIf { it.elementType == WHITE_SPACE } - ?.let { visitWhitespace(it, autoCorrect, emit) } + ?.let { visitWhitespace(it, emit) } // No white space expected between parameter type list and the constructor except when followed by compound // constructor @@ -116,14 +116,14 @@ public class TypeParameterListSpacingRule : // class Bar @SomeAnnotation constructor(...) if (whiteSpace.text != " ") { emit(whiteSpace.startOffset, "Expected a single space", true) - if (autoCorrect) { - // If line is to be wrapped this should have been done by other rules before running this rule - whiteSpace.upsertWhitespaceBeforeMe(" ") - } + .ifAutocorrectAllowed { + // If line is to be wrapped this should have been done by other rules before running this rule + whiteSpace.upsertWhitespaceBeforeMe(" ") + } } } } else { - visitWhitespace(whiteSpace, autoCorrect, emit) + visitWhitespace(whiteSpace, emit) } } @@ -132,33 +132,31 @@ public class TypeParameterListSpacingRule : node .nextSibling() ?.takeIf { it.elementType == WHITE_SPACE && it.nextCodeSibling()?.elementType == CLASS_BODY } - ?.let { singleSpaceExpected(it, autoCorrect, emit) } + ?.let { singleSpaceExpected(it, emit) } } private fun visitTypeAliasDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // No white space expected between typealias keyword name and parameter list // typealias Bar node .prevSibling() ?.takeIf { it.elementType == WHITE_SPACE } - ?.let { visitWhitespace(it, autoCorrect, emit) } + ?.let { visitWhitespace(it, emit) } // No white space expected between parameter type list and equals sign // typealias Bar = ... node .nextSibling() ?.takeIf { it.elementType == WHITE_SPACE && it.nextCodeSibling()?.elementType == EQ } - ?.let { singleSpaceExpected(it, autoCorrect, emit) } + ?.let { singleSpaceExpected(it, emit) } } private fun visitFunctionDeclaration( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { // Single space expected before type parameter list of function // fun foo(...) @@ -166,9 +164,9 @@ public class TypeParameterListSpacingRule : .prevLeaf(includeEmpty = true) ?.let { prevLeaf -> if (prevLeaf.elementType == WHITE_SPACE) { - singleSpaceExpected(prevLeaf, autoCorrect, emit) + singleSpaceExpected(prevLeaf, emit) } else { - singleSpaceExpected(node.firstChildNode, autoCorrect, emit) + singleSpaceExpected(node.firstChildNode, emit) } } @@ -179,14 +177,13 @@ public class TypeParameterListSpacingRule : .lastChildNode .nextLeaf(includeEmpty = true) ?.let { nextSibling -> - singleSpaceExpected(nextSibling, autoCorrect, emit) + singleSpaceExpected(nextSibling, emit) } } private fun visitInsideTypeParameterList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .findChildByType(LT) @@ -199,7 +196,7 @@ public class TypeParameterListSpacingRule : } else { "" } - visitWhitespace(it, autoCorrect, emit, expectedWhitespace) + visitWhitespace(it, emit, expectedWhitespace) } node @@ -213,14 +210,13 @@ public class TypeParameterListSpacingRule : } else { "" } - visitWhitespace(it, autoCorrect, emit, expectedWhitespace) + visitWhitespace(it, emit, expectedWhitespace) } } private fun visitWhitespace( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, expectedWhitespace: String = "", ) { if (node.text == expectedWhitespace) { @@ -233,8 +229,7 @@ public class TypeParameterListSpacingRule : node.startOffset, "No whitespace expected", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node.treeParent.removeChild(node) } } @@ -244,8 +239,7 @@ public class TypeParameterListSpacingRule : node.startOffset, "Expected a newline", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node as LeafPsiElement).rawReplaceWithText(expectedWhitespace) } } @@ -255,8 +249,7 @@ public class TypeParameterListSpacingRule : node.startOffset, "Expected a single space", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node as LeafPsiElement).rawReplaceWithText(expectedWhitespace) } } @@ -265,8 +258,7 @@ public class TypeParameterListSpacingRule : private fun singleSpaceExpected( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when { node.text == " " -> Unit @@ -276,8 +268,7 @@ public class TypeParameterListSpacingRule : node.startOffset, "Expected a single space instead of newline", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { (node as LeafPsiElement).rawReplaceWithText(" ") } } @@ -287,8 +278,7 @@ public class TypeParameterListSpacingRule : node.startOffset, "Expected a single space", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node.upsertWhitespaceBeforeMe(" ") } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRule.kt index 2b6c9092a5..0948c3ae7f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.LAMBDA_ARGUMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.LPAR @@ -10,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE import com.pinterest.ktlint.rule.engine.core.api.children +import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.ruleset.standard.StandardRule @@ -23,8 +25,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class UnnecessaryParenthesesBeforeTrailingLambdaRule : StandardRule("unnecessary-parentheses-before-trailing-lambda") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOf(CALL_EXPRESSION) && node.isEmptyArgumentList() && @@ -34,8 +35,7 @@ public class UnnecessaryParenthesesBeforeTrailingLambdaRule : StandardRule("unne node.startOffset, "Empty parentheses in function call followed by lambda are unnecessary", true, - ) - if (autoCorrect) { + ).ifAutocorrectAllowed { node.removeChild(node) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt index 464cbbac3b..ca6f2f0c79 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint @@ -19,8 +20,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class ValueArgumentCommentRule : StandardRule("value-argument-comment") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfComment() && node.treeParent.elementType == VALUE_ARGUMENT) { // Disallow: diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt index 57def81637..828abc2a76 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -20,8 +21,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode public class ValueParameterCommentRule : StandardRule("value-parameter-comment") { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.isPartOfComment() && node.treeParent.elementType == VALUE_PARAMETER) { if (node.elementType == KDOC && node.treeParent.firstChildNode == node) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt index f4add90fca..3d4e5e0fb3 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt @@ -1,6 +1,8 @@ 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.ANNOTATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW @@ -55,6 +57,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PR import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY_OFF import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.hasNewLineInClosedRange +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.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment @@ -128,25 +131,23 @@ public class WrappingRule : override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { when (node.elementType) { - BLOCK -> beforeVisitBlock(node, autoCorrect, emit) - LPAR, LBRACKET -> rearrangeBlock(node, autoCorrect, emit) - SUPER_TYPE_LIST -> rearrangeSuperTypeList(node, autoCorrect, emit) - VALUE_PARAMETER_LIST, VALUE_ARGUMENT_LIST -> rearrangeValueList(node, autoCorrect, emit) - TYPE_ARGUMENT_LIST, TYPE_PARAMETER_LIST -> rearrangeTypeArgumentList(node, autoCorrect, emit) - ARROW -> rearrangeArrow(node, autoCorrect, emit) + BLOCK -> beforeVisitBlock(node, emit) + LPAR, LBRACKET -> rearrangeBlock(node, emit) + SUPER_TYPE_LIST -> rearrangeSuperTypeList(node, emit) + VALUE_PARAMETER_LIST, VALUE_ARGUMENT_LIST -> rearrangeValueList(node, emit) + TYPE_ARGUMENT_LIST, TYPE_PARAMETER_LIST -> rearrangeTypeArgumentList(node, emit) + ARROW -> rearrangeArrow(node, emit) WHITE_SPACE -> line += node.text.count { it == '\n' } - CLOSING_QUOTE -> rearrangeClosingQuote(node, autoCorrect, emit) + CLOSING_QUOTE -> rearrangeClosingQuote(node, emit) } } private fun beforeVisitBlock( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { require(node.elementType == BLOCK) @@ -170,7 +171,7 @@ public class WrappingRule : ?.takeIf { it.elementType == RBRACE } ?.let { rbrace -> if (hasNewLineInClosedRange(lbrace, rbrace)) { - requireNewlineAfterLeaf(lbrace, autoCorrect, emit) + requireNewlineAfterLeaf(lbrace, emit) } } @@ -187,7 +188,7 @@ public class WrappingRule : .takeWhile { !it.isWhiteSpaceWithNewline() } .sumOf { it.textLength } if (lengthUntilBeginOfLine + lengthUntilEndOfLine > maxLineLength) { - requireNewlineAfterLeaf(lbrace, autoCorrect, emit) + requireNewlineAfterLeaf(lbrace, emit) } } } @@ -206,8 +207,7 @@ public class WrappingRule : private fun rearrangeBlock( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val closingElementType = MATCHING_RTOKEN_MAP[node.elementType] var newlineInBetween = false @@ -281,17 +281,16 @@ public class WrappingRule : // } node.treeNext?.elementType != CONDITION ) { - requireNewlineAfterLeaf(node, autoCorrect, emit) + requireNewlineAfterLeaf(node, emit) } if (!closingElement.prevLeaf().isWhiteSpaceWithNewline()) { - requireNewlineBeforeLeaf(closingElement, autoCorrect, emit, indentConfig.parentIndentOf(node)) + requireNewlineBeforeLeaf(closingElement, emit, indentConfig.parentIndentOf(node)) } } private fun rearrangeSuperTypeList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val entries = (node.psi as KtSuperTypeList).entries if ( @@ -321,7 +320,7 @@ public class WrappingRule : !colon.prevLeaf().isWhiteSpaceWithNewline() && colon.prevCodeLeaf().let { it?.elementType != RPAR || !it.prevLeaf().isWhiteSpaceWithNewline() } ) { - requireNewlineAfterLeaf(colon, autoCorrect, emit) + requireNewlineAfterLeaf(colon, emit) } } // put entries on separate lines @@ -332,7 +331,6 @@ public class WrappingRule : ) { requireNewlineAfterLeaf( nodeAfterWhichNewlineIsRequired = c, - autoCorrect = autoCorrect, emit = emit, indent = node.indent(), ) @@ -347,8 +345,7 @@ public class WrappingRule : private fun rearrangeValueList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { for (c in node.children()) { val hasLineBreak = @@ -376,7 +373,7 @@ public class WrappingRule : prevSibling?.elementType == COMMA && !prevSibling.treeNext.isWhiteSpaceWithNewline() ) { - requireNewlineAfterLeaf(prevSibling, autoCorrect, emit) + requireNewlineAfterLeaf(prevSibling, emit) } // insert \n after multi-line value val nextSibling = c.nextSibling { it.elementType != WHITE_SPACE } @@ -391,7 +388,7 @@ public class WrappingRule : // c, d nextSibling.treeNext?.treeNext?.psi !is PsiComment ) { - requireNewlineAfterLeaf(nextSibling, autoCorrect, emit) + requireNewlineAfterLeaf(nextSibling, emit) } } } @@ -403,8 +400,7 @@ public class WrappingRule : private fun rearrangeTypeArgumentList( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.textContains('\n')) { // Each type projection must be preceded with a whitespace containing a newline @@ -417,9 +413,9 @@ public class WrappingRule : .let { prevSibling -> if (prevSibling?.elementType == LT || prevSibling.isWhiteSpaceWithoutNewline()) { emit(typeProjection.startOffset, "A newline was expected before '${typeProjection.text}'", true) - if (autoCorrect) { - typeProjection.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + typeProjection.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + } } } } @@ -431,9 +427,9 @@ public class WrappingRule : val prevSibling = closingAngle.prevSibling { !it.isPartOfComment() } if (prevSibling?.elementType != WHITE_SPACE || prevSibling.isWhiteSpaceWithoutNewline()) { emit(closingAngle.startOffset, "A newline was expected before '${closingAngle.text}'", true) - if (autoCorrect) { - closingAngle.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) - } + .ifAutocorrectAllowed { + closingAngle.upsertWhitespaceBeforeMe(indentConfig.siblingIndentOf(node)) + } } } } @@ -441,8 +437,7 @@ public class WrappingRule : private fun rearrangeClosingQuote( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node .treeParent @@ -465,12 +460,15 @@ public class WrappingRule : node.startOffset, "Missing newline before \"\"\"", true, - ) - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") + + "inserted newline before (closing) \"\"\"" + } + }.ifAutocorrectAllowed { node as LeafPsiElement node.rawInsertBeforeMe(LeafPsiElement(LITERAL_STRING_TEMPLATE_ENTRY, "\n")) } - LOGGER.trace { "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline before (closing) \"\"\"" } } } @@ -500,8 +498,7 @@ public class WrappingRule : private fun rearrangeArrow( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { val p = node.treeParent if ( @@ -530,35 +527,35 @@ public class WrappingRule : return } if (!node.nextCodeLeaf()?.prevLeaf().isWhiteSpaceWithNewline()) { - requireNewlineAfterLeaf(node, autoCorrect, emit) + requireNewlineAfterLeaf(node, emit) } val r = node.nextSibling { it.elementType == RBRACE } ?: return if (!r.prevLeaf().isWhiteSpaceWithNewline()) { - requireNewlineBeforeLeaf(r, autoCorrect, emit, node.indent()) + requireNewlineBeforeLeaf(r, emit, node.indent()) } } private fun requireNewlineBeforeLeaf( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, indent: String, ) { emit( node.startOffset - 1, """Missing newline before "${node.text}"""", true, - ) - LOGGER.trace { "$line: " + ((if (!autoCorrect) "would have " else "") + "inserted newline before ${node.text}") } - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") + "inserted newline before ${node.text}" + } + }.ifAutocorrectAllowed { node.upsertWhitespaceBeforeMe(indent) } } private fun requireNewlineAfterLeaf( nodeAfterWhichNewlineIsRequired: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, indent: String? = null, nodeToFix: ASTNode = nodeAfterWhichNewlineIsRequired, ) { @@ -566,11 +563,12 @@ public class WrappingRule : nodeAfterWhichNewlineIsRequired.startOffset + 1, """Missing newline after "${nodeAfterWhichNewlineIsRequired.text}"""", true, - ) - LOGGER.trace { - "$line: " + (if (!autoCorrect) "would have " else "") + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" - } - if (autoCorrect) { + ).also { autocorrectDecision -> + LOGGER.trace { + "$line: " + (if (autocorrectDecision == NO_AUTOCORRECT) "would have " else "") + + "inserted newline after ${nodeAfterWhichNewlineIsRequired.text}" + } + }.ifAutocorrectAllowed { val tempIndent = indent ?: (indentConfig.childIndentOf(nodeToFix)) nodeToFix.upsertWhitespaceAfterMe(tempIndent) } @@ -645,8 +643,7 @@ public class WrappingRule : override fun afterVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == BLOCK) { val lbrace = @@ -662,7 +659,6 @@ public class WrappingRule : if (hasNewLineInClosedRange(lbrace, rbrace)) { requireNewlineBeforeLeaf( rbrace, - autoCorrect, emit, indentConfig.parentIndentOf(node), ) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRuleTest.kt index 84aa1c3e3c..c998dca9bb 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRuleTest.kt @@ -91,6 +91,12 @@ class SpacingAroundAngleBracketsRuleTest { List< String > > > {} + public class Foo8< + + Bar1 : String, + Bar2 : String + + > {} """.trimIndent() val formattedCode = """ @@ -107,6 +113,10 @@ class SpacingAroundAngleBracketsRuleTest { List > > {} + public class Foo8< + Bar1 : String, + Bar2 : String + > {} """.trimIndent() spacingAroundAngleBracketsRuleAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } @@ -120,6 +130,8 @@ class SpacingAroundAngleBracketsRuleTest { LintViolation(8, 19, "Unexpected spacing before \">\""), LintViolation(13, 14, "Unexpected spacing after \"<\""), LintViolation(13, 21, "Unexpected spacing before \">\""), + LintViolation(16, 19, "Single newline expected after \"<\""), + LintViolation(19, 18, "Single newline expected before \">\""), ).isFormattedAs(formattedCode) } diff --git a/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt b/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt index 55427908db..b1ca7882dd 100644 --- a/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt +++ b/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt @@ -1,7 +1,9 @@ package yourpkgname +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -14,11 +16,11 @@ public class NoVarRule : repositoryUrl = "https://github.com/your/project/", issueTrackerUrl = "https://github.com/your/project/issues", ), - ) { + ), + RuleAutocorrectApproveHandler { override fun beforeVisitChildNodes( node: ASTNode, - autoCorrect: Boolean, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { if (node.elementType == ElementType.VAR_KEYWORD) { emit(node.startOffset, "Unexpected var, use val instead", false) From 171e9d2c24c95fadedce9124a1e74374773f22d0 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 20:38:38 +0200 Subject: [PATCH 15/33] Fix "cannot be auto-corrected" in lint error log --- .../com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt index aa52d9c208..09e59b6a55 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt @@ -482,7 +482,7 @@ internal class KtlintCommandLine : detail = lintError .detail - .applyIf(corrected) { "$this (cannot be auto-corrected)" }, + .applyIf(!corrected) { "$this (cannot be auto-corrected)" }, status = if (corrected) { FORMAT_IS_AUTOCORRECTED From 1db5caec8de047b37344e348761b5fe729256171 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:02:22 +0200 Subject: [PATCH 16/33] Use new format function in ktlint CLI --- .../api/consumer/KtLintRuleEngineTest.kt | 8 +-- .../ktlint/cli/internal/KtlintCommandLine.kt | 50 ++++++++++--------- .../com/pinterest/ktlint/cli/SimpleCLITest.kt | 2 +- .../rule/engine/api/KtLintRuleEngine.kt | 14 +++++- .../rule/engine/internal/CodeFormatter.kt | 2 +- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index 4d52be3aef..bc6a2a7ac6 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -370,7 +370,7 @@ class KtLintRuleEngineTest { ) } - val lintErrors = mutableListOf() + val lintErrors = mutableSetOf() val actual = ktLintRuleEngine.format( code = Code.fromFile(File(filePath)), @@ -471,7 +471,7 @@ class KtLintRuleEngineTest { @Test fun `Given defaultAutocorrect is disabled`() { - val lintErrors = mutableListOf() + val lintErrors = mutableSetOf() val actual = ktLintRuleEngine.format( code = @@ -582,7 +582,7 @@ class KtLintRuleEngineTest { @Test fun `Given defaultAutocorrect is disabled`() { - val lintErrors = mutableListOf() + val lintErrors = mutableSetOf() val actual = ktLintRuleEngine.format( code = @@ -621,7 +621,7 @@ class KtLintRuleEngineTest { @Test fun `Given a kotlin code snippet that does contain multiple errors then only format the lint error at specific offset and message`() { - val lintErrors = mutableListOf() + val lintErrors = mutableSetOf() val actual = ktLintRuleEngine .format( diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt index 09e59b6a55..2e425b62d7 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt @@ -39,6 +39,8 @@ import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.plus import com.pinterest.ktlint.rule.engine.api.KtLintParseException import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.KtLintRuleException +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.ALLOW_AUTOCORRECT +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision.NO_AUTOCORRECT import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution @@ -473,29 +475,31 @@ internal class KtlintCommandLine : val ktlintCliErrors = mutableListOf() try { ktLintRuleEngine - .format(code) { lintError, corrected -> - val ktlintCliError = - KtlintCliError( - line = lintError.line, - col = lintError.col, - ruleId = lintError.ruleId.value, - detail = - lintError - .detail - .applyIf(!corrected) { "$this (cannot be auto-corrected)" }, - status = - if (corrected) { - FORMAT_IS_AUTOCORRECTED - } else { - LINT_CAN_NOT_BE_AUTOCORRECTED - }, - ) - if (baselineLintErrors.doesNotContain(ktlintCliError)) { - ktlintCliErrors.add(ktlintCliError) - if (!corrected) { - tripped.set(true) - } - } + .format(code) { lintError -> + KtlintCliError( + line = lintError.line, + col = lintError.col, + ruleId = lintError.ruleId.value, + detail = + lintError + .detail + .applyIf(!lintError.canBeAutoCorrected) { "$this (cannot be auto-corrected)" }, + status = + if (lintError.canBeAutoCorrected) { + FORMAT_IS_AUTOCORRECTED + } else { + LINT_CAN_NOT_BE_AUTOCORRECTED + }, + ).takeIf { baselineLintErrors.doesNotContain(it) } + ?.let { ktlintCliError -> + ktlintCliErrors.add(ktlintCliError) + if (lintError.canBeAutoCorrected) { + ALLOW_AUTOCORRECT + } else { + tripped.set(true) + NO_AUTOCORRECT + } + } ?: NO_AUTOCORRECT }.also { formattedFileContent -> when { code.isStdIn -> print(formattedFileContent) diff --git a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/SimpleCLITest.kt b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/SimpleCLITest.kt index e1d7524f87..3912c56af3 100644 --- a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/SimpleCLITest.kt +++ b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/SimpleCLITest.kt @@ -153,7 +153,7 @@ class SimpleCLITest { } @Test - fun `Given some code with an error which can be autocorrected then return from from with the normal exit code`( + fun `Given some code with an error which can be autocorrected then return with the normal exit code`( @TempDir tempDir: Path, ) { diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 62b739aa13..c1dc80fd6e 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -113,6 +113,12 @@ public class KtLintRuleEngine( * autocorrected *and* the rule that found that the violation has implemented the [RuleAutocorrectApproveHandler] interface, the API * Consumer determines whether that [LintError] is to autocorrected, or not. * + * When autocorrecting a [LintError] it is possible that other violations are introduced. By default, format is run up until + * [MAX_FORMAT_RUNS_PER_FILE] times. It is still possible that violations remain after the last run. This is a trait-off between solving + * as many errors as possible versus bad performance in case an endless loop of violations exists. In case the [callback] is implemented + * to let the user of the API Consumer to decide which [LintError] it to be autocorrected, or not, it might be better to disable this + * behavior by disabling [rerunWhenLintErrorIsAutocorrected]. + * * In case the rule has not implemented the [RuleAutocorrectApproveHandler] interface, then the result of the [callback] is ignored as * the rule is not able to process it. For such rules the [defaultAutocorrect] determines whether autocorrect for this rule is to be * applied, or not. By default, the autocorrect will be applied (backwards compatability). @@ -122,13 +128,19 @@ public class KtLintRuleEngine( */ public fun format( code: Code, + rerunWhenLintErrorIsAutocorrected: Boolean = true, defaultAutocorrect: Boolean = true, callback: (LintError) -> AutocorrectDecision, ): String = codeFormatter.format( code = code, autocorrectHandler = LintErrorAutocorrectHandler(defaultAutocorrect, callback), - maxFormatRunsPerFile = 1, + maxFormatRunsPerFile = + if (rerunWhenLintErrorIsAutocorrected) { + MAX_FORMAT_RUNS_PER_FILE + } else { + 1 + }, ) /** diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index c61280af55..14f29ae602 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -46,7 +46,7 @@ internal class CodeFormatter( code: Code, autocorrectHandler: AutocorrectHandler, maxFormatRunsPerFile: Int, - ): Pair>> { + ): Pair>> { with(RuleExecutionContext.createRuleExecutionContext(ktLintRuleEngine, code)) { val errors = mutableSetOf>() var formatRunCount = 0 From 4b3c5ea5ead2bf487999e8a826a4064f1e82d46e Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:56:21 +0200 Subject: [PATCH 17/33] Rename variable --- .../ktlint/cli/internal/KtlintCommandLine.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt index 2e425b62d7..659031260b 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt @@ -233,7 +233,7 @@ internal class KtlintCommandLine : private lateinit var patterns: List - private val tripped = AtomicBoolean() + private val containsUnfixedLintErrors = AtomicBoolean() private val fileNumber = AtomicInteger() private val errorNumber = AtomicInteger() private val adviseToUseFormat = AtomicBoolean() @@ -321,7 +321,7 @@ internal class KtlintCommandLine : // at least one kotlin file. logger.warn { "No files matched $patterns" } } - if (tripped.get()) { + if (containsUnfixedLintErrors.get()) { exitKtLintProcess(1) } else { exitKtLintProcess(0) @@ -496,7 +496,7 @@ internal class KtlintCommandLine : if (lintError.canBeAutoCorrected) { ALLOW_AUTOCORRECT } else { - tripped.set(true) + containsUnfixedLintErrors.set(true) NO_AUTOCORRECT } } ?: NO_AUTOCORRECT @@ -541,7 +541,7 @@ internal class KtlintCommandLine : } } else { ktlintCliErrors.add(e.toKtlintCliError(code)) - tripped.set(true) + containsUnfixedLintErrors.set(true) code.content // making sure `cat file | ktlint --stdin > file` is (relatively) safe } } @@ -571,7 +571,7 @@ internal class KtlintCommandLine : ) if (baselineLintErrors.doesNotContain(ktlintCliError)) { ktlintCliErrors.add(ktlintCliError) - tripped.set(true) + containsUnfixedLintErrors.set(true) } } } catch (e: Exception) { @@ -604,7 +604,7 @@ internal class KtlintCommandLine : } } else { ktlintCliErrors.add(e.toKtlintCliError(code)) - tripped.set(true) + containsUnfixedLintErrors.set(true) } } return ktlintCliErrors.toList() From fe6e0fddfbae4c324526e44107472bf31def118b Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:56:30 +0200 Subject: [PATCH 18/33] Fix documentation --- .../snapshot/docs/api/custom-integration.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/documentation/snapshot/docs/api/custom-integration.md b/documentation/snapshot/docs/api/custom-integration.md index 8d9cb45a54..7a62ecaf7c 100644 --- a/documentation/snapshot/docs/api/custom-integration.md +++ b/documentation/snapshot/docs/api/custom-integration.md @@ -94,45 +94,45 @@ val code = Code.fromSnippet( The `lint` function is invoked with an optional lambda. Once linting is complete, the lambda will be called for each `LintError` which is found. ```kotlin title="Invoking lint" ktLintRuleEngine - .lint(code) { lintError -> - // handle - } + .lint(code) { lintError -> + // handle + } ``` -The `format` function is invoked with a lambda. The lambda is called for each `LintError` which is found. If the `LintError` can be autocorrected, the return value of the lambda instructs the rule whether this specific `LintError` is to be autocorrect, or not. If the `LintError` can not be autocorrected, the return result of the lambda is ignored. The formatted code is returned as result of the function. +The `format` function is invoked with a lambda. The lambda is called for each `LintError` which is found. If the `LintError` can be autocorrected, the return value of the lambda instructs the rule whether this specific `LintError` is to be autocorrected, or not. If the `LintError` can not be autocorrected, the return result of the lambda is ignored. The formatted code is returned as result of the function. -The new `format` function allows the API Consumer to decided which LintError is to be autocorrected, or not. This is most interesting for API Consumers that let their user interactively decide per LintError how it has to be handled. For example see the `ktlint-intellij-plugin` which in 'manual' mode displays all lint violations, which that on a case by case basis can be autocorrected. +The new `format` function allows the API Consumer to decided which LintError is to be autocorrected, or not. This is most interesting for API Consumers that let their user interactively decide per `LintError` how it has to be handled. For example see the `ktlint-intellij-plugin` which in 'manual' mode displays all lint violations, which allows the user to decide which `LintError` is to be autocorrected. !!! note - The lambda of the legacy version of the `format` just takes a single parameter and does not return a value. + The difference with the legacy version of the `format` is subtle. It takes two parameters (a `LintError` and `Boolean` denoting whether the `LintError` is corrected), and it does not return a value. ```kotlin title="Invoke format (preferred starting from Ktlint 1.3)" val formattedCode = - ktLintRuleEngine - .format(code) { lintError -> - if (lintError.canBeAutocorrected) { - // Return AutocorrectDecision.ALLOW_AUTOCORRECT to execute the autocorrect of this lintError if this is supported by the rule. - // Return AutocorrectDecision.NO_AUTOCORRECT if the LintError should not be corrected even if is supported by the rule. - } else { - // In case the LintError can not be autocorrected, the return value of the lambda will be ignored. - // For clarity reasons it is advised to return AutocorrectDecision.NO_AUTOCORRECT in case the LintError can not be autocorrected. - AutocorrectDecision.NO_AUTOCORRECT + ktLintRuleEngine + .format(code) { lintError -> + if (lintError.canBeAutoCorrected) { + // Return AutocorrectDecision.ALLOW_AUTOCORRECT to execute the autocorrect of this lintError if this is supported by the rule. + // Return AutocorrectDecision.NO_AUTOCORRECT if the LintError should not be corrected even if is supported by the rule. + } else { + // In case the LintError can not be autocorrected, the return value of the lambda will be ignored. + // For clarity reasons it is advised to return AutocorrectDecision.NO_AUTOCORRECT in case the LintError can not be autocorrected. + AutocorrectDecision.NO_AUTOCORRECT + } } - } ``` !!! warning - Rules need to implement the interface `RuleAutocorrectApproveHandler` in order to let the API Consumer to decide whether a `LintError` is to be autocorrected, or not. This interface is implemented for all rules provided via the Ktlint project starting from version 1.3. However, external rulesets may not have implemented this interface on their rulesets though. Contact the maintainer of such ruleset to implement this. + Rules need to implement the interface `RuleAutocorrectApproveHandler` in order to let the API Consumer decide whether a `LintError` is to be autocorrected, or not. This interface is implemented for all rules provided via the Ktlint project starting from version 1.3. However, external rulesets may not have implemented this interface on their rulesets though. Contact the maintainer of such a ruleset to implement this interface. The (legacy) `format` function is invoked with an optional lambda. Once formatting is complete, the lambda will be called for each `LintError` which is found. The (legacy) `format` function fixes all `LintErrors` for which an autocorrect is available. The formatted code is returned as result of the function. ```kotlin title="Invoke format (deprecated as of Ktlint 1.3, will be removed in Ktlint 2.0)" // Up until Ktlint 1.2.1 the format was invoked with a lambda having two parameters and not returning a result. This function will be removed in Ktlint 2.0 val formattedCode = - ktLintRuleEngine - .format(code) { lintError, corrected -> - // handle - } + ktLintRuleEngine + .format(code) { lintError, corrected -> + // handle + } ``` ## Logging From a89d9ab14287957083f771c5378cca33869286b2 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:57:15 +0200 Subject: [PATCH 19/33] Add deprecation notice --- .../com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index c1dc80fd6e..32ff89ec00 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -103,6 +103,7 @@ public class KtLintRuleEngine( * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation */ + @Deprecated(message = "Marked for removal in Ktlint 2.0") public fun format( code: Code, callback: (LintError, Boolean) -> Unit = { _, _ -> }, From 10c22368066fd45ba7ecfe7b86919b815cc82ceb Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:57:28 +0200 Subject: [PATCH 20/33] Improve API documentation --- .../pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 32ff89ec00..169bca3ad3 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -97,8 +97,8 @@ public class KtLintRuleEngine( * If [code] contains lint errors which have been autocorrected, then the resulting code is formatted again (up until * [MAX_FORMAT_RUNS_PER_FILE] times) in order to fix lint errors that might result from the previous formatting run. * - * [callback] is invoked once for each unique [LintError] found during all runs. Note that [callback] is only invoked once all format - * runs have been completed. + * [callback] is invoked once for each [LintError] found during any runs. As of that the [callback] might be invoked multiple times for + * the same [LintError]. * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation @@ -124,6 +124,9 @@ public class KtLintRuleEngine( * the rule is not able to process it. For such rules the [defaultAutocorrect] determines whether autocorrect for this rule is to be * applied, or not. By default, the autocorrect will be applied (backwards compatability). * + * [callback] is invoked once for each [LintError] found during any runs. As of that the [callback] might be invoked multiple times for + * the same [LintError]. + * * @throws KtLintParseException if text is not a valid Kotlin code * @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation */ From 8aa7bf00dcd617d2d42e09622feac2aabc2de0e0 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 26 May 2024 21:58:20 +0200 Subject: [PATCH 21/33] Update API contract --- ktlint-rule-engine/api/ktlint-rule-engine.api | 10 +- .../api/ktlint-ruleset-standard.api | 199 +++++++++--------- 2 files changed, 106 insertions(+), 103 deletions(-) diff --git a/ktlint-rule-engine/api/ktlint-rule-engine.api b/ktlint-rule-engine/api/ktlint-rule-engine.api index 66d4d289ed..a63e2d76cb 100644 --- a/ktlint-rule-engine/api/ktlint-rule-engine.api +++ b/ktlint-rule-engine/api/ktlint-rule-engine.api @@ -69,9 +69,9 @@ public final class com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine { public synthetic fun (Ljava/util/Set;Lcom/pinterest/ktlint/rule/engine/api/EditorConfigDefaults;Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride;ZLjava/nio/file/FileSystem;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun editorConfigFilePaths (Ljava/nio/file/Path;)Ljava/util/List; public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;ZLkotlin/jvm/functions/Function1;)Ljava/lang/String; + public final fun format (Lcom/pinterest/ktlint/rule/engine/api/Code;ZZLkotlin/jvm/functions/Function1;)Ljava/lang/String; public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/String; - public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/String; + public static synthetic fun format$default (Lcom/pinterest/ktlint/rule/engine/api/KtLintRuleEngine;Lcom/pinterest/ktlint/rule/engine/api/Code;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/String; public final fun generateKotlinEditorConfigSection (Ljava/nio/file/Path;)Ljava/lang/String; public final fun getEditorConfigDefaults ()Lcom/pinterest/ktlint/rule/engine/api/EditorConfigDefaults; public final fun getEditorConfigOverride ()Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride; @@ -140,14 +140,16 @@ public final class com/pinterest/ktlint/rule/engine/api/LintError { public fun toString ()Ljava/lang/String; } -public class com/pinterest/ktlint/rule/engine/internal/rules/InternalRule : com/pinterest/ktlint/rule/engine/core/api/Rule { +public class com/pinterest/ktlint/rule/engine/internal/rules/InternalRule : com/pinterest/ktlint/rule/engine/core/api/Rule, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun getUsesEditorConfigProperties ()Ljava/util/Set; public fun getVisitorModifiers ()Ljava/util/Set; } public final class com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRule : com/pinterest/ktlint/rule/engine/internal/rules/InternalRule { public fun (Ljava/util/List;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleKt { diff --git a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api index 33fe298f74..9952db820e 100644 --- a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api +++ b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api @@ -1,4 +1,6 @@ -public class com/pinterest/ktlint/ruleset/standard/StandardRule : com/pinterest/ktlint/rule/engine/core/api/Rule { +public class com/pinterest/ktlint/ruleset/standard/StandardRule : com/pinterest/ktlint/rule/engine/core/api/Rule, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun getUsesEditorConfigProperties ()Ljava/util/Set; public fun getVisitorModifiers ()Ljava/util/Set; } @@ -11,7 +13,7 @@ public final class com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider public final class com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleKt { @@ -21,7 +23,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/AnnotationRuleKt public final class com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public static final field ERROR_MESSAGE Ljava/lang/String; public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRuleKt { @@ -32,7 +34,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrapp public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule$Companion { @@ -46,7 +48,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrapp public final class com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNamingRuleKt { @@ -56,7 +58,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BackingPropertyNa public final class com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRuleKt { @@ -65,7 +67,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionW public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRuleKt { @@ -75,7 +77,6 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDe public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental, com/pinterest/ktlint/rule/engine/core/api/RuleAutocorrectApproveHandler { public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenWhenConditions$Companion; public fun ()V - public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } @@ -90,7 +91,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/BlankLineBetweenW public final class com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/BlockCommentInitialStarAlignmentRuleKt { @@ -101,7 +102,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContin public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule$Companion { @@ -115,7 +116,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContin public final class com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRuleKt { @@ -124,7 +125,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ChainWrappingRule public final class com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRuleKt { @@ -135,7 +136,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRul public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule$Companion { @@ -148,7 +149,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRul public final class com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRuleKt { @@ -157,7 +158,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/CommentSpacingRul public final class com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRuleKt { @@ -167,7 +168,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/CommentWrappingRu public final class com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ConditionWrappingRuleKt { @@ -177,7 +178,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ConditionWrapping public final class com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRuleKt { @@ -186,7 +187,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWr public final class com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleKt { @@ -195,7 +196,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommen public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRuleKt { @@ -205,7 +206,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCase public final class com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRuleKt { @@ -214,7 +215,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/EnumWrappingRuleK public final class com/pinterest/ktlint/ruleset/standard/rules/FilenameRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FilenameRuleKt { @@ -224,7 +225,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FilenameRuleKt { public final class com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRuleKt { @@ -233,7 +234,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FinalNewlineRuleK public final class com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacingRuleKt { @@ -243,7 +244,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunKeywordSpacing public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressionBodyRuleKt { @@ -253,7 +254,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionExpressio public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleKt { @@ -264,7 +265,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRul public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRule$Companion { @@ -278,7 +279,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionNamingRul public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionReturnTypeSpacingRuleKt { @@ -289,7 +290,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionSignature public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule$Companion { @@ -312,7 +313,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionSignature public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBodySpacingRuleKt { @@ -321,7 +322,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBo public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRuleKt { @@ -330,7 +331,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModif public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRuleKt { @@ -340,7 +341,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeRefer public final class com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRuleKt { @@ -350,7 +351,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/IfElseBracingRule public final class com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleKt { @@ -361,7 +362,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRul public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule$Companion { @@ -388,7 +389,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleKt public final class com/pinterest/ktlint/ruleset/standard/rules/KdocRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/KdocRuleKt { @@ -397,7 +398,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/KdocRuleKt { public final class com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRuleKt { @@ -408,7 +409,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule$Companion { @@ -421,7 +422,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule public final class com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOperatorsRuleKt { @@ -430,7 +431,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOpe public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRuleKt { @@ -439,7 +440,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpaci public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRuleKt { @@ -449,7 +450,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierOrderRule public final class com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRuleKt { @@ -459,7 +460,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MultiLineIfElseRu public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingRuleKt { @@ -469,7 +470,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressi public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRuleKt { @@ -478,7 +479,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MultilineLoopRule public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBeforeRbraceRuleKt { @@ -487,7 +488,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineBefore public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInListRuleKt { @@ -496,7 +497,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLineInList public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInChainedMethodCallsRuleKt { @@ -505,7 +506,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoBlankLinesInCha public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlankLinesRuleKt { @@ -514,7 +515,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveBlan public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveCommentsRuleKt { @@ -523,7 +524,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoConsecutiveComm public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyRuleKt { @@ -532,7 +533,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyClassBodyR public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRuleKt { @@ -542,7 +543,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFileRuleKt public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInClassBodyRuleKt { @@ -551,7 +552,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineI public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineInMethodBlockRuleKt { @@ -560,7 +561,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoEmptyFirstLineI public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterElseRuleKt { @@ -569,7 +570,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakAfterE public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRuleKt { @@ -578,7 +579,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBefore public final class com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesRuleKt { @@ -587,7 +588,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoMultipleSpacesR public final class com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRuleKt { @@ -596,7 +597,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRuleK public final class com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleKt { @@ -605,7 +606,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlock public final class com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRuleKt { @@ -614,7 +615,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesR public final class com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRuleKt { @@ -623,8 +624,8 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoUnitReturnRuleK public final class com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRuleKt { @@ -635,7 +636,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImports public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule$Companion { @@ -648,7 +649,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImports public final class com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpacingRuleKt { @@ -657,7 +658,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/NullableTypeSpaci public final class com/pinterest/ktlint/ruleset/standard/rules/PackageNameRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/PackageNameRuleKt { @@ -667,7 +668,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/PackageNameRuleKt public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRuleKt { @@ -677,7 +678,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpac public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleKt { @@ -687,7 +688,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrap public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterWrappingRuleKt { @@ -697,7 +698,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ParameterWrapping public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public static final field SERIAL_VERSION_UID_PROPERTY_NAME Ljava/lang/String; public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRuleKt { @@ -707,7 +708,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRul public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingRuleKt { @@ -716,7 +717,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/PropertyWrappingR public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngleBracketsRuleKt { @@ -725,7 +726,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundAngl public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleKt { @@ -734,7 +735,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColo public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCommaRuleKt { @@ -744,7 +745,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundComm public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRuleKt { @@ -753,7 +754,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurl public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotRuleKt { @@ -762,7 +763,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDotR public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRuleKt { @@ -771,7 +772,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoub public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeywordRuleKt { @@ -780,7 +781,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundKeyw public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOperatorsRuleKt { @@ -789,7 +790,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundOper public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRuleKt { @@ -798,7 +799,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundPare public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRangeOperatorRuleKt { @@ -807,7 +808,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundRang public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSquareBracketsRuleKt { @@ -816,7 +817,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundSqua public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnaryOperatorRuleKt { @@ -825,7 +826,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundUnar public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRuleKt { @@ -834,7 +835,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDec public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRuleKt { @@ -843,7 +844,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDec public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFunctionNameAndOpeningParenthesisRuleKt { @@ -853,7 +854,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenFun public final class com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/StatementWrappingRuleKt { @@ -864,7 +865,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/StringTemplateInd public static final field RAW_STRING_LITERAL_QUOTES Ljava/lang/String; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleKt { @@ -873,7 +874,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/StringTemplateInd public final class com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRuleKt { @@ -884,7 +885,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCa public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule$Companion { @@ -899,7 +900,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDe public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule$Companion; public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule$Companion { @@ -913,7 +914,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDe public final class com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$OfficialCodeStyle { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySpacingRuleKt { @@ -922,7 +923,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySp public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleKt { @@ -932,7 +933,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentComme public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRuleKt { @@ -941,7 +942,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListS public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleKt { @@ -951,7 +952,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterComm public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRuleKt { @@ -960,7 +961,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterList public final class com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParenthesesBeforeTrailingLambdaRuleKt { @@ -969,7 +970,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParent public final class com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleKt { @@ -978,7 +979,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentComm public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleKt { @@ -987,9 +988,9 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCom public final class com/pinterest/ktlint/ruleset/standard/rules/WrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V - public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V - public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V } public final class com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleKt { From a54218ec6b9b5d88e13b0e5b7c99e2422fcb2048 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Mon, 27 May 2024 20:22:13 +0200 Subject: [PATCH 22/33] Update documentation --- .../snapshot/docs/api/custom-integration.md | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/documentation/snapshot/docs/api/custom-integration.md b/documentation/snapshot/docs/api/custom-integration.md index 7a62ecaf7c..668e35f5fa 100644 --- a/documentation/snapshot/docs/api/custom-integration.md +++ b/documentation/snapshot/docs/api/custom-integration.md @@ -15,7 +15,7 @@ val ktLintRuleEngine = ### Rule provider -The `KtLintRuleEngine` must be configured with at least one `RuleProvider`. A `RuleProvider` is a lambda which upon request of the `KtLintRuleEngine` provides a new instance of a specific rule. You can either provide any of the standard rules provided by KtLint or with your own custom rules, or with a combination of both. +The `KtLintRuleEngine` must be configured with at least one `RuleProvider`. A `RuleProvider` is a lambda which upon request of the `KtLintRuleEngine` provides a new instance of a specific rule. You can either provide any of the standard rules provided by KtLint, or your own custom rules, or a combination of both. ```kotlin title="Creating a set of RuleProviders" val KTLINT_API_CONSUMER_RULE_PROVIDERS = setOf( @@ -101,12 +101,12 @@ ktLintRuleEngine The `format` function is invoked with a lambda. The lambda is called for each `LintError` which is found. If the `LintError` can be autocorrected, the return value of the lambda instructs the rule whether this specific `LintError` is to be autocorrected, or not. If the `LintError` can not be autocorrected, the return result of the lambda is ignored. The formatted code is returned as result of the function. -The new `format` function allows the API Consumer to decided which LintError is to be autocorrected, or not. This is most interesting for API Consumers that let their user interactively decide per `LintError` how it has to be handled. For example see the `ktlint-intellij-plugin` which in 'manual' mode displays all lint violations, which allows the user to decide which `LintError` is to be autocorrected. +The new `format` function allows the API Consumer to decide which LintError is to be autocorrected, or not. This is most interesting for API Consumers that let their user interactively decide per `LintError` how it has to be handled. For example see the `ktlint-intellij-plugin` which in 'manual' mode displays all lint violations, which allows the user to decide which `LintError` is to be autocorrected. !!! note The difference with the legacy version of the `format` is subtle. It takes two parameters (a `LintError` and `Boolean` denoting whether the `LintError` is corrected), and it does not return a value. -```kotlin title="Invoke format (preferred starting from Ktlint 1.3)" +```kotlin title="Invoke format (preferred, starting from Ktlint 1.3)" val formattedCode = ktLintRuleEngine .format(code) { lintError -> @@ -135,6 +135,63 @@ val formattedCode = } ``` +### Rule & RuleAutocorrectApproveHandler + +!!! note + Providers of custom rules are strongly encouraged to implement `RuleAutocorrectApproveHandler` interface as described below. The `ktlint-intellij-plugin`, which will be updated soon after the 1.3 release of Ktlint, make use of this new functionality. If your ruleset is used by users of the plugin, it is very likely that they want to be able to autocorrect individual `LintErrors` or to format a block of code (e.g. a selection) in a file. This functionality will only be available for rules that have implemented this interface. + +In Ktlint 1.3 the `RuleAutocorrectApproveHandler` interface is added. This interface adds the ability that the API Consumer decides per `LintError` whether it needs to autocorrected, or not. In Ktlint 2.0 the methods `beforeVisitChildNodes` and `afterVisitChildNodes` of the `Rule` class will be replaced with the new versions which are now added to the `RuleAutocorrectApproveHandler` interface as is shown below (the signature for `afterVisitChildNodes` is changed similarly): + + + + + + +
+ +```kotlin title="Deprecated signature in `Rule` class" +public open fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: ( + offset: Int, + errorMessage: String, + canBeAutoCorrected: Boolean + ) -> Unit, +) +``` + + + +```kotlin title="New signature in `RuleAutocorrectApproveHandler` interface" +public fun beforeVisitChildNodes( + node: ASTNode, + emit: ( + offset: Int, + errorMessage: String, + canBeAutoCorrected: Boolean + ) -> AutocorrectDecision, +) +``` + +
+ +The `autoCorrect` parameter is no longer passed to the method. Instead, the `emit` lambda now returns the value `AutocorrectDecision.ALLOW_AUTOCORRECT` or `AutocorrectDecision.NO_AUTOCORRECT`. + +In case a `LintError` is detected, and can be autocorrected, the `LintError` can be processed as shown below: + +```kotlin +emit(node.startOffset, "some detail message", true) + .ifAutocorrectAllowed { + // Autocorrect the LintError + } +``` + +In case the `LintError` can not be autocorrected, if suffices to emit the violation only: +```kotlin +emit(node.startOffset, "some detail message", false) +``` + ## Logging Ktlint uses the `io.github.oshai:kotlin-logging` which is a `slf4j` wrapper. As API consumer you can choose which logging framework you want to use and configure that framework to your exact needs. The [basic API Consumer](https://github.com/pinterest/ktlint/blob/master/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt) contains an example with `org.slf4j:slf4j-simple` as logging provider and a customized configuration which shows logging at `DEBUG` level for all classes except one specific class which only displays logging at `WARN` level. From c3504d0e4a28b68889af61a48730d92e6e2ab156 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Mon, 27 May 2024 21:14:20 +0200 Subject: [PATCH 23/33] Remove CodeLinter Linting is same as Format with autocorrect disabled always --- .../rule/engine/api/KtLintRuleEngine.kt | 18 ++++-- .../ktlint/rule/engine/internal/CodeLinter.kt | 57 ------------------- 2 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt index 169bca3ad3..d7d76b0474 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintRuleEngine.kt @@ -14,12 +14,12 @@ import com.pinterest.ktlint.rule.engine.core.api.propertyTypes import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.rule.engine.internal.AllAutocorrectHandler import com.pinterest.ktlint.rule.engine.internal.CodeFormatter -import com.pinterest.ktlint.rule.engine.internal.CodeLinter import com.pinterest.ktlint.rule.engine.internal.EditorConfigFinder import com.pinterest.ktlint.rule.engine.internal.EditorConfigGenerator import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoader import com.pinterest.ktlint.rule.engine.internal.EditorConfigLoaderEc4j import com.pinterest.ktlint.rule.engine.internal.LintErrorAutocorrectHandler +import com.pinterest.ktlint.rule.engine.internal.NoneAutocorrectHandler import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext import com.pinterest.ktlint.rule.engine.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE import org.ec4j.core.Resource @@ -75,7 +75,6 @@ public class KtLintRuleEngine( editorConfigOverride, ) - private val codeLinter = CodeLinter(this) private val codeFormatter = CodeFormatter(this) /** @@ -88,7 +87,14 @@ public class KtLintRuleEngine( public fun lint( code: Code, callback: (LintError) -> Unit = { }, - ): Unit = codeLinter.lint(code, callback) + ) { + codeFormatter.format( + code = code, + autocorrectHandler = NoneAutocorrectHandler, + callback = { lintError, _ -> callback(lintError) }, + maxFormatRunsPerFile = 1, + ) + } /** * Fix all style violations in [code] for lint errors when possible. If [code] is passed as file reference then the '.editorconfig' @@ -118,7 +124,7 @@ public class KtLintRuleEngine( * [MAX_FORMAT_RUNS_PER_FILE] times. It is still possible that violations remain after the last run. This is a trait-off between solving * as many errors as possible versus bad performance in case an endless loop of violations exists. In case the [callback] is implemented * to let the user of the API Consumer to decide which [LintError] it to be autocorrected, or not, it might be better to disable this - * behavior by disabling [rerunWhenLintErrorIsAutocorrected]. + * behavior by disabling [rerunAfterAutocorrect]. * * In case the rule has not implemented the [RuleAutocorrectApproveHandler] interface, then the result of the [callback] is ignored as * the rule is not able to process it. For such rules the [defaultAutocorrect] determines whether autocorrect for this rule is to be @@ -132,7 +138,7 @@ public class KtLintRuleEngine( */ public fun format( code: Code, - rerunWhenLintErrorIsAutocorrected: Boolean = true, + rerunAfterAutocorrect: Boolean = true, defaultAutocorrect: Boolean = true, callback: (LintError) -> AutocorrectDecision, ): String = @@ -140,7 +146,7 @@ public class KtLintRuleEngine( code = code, autocorrectHandler = LintErrorAutocorrectHandler(defaultAutocorrect, callback), maxFormatRunsPerFile = - if (rerunWhenLintErrorIsAutocorrected) { + if (rerunAfterAutocorrect) { MAX_FORMAT_RUNS_PER_FILE } else { 1 diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt deleted file mode 100644 index 1274075dd1..0000000000 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeLinter.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.pinterest.ktlint.rule.engine.internal - -import com.pinterest.ktlint.logger.api.initKtLintKLogger -import com.pinterest.ktlint.rule.engine.api.Code -import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine -import com.pinterest.ktlint.rule.engine.api.LintError -import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision -import com.pinterest.ktlint.rule.engine.core.api.Rule -import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.Companion.createRuleExecutionContext -import io.github.oshai.kotlinlogging.KotlinLogging - -private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() - -internal class CodeLinter( - val ktLintRuleEngine: KtLintRuleEngine, -) { - fun lint( - code: Code, - callback: (LintError) -> Unit = { }, - ) { - LOGGER.debug { "Starting with linting file '${code.fileNameOrStdin()}'" } - executeLint(code) - .sortedWith(lintErrorLineAndColumnComparator { it }) - .forEach { e -> callback(e) } - LOGGER.debug { "Finished with linting file '${code.fileNameOrStdin()}'" } - } - - private fun executeLint(code: Code): MutableList = - with(createRuleExecutionContext(ktLintRuleEngine, code)) { - val errors = mutableListOf() - VisitorProvider(ruleProviders) - .rules - .forEach { rule -> executeRule(rule, code).let { errors.addAll(it) } } - errors - } - - private fun RuleExecutionContext.executeRule( - rule: Rule, - code: Code, - ): List { - val errors = mutableListOf() - executeRule(rule, NoneAutocorrectHandler) { offset, errorMessage, canBeAutoCorrected -> - val (line, col) = this.positionInTextLocator(offset) - LintError(line, col, rule.ruleId, errorMessage, canBeAutoCorrected) - .let { lintError -> - errors.add(lintError) - // In trace mode report the violation immediately. The order in which violations are actually found might be - // different from the order in which they are reported. For debugging purposes it can be helpful to know the - // exact order in which violations are being solved. - LOGGER.trace { "Lint violation: ${lintError.logMessage(code)}" } - } - // No need to ask for approval in lint mode - AutocorrectDecision.NO_AUTOCORRECT - } - return errors - } -} From dede1d4520869b5d6f6663280ee420b5c3c6271a Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 09:43:30 +0200 Subject: [PATCH 24/33] Update log message --- .../pinterest/ktlint/rule/engine/internal/CodeFormatter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 14f29ae602..66b1b06275 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -22,7 +22,7 @@ internal class CodeFormatter( callback: (LintError, Boolean) -> Unit = { _, _ -> }, maxFormatRunsPerFile: Int, ): String { - LOGGER.debug { "Starting with formatting file '${code.fileNameOrStdin()}'" } + LOGGER.debug { "Starting with processing file '${code.fileNameOrStdin()}'" } val (formattedCode, errors) = format(code, autocorrectHandler, maxFormatRunsPerFile) @@ -31,7 +31,7 @@ internal class CodeFormatter( .forEach { (e, corrected) -> callback(e, corrected) } return (code.utf8Bom() + formattedCode).also { - LOGGER.debug { "Finished with formatting file '${code.fileNameOrStdin()}'" } + LOGGER.debug { "Finished with processing file '${code.fileNameOrStdin()}'" } } } From 22b0efcfb278d0db2085dcf13e2a083910ce6582 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 09:44:04 +0200 Subject: [PATCH 25/33] Inline extension methods --- .../ktlint/rule/engine/internal/CodeFormatter.kt | 12 ++++++++++++ .../internal/LintErrorLineAndColumnComparator.kt | 7 ------- .../rule/engine/internal/LintErrorLogMessage.kt | 12 ------------ 3 files changed, 12 insertions(+), 19 deletions(-) delete mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt delete mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 66b1b06275..0e588a9c92 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -158,4 +158,16 @@ internal class CodeFormatter( } private fun Code.doesNotContain(char: Char) = content.lastIndexOf(char) != -1 + + private fun lintErrorLineAndColumnComparator(transformer: (T) -> LintError) = + compareBy { transformer(it).line } + .then(compareBy { transformer(it).col }) + + private fun LintError.logMessage(code: Code) = + "${code.fileNameOrStdin()}:$line:$col: $detail ($ruleId)" + + if (canBeAutoCorrected) { + "" + } else { + " [cannot be autocorrected]" + } } diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt deleted file mode 100644 index bb4d238f1e..0000000000 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLineAndColumnComparator.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.pinterest.ktlint.rule.engine.internal - -import com.pinterest.ktlint.rule.engine.api.LintError - -internal fun lintErrorLineAndColumnComparator(transformer: (T) -> LintError) = - compareBy { transformer(it).line } - .then(compareBy { transformer(it).col }) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt deleted file mode 100644 index f534cc1858..0000000000 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/LintErrorLogMessage.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.pinterest.ktlint.rule.engine.internal - -import com.pinterest.ktlint.rule.engine.api.Code -import com.pinterest.ktlint.rule.engine.api.LintError - -internal fun LintError.logMessage(code: Code) = - "${code.fileNameOrStdin()}:$line:$col: $detail ($ruleId)" + - if (canBeAutoCorrected) { - "" - } else { - " [cannot be autocorrected]" - } From ec0b87ff405462018f55b5be5b699bdc4f07656d Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 09:44:13 +0200 Subject: [PATCH 26/33] Rename methods --- .../ktlint/rule/engine/internal/RuleExecutionContext.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 711ddad12c..220f4d6ee0 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -93,9 +93,9 @@ internal class RuleExecutionContext private constructor( if (rule.shouldContinueTraversalOfAST()) { try { if (rule is RuleAutocorrectApproveHandler) { - executeRuleWithApproveHandlerOnNodeRecursivelyKtlint(node, rule, autocorrectHandler, suppress, emitAndApprove) + executeRuleWithAutocorrectApproveHandlerOnNodeRecursively(node, rule, autocorrectHandler, suppress, emitAndApprove) } else { - executeRuleOnNodeRecursivelyKtlint(node, rule, autocorrectHandler, suppress, emitAndApprove) + executeRuleWithoutAutocorrectApproveHandlerOnNodeRecursively(node, rule, autocorrectHandler, suppress, emitAndApprove) } } catch (e: Exception) { if (autocorrectHandler is NoneAutocorrectHandler) { @@ -123,13 +123,14 @@ internal class RuleExecutionContext private constructor( } @Deprecated(message = "Remove in Ktlint 2.0") - private fun executeRuleOnNodeRecursivelyKtlint( + private fun executeRuleWithoutAutocorrectApproveHandlerOnNodeRecursively( node: ASTNode, rule: Rule, autocorrectHandler: AutocorrectHandler, suppress: Boolean, emitAndApprove: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { + require(rule !is RuleAutocorrectApproveHandler) val autoCorrect = autocorrectHandler is AllAutocorrectHandler || ( @@ -157,7 +158,7 @@ internal class RuleExecutionContext private constructor( } } - private fun executeRuleWithApproveHandlerOnNodeRecursivelyKtlint( + private fun executeRuleWithAutocorrectApproveHandlerOnNodeRecursively( node: ASTNode, rule: Rule, autocorrectHandler: AutocorrectHandler, From 004c7a7e7f7a3db8c3505b7c53a92140e720c5aa Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 11:30:09 +0200 Subject: [PATCH 27/33] Try to fix test on Windows Closes #2658 --- .../com/pinterest/ktlint/rule/engine/api/KtLintTest.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 96063b4ac6..8bf86c7a32 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -31,6 +31,7 @@ import org.assertj.core.api.Assertions.assertThat import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.utils.addToStdlib.applyIf import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -285,7 +286,12 @@ class KtLintTest { @Test fun testFormatUnicodeBom() { - val code = getResourceAsText("spec/format-unicode-bom.kt.spec") + val code = + getResourceAsText("spec/format-unicode-bom.kt.spec") + .applyIf(System.lineSeparator() != "\n") { + // On Windows change the input code to conform with the default lineSeparator "\r\n" as otherwise the assertion fails + replace("\n", System.lineSeparator()) + } val actual = KtLintRuleEngine( @@ -295,6 +301,8 @@ class KtLintTest { ), ).format(Code.fromSnippet(code)) + // + assertThat(code.toByteArray()).isEqualTo(actual.toByteArray()) assertThat(actual).isEqualTo(code) } From 9363bdaa44e8e8d27de25a3f1fbf6f2b74d5eb46 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 12:01:23 +0200 Subject: [PATCH 28/33] Try to fix test on Windows Closes #2658 --- .../ktlint/rule/engine/api/KtLintTest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 8bf86c7a32..57ea2867bb 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -24,14 +24,15 @@ import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAsLateA import com.pinterest.ktlint.rule.engine.core.api.RuleAutocorrectApproveHandler import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isRoot import org.assertj.core.api.Assertions.assertThat +import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType -import org.jetbrains.kotlin.utils.addToStdlib.applyIf import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -286,12 +287,7 @@ class KtLintTest { @Test fun testFormatUnicodeBom() { - val code = - getResourceAsText("spec/format-unicode-bom.kt.spec") - .applyIf(System.lineSeparator() != "\n") { - // On Windows change the input code to conform with the default lineSeparator "\r\n" as otherwise the assertion fails - replace("\n", System.lineSeparator()) - } + val code = getResourceAsText("spec/format-unicode-bom.kt.spec") val actual = KtLintRuleEngine( @@ -299,10 +295,14 @@ class KtLintTest { setOf( RuleProvider { DummyRule() }, ), + editorConfigOverride = + EditorConfigOverride.from( + // The code sample use LF as line separator, so ensure that formatted code uses that as well, as otherwise the test + // breaks on Windows OS + END_OF_LINE_PROPERTY to PropertyType.EndOfLineValue.lf, + ), ).format(Code.fromSnippet(code)) - // - assertThat(code.toByteArray()).isEqualTo(actual.toByteArray()) assertThat(actual).isEqualTo(code) } From 78cb2d8f235626de02423fea307d22d51cd6cdda Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 14:20:44 +0200 Subject: [PATCH 29/33] Replace call to deprecated "format", except the one that need to test the legacy format Closes #2658 --- .../ktlint/api/consumer/KtlintApiConsumer.kt | 3 ++- .../api/consumer/KtLintRuleEngineTest.kt | 3 +++ .../ktlint/rule/engine/api/KtLintTest.kt | 27 +++++++++++-------- .../internal/SuppressionLocatorBuilderTest.kt | 2 +- .../pinterest/ktlint/test/KtLintAssertThat.kt | 8 +++++- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt index 1df59112c6..6819c6bf43 100644 --- a/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt +++ b/ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt @@ -8,6 +8,7 @@ import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride import com.pinterest.ktlint.rule.engine.api.EditorConfigPropertyRegistry import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EXPERIMENTAL_RULES_EXECUTION_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY @@ -112,7 +113,7 @@ public fun main() { """.trimIndent() } apiConsumerKtLintRuleEngine - .format(codeFile) + .format(codeFile) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } .also { LOGGER.info { "Code formatted by KtLint:\n$it" } } diff --git a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt index bc6a2a7ac6..74d1f804d9 100644 --- a/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt +++ b/ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt @@ -183,6 +183,7 @@ class KtLintRuleEngineTest { val lintErrors = mutableListOf() val actual = + @Suppress("DEPRECATION") ktLintRuleEngine.format( code = Code.fromFile(File(filePath)), ) { lintError, _ -> lintErrors.add(lintError) } @@ -206,6 +207,7 @@ class KtLintRuleEngineTest { fun `Given a kotlin code snippet that does contain an indentation error`() { val lintErrors = mutableListOf() val actual = + @Suppress("DEPRECATION") ktLintRuleEngine.format( code = Code.fromSnippet( @@ -237,6 +239,7 @@ class KtLintRuleEngineTest { fun `Given a kotlin script code snippet that does contain an indentation error`() { val lintErrors = mutableListOf() val actual = + @Suppress("DEPRECATION") ktLintRuleEngine.format( code = Code.fromSnippet( diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 57ea2867bb..07333ecbc7 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -124,7 +124,7 @@ class KtLintTest { } }, ), - ).format(Code.fromSnippet("fun main() {}")) + ).format(Code.fromSnippet("fun main() {}")) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(numberOfRootNodesVisited).isEqualTo(1) } @@ -140,14 +140,14 @@ class KtLintTest { val foo = "$STRING_VALUE_NOT_TO_BE_CORRECTED" val bar = "$STRING_VALUE_AFTER_AUTOCORRECT" """.trimIndent() - val callbacks = mutableListOf() + val callbacks = mutableSetOf() val actualFormattedCode = KtLintRuleEngine( ruleProviders = setOf( RuleProvider { AutoCorrectErrorRule() }, ), - ).format(Code.fromSnippet(code)) { e, corrected -> + ).format(Code.fromSnippet(code)) { e -> callbacks.add( CallbackResult( line = e.line, @@ -155,9 +155,14 @@ class KtLintTest { ruleId = e.ruleId, detail = e.detail, canBeAutoCorrected = e.canBeAutoCorrected, - corrected = corrected, + corrected = e.canBeAutoCorrected, ), ) + if (e.canBeAutoCorrected) { + AutocorrectDecision.ALLOW_AUTOCORRECT + } else { + AutocorrectDecision.NO_AUTOCORRECT + } } assertThat(actualFormattedCode).isEqualTo(formattedCode) assertThat(callbacks).containsExactly( @@ -301,7 +306,7 @@ class KtLintTest { // breaks on Windows OS END_OF_LINE_PROPERTY to PropertyType.EndOfLineValue.lf, ), - ).format(Code.fromSnippet(code)) + ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(actual).isEqualTo(code) } @@ -322,7 +327,7 @@ class KtLintTest { ) }, ), - ).format(Code.fromSnippet("class Foo")) + ).format(Code.fromSnippet("class Foo")) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(ruleExecutionCalls).containsExactly( RuleExecutionCall(SimpleTestRule.RULE_ID_STOP_TRAVERSAL, BEFORE_FIRST), @@ -357,7 +362,7 @@ class KtLintTest { ) }, ), - ).format(Code.fromSnippet(code)) + ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(ruleExecutionCalls) .filteredOn { it.elementType == null || it.classIdentifier != null } @@ -398,7 +403,7 @@ class KtLintTest { ) }, ), - ).format(Code.fromSnippet(code)) + ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(ruleExecutionCalls) .filteredOn { it.elementType == null || it.classIdentifier != null } @@ -428,7 +433,7 @@ class KtLintTest { ) }, ), - ).format(Code.fromSnippet("class Foo")) + ).format(Code.fromSnippet("class Foo")) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(ruleExecutionCalls).containsExactly( RuleExecutionCall(SimpleTestRule.RULE_ID_STOP_TRAVERSAL, BEFORE_FIRST), @@ -448,7 +453,7 @@ class KtLintTest { setOf( RuleProvider { WithStateRule() }, ), - ).format(EMPTY_CODE_SNIPPET) + ).format(EMPTY_CODE_SNIPPET) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } } @Test @@ -465,7 +470,7 @@ class KtLintTest { setOf( RuleProvider { AutoCorrectErrorRule() }, ), - ).format(Code.fromSnippet(code)) + ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(actualFormattedCode).isEqualTo(code) } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt index 17fd03f6b1..de4dc18ea9 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocatorBuilderTest.kt @@ -366,7 +366,7 @@ class SuppressionLocatorBuilderTest { .plus( STANDARD_NO_FOO_IDENTIFIER_RULE_ID.createRuleExecutionEditorConfigProperty() to RuleExecution.enabled, ), - ).format(Code.fromSnippet(code)) { _, _ -> } + ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } assertThat(actual).isEqualTo(formattedCode) } diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt index 54aa9561bf..533d7f41d1 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt @@ -9,6 +9,7 @@ import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.EMPTY import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.plus import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError +import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -699,7 +700,12 @@ public class KtLintAssertThatAssertable( private fun format(): Pair> { val lintErrors = mutableSetOf() - val formattedCode = createKtLintRuleEngine().format(code) { lintError, _ -> lintErrors.add(lintError) } + val formattedCode = + createKtLintRuleEngine() + .format(code) { lintError -> + lintErrors.add(lintError) + AutocorrectDecision.ALLOW_AUTOCORRECT + } return Pair(formattedCode, lintErrors) } From 036fcbbdf5dc13480e6c45b78f3f592fecb27e87 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 14:48:05 +0200 Subject: [PATCH 30/33] Try to fix test Windows OS Closes #2658 --- .../pinterest/ktlint/rule/engine/internal/CodeFormatter.kt | 4 ++-- .../kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt index 0e588a9c92..e3e9b82ceb 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/CodeFormatter.kt @@ -152,9 +152,9 @@ internal class CodeFormatter( eolEditorConfigProperty == PropertyType.EndOfLineValue.crlf || eolEditorConfigProperty != PropertyType.EndOfLineValue.lf && doesNotContain('\r') -> - "\r\n" + "\r\n".also { LOGGER.debug { "line separator: ${eolEditorConfigProperty.name} --> CRLF" } } - else -> "\n" + else -> "\n".also { LOGGER.debug { "line separator: ${eolEditorConfigProperty.name} --> LF" } } } private fun Code.doesNotContain(char: Char) = content.lastIndexOf(char) != -1 diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 07333ecbc7..795115603b 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -308,6 +308,7 @@ class KtLintTest { ), ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } + assertThat(actual.toByteArray()).isEqualTo(code.toByteArray()) assertThat(actual).isEqualTo(code) } From f9f7847268c47790ee8ec7549bf39a829911e754 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 15:01:46 +0200 Subject: [PATCH 31/33] Try to fix test Windows OS Closes #2658 --- .../ktlint/rule/engine/api/KtLintTest.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 795115603b..850b133d1d 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -28,6 +28,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPER import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isRoot +import io.github.oshai.kotlinlogging.KotlinLogging import org.assertj.core.api.Assertions.assertThat import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -36,6 +37,8 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() + class KtLintTest { /** * API Consumers directly use the ktlint-rule-engine module. Tests in this module should guarantee that the API is kept stable. @@ -292,7 +295,20 @@ class KtLintTest { @Test fun testFormatUnicodeBom() { - val code = getResourceAsText("spec/format-unicode-bom.kt.spec") + val originalCode = + getResourceAsText("spec/format-unicode-bom.kt.spec") + val code = + originalCode + .replace("\r\n", "\n") + .replace("\r", "\n") + if (code != originalCode) { + LOGGER.debug { + """ + Original code: ${originalCode.toByteArray()} + Changed code : ${code.toByteArray()} + """.trimIndent() + } + } val actual = KtLintRuleEngine( From ab72b6302b143db75d0cf937b8b52bff790b2f46 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 15:10:27 +0200 Subject: [PATCH 32/33] Try to fix test Windows OS Closes #2658 --- .../kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 850b133d1d..cb4749fec4 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.rule.engine.api +import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.AUTOCORRECT_ERROR_RULE_ID import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.ERROR_MESSAGE_CAN_BE_AUTOCORRECTED import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.ERROR_MESSAGE_CAN_NOT_BE_AUTOCORRECTED From 3460464fc4554db0ca286294264bd4a8c791069f Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 28 May 2024 15:21:08 +0200 Subject: [PATCH 33/33] Try to fix test Windows OS Closes #2658 --- .../ktlint/rule/engine/api/KtLintTest.kt | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index cb4749fec4..9ab4a9dae7 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -1,6 +1,5 @@ package com.pinterest.ktlint.rule.engine.api -import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.AUTOCORRECT_ERROR_RULE_ID import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.ERROR_MESSAGE_CAN_BE_AUTOCORRECTED import com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.Companion.ERROR_MESSAGE_CAN_NOT_BE_AUTOCORRECTED @@ -29,7 +28,6 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.END_OF_LINE_PROPER import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed import com.pinterest.ktlint.rule.engine.core.api.isRoot -import io.github.oshai.kotlinlogging.KotlinLogging import org.assertj.core.api.Assertions.assertThat import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -38,8 +36,6 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() - class KtLintTest { /** * API Consumers directly use the ktlint-rule-engine module. Tests in this module should guarantee that the API is kept stable. @@ -296,20 +292,11 @@ class KtLintTest { @Test fun testFormatUnicodeBom() { - val originalCode = - getResourceAsText("spec/format-unicode-bom.kt.spec") val code = - originalCode + getResourceAsText("spec/format-unicode-bom.kt.spec") + // Standardize code to use LF as line separator regardless of OS .replace("\r\n", "\n") .replace("\r", "\n") - if (code != originalCode) { - LOGGER.debug { - """ - Original code: ${originalCode.toByteArray()} - Changed code : ${code.toByteArray()} - """.trimIndent() - } - } val actual = KtLintRuleEngine( @@ -325,7 +312,6 @@ class KtLintTest { ), ).format(Code.fromSnippet(code)) { _ -> AutocorrectDecision.ALLOW_AUTOCORRECT } - assertThat(actual.toByteArray()).isEqualTo(code.toByteArray()) assertThat(actual).isEqualTo(code) }