Skip to content

Commit c806358

Browse files
Reduce usages of psi functions (#2901)
Using psi functions from the kotlin embedded compiler will incurr performance penalties in certain use case as some psi functionality checks for cancellations. See #2901 for more background. Closes #2901 Co-authored-by: Paul Dingemans <[email protected]>
1 parent 7ecb15a commit c806358

32 files changed

+489
-283
lines changed

ktlint-rule-engine-core/api/ktlint-rule-engine-core.api

+14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt
33
public static final fun beforeCodeSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z
44
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
55
public static final fun children (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lkotlin/sequences/Sequence;
6+
public static final fun dummyPsiElement (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lorg/jetbrains/kotlin/com/intellij/psi/PsiElement;
7+
public static final fun endOffset (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)I
8+
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;
69
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;
710
public static final fun firstChildLeafOrSelf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;
811
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
1114
public static final fun indent (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Z)Ljava/lang/String;
1215
public static synthetic fun indent$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Ljava/lang/String;
1316
public static final fun isCodeLeaf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z
17+
public static final fun isDeclaration (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z
18+
public static final fun isKtAnnotated (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z
1419
public static final fun isLeaf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z
1520
public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/reflect/KClass;)Z
1621
public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z
22+
public static final fun isPartOf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet;)Z
1723
public static final fun isPartOfComment (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Z
1824
public static final fun isPartOfCompositeElementOfType (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z
1925
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
5662
public static synthetic fun prevLeaf$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;
5763
public static final fun prevSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;
5864
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;
65+
public static final fun recursiveChildren (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Z)Lkotlin/sequences/Sequence;
66+
public static synthetic fun recursiveChildren$default (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZILjava/lang/Object;)Lkotlin/sequences/Sequence;
5967
public static final fun remove (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)V
6068
public static final fun replaceWith (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)V
6169
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
524532
public static fun values ()[Lcom/pinterest/ktlint/rule/engine/core/api/SinceKtlint$Status;
525533
}
526534

535+
public final class com/pinterest/ktlint/rule/engine/core/api/TokenSets {
536+
public static final field INSTANCE Lcom/pinterest/ktlint/rule/engine/core/api/TokenSets;
537+
public final fun getCOMMENTS ()Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet;
538+
public final fun getCONTROL_FLOW_KEYWORDS ()Lorg/jetbrains/kotlin/com/intellij/psi/tree/TokenSet;
539+
}
540+
527541
public final class com/pinterest/ktlint/rule/engine/core/api/editorconfig/CodeStyleEditorConfigPropertyKt {
528542
public static final fun getCODE_STYLE_PROPERTY ()Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty;
529543
public static final fun getCODE_STYLE_PROPERTY_TYPE ()Lorg/ec4j/core/model/PropertyType$LowerCasingPropertyType;

ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt

+123-3
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,34 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VAL_KEYWORD
77
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VARARG_KEYWORD
88
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VAR_KEYWORD
99
import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE
10+
import org.jetbrains.kotlin.KtNodeType
11+
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
12+
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
1013
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
11-
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
14+
import org.jetbrains.kotlin.com.intellij.mock.MockProject
15+
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
1216
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
17+
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
1318
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
1419
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
1520
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
1621
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
22+
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
23+
import org.jetbrains.kotlin.config.CompilerConfiguration
24+
import org.jetbrains.kotlin.idea.KotlinLanguage
25+
import org.jetbrains.kotlin.lexer.KtKeywordToken
26+
import org.jetbrains.kotlin.lexer.KtToken
27+
import org.jetbrains.kotlin.psi.KtAnnotated
28+
import org.jetbrains.kotlin.psi.KtElement
29+
import org.jetbrains.kotlin.psi.KtFile
1730
import org.jetbrains.kotlin.psi.psiUtil.leaves
31+
import org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType
32+
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementType
33+
import org.jetbrains.kotlin.psi.stubs.elements.KtTokenSets
1834
import org.jetbrains.kotlin.util.prefixIfNot
1935
import org.jetbrains.kotlin.utils.addToStdlib.applyIf
36+
import kotlin.contracts.ExperimentalContracts
37+
import kotlin.contracts.contract
2038
import kotlin.reflect.KClass
2139

2240
public fun ASTNode.nextLeaf(
@@ -183,11 +201,18 @@ public fun ASTNode.parent(
183201
return null
184202
}
185203

204+
public fun ASTNode.isPartOf(tokenSet: TokenSet): Boolean = parent(strict = false) { tokenSet.contains(it.elementType) } != null
205+
186206
/**
187207
* @param elementType [ElementType].*
188208
*/
189209
public fun ASTNode.isPartOf(elementType: IElementType): Boolean = parent(elementType, strict = false) != null
190210

211+
@Deprecated(
212+
"Marked for removal in Ktlint 2.x. Replace with ASTNode.isPartOf(elementType: IElementType) or ASTNode.isPartOf(tokenSet: TokenSet). " +
213+
"This method might cause performance issues, see https://github.com/pinterest/ktlint/pull/2901",
214+
replaceWith = ReplaceWith("this.isPartOf(elementTypeOrTokenSet)"),
215+
)
191216
public fun ASTNode.isPartOf(klass: KClass<out PsiElement>): Boolean {
192217
var n: ASTNode? = this
193218
while (n != null) {
@@ -207,7 +232,13 @@ public fun ASTNode.findCompositeParentElementOfType(iElementType: IElementType):
207232

208233
public fun ASTNode.isPartOfString(): Boolean = parent(STRING_TEMPLATE, strict = false) != null
209234

210-
public fun ASTNode?.isWhiteSpace(): Boolean = this != null && elementType == WHITE_SPACE
235+
@OptIn(ExperimentalContracts::class)
236+
public fun ASTNode?.isWhiteSpace(): Boolean {
237+
contract {
238+
returns(true) implies (this@isWhiteSpace != null)
239+
}
240+
return this != null && elementType == WHITE_SPACE
241+
}
211242

212243
public fun ASTNode?.isWhiteSpaceWithNewline(): Boolean = this != null && elementType == WHITE_SPACE && textContains('\n')
213244

@@ -223,10 +254,18 @@ public fun ASTNode.isLeaf(): Boolean = firstChildNode == null
223254
*/
224255
public fun ASTNode.isCodeLeaf(): Boolean = isLeaf() && !isWhiteSpace() && !isPartOfComment()
225256

226-
public fun ASTNode.isPartOfComment(): Boolean = parent(strict = false) { it.psi is PsiComment } != null
257+
public fun ASTNode.isPartOfComment(): Boolean = isPartOf(TokenSets.COMMENTS)
227258

228259
public fun ASTNode.children(): Sequence<ASTNode> = generateSequence(firstChildNode) { node -> node.treeNext }
229260

261+
public fun ASTNode.recursiveChildren(includeSelf: Boolean = false): Sequence<ASTNode> =
262+
sequence {
263+
if (includeSelf) {
264+
yield(this@recursiveChildren)
265+
}
266+
children().forEach { yieldAll(it.recursiveChildren(includeSelf = true)) }
267+
}
268+
230269
/**
231270
* Updates or inserts a new whitespace element with [text] before the given node. If the node itself is a whitespace
232271
* 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) {
555594
public fun ASTNode.remove() {
556595
treeParent.removeChild(this)
557596
}
597+
598+
/**
599+
* Searches the receiver [ASTNode] recursively, returning the first child with type [elementType]. If none are found, returns `null`.
600+
* If [includeSelf] is `true`, includes the receiver in the search. The receiver would then be the first element searched, so it is
601+
* guaranteed to be returned if it has type [elementType].
602+
*/
603+
public fun ASTNode.findChildByTypeRecursively(
604+
elementType: IElementType,
605+
includeSelf: Boolean,
606+
): ASTNode? = recursiveChildren(includeSelf).firstOrNull { it.elementType == elementType }
607+
608+
/**
609+
* Returns the end offset of the text of this [ASTNode]
610+
*/
611+
public fun ASTNode.endOffset(): Int = textRange.endOffset
612+
613+
private val elementTypeCache = hashMapOf<IElementType, PsiElement>()
614+
615+
/**
616+
* Checks if the [AstNode] extends the [KtAnnotated] interface. Using this function to check the interface is more performant than checking
617+
* whether `psi is KtAnnotated` as the psi does not need to be derived from [ASTNode].
618+
*/
619+
public fun ASTNode.isKtAnnotated(): Boolean = psiType { it is KtAnnotated }
620+
621+
private inline fun ASTNode.psiType(predicate: (psiElement: PsiElement) -> Boolean): Boolean = predicate(dummyPsiElement())
622+
623+
/**
624+
* Checks if the [AstNode] extends the [T] interface which implements [KtElement]. Call this function like:
625+
* ```
626+
* astNode.isPsiType<KtAnnotated>()
627+
* ```
628+
* Using this function to check the [PsiElement] type of the [ASTNode] is more performant than checking whether `astNode.psi is KtAnnotated`
629+
* as the psi does not need to be derived from [ASTNode].
630+
*/
631+
public inline fun <reified T : KtElement> ASTNode.isPsiType(): Boolean = this.dummyPsiElement() is T
632+
633+
/**
634+
* FOR INTERNAL USE ONLY. The returned element is a stub version of a [PsiElement] of the same type as the given [ASTNode]. The returned
635+
* result may only be used to validate the type of the [PsiElement].
636+
*/
637+
public fun ASTNode.dummyPsiElement(): PsiElement =
638+
elementTypeCache
639+
.getOrPut(elementType) {
640+
// Create a dummy Psi element based on the current node, so that we can store the Psi Type for this ElementType.
641+
// Creating this cache entry once per elementType is cheaper than accessing the psi for every node.
642+
when (elementType) {
643+
is KtFileElementType -> createDummyKtFile()
644+
is KtKeywordToken -> this as PsiElement
645+
is KtNodeType -> (elementType as KtNodeType).createPsi(this)
646+
is KtStubElementType<*, *> -> (elementType as KtStubElementType<*, *>).createPsiFromAst(this)
647+
is KtToken -> this as PsiElement
648+
else -> throw NotImplementedError("Cannot create dummy psi for $elementType (${elementType::class})")
649+
}
650+
}
651+
652+
private fun createDummyKtFile(): KtFile {
653+
val disposable = Disposer.newDisposable()
654+
try {
655+
val project =
656+
KotlinCoreEnvironment
657+
.createForProduction(
658+
disposable,
659+
CompilerConfiguration(),
660+
EnvironmentConfigFiles.JVM_CONFIG_FILES,
661+
).project as MockProject
662+
663+
return PsiFileFactory
664+
.getInstance(project)
665+
.createFileFromText("dummy-file.kt", KotlinLanguage.INSTANCE, "") as KtFile
666+
} finally {
667+
// Dispose explicitly to (possibly) prevent memory leak
668+
// https://discuss.kotlinlang.org/t/memory-leak-in-kotlincoreenvironment-and-kotlintojvmbytecodecompiler/21950
669+
// https://youtrack.jetbrains.com/issue/KT-47044
670+
disposable.dispose()
671+
}
672+
}
673+
674+
/**
675+
* Returns true if the receiver is not null, and it represents a declaration
676+
*/
677+
public fun ASTNode?.isDeclaration(): Boolean = this != null && elementType in KtTokenSets.DECLARATION_TYPES
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.pinterest.ktlint.rule.engine.core.api
2+
3+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.DO_KEYWORD
4+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FOR_KEYWORD
5+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.IF_KEYWORD
6+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_KEYWORD
7+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.TRY_KEYWORD
8+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_KEYWORD
9+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHILE_KEYWORD
10+
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
11+
import org.jetbrains.kotlin.lexer.KtTokens
12+
13+
public object TokenSets {
14+
public val COMMENTS: TokenSet = KtTokens.COMMENTS
15+
16+
/**
17+
*
18+
* Reference: This is a subset of [KotlinExpressionParsing.EXPRESSION_FIRST]
19+
*/
20+
public val CONTROL_FLOW_KEYWORDS: TokenSet =
21+
TokenSet.create(
22+
IF_KEYWORD, // if
23+
WHEN_KEYWORD, // when
24+
TRY_KEYWORD, // try
25+
OBJECT_KEYWORD, // object
26+
// loop
27+
FOR_KEYWORD,
28+
WHILE_KEYWORD,
29+
DO_KEYWORD,
30+
)
31+
}

ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt

+51
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine
55
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY
66
import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS
77
import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY
8+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_KEYWORD
89
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY
910
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE
1011
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN
@@ -25,6 +26,7 @@ import org.assertj.core.api.Assertions.assertThatNoException
2526
import org.assertj.core.api.Assertions.entry
2627
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
2728
import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
29+
import org.jetbrains.kotlin.psi.KtAnnotated
2830
import org.jetbrains.kotlin.psi.psiUtil.leaves
2931
import org.junit.jupiter.api.Nested
3032
import org.junit.jupiter.api.Test
@@ -1089,6 +1091,55 @@ class ASTNodeExtensionTest {
10891091
}
10901092
}
10911093

1094+
@Nested
1095+
inner class FindChildByTypeRecursively {
1096+
@Test
1097+
fun `Given a node with a target type return non-null`() {
1098+
val code =
1099+
"""
1100+
class MyClass {
1101+
fun foo() = 42
1102+
}
1103+
""".trimIndent()
1104+
val result =
1105+
transformCodeToAST(code)
1106+
.findChildByTypeRecursively(FUN, includeSelf = false)
1107+
assertThat(result).isNotNull()
1108+
}
1109+
1110+
@Test
1111+
fun `Given a node without a target type return null`() {
1112+
val code =
1113+
"""
1114+
class MyClass {
1115+
1116+
}
1117+
""".trimIndent()
1118+
val result =
1119+
transformCodeToAST(code)
1120+
.findChildByTypeRecursively(FUN, includeSelf = false)
1121+
assertThat(result).isNull()
1122+
}
1123+
}
1124+
1125+
@Test
1126+
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`() {
1127+
val code =
1128+
"""
1129+
class Foo
1130+
""".trimIndent()
1131+
1132+
val actual = transformCodeToAST(code).findChildByType(CLASS)!!
1133+
1134+
assertThat(actual.isKtAnnotated()).isTrue()
1135+
assertThat(actual.findChildByType(CLASS_KEYWORD)!!.isKtAnnotated()).isFalse()
1136+
assertThat(actual.findChildByType(IDENTIFIER)!!.isKtAnnotated()).isFalse()
1137+
1138+
assertThat(actual.isPsiType<KtAnnotated>()).isTrue()
1139+
assertThat(actual.findChildByType(CLASS_KEYWORD)!!.isPsiType<KtAnnotated>()).isFalse()
1140+
assertThat(actual.findChildByType(IDENTIFIER)!!.isPsiType<KtAnnotated>()).isFalse()
1141+
}
1142+
10921143
private inline fun String.transformAst(block: FileASTNode.() -> Unit): FileASTNode =
10931144
transformCodeToAST(this)
10941145
.apply(block)

ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppression.kt

+9-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT
1111
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT_LIST
1212
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER
1313
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST
14+
import com.pinterest.ktlint.rule.engine.core.api.findChildByTypeRecursively
1415
import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf
1516
import com.pinterest.ktlint.rule.engine.core.api.indent
1617
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment
@@ -30,7 +31,6 @@ import org.jetbrains.kotlin.psi.KtBinaryExpression
3031
import org.jetbrains.kotlin.psi.KtBlockExpression
3132
import org.jetbrains.kotlin.psi.KtClass
3233
import org.jetbrains.kotlin.psi.KtClassInitializer
33-
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
3434
import org.jetbrains.kotlin.psi.KtDeclaration
3535
import org.jetbrains.kotlin.psi.KtDeclarationModifierList
3636
import org.jetbrains.kotlin.psi.KtExpression
@@ -47,7 +47,6 @@ import org.jetbrains.kotlin.psi.KtScript
4747
import org.jetbrains.kotlin.psi.KtScriptInitializer
4848
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
4949
import org.jetbrains.kotlin.psi.psiUtil.children
50-
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
5150
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
5251
import org.jetbrains.kotlin.util.prefixIfNot
5352

@@ -202,12 +201,14 @@ private fun ASTNode.existingSuppressions() =
202201
existingSuppressionsFromNamedArgumentOrNull()
203202
?: getValueArguments()
204203

205-
private fun ASTNode.existingSuppressionsFromNamedArgumentOrNull() =
206-
psi
207-
.findDescendantOfType<KtCollectionLiteralExpression>()
208-
?.children
209-
?.map { it.text }
210-
?.toSet()
204+
private fun ASTNode.existingSuppressionsFromNamedArgumentOrNull(): Set<String>? =
205+
findChildByTypeRecursively(ElementType.COLLECTION_LITERAL_EXPRESSION, includeSelf = false)
206+
?.run {
207+
children()
208+
.filter { it.elementType == ElementType.STRING_TEMPLATE }
209+
.map { it.text }
210+
.toSet()
211+
}
211212

212213
private fun ASTNode.findSuppressionAnnotations(): Map<SuppressAnnotationType, ASTNode> =
213214
if (this.isRoot()) {

0 commit comments

Comments
 (0)