[LL API] Add more flexible 'preferBodyPredicate'

The previous API required to pass the exact declaration element to
the 'ContextCollector'. Now it's possible to get a 'BODY' context for
the target ancestor.
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
index 9f76ddd..a337b55 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt
@@ -172,8 +172,9 @@
 
         firCodeFragment.codeFragmentContext = if (contextKtFile != null) {
             val contextFirFile = resolveSession.getOrBuildFirFile(contextKtFile)
+            val sessionHolder = transformer.components
 
-            val elementContext = ContextCollector.process(contextFirFile, transformer.components, contextPsiElement, preferBody = true)
+            val elementContext = ContextCollector.process(contextFirFile, sessionHolder, contextPsiElement) { it === contextPsiElement }
                 ?: errorWithAttachment("Cannot find enclosing context for ${contextPsiElement::class}") {
                     withPsiEntry("contextPsiElement", contextPsiElement)
                 }
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt
index af23b7f..6249622 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt
@@ -61,9 +61,17 @@
     }
 
     /**
-     * Process the [file], collecting contexts for the [targetElement] and all its PSI tree parents.
+     * Get the most precise context available for the [targetElement] in the [file].
+     *
+     * @param file The file to process.
+     * @param holder A [SessionHolder] for the session that owns a [file].
+     * @param targetElement The most precise element for which the context is required.
+     * @param preferBodyPredicate A predicate filtering elements for those the [ContextKind.BODY] is preferred.
+     *
+     * Returns the context of the [targetElement] if available, or of one of its tree parents.
+     * Returns `null` if the context was not collected.
      */
-    fun process(file: FirFile, holder: SessionHolder, targetElement: PsiElement, preferBody: Boolean): Context? {
+    fun process(file: FirFile, holder: SessionHolder, targetElement: PsiElement, preferBodyPredicate: (PsiElement) -> Boolean): Context? {
         val acceptedElements = targetElement.parentsWithSelf.toSet()
 
         val contextProvider = process(computeResolveTarget(file, targetElement), holder) { candidate ->
@@ -74,14 +82,21 @@
             }
         }
 
-        if (preferBody) {
-            val bodyContext = contextProvider[targetElement, ContextKind.BODY]
-            if (bodyContext != null) {
-                return bodyContext
+        for (acceptedElement in acceptedElements) {
+            if (preferBodyPredicate(acceptedElement)) {
+                val bodyContext = contextProvider[acceptedElement, ContextKind.BODY]
+                if (bodyContext != null) {
+                    return bodyContext
+                }
+            }
+
+            val elementContext = contextProvider[acceptedElement, ContextKind.SELF]
+            if (elementContext != null) {
+                return elementContext
             }
         }
 
-        return acceptedElements.firstNotNullOfOrNull { contextProvider[it, ContextKind.SELF] }
+        return null
     }
 
     private fun computeResolveTarget(file: FirFile, targetElement: PsiElement): LLFirResolveTarget {