diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77e2104583..90d32ee1ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,13 +3,13 @@ java-compilation = "21" # The java-target version is the lowest supported LTS version of Java. Jar's produced are bytecode compatible with this version. java-target = "8" -kotlin = "2.1.0" -kotlinDev = "2.1.0" +kotlin = "2.1.10" +kotlinDev = "2.1.10" [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } checksum = "org.gradle.crypto.checksum:1.4.0" -shadow = "com.gradleup.shadow:8.3.5" +shadow = "com.gradleup.shadow:8.3.6" kotlinx-binary-compatibiltiy-validator = "org.jetbrains.kotlinx.binary-compatibility-validator:0.17.0" [libraries] @@ -19,18 +19,18 @@ kotlin-plugin-dev = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", vers clikt = "com.github.ajalt.clikt:clikt:5.0.2" dokka = "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0" ec4j = "org.ec4j.core:ec4j-core:1.1.0" -logging = "io.github.oshai:kotlin-logging-jvm:7.0.3" +logging = "io.github.oshai:kotlin-logging-jvm:7.0.4" slf4j = "org.slf4j:slf4j-simple:2.0.16" poko = "dev.drewhamilton.poko:poko-gradle-plugin:0.18.2" # Use logback-classic as the logger for kotlin-logging / slf4j as it allow changing the log level at runtime. # TODO: Update "renovate.json" once logback-classic is updated to 1.4 (once java8 support for ktlint is dropped) logback = "ch.qos.logback:logback-classic:1.3.15" -logcaptor = "io.github.hakky54:logcaptor:2.10.0" +logcaptor = "io.github.hakky54:logcaptor:2.10.1" # Required for logback-test.xml conditional configuration so that trace-logging in unit test can be automatically enabled using an # environment variable janino = "org.codehaus.janino:janino:3.1.12" # Testing libraries junit5 = "org.junit.jupiter:junit-jupiter:5.11.4" -assertj = "org.assertj:assertj-core:3.27.2" +assertj = "org.assertj:assertj-core:3.27.3" sarif4k = "io.github.detekt.sarif4k:sarif4k:0.6.0" jimfs = "com.google.jimfs:jimfs:1.3.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1b837a19c..d71047787f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME 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 a1a00a5a7d..ec8b9de636 100644 --- a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api +++ b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api @@ -3,6 +3,9 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt public static final fun beforeCodeSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z public static final fun betweenCodeSiblings (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z public static final fun children (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lkotlin/sequences/Sequence; + public static final fun dummyPsiElement (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lorg/jetbrains/kotlin/com/intellij/psi/PsiElement; + public static final fun endOffset (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)I + public static final fun findChildByTypeRecursively (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;Z)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static final fun findCompositeParentElementOfType (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static final fun firstChildLeafOrSelf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static final fun getColumn (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)I @@ -11,9 +14,12 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt public static final fun indent (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Z)Ljava/lang/String; public static synthetic fun indent$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Ljava/lang/String; public static final fun isCodeLeaf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z + public static final fun isDeclaration (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z + public static final fun isKtAnnotated (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z public static final fun isLeaf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/reflect/KClass;)Z public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z + public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet;)Z public static final fun isPartOfComment (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z public static final fun isPartOfCompositeElementOfType (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z public static final fun isPartOfString (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z @@ -56,6 +62,8 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt public static synthetic fun prevLeaf$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static final fun prevSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static synthetic fun prevSibling$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; + public static final fun recursiveChildren (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Z)Lkotlin/sequences/Sequence; + public static synthetic fun recursiveChildren$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Lkotlin/sequences/Sequence; public static final fun remove (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)V public static final fun replaceWith (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)V public static final fun upsertWhitespaceAfterMe (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Ljava/lang/String;)V @@ -524,6 +532,12 @@ public final class com/pinterest/ktlint/rule/engine/core/api/SinceKtlint$Status public static fun values ()[Lcom/pinterest/ktlint/rule/engine/core/api/SinceKtlint$Status; } +public final class com/pinterest/ktlint/rule/engine/core/api/TokenSets { + public static final field INSTANCE Lcom/pinterest/ktlint/rule/engine/core/api/TokenSets; + public final fun getCOMMENTS ()Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet; + public final fun getCONTROL_FLOW_KEYWORDS ()Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet; +} + public final class com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigPropertyKt { public static final fun getCODE_STYLE_PROPERTY ()Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty; public static final fun getCODE_STYLE_PROPERTY_TYPE ()Lorg/ec4j/core/model/PropertyType$LowerCasingPropertyType; diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt index 0eccbd4769..0b5810f249 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt @@ -7,16 +7,34 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VAL_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.VARARG_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.VAR_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE +import org.jetbrains.kotlin.KtNodeType +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment +import org.jetbrains.kotlin.com.intellij.mock.MockProject +import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.com.intellij.psi.PsiElement +import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.psi.KtAnnotated +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.leaves +import org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementType +import org.jetbrains.kotlin.psi.stubs.elements.KtTokenSets import org.jetbrains.kotlin.util.prefixIfNot import org.jetbrains.kotlin.utils.addToStdlib.applyIf +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.reflect.KClass public fun ASTNode.nextLeaf( @@ -183,11 +201,18 @@ public fun ASTNode.parent( return null } +public fun ASTNode.isPartOf(tokenSet: TokenSet): Boolean = parent(strict = false) { tokenSet.contains(it.elementType) } != null + /** * @param elementType [ElementType].* */ public fun ASTNode.isPartOf(elementType: IElementType): Boolean = parent(elementType, strict = false) != null +@Deprecated( + "Marked for removal in Ktlint 2.x. Replace with ASTNode.isPartOf(elementType: IElementType) or ASTNode.isPartOf(tokenSet: TokenSet). " + + "This method might cause performance issues, see https://github.com/pinterest/ktlint/pull/2901", + replaceWith = ReplaceWith("this.isPartOf(elementTypeOrTokenSet)"), +) public fun ASTNode.isPartOf(klass: KClass): Boolean { var n: ASTNode? = this while (n != null) { @@ -207,7 +232,13 @@ public fun ASTNode.findCompositeParentElementOfType(iElementType: IElementType): public fun ASTNode.isPartOfString(): Boolean = parent(STRING_TEMPLATE, strict = false) != null -public fun ASTNode?.isWhiteSpace(): Boolean = this != null && elementType == WHITE_SPACE +@OptIn(ExperimentalContracts::class) +public fun ASTNode?.isWhiteSpace(): Boolean { + contract { + returns(true) implies (this@isWhiteSpace != null) + } + return this != null && elementType == WHITE_SPACE +} public fun ASTNode?.isWhiteSpaceWithNewline(): Boolean = this != null && elementType == WHITE_SPACE && textContains('\n') @@ -223,10 +254,18 @@ public fun ASTNode.isLeaf(): Boolean = firstChildNode == null */ public fun ASTNode.isCodeLeaf(): Boolean = isLeaf() && !isWhiteSpace() && !isPartOfComment() -public fun ASTNode.isPartOfComment(): Boolean = parent(strict = false) { it.psi is PsiComment } != null +public fun ASTNode.isPartOfComment(): Boolean = isPartOf(TokenSets.COMMENTS) public fun ASTNode.children(): Sequence = generateSequence(firstChildNode) { node -> node.treeNext } +public fun ASTNode.recursiveChildren(includeSelf: Boolean = false): Sequence = + sequence { + if (includeSelf) { + yield(this@recursiveChildren) + } + children().forEach { yieldAll(it.recursiveChildren(includeSelf = true)) } + } + /** * Updates or inserts a new whitespace element with [text] before the given node. If the node itself is a whitespace * then its contents is replaced with [text]. If the node is a (nested) composite element, the whitespace element is @@ -555,3 +594,84 @@ public fun ASTNode.replaceWith(node: ASTNode) { public fun ASTNode.remove() { treeParent.removeChild(this) } + +/** + * Searches the receiver [ASTNode] recursively, returning the first child with type [elementType]. If none are found, returns `null`. + * If [includeSelf] is `true`, includes the receiver in the search. The receiver would then be the first element searched, so it is + * guaranteed to be returned if it has type [elementType]. + */ +public fun ASTNode.findChildByTypeRecursively( + elementType: IElementType, + includeSelf: Boolean, +): ASTNode? = recursiveChildren(includeSelf).firstOrNull { it.elementType == elementType } + +/** + * Returns the end offset of the text of this [ASTNode] + */ +public fun ASTNode.endOffset(): Int = textRange.endOffset + +private val elementTypeCache = hashMapOf() + +/** + * Checks if the [AstNode] extends the [KtAnnotated] interface. Using this function to check the interface is more performant than checking + * whether `psi is KtAnnotated` as the psi does not need to be derived from [ASTNode]. + */ +public fun ASTNode.isKtAnnotated(): Boolean = psiType { it is KtAnnotated } + +private inline fun ASTNode.psiType(predicate: (psiElement: PsiElement) -> Boolean): Boolean = predicate(dummyPsiElement()) + +/** + * Checks if the [AstNode] extends the [T] interface which implements [KtElement]. Call this function like: + * ``` + * astNode.isPsiType() + * ``` + * Using this function to check the [PsiElement] type of the [ASTNode] is more performant than checking whether `astNode.psi is KtAnnotated` + * as the psi does not need to be derived from [ASTNode]. + */ +public inline fun ASTNode.isPsiType(): Boolean = this.dummyPsiElement() is T + +/** + * FOR INTERNAL USE ONLY. The returned element is a stub version of a [PsiElement] of the same type as the given [ASTNode]. The returned + * result may only be used to validate the type of the [PsiElement]. + */ +public fun ASTNode.dummyPsiElement(): PsiElement = + elementTypeCache + .getOrPut(elementType) { + // Create a dummy Psi element based on the current node, so that we can store the Psi Type for this ElementType. + // Creating this cache entry once per elementType is cheaper than accessing the psi for every node. + when (elementType) { + is KtFileElementType -> createDummyKtFile() + is KtKeywordToken -> this as PsiElement + is KtNodeType -> (elementType as KtNodeType).createPsi(this) + is KtStubElementType<*, *> -> (elementType as KtStubElementType<*, *>).createPsiFromAst(this) + is KtToken -> this as PsiElement + else -> throw NotImplementedError("Cannot create dummy psi for $elementType (${elementType::class})") + } + } + +private fun createDummyKtFile(): KtFile { + val disposable = Disposer.newDisposable() + try { + val project = + KotlinCoreEnvironment + .createForProduction( + disposable, + CompilerConfiguration(), + EnvironmentConfigFiles.JVM_CONFIG_FILES, + ).project as MockProject + + return PsiFileFactory + .getInstance(project) + .createFileFromText("dummy-file.kt", KotlinLanguage.INSTANCE, "") as KtFile + } finally { + // Dispose explicitly to (possibly) prevent memory leak + // https://discuss.kotlinlang.org/t/memory-leak-in-kotlincoreenvironment-and-kotlintojvmbytecodecompiler/21950 + // https://youtrack.jetbrains.com/issue/KT-47044 + disposable.dispose() + } +} + +/** + * Returns true if the receiver is not null, and it represents a declaration + */ +public fun ASTNode?.isDeclaration(): Boolean = this != null && elementType in KtTokenSets.DECLARATION_TYPES diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/TokenSets.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/TokenSets.kt new file mode 100644 index 0000000000..d7ee3cec07 --- /dev/null +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/TokenSets.kt @@ -0,0 +1,31 @@ +package com.pinterest.ktlint.rule.engine.core.api + +import com.pinterest.ktlint.rule.engine.core.api.ElementType.DO_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.FOR_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.IF_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TRY_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_KEYWORD +import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHILE_KEYWORD +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.lexer.KtTokens + +public object TokenSets { + public val COMMENTS: TokenSet = KtTokens.COMMENTS + + /** + * + * Reference: This is a subset of [KotlinExpressionParsing.EXPRESSION_FIRST] + */ + public val CONTROL_FLOW_KEYWORDS: TokenSet = + TokenSet.create( + IF_KEYWORD, // if + WHEN_KEYWORD, // when + TRY_KEYWORD, // try + OBJECT_KEYWORD, // object + // loop + FOR_KEYWORD, + WHILE_KEYWORD, + DO_KEYWORD, + ) +} 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 8bc0dc0084..10e87a6096 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 @@ -5,6 +5,7 @@ import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine 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 +import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN @@ -25,6 +26,7 @@ import org.assertj.core.api.Assertions.assertThatNoException import org.assertj.core.api.Assertions.entry import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.lang.FileASTNode +import org.jetbrains.kotlin.psi.KtAnnotated import org.jetbrains.kotlin.psi.psiUtil.leaves import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -1089,6 +1091,55 @@ class ASTNodeExtensionTest { } } + @Nested + inner class FindChildByTypeRecursively { + @Test + fun `Given a node with a target type return non-null`() { + val code = + """ + class MyClass { + fun foo() = 42 + } + """.trimIndent() + val result = + transformCodeToAST(code) + .findChildByTypeRecursively(FUN, includeSelf = false) + assertThat(result).isNotNull() + } + + @Test + fun `Given a node without a target type return null`() { + val code = + """ + class MyClass { + + } + """.trimIndent() + val result = + transformCodeToAST(code) + .findChildByTypeRecursively(FUN, includeSelf = false) + assertThat(result).isNull() + } + } + + @Test + fun `Given a simple class declaration without body then the declaration itself is derived from KtAnnotated while its child elements are not derived from KtAnnotated`() { + val code = + """ + class Foo + """.trimIndent() + + val actual = transformCodeToAST(code).findChildByType(CLASS)!! + + assertThat(actual.isKtAnnotated()).isTrue() + assertThat(actual.findChildByType(CLASS_KEYWORD)!!.isKtAnnotated()).isFalse() + assertThat(actual.findChildByType(IDENTIFIER)!!.isKtAnnotated()).isFalse() + + assertThat(actual.isPsiType()).isTrue() + assertThat(actual.findChildByType(CLASS_KEYWORD)!!.isPsiType()).isFalse() + assertThat(actual.findChildByType(IDENTIFIER)!!.isPsiType()).isFalse() + } + private inline fun String.transformAst(block: FileASTNode.() -> Unit): FileASTNode = transformCodeToAST(this) .apply(block) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt index 0db38f466e..d87df0bd13 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt @@ -11,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST +import com.pinterest.ktlint.rule.engine.core.api.findChildByTypeRecursively import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment @@ -30,7 +31,6 @@ import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassInitializer -import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclarationModifierList import org.jetbrains.kotlin.psi.KtExpression @@ -47,7 +47,6 @@ import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScriptInitializer import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.psiUtil.children -import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.util.prefixIfNot @@ -202,12 +201,14 @@ private fun ASTNode.existingSuppressions() = existingSuppressionsFromNamedArgumentOrNull() ?: getValueArguments() -private fun ASTNode.existingSuppressionsFromNamedArgumentOrNull() = - psi - .findDescendantOfType() - ?.children - ?.map { it.text } - ?.toSet() +private fun ASTNode.existingSuppressionsFromNamedArgumentOrNull(): Set? = + findChildByTypeRecursively(ElementType.COLLECTION_LITERAL_EXPRESSION, includeSelf = false) + ?.run { + children() + .filter { it.elementType == ElementType.STRING_TEMPLATE } + .map { it.text } + .toSet() + } private fun ASTNode.findSuppressionAnnotations(): Map = if (this.isRoot()) { diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt index d0f0433555..9d59cf71ee 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressionLocator.kt @@ -1,20 +1,20 @@ package com.pinterest.ktlint.rule.engine.internal +import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE import com.pinterest.ktlint.rule.engine.core.api.IgnoreKtlintSuppressions 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.TokenSets import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig +import com.pinterest.ktlint.rule.engine.core.api.isPsiType 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.recursiveChildren import com.pinterest.ktlint.rule.engine.internal.SuppressionLocator.CommentSuppressionHint.Type.BLOCK_END import com.pinterest.ktlint.rule.engine.internal.SuppressionLocator.CommentSuppressionHint.Type.BLOCK_START import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.psi.KtAnnotated -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.ValueArgument -import org.jetbrains.kotlin.psi.psiUtil.endOffset -import org.jetbrains.kotlin.psi.psiUtil.startOffset internal class SuppressionLocator( editorConfig: EditorConfig, @@ -57,32 +57,23 @@ internal class SuppressionLocator( private fun findSuppressionHints(rootNode: ASTNode): List { val suppressionHints = ArrayList() val commentSuppressionsHints = mutableListOf() - rootNode.findSuppressionHints { node -> - when (val psi = node.psi) { - is PsiComment -> { + rootNode.recursiveChildren(includeSelf = true).forEach { node -> + when (node.elementType) { + in TokenSets.COMMENTS -> { node .createSuppressionHintFromComment() - ?.let { commentSuppressionsHints.add(it) } + ?.let(commentSuppressionsHints::add) } - is KtAnnotated -> { - psi - .createSuppressionHintFromAnnotations() - ?.let { suppressionHints.add(it) } + ElementType.ANNOTATION_ENTRY -> { + node + .takeIf { it.isSuppressAnnotation() } + ?.createSuppressionHintFromAnnotations() + ?.let(suppressionHints::add) } } } - - return suppressionHints.plus( - commentSuppressionsHints.toSuppressionHints(), - ) - } - - private fun ASTNode.findSuppressionHints(block: (node: ASTNode) -> Unit) { - block(this) - this - .getChildren(null) - .forEach { it.findSuppressionHints(block) } + return suppressionHints + commentSuppressionsHints.toSuppressionHints() } private fun ASTNode.createSuppressionHintFromComment(): CommentSuppressionHint? = @@ -178,63 +169,56 @@ internal class SuppressionLocator( private fun List.tail() = this.subList(1, this.size) - private fun KtAnnotated.createSuppressionHintFromAnnotations(): SuppressionHint? = - annotationEntries - .filter { - it - .calleeExpression - ?.constructorReferenceExpression - ?.getReferencedName() in SUPPRESS_ANNOTATIONS - }.flatMap(KtAnnotationEntry::getValueArguments) - .flatMap { it.findRuleSuppressionIds() } - .let { suppressedRuleIds -> - when { - suppressedRuleIds.isEmpty() -> { - null - } + private fun ASTNode.isSuppressAnnotation(): Boolean = + findChildByType(ElementType.CONSTRUCTOR_CALLEE) + ?.findChildByType(ElementType.TYPE_REFERENCE) + ?.text in SUPPRESS_ANNOTATIONS - suppressedRuleIds.contains(ALL_KTLINT_RULES_SUPPRESSION_ID) -> { - SuppressionHint( - IntRange(startOffset, endOffset - 1), - emptySet(), - ) - } - - else -> { - SuppressionHint( - IntRange(startOffset, endOffset - 1), - suppressedRuleIds.toSet(), - ) - } + private fun ASTNode.createSuppressionHintFromAnnotations(): SuppressionHint? { + val suppressedRuleIds = + recursiveChildren() + .filter { it.elementType == ElementType.VALUE_ARGUMENT } + .flatMapTo(mutableListOf()) { + it.text.removeSurrounding("\"").findRuleSuppressionIds() } - } - private fun ValueArgument.findRuleSuppressionIds(): List = - getArgumentExpression() - ?.text - ?.removeSurrounding("\"") - ?.let { argumentExpressionText -> - when { - argumentExpressionText == "ktlint" -> { - // Disable all rules - listOf(ALL_KTLINT_RULES_SUPPRESSION_ID) - } + if (suppressedRuleIds.isEmpty()) return null - argumentExpressionText.startsWith("ktlint:") -> { - // Disable specific rule. For backwards compatibility prefix rules without rule set id with the "standard" rule set - // id. Note that the KtlintSuppressionRule will emit a lint violation on the id. So this fix is only applicable for - // code bases in which the rule and suppression id's have not yet been fixed. - argumentExpressionText - .removePrefix("ktlint:") - .let { listOf(RuleId.prefixWithStandardRuleSetIdWhenMissing(it)) } - } +// val owner = parent { it.isKtAnnotated() } ?: return null + val owner = parent { it.isPsiType() } ?: return null - else -> { - // Disable specific rule if the annotation value is mapped to a specific rule - SUPPRESS_ANNOTATION_RULE_MAP[argumentExpressionText] - } - } - }.orEmpty() + val textRange = owner.textRange + + return SuppressionHint( + IntRange(textRange.startOffset, textRange.endOffset - 1), + if (suppressedRuleIds.contains(ALL_KTLINT_RULES_SUPPRESSION_ID)) { + emptySet() + } else { + suppressedRuleIds.toSet() + }, + ) + } + + private fun String.findRuleSuppressionIds(): List = + when { + this == "ktlint" -> { + // Disable all rules + listOf(ALL_KTLINT_RULES_SUPPRESSION_ID) + } + + startsWith("ktlint:") -> { + // Disable specific rule. For backwards compatibility prefix rules without rule set id with the "standard" rule set + // id. Note that the KtlintSuppressionRule will emit a lint violation on the id. So this fix is only applicable for + // code bases in which the rule and suppression id's have not yet been fixed. + removePrefix("ktlint:") + .let { listOf(RuleId.prefixWithStandardRuleSetIdWhenMissing(it)) } + } + + else -> { + // Disable specific rule if the annotation value is mapped to a specific rule + SUPPRESS_ANNOTATION_RULE_MAP[this].orEmpty() + } + } /** * @param range zero-based range of lines where lint errors should be suppressed 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 eba445d21c..23cdd8a068 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 @@ -10,6 +10,7 @@ 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.IgnoreKtlintSuppressions import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.findChildByTypeRecursively 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 @@ -42,9 +43,9 @@ import org.jetbrains.kotlin.psi.KtScriptInitializer import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.psi.KtValueArgumentList -import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.utils.addToStdlib.applyIf /** @@ -105,9 +106,7 @@ public class KtlintSuppressionRule( emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node - .psi - .findDescendantOfType() - ?.node + .findChildByTypeRecursively(ElementType.LITERAL_STRING_TEMPLATE_ENTRY, includeSelf = true) ?.let { literalStringTemplateEntry -> val prefixedSuppression = literalStringTemplateEntry 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 fcb611475c..9e78571a1f 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 @@ -4,6 +4,7 @@ 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 +import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_TARGET import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONSTRUCTOR_KEYWORD @@ -43,11 +44,9 @@ import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe -import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget -import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.psiUtil.siblings import org.jetbrains.kotlin.utils.addToStdlib.ifTrue @@ -346,11 +345,10 @@ public class AnnotationRule : private fun ASTNode.isNotReceiverTargetAnnotation() = getAnnotationUseSiteTarget() != AnnotationUseSiteTarget.RECEIVER - private fun ASTNode.getAnnotationUseSiteTarget() = - psi - .safeAs() - ?.useSiteTarget - ?.getAnnotationUseSiteTarget() + private fun ASTNode.getAnnotationUseSiteTarget(): AnnotationUseSiteTarget? = + takeIf { it.elementType == ANNOTATION_ENTRY } + ?.findChildByType(ANNOTATION_TARGET) + ?.let { USE_SITE_TARGETS[it.text] } private fun ASTNode.isAnnotationEntryWithValueArgumentList() = getAnnotationEntryValueArgumentList() != null @@ -469,6 +467,7 @@ public class AnnotationRule : FILE_ANNOTATION_LIST, MODIFIER_LIST, ) + val USE_SITE_TARGETS = AnnotationUseSiteTarget.entries.associateBy { it.renderName } } } 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 ae1709a2b2..a9ed2179b5 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 @@ -6,6 +6,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.endOffset 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 @@ -79,7 +80,7 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { }, { // Disallow multiple white spaces as well as comments - if (it.psi is PsiWhiteSpace) { + if (it.isWhiteSpace()) { val s = it.text // Ensure at least one occurrence of two line breaks s.indexOf("\n") != s.lastIndexOf("\n") @@ -90,8 +91,7 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { ) if (next != null) { if (node.elementType != ElementType.FILE_ANNOTATION_LIST && next.isPartOfComment()) { - val psi = node.psi - emit(psi.endOffset, ERROR_MESSAGE, true) + emit(node.endOffset(), ERROR_MESSAGE, true) .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 @@ -121,8 +121,7 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { if (whiteSpaces.isNotEmpty() && node.elementType != ElementType.FILE_ANNOTATION_LIST) { // Check to make sure there are multi breaks between annotations if (whiteSpaces.any { psi -> psi.textToCharArray().count { it == '\n' } > 1 }) { - val psi = node.psi - emit(psi.endOffset, ERROR_MESSAGE, true) + emit(node.endOffset(), ERROR_MESSAGE, true) .ifAutocorrectAllowed { removeIntraLineBreaks(node, annotations.last()) removeExtraLineBreaks(node) 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 fa4d840e6f..3215cb1065 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 @@ -4,7 +4,6 @@ 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 -import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_LITERAL import com.pinterest.ktlint.rule.engine.core.api.ElementType.LPAR @@ -20,6 +19,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.TokenSets 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.editorconfig.INDENT_SIZE_PROPERTY @@ -38,12 +38,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl -import org.jetbrains.kotlin.psi.KtContainerNode -import org.jetbrains.kotlin.psi.KtDoWhileExpression -import org.jetbrains.kotlin.psi.KtIfExpression -import org.jetbrains.kotlin.psi.KtWhileExpression import org.jetbrains.kotlin.psi.psiUtil.children -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType /** * https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting @@ -288,18 +283,8 @@ public class ArgumentListWrappingRule : } private fun ASTNode.isOnSameLineAsControlFlowKeyword(): Boolean { - val containerNode = psi.getStrictParentOfType() ?: return false - if (containerNode.node.elementType == ELSE) return false - val controlFlowKeyword = - when (val parent = containerNode.parent) { - is KtIfExpression -> parent.ifKeyword.node - is KtWhileExpression -> parent.firstChild.node - is KtDoWhileExpression -> parent.whileKeyword?.node - else -> null - } ?: return false - var prevLeaf = prevLeaf() ?: return false - while (prevLeaf != controlFlowKeyword) { + while (prevLeaf.elementType !in TokenSets.CONTROL_FLOW_KEYWORDS) { if (prevLeaf.isWhiteSpaceWithNewline()) return false prevLeaf = prevLeaf.prevLeaf() ?: return false } 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 29a28e18cb..f9e9f1c669 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 @@ -24,17 +24,15 @@ 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.isDeclaration 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.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe -import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtFunctionLiteral /** * Insert a blank line before declarations. No blank line is inserted before between a class or method signature and the first declaration @@ -150,11 +148,9 @@ public class BlankLineBeforeDeclarationRule : } node - .takeIf { it.psi is KtDeclaration } - ?.takeIf { - val prevLeaf = it.prevLeaf() - prevLeaf != null && (!prevLeaf.isWhiteSpace() || !prevLeaf.text.startsWith("\n\n")) - }?.let { insertBeforeNode -> + .takeIf { it.isDeclaration() } + ?.takeUnless { it.prevLeaf().isBlankLine() } + ?.let { insertBeforeNode -> emit(insertBeforeNode.startOffset, "Expected a blank line for this declaration", true) .ifAutocorrectAllowed { insertBeforeNode.upsertWhitespaceBeforeMe("\n".plus(node.indent())) @@ -162,6 +158,8 @@ public class BlankLineBeforeDeclarationRule : } } + private fun ASTNode?.isBlankLine() = this == null || text.startsWith("\n\n") + private fun ASTNode.isFirstCodeSiblingInClassBody() = this == treeParent @@ -181,10 +179,8 @@ public class BlankLineBeforeDeclarationRule : treeParent .takeIf { it.elementType == BLOCK && it.treeParent.elementType == FUNCTION_LITERAL } ?.treeParent - ?.psi - ?.safeAs() - ?.bodyExpression - ?.node + ?.takeIf { it.elementType == FUNCTION_LITERAL } + ?.findChildByType(BLOCK) ?.children() ?.firstOrNull { !it.isWhiteSpace() && !it.isPartOfComment() } 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 381f96ad3d..0d294a2bb2 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 @@ -39,6 +39,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PR 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.isLeaf 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 @@ -54,7 +55,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.com.intellij.psi.impl.source.tree.LeafElement /** * Formats the class signature according to https://kotlinlang.org/docs/coding-conventions.html#class-headers @@ -651,7 +651,7 @@ public class ClassSignatureRule : private fun List.collectLeavesRecursively(): List = flatMap { it.collectLeavesRecursively() } private fun ASTNode.collectLeavesRecursively(): List = - if (psi is LeafElement) { + if (isLeaf()) { listOf(this) } else { children() 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 2fc96fdac7..cbd9ab7ed8 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,6 +1,7 @@ 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 @@ -13,7 +14,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.internal.regExIgnoringDiacrit import org.ec4j.core.model.PropertyType import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement -import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtPsiUtil /** * https://kotlinlang.org/docs/coding-conventions.html#property-names @@ -57,8 +58,9 @@ public class EnumEntryNameCaseRule : if (node !is CompositeElement) { return } - val enumEntry = node.psi as? KtEnumEntry ?: return - val name = enumEntry.name ?: return + if (node.elementType != ElementType.ENUM_ENTRY) return + val nameNode = node.findChildByType(ElementType.IDENTIFIER) ?: return + val name = KtPsiUtil.unquoteIdentifier(nameNode.text) if (!name.matches(enumEntryCasingRegex)) { emit(node.startOffset, enumEntryCasingViolation, false) 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 63d9ba4080..bb18315407 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,6 +1,7 @@ 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.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY @@ -17,6 +18,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.hasModifier 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 @@ -29,7 +31,6 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.psi.KtClass /** * @@ -61,7 +62,7 @@ public class EnumWrappingRule : ) { node .takeIf { node.elementType == CLASS } - ?.takeIf { (node.psi as KtClass).isEnum() } + ?.takeIf { node.hasModifier(ElementType.ENUM_KEYWORD) } ?.findChildByType(CLASS_BODY) ?.let { classBody -> visitEnumClass(classBody, emit) 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 20cda849ac..cb3e21e25f 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 @@ -38,6 +38,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PR 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.isLeaf 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.nextCodeLeaf @@ -56,7 +57,6 @@ import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.Functio import org.ec4j.core.model.PropertyType import org.ec4j.core.model.PropertyType.PropertyValueParser.EnumValueParser 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.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.utils.addToStdlib.ifTrue @@ -700,7 +700,7 @@ public class FunctionSignatureRule : private fun List.collectLeavesRecursively(): List = flatMap { it.collectLeavesRecursively() } private fun ASTNode.collectLeavesRecursively(): List = - if (psi is LeafElement) { + if (isLeaf()) { listOf(this) } else { children() 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 10495e83a4..c868962f9a 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 @@ -90,6 +90,7 @@ 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 +import com.pinterest.ktlint.rule.engine.core.api.TokenSets import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.column import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY @@ -124,7 +125,6 @@ import io.github.oshai.kotlinlogging.KotlinLogging import org.ec4j.core.model.PropertyType import org.ec4j.core.model.PropertyType.PropertyValueParser import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.psi.KtStringTemplateExpression import org.jetbrains.kotlin.psi.psiUtil.leaves @@ -1191,7 +1191,7 @@ public class IndentationRule : } nextLeaf - ?.parent(strict = false) { it.psi is PsiComment } + ?.parent(strict = false) { it.elementType in TokenSets.COMMENTS } ?.let { comment -> if (text.endsWith("\n")) { processedButNoIndentationChangedNeeded() 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 87b2e8cebf..3d751b85d2 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,6 +1,7 @@ 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.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE @@ -16,6 +17,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RULE_EXECUTION_PRO import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution import com.pinterest.ktlint.rule.engine.core.api.editorconfig.ktLintRuleExecutionPropertyName 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 import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.leavesOnLine @@ -26,11 +28,7 @@ import com.pinterest.ktlint.rule.engine.core.api.prevLeaf 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.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.kdoc.psi.api.KDoc -import org.jetbrains.kotlin.psi.KtImportDirective -import org.jetbrains.kotlin.psi.KtPackageDirective @SinceKtlint("0.9", STABLE) public class MaxLineLengthRule : @@ -82,9 +80,9 @@ public class MaxLineLengthRule : .takeIf { it is LeafPsiElement } ?.takeIf { it.nextLeaf() == null || it.nextLeaf().isWhiteSpaceWithNewline() } ?.takeIf { it.lineLength() > maxLineLength } - ?.takeUnless { it.isPartOf(KtPackageDirective::class) } - ?.takeUnless { it.isPartOf(KtImportDirective::class) } - ?.takeUnless { it.isPartOf(KDoc::class) } + ?.takeUnless { it.isPartOf(ElementType.PACKAGE_DIRECTIVE) } + ?.takeUnless { it.isPartOf(ElementType.IMPORT_DIRECTIVE) } + ?.takeUnless { it.isPartOf(ElementType.KDOC) } ?.takeUnless { it.isPartOfRawMultiLineString() } ?.takeUnless { it.isLineOnlyContainingSingleTemplateString() } ?.takeUnless { it.elementType == COMMA && it.prevLeaf()?.isLineOnlyContainingSingleTemplateString() ?: false } @@ -130,7 +128,7 @@ public class MaxLineLengthRule : ?: false private fun ASTNode.isLineOnlyContainingComment() = - isPartOf(PsiComment::class) && + isPartOfComment() && (prevLeaf() == null || prevLeaf().isWhiteSpaceWithNewline()) public companion object { 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 021243e44b..2f10b864f3 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,6 +1,7 @@ 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.ABSTRACT_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ACTUAL_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY @@ -34,8 +35,6 @@ 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 @SinceKtlint("0.7", STABLE) public class ModifierOrderRule : StandardRule("modifier-order") { @@ -43,7 +42,7 @@ public class ModifierOrderRule : StandardRule("modifier-order") { node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - if (node.psi is KtDeclarationModifierList) { + if (node.elementType == ElementType.MODIFIER_LIST) { val modifierArr = node.getChildren(tokenSet) val sorted = modifierArr.copyOf().apply { sortWith(compareBy { ORDERED_MODIFIERS.indexOf(it.elementType) }) } if (!modifierArr.contentEquals(sorted)) { @@ -69,7 +68,7 @@ public class ModifierOrderRule : StandardRule("modifier-order") { } private fun squashAnnotations(sorted: Array): List { - val nonAnnotationModifiers = sorted.filter { it.psi !is KtAnnotationEntry } + val nonAnnotationModifiers = sorted.filter { it.elementType != ElementType.ANNOTATION_ENTRY } return if (nonAnnotationModifiers.size != sorted.size) { listOf("@Annotation...") + nonAnnotationModifiers.map { it.text } } else { 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 ed68b38340..55cf24a32a 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,6 +1,7 @@ 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.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE @@ -15,7 +16,6 @@ import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.remove import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.psi.KtObjectLiteralExpression @SinceKtlint("0.9", STABLE) public class NoEmptyClassBodyRule : StandardRule("no-empty-class-body") { @@ -28,7 +28,7 @@ public class NoEmptyClassBodyRule : StandardRule("no-empty-class-body") { n.elementType == LBRACE && n.nextLeaf { it.elementType != WHITE_SPACE }?.elementType == RBRACE } == true && - !node.isPartOf(KtObjectLiteralExpression::class) && + !node.isPartOf(ElementType.OBJECT_LITERAL) && node .treeParent .firstChildNode 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 d4ccd8b115..d571ab863b 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,6 +1,7 @@ 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.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY @@ -26,13 +27,10 @@ import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace -import org.jetbrains.kotlin.kdoc.psi.api.KDoc import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtDoWhileExpression import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtLoopExpression -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @SinceKtlint("0.1", STABLE) public class NoSemicolonsRule : @@ -84,11 +82,10 @@ public class NoSemicolonsRule : this is PsiWhiteSpace -> { nextLeaf { - val psi = it.psi it !is PsiWhiteSpace && it !is PsiComment && - psi.getStrictParentOfType() == null && - psi.getStrictParentOfType() == null + it.parent(ElementType.KDOC) == null && + it.parent(ElementType.ANNOTATION_ENTRY) == null }.let { nextLeaf -> nextLeaf == null || // \s+ and then eof 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 3d78892c69..f7df20a84b 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,6 +1,7 @@ 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.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -13,7 +14,6 @@ import com.pinterest.ktlint.rule.engine.core.api.parent import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.kdoc.psi.api.KDoc @SinceKtlint("0.1", STABLE) public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { @@ -74,7 +74,7 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { } } - private fun ASTNode.isPartOfKDoc() = parent(strict = false) { it.psi is KDoc } != null + private fun ASTNode.isPartOfKDoc() = parent(strict = false) { it.elementType == ElementType.KDOC } != null private fun ASTNode.hasTrailingSpacesBeforeNewline() = text.contains(SPACE_OR_TAB_BEFORE_NEWLINE_REGEX) 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 66cded850f..45e3b393cf 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,6 +1,7 @@ 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.BY_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE @@ -23,15 +24,11 @@ import com.pinterest.ktlint.rule.engine.core.api.nextSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.prevSibling import com.pinterest.ktlint.rule.engine.core.api.remove -import com.pinterest.ktlint.rule.engine.core.util.safeAs import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement import org.jetbrains.kotlin.kdoc.lexer.KDocTokens.MARKDOWN_LINK -import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink -import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtPackageDirective @@ -85,14 +82,13 @@ public class NoUnusedImportsRule : } MARKDOWN_LINK -> { - node - .psi - .safeAs() - ?.let { kdocLink -> - val linkText = kdocLink.getLinkText().removeBackticksAndTrim() - ref.add(Reference(linkText.split('.').first(), false)) - ref.add(Reference(linkText.split('.').last(), false)) - } + val linkText = + node + .text + .removeSurrounding("[", "]") + .removeBackticksAndTrim() + ref.add(Reference(linkText.split('.').first(), false)) + ref.add(Reference(linkText.split('.').last(), false)) } REFERENCE_EXPRESSION, OPERATION_REFERENCE -> { @@ -110,7 +106,7 @@ public class NoUnusedImportsRule : ref.add( Reference( it.removeBackticksAndTrim(), - node.psi.parentDotQualifiedExpression() != null, + node.isParentDotQualifiedExpressionOrNull(), ), ) } @@ -281,11 +277,22 @@ public class NoUnusedImportsRule : private fun String.isComponentN() = COMPONENT_N_REGEX.matches(this) - private fun PsiElement.parentDotQualifiedExpression(): KtDotQualifiedExpression? { - val callOrThis = (parent as? KtCallExpression)?.takeIf { it.calleeExpression == this } ?: this - return (callOrThis.parent as? KtDotQualifiedExpression)?.takeIf { it.selectorExpression == callOrThis } + private fun ASTNode.isParentDotQualifiedExpressionOrNull(): Boolean { + val callExpressionOrThis = parentCallExpressionOrNull() ?: this + return callExpressionOrThis.isDotQualifiedExpression() } + private fun ASTNode.parentCallExpressionOrNull() = + treeParent + .takeIf { it.elementType == ElementType.CALL_EXPRESSION } + + private fun ASTNode.isDotQualifiedExpression() = + treeParent + ?.takeIf { it.elementType == DOT_QUALIFIED_EXPRESSION } + ?.let { it.psi as? KtDotQualifiedExpression } + ?.takeIf { it.selectorExpression?.node == this } + .let { it != null } + private fun String.removeBackticksAndTrim() = replace("`", "").trim() private data class Reference( 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 fbf04822e2..b7e711ed6e 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 @@ -90,9 +90,10 @@ public class PropertyNamingRule : it == SERIAL_VERSION_UID_PROPERTY_NAME }?.takeUnless { it.matches(constantNamingProperty.regEx) } ?.let { + val expectedNaming = constantNamingProperty.name.replace("_", " ") emit( identifier.startOffset, - "Property name should use the screaming snake case notation when the value can not be changed", + "Property name should use the $expectedNaming notation when the value can not be changed", false, ) } 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 66400cdc8b..1c740e8828 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,6 +1,7 @@ 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.COLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ import com.pinterest.ktlint.rule.engine.core.api.RuleId @@ -21,15 +22,7 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.psi.KtAnnotation -import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtConstructor -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.psi.KtTypeConstraint -import org.jetbrains.kotlin.psi.KtTypeParameterList import org.jetbrains.kotlin.psi.psiUtil.siblings import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -50,7 +43,7 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - val psiParent = node.psi.parent + val parentType = node.treeParent.elementType val prevLeaf = node.prevLeaf() if (prevLeaf != null && prevLeaf.isWhiteSpaceWithNewline()) { emit(prevLeaf.startOffset, "Unexpected newline before \":\"", true) @@ -62,7 +55,7 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { .toList() .reversed() when { - psiParent is KtProperty || psiParent is KtNamedFunction -> { + parentType == ElementType.PROPERTY || parentType == ElementType.FUN -> { val equalsSignElement = node .siblings(forward = true) @@ -186,27 +179,29 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { private inline val ASTNode.spacingBefore: Boolean get() = - when { - psi.parent is KtClassOrObject -> { + when (treeParent.elementType) { + + ElementType.CLASS, + ElementType.OBJECT_DECLARATION, + -> { true } - psi.parent is KtConstructor<*> -> { + ElementType.SECONDARY_CONSTRUCTOR -> { // constructor : this/super true } - psi.parent is KtTypeConstraint -> { + ElementType.TYPE_CONSTRAINT -> { // where T : S true } - psi.parent.parent is KtTypeParameterList -> { - true - } - else -> { - false + when (treeParent.treeParent.elementType) { + ElementType.TYPE_PARAMETER_LIST -> true + else -> false + } } } @@ -215,8 +210,8 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { private inline val ASTNode.spacingAfter: Boolean get() = - when (psi.parent) { - is KtAnnotation, is KtAnnotationEntry -> true + when (treeParent.elementType) { + ElementType.ANNOTATION, ElementType.ANNOTATION_ENTRY -> true else -> false } 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 692ff06ed7..10420a083b 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 @@ -39,7 +39,7 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing") // String::length, ::isOdd if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd removeSingleWhiteSpace = true - !prevLeaf.textContains('\n') && prevLeaf.psi.textLength > 1 + !prevLeaf.textContains('\n') && prevLeaf.textLength > 1 } else { // String::length, List::isEmpty !prevLeaf.textContains('\n') } 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 c05cb938a6..43e56a4361 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,6 +1,7 @@ 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.ANDAND import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.DIV @@ -41,7 +42,6 @@ import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet -import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtOperationExpression import org.jetbrains.kotlin.psi.KtPrefixExpression @@ -118,7 +118,7 @@ public class SpacingAroundOperatorsRule : StandardRule("op-spacing") { private fun ASTNode.isImport() = // import * - isPartOf(KtImportDirective::class) + isPartOf(ElementType.IMPORT_DIRECTIVE) private companion object { private val OPERATORS = 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 bb0e59c8c7..261e14a3c5 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,7 +1,9 @@ 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.MODIFIER_LIST +import com.pinterest.ktlint.rule.engine.core.api.ElementType.PROPERTY_ACCESSOR 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 @@ -9,17 +11,15 @@ 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.isDeclaration 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.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf +import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments import org.jetbrains.kotlin.psi.psiUtil.leaves /** @@ -32,19 +32,20 @@ public class SpacingBetweenDeclarationsWithAnnotationsRule : StandardRule("spaci node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - if (node.psi is KtDeclaration && node.isAnnotated()) { + if ((node.isDeclarationOrPropertyAccessor()) && node.isAnnotated()) { visitDeclaration(node, emit) } } + private fun ASTNode.isDeclarationOrPropertyAccessor() = isDeclaration() || elementType == PROPERTY_ACCESSOR + private fun visitDeclaration( node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { node - .psi - ?.getPrevSiblingIgnoringWhitespaceAndComments(withItself = false) - ?.takeIf { it is KtDeclaration } + .prevCodeSibling() + ?.takeIf { it.isDeclarationOrPropertyAccessor() } ?.takeIf { prevDeclaration -> hasNoBlankLineBetweenDeclarations(node, prevDeclaration) } ?.let { val prevLeaf = node.prevCodeLeaf()?.nextLeaf { it.isWhiteSpace() }!! @@ -61,16 +62,16 @@ public class SpacingBetweenDeclarationsWithAnnotationsRule : StandardRule("spaci private fun ASTNode.isAnnotated(): Boolean = findChildByType(MODIFIER_LIST) ?.children() - ?.any { it.psi is KtAnnotationEntry } + ?.any { it.elementType == ANNOTATION_ENTRY } ?: false private fun hasNoBlankLineBetweenDeclarations( node: ASTNode, - prevDeclaration: PsiElement, + prevDeclaration: ASTNode, ) = node .leaves(false) .takeWhile { it.isWhiteSpace() || it.isPartOfComment() } - .takeWhile { it.psi != prevDeclaration } + .takeWhile { it != prevDeclaration } .none { it.isBlankLine() } private fun ASTNode.isBlankLine() = isWhiteSpace() && text.count { it == '\n' } > 1 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 8663d50e5e..9fb4ce6f52 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,23 +1,20 @@ 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.TokenSets import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed +import com.pinterest.ktlint.rule.engine.core.api.isDeclaration +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace +import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling 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 import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment -import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace -import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments -import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement /** * @see https://youtrack.jetbrains.com/issue/KT-35088 @@ -29,29 +26,39 @@ public class SpacingBetweenDeclarationsWithCommentsRule : StandardRule("spacing- node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - if (node is PsiComment) { - val declaration = node.parent as? KtDeclaration ?: return - val isTailComment = node.startOffset > declaration.startOffset - if (isTailComment || declaration.getPrevSiblingIgnoringWhitespaceAndComments() !is KtDeclaration) return + node + .takeIf { it.elementType in TokenSets.COMMENTS } + ?.takeUnless { it.isTailComment() } + ?.treeParent + ?.takeIf { it.isDeclaration() } + ?.takeIf { it.prevCodeSibling().isDeclaration() } + ?.let { visitCommentedDeclaration(it, emit) } + } + + private fun ASTNode.isTailComment() = startOffset > treeParent.startOffset - val prevSibling = declaration.node.prevSibling { it.elementType != WHITE_SPACE } - if (prevSibling != null && - prevSibling.elementType != FILE && - prevSibling !is PsiComment - ) { - if (declaration.prevSibling is PsiWhiteSpace && declaration.prevSibling.text.count { it == '\n' } < 2) { - emit( - node.startOffset, - "Declarations and declarations with comments should have an empty space between.", - true, - ).ifAutocorrectAllowed { - val indent = node.prevLeaf()?.text?.trim('\n') ?: "" - (declaration.prevSibling.node as LeafPsiElement).rawReplaceWithText("\n\n$indent") + private fun visitCommentedDeclaration( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, + ) { + node + .prevSibling() + ?.takeUnless { it.isBlankLine() } + ?.let { prevSibling -> + emit(node.startOffset, "Declarations and declarations with comments should have an empty space between.", true) + .ifAutocorrectAllowed { + val indent = + node + .prevLeaf() + ?.text + ?.trim('\n') + ?: "" + (prevSibling as LeafElement).rawReplaceWithText("\n\n$indent") } - } } - } } + + private fun ASTNode.isBlankLine() = isWhiteSpace() && text.startsWith("\n\n") } public val SPACING_BETWEEN_DECLARATIONS_WITH_COMMENTS_RULE_ID: RuleId = SpacingBetweenDeclarationsWithCommentsRule().ruleId 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 cfa3387d90..0461c403a2 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 @@ -23,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.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.isWhiteSpace @@ -34,8 +35,6 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.utils.addToStdlib.applyIf @SinceKtlint("0.50", EXPERIMENTAL) @@ -163,7 +162,7 @@ public class StatementWrappingRule : private inline val ASTNode.isEnumClassOnSingleLine: Boolean get() = - if (psi.isEnumClass) { + if (isEnumClass) { val lastChildLeaf = lastChildLeafOrSelf() // Ignore the leading comment noNewLineInClosedRange(firstCodeLeafOrNull!!, lastChildLeaf) @@ -182,8 +181,8 @@ public class StatementWrappingRule : }?.firstOrNull() ?.firstChildLeafOrSelf() - private inline val PsiElement.isEnumClass: Boolean - get() = (this as? KtClass)?.isEnum() ?: false + private inline val ASTNode.isEnumClass: Boolean + get() = elementType == ElementType.CLASS && hasModifier(ElementType.ENUM_KEYWORD) private inline val ASTNode.indentAsChild: String get() = indent().plus(indentConfig.indent) 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 b1058b77da..6366168d36 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 @@ -22,9 +22,12 @@ 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.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.isCodeLeaf +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 import com.pinterest.ktlint.rule.engine.core.api.noNewLineInClosedRange import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf @@ -38,14 +41,12 @@ import org.ec4j.core.model.PropertyType.PropertyValueParser import org.jetbrains.kotlin.KtNodeTypes.WHEN_ENTRY_GUARD import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiElement -import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet -import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression import org.jetbrains.kotlin.psi.KtDestructuringDeclaration -import org.jetbrains.kotlin.psi.KtEnumEntry import org.jetbrains.kotlin.psi.KtFunctionLiteral import org.jetbrains.kotlin.psi.KtParameterList import org.jetbrains.kotlin.psi.KtValueArgumentList @@ -192,11 +193,10 @@ public class TrailingCommaOnDeclarationSiteRule : node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - val psi = node.psi - require(psi is KtClass) + require(node.elementType == ElementType.CLASS) node - .takeIf { psi.isEnum() } + .takeIf { node.hasModifier(ElementType.ENUM_KEYWORD) } ?.findChildByType(ElementType.CLASS_BODY) ?.takeUnless { it.noEnumEntries() } ?.let { classBody -> @@ -224,12 +224,12 @@ public class TrailingCommaOnDeclarationSiteRule : } } - private fun ASTNode.noEnumEntries() = children().none { it.psi is KtEnumEntry } + private fun ASTNode.noEnumEntries() = children().none { it.elementType == ElementType.ENUM_ENTRY } private fun ASTNode.lastTwoEnumEntriesAreOnSameLine(): Boolean { val lastTwoEnumEntries = children() - .filter { it.psi is KtEnumEntry } + .filter { it.elementType == ElementType.ENUM_ENTRY } .toList() .takeLast(2) @@ -244,7 +244,7 @@ public class TrailingCommaOnDeclarationSiteRule : */ private fun ASTNode.findNodeAfterLastEnumEntry() = children() - .lastOrNull { it.psi is KtEnumEntry } + .lastOrNull { it.elementType == ElementType.ENUM_ENTRY } ?.children() ?.singleOrNull { it.elementType == SEMICOLON } ?: lastChildNode @@ -304,10 +304,7 @@ public class TrailingCommaOnDeclarationSiteRule : TrailingCommaState.MISSING -> { if (isTrailingCommaAllowed) { val leafBeforeArrowOrNull = leafBeforeArrowOrNull() - val addNewLine = - leafBeforeArrowOrNull - ?.let { !(leafBeforeArrowOrNull is PsiWhiteSpace && leafBeforeArrowOrNull.textContains('\n')) } - ?: false + val addNewLine = !(leafBeforeArrowOrNull?.isWhiteSpaceWithNewline() ?: true) val prevNode = inspectNode.prevCodeLeaf()!! if (addNewLine) { emit( @@ -327,8 +324,8 @@ public class TrailingCommaOnDeclarationSiteRule : prevNode .treeParent .indent() - if (leafBeforeArrowOrNull is PsiWhiteSpace) { - (leafBeforeArrowOrNull as LeafPsiElement).rawReplaceWithText(indent) + if (leafBeforeArrowOrNull.isWhiteSpace()) { + (leafBeforeArrowOrNull as LeafElement).rawReplaceWithText(indent) } else { inspectNode .prevCodeLeaf() @@ -341,7 +338,7 @@ public class TrailingCommaOnDeclarationSiteRule : if (inspectNode.treeParent.elementType == ElementType.ENUM_ENTRY) { val parentIndent = - (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text + (prevNode.treeParent.prevLeaf()?.takeIf { it.isWhiteSpace() })?.text ?: prevNode.indent() (inspectNode as LeafPsiElement).apply { this.treeParent.addChild(LeafPsiElement(COMMA, ","), this) @@ -416,23 +413,9 @@ public class TrailingCommaOnDeclarationSiteRule : } private fun ASTNode.leafBeforeArrowOrNull() = - when (psi) { - is KtWhenEntry -> { - (psi as KtWhenEntry) - .arrow - ?.prevLeaf() - } - - is KtFunctionLiteral -> { - (psi as KtFunctionLiteral) - .arrow - ?.prevLeaf() - } - - else -> { - null - } - } + takeIf { it.elementType == WHEN_ENTRY || it.elementType == FUNCTION_LITERAL } + ?.findChildByType(ARROW) + ?.prevLeaf() private fun ASTNode.findPreviousTrailingCommaNodeOrNull(): ASTNode? { val codeLeaf = 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 c4551d4b35..9ad5e7e6e9 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 @@ -49,6 +49,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.TokenSets 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 @@ -78,7 +79,6 @@ import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import io.github.oshai.kotlinlogging.KotlinLogging import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet @@ -386,7 +386,7 @@ public class WrappingRule : // value( // ), // a comment // c, d - nextSibling.treeNext?.treeNext?.psi !is PsiComment + nextSibling.treeNext?.treeNext?.let { !TokenSets.COMMENTS.contains(it.elementType) } != false ) { requireNewlineAfterLeaf(nextSibling, emit) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRuleTest.kt index 67adbc66e3..b57c96b9ca 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/PropertyNamingRuleTest.kt @@ -83,11 +83,11 @@ class PropertyNamingRuleTest { propertyNamingRuleAssertThat(code) .withEditorConfigOverride(PropertyNamingRule.CONSTANT_NAMING_PROPERTY to pascal_case) .hasLintViolationsWithoutAutoCorrect( - LintViolation(1, 11, "Property name should use the screaming snake case notation when the value can not be changed"), + LintViolation(1, 11, "Property name should use the pascal case notation when the value can not be changed"), // FOO cannot be reported as not meeting the pascal case requirement as it could be an abbreviation of 3 separate words // starting with 'F', 'O' and 'O' respectively - LintViolation(3, 11, "Property name should use the screaming snake case notation when the value can not be changed"), - LintViolation(4, 11, "Property name should use the screaming snake case notation when the value can not be changed"), + LintViolation(3, 11, "Property name should use the pascal case notation when the value can not be changed"), + LintViolation(4, 11, "Property name should use the pascal case notation when the value can not be changed"), ) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRuleTest.kt index 72e3a4edaa..a1152bd4ce 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithAnnotationsRuleTest.kt @@ -306,4 +306,30 @@ class SpacingBetweenDeclarationsWithAnnotationsRuleTest { .hasLintViolation(3, 1, "Declarations and declarations with annotations should have an empty space between.") .isFormattedAs(formattedCode) } + + @Test + fun `Issue 2901 - Given a variable with property accessors not separated by a blank line, and the second accessor is annotated then add a blank in between`() { + val code = + """ + public var foo: Boolean + get() = false + @Foo + set(value) { + foo = value + } + """.trimIndent() + val formattedCode = + """ + public var foo: Boolean + get() = false + + @Foo + set(value) { + foo = value + } + """.trimIndent() + spacingBetweenDeclarationsWithAnnotationsRuleAssertThat(code) + .hasLintViolation(3, 1, "Declarations and declarations with annotations should have an empty space between.") + .isFormattedAs(formattedCode) + } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRuleTest.kt index e71b974e3b..3704b83ffa 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingBetweenDeclarationsWithCommentsRuleTest.kt @@ -135,4 +135,16 @@ class SpacingBetweenDeclarationsWithCommentsRuleTest { """.trimIndent() spacingBetweenDeclarationsWithCommentsRuleAssertThat(code).hasNoLintViolations() } + + @Test + fun `Given consecutive declarations, and the second declaration contains an inner comment on a separate line then do nothing`() { + val code = + """ + val bar = "bar" + val foo = + // Some comment + "foo" + """.trimIndent() + spacingBetweenDeclarationsWithCommentsRuleAssertThat(code).hasNoLintViolations() + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5a98670d3e..0f654d200b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { } plugins { - id("com.gradle.develocity") version "3.19" + id("com.gradle.develocity") version "3.19.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" }