Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove usages of getPsi() #2901

Merged
merged 74 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
7f8984c
implement ASTNode.isPartOfComment without psi
mgroth0 Dec 8, 2024
2562354
remove psi from existingSuppressionsFromNamedArgumentOrNull
mgroth0 Dec 10, 2024
a05595f
remove psi from visitKtlintSuppressionInAnnotation
mgroth0 Dec 10, 2024
1b76046
remove psi from getAnnotationUseSiteTarget
mgroth0 Dec 10, 2024
546dfca
remove psi from isOnSameLineAsControlFlowKeyword
mgroth0 Dec 10, 2024
5f0f4a8
reduce psi in NoUnusedImportsRule
mgroth0 Dec 10, 2024
7e2f282
remove psi from SpacingAroundColonRule
mgroth0 Dec 10, 2024
bd1596b
remove psi from SpacingAroundDoubleColonRule
mgroth0 Dec 10, 2024
d0c798e
remove psi from StatementWrappingRule
mgroth0 Dec 10, 2024
0058330
reduce psi in TrailingCommaOnDeclarationSiteRule
mgroth0 Dec 10, 2024
a10fd38
deprecate and replace usages of "isPartOf(klass: KClass<out PsiElemen…
mgroth0 Dec 10, 2024
94624ba
remove psi from SuppressionLocator
mgroth0 Dec 10, 2024
e962b64
simple psi removals
mgroth0 Dec 10, 2024
c8d1a54
refactor token sets
mgroth0 Dec 10, 2024
efd32be
remove psi from BlankLineBeforeDeclarationRule
mgroth0 Dec 10, 2024
6094819
remove psi from EnumEntryNameCaseRule
mgroth0 Dec 10, 2024
e2846c1
changs from review
mgroth0 Dec 20, 2024
fe71a48
add kdoc for findChildByTypeRecursively and endOffset
mgroth0 Dec 29, 2024
d92140d
remove a getPsi() from TrailingCommaOnDeclarationSiteRule
mgroth0 Dec 29, 2024
ffda19b
add contract to isWhiteSpace
mgroth0 Dec 29, 2024
3efbf7a
make token sets more maintainable
mgroth0 Dec 29, 2024
cb757a9
alternative solution to token set
mgroth0 Dec 29, 2024
4574a59
Refactor - use less Psi functions
paul-dingemans Dec 30, 2024
bcc14a6
Generify alternative solution to token set
paul-dingemans Jan 1, 2025
2c7c9e8
Respect max line length in kdoc
paul-dingemans Jan 1, 2025
72fb3c0
clean up alternative psi-type-checking strategy and remove getPsi() c…
mgroth0 Jan 1, 2025
2ecce76
Create dummy KtFile with PsiFileFactory analog to KotlinPsiFileFactory
paul-dingemans Jan 2, 2025
729425e
make isDeclaration() match previous behavior
mgroth0 Jan 6, 2025
0a8ab03
make isDeclaration public and use it in all rules
mgroth0 Jan 6, 2025
d6a5985
consider PROPERTY_ACCESSOR a declaration to match previous behavior
mgroth0 Jan 6, 2025
1ac41d3
make SpacingBetweenDeclarationsWithCommentsRule work correctly when t…
mgroth0 Jan 6, 2025
61eec00
fix(deps): update dependency io.github.hakky54:logcaptor to v2.10.1 (…
renovate[bot] Jan 12, 2025
7c7ce03
fix(deps): update dependency org.assertj:assertj-core to v3.27.3 (#2927)
renovate[bot] Jan 19, 2025
3b3de8c
chore(deps): update plugin com.gradle.develocity to v3.19.1 (#2929)
renovate[bot] Jan 27, 2025
3bdcc25
chore(deps): update dependency gradle to v8.12.1 (#2930)
renovate[bot] Jan 27, 2025
2f96c19
fix(deps): update kotlin monorepo to v2.1.10 (#2931)
renovate[bot] Jan 27, 2025
de2cb2d
fix(deps): update dependency org.jetbrains.kotlin:kotlin-gradle-plugi…
renovate[bot] Feb 3, 2025
cc7b874
chore(deps): update plugin shadow to v8.3.6 (#2935)
renovate[bot] Feb 3, 2025
1692979
Fix incorrect error message in PropertyNamingRule when enforcing Pasc…
lsurvila Feb 3, 2025
c160383
fix(deps): update dependency io.github.oshai:kotlin-logging-jvm to v7…
renovate[bot] Feb 8, 2025
61ac7b8
implement ASTNode.isPartOfComment without psi
mgroth0 Dec 8, 2024
0d361af
remove psi from existingSuppressionsFromNamedArgumentOrNull
mgroth0 Dec 10, 2024
354dcac
remove psi from visitKtlintSuppressionInAnnotation
mgroth0 Dec 10, 2024
3e30bdc
remove psi from getAnnotationUseSiteTarget
mgroth0 Dec 10, 2024
cb90a91
remove psi from isOnSameLineAsControlFlowKeyword
mgroth0 Dec 10, 2024
85d827f
reduce psi in NoUnusedImportsRule
mgroth0 Dec 10, 2024
8b43e13
remove psi from SpacingAroundColonRule
mgroth0 Dec 10, 2024
b1ec1e5
remove psi from SpacingAroundDoubleColonRule
mgroth0 Dec 10, 2024
deadf90
remove psi from StatementWrappingRule
mgroth0 Dec 10, 2024
e0b1e27
reduce psi in TrailingCommaOnDeclarationSiteRule
mgroth0 Dec 10, 2024
b8410a3
deprecate and replace usages of "isPartOf(klass: KClass<out PsiElemen…
mgroth0 Dec 10, 2024
036a957
remove psi from SuppressionLocator
mgroth0 Dec 10, 2024
8904017
simple psi removals
mgroth0 Dec 10, 2024
a48b0a3
refactor token sets
mgroth0 Dec 10, 2024
4fab5ee
remove psi from BlankLineBeforeDeclarationRule
mgroth0 Dec 10, 2024
911330c
remove psi from EnumEntryNameCaseRule
mgroth0 Dec 10, 2024
2e7bf04
changs from review
mgroth0 Dec 20, 2024
a34bd56
add kdoc for findChildByTypeRecursively and endOffset
mgroth0 Dec 29, 2024
b082814
remove a getPsi() from TrailingCommaOnDeclarationSiteRule
mgroth0 Dec 29, 2024
b0f79f7
add contract to isWhiteSpace
mgroth0 Dec 29, 2024
58acdb5
make token sets more maintainable
mgroth0 Dec 29, 2024
08d648f
alternative solution to token set
mgroth0 Dec 29, 2024
cfbe206
Refactor - use less Psi functions
paul-dingemans Dec 30, 2024
a48e45d
Generify alternative solution to token set
paul-dingemans Jan 1, 2025
c525dda
Respect max line length in kdoc
paul-dingemans Jan 1, 2025
db23bcc
clean up alternative psi-type-checking strategy and remove getPsi() c…
mgroth0 Jan 1, 2025
6e2d805
Create dummy KtFile with PsiFileFactory analog to KotlinPsiFileFactory
paul-dingemans Jan 2, 2025
e0f9d82
make isDeclaration() match previous behavior
mgroth0 Jan 6, 2025
631e2fc
make isDeclaration public and use it in all rules
mgroth0 Jan 6, 2025
564268e
consider PROPERTY_ACCESSOR a declaration to match previous behavior
mgroth0 Jan 6, 2025
31b598c
make SpacingBetweenDeclarationsWithCommentsRule work correctly when t…
mgroth0 Jan 6, 2025
90487fc
Create dummy KtFile with PsiFileFactory analog to KotlinPsiFileFactory
paul-dingemans Feb 9, 2025
00a4664
Refactor for readability
paul-dingemans Feb 9, 2025
40d2918
Merge remote-tracking branch 'github-desktop-mgroth0/no-psi' into pr/…
paul-dingemans Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 14 additions & 0 deletions ktlint-rule-engine-core/api/ktlint-rule-engine-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<out PsiElement>): Boolean {
var n: ASTNode? = this
while (n != null) {
Expand All @@ -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')

Expand All @@ -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<ASTNode> = generateSequence(firstChildNode) { node -> node.treeNext }

public fun ASTNode.recursiveChildren(includeSelf: Boolean = false): Sequence<ASTNode> =
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
Expand Down Expand Up @@ -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<IElementType, PsiElement>()

/**
* 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<KtAnnotated>()
* ```
* 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 <reified T : KtElement> 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
Original file line number Diff line number Diff line change
@@ -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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<KtAnnotated>()).isTrue()
assertThat(actual.findChildByType(CLASS_KEYWORD)!!.isPsiType<KtAnnotated>()).isFalse()
assertThat(actual.findChildByType(IDENTIFIER)!!.isPsiType<KtAnnotated>()).isFalse()
}

private inline fun String.transformAst(block: FileASTNode.() -> Unit): FileASTNode =
transformCodeToAST(this)
.apply(block)
Expand Down
Loading