diff --git a/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/KotlinCallHighlighterExtension.kt b/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/KotlinCallHighlighterExtension.kt
new file mode 100644
index 000000000000..743631097780
--- /dev/null
+++ b/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/KotlinCallHighlighterExtension.kt
@@ -0,0 +1,33 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.kotlin.idea.highlighting
+
+import com.intellij.codeInsight.daemon.impl.HighlightInfoType
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.calls.KtCall
+import org.jetbrains.kotlin.idea.highlighting.highlighters.FunctionCallHighlighter
+
+/**
+ * An interface for an extension point that enables a user-defined call highlighter.
+ *
+ * To add a new custom call highlighter, you have to add
+ *
+ *
+ *
+ * in XML. The "implementation" must be a class that implements this interface. See highlighting-k2.xml.
+ */
+interface KotlinCallHighlighterExtension {
+ /**
+ * A function determines how to highlight [call] for PSI [elementToHighlight].
+ *
+ * Note that this function is used by [FunctionCallHighlighter]. When this function returns a non-null [HighlightInfoType],
+ * [FunctionCallHighlighter] uses it instead of the default one to highlight [call].
+ */
+ context(KtAnalysisSession)
+ fun highlightCall(elementToHighlight: PsiElement, call: KtCall): HighlightInfoType?
+
+ companion object {
+ val EP_NAME = ExtensionPointName.create("org.jetbrains.kotlin.callHighlighterExtension")
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/highlighters/FunctionCallHighlighter.kt b/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/highlighters/FunctionCallHighlighter.kt
index 21ee5222c9a5..4d9872d522ca 100644
--- a/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/highlighters/FunctionCallHighlighter.kt
+++ b/plugins/kotlin/highlighting/src/org/jetbrains/kotlin/idea/highlighting/highlighters/FunctionCallHighlighter.kt
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.idea.base.highlighting.HighlightingFactory
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightInfoTypeSemanticNames
+import org.jetbrains.kotlin.idea.highlighting.KotlinCallHighlighterExtension
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.Name
@@ -38,8 +39,8 @@ internal class FunctionCallHighlighter(
if (operationReference.isAssignment()) return emptyList()
val call = expression.resolveCall()?.successfulCallOrNull() ?: return emptyList()
if (call is KtSimpleFunctionCall && (call.symbol as? KtFunctionSymbol)?.isOperator == true) return emptyList()
- val h = getTextAttributesForCall(call)?.let { attributes ->
- HighlightingFactory.highlightName(operationReference, attributes)
+ val h = getDefaultHighlightInfoTypeForCall(call)?.let { highlightInfoType ->
+ HighlightingFactory.highlightName(operationReference, highlightInfoType)
}
return listOfNotNull(h)
}
@@ -49,20 +50,21 @@ internal class FunctionCallHighlighter(
context(KtAnalysisSession)
private fun highlightCallExpression(expression: KtCallExpression): List {
- return listOfNotNull(expression.calleeExpression
- ?.takeUnless { it is KtLambdaExpression }
- ?.takeUnless { it is KtCallExpression /* KT-16159 */ }
- ?.let { callee ->
- expression.resolveCall()?.singleCallOrNull()?.let { call ->
- getTextAttributesForCall(call)?.let { attributes ->
- HighlightingFactory.highlightName (callee, attributes)
- }
- }
- })
+ val callee = expression.calleeExpression ?: return emptyList()
+ val call = expression.resolveCall()?.singleCallOrNull() ?: return emptyList()
+ if (callee is KtLambdaExpression || callee is KtCallExpression /* KT-16159 */) return emptyList()
+ val highlightInfoType = getHighlightInfoTypeForCallFromExtension(callee, call)
+ ?: getDefaultHighlightInfoTypeForCall(call)
+ ?: return emptyList()
+ return listOfNotNull(HighlightingFactory.highlightName(callee, highlightInfoType))
}
context(KtAnalysisSession)
- private fun getTextAttributesForCall(call: KtCall): HighlightInfoType? {
+ private fun getHighlightInfoTypeForCallFromExtension(callee: KtExpression, call: KtCall): HighlightInfoType? =
+ KotlinCallHighlighterExtension.EP_NAME.getExtensions(project).firstNotNullOfOrNull { it.highlightCall(callee, call) }
+
+ context(KtAnalysisSession)
+ private fun getDefaultHighlightInfoTypeForCall(call: KtCall): HighlightInfoType? {
if (call !is KtSimpleFunctionCall) return null
return when (val function = call.symbol) {
is KtConstructorSymbol -> KotlinHighlightInfoTypeSemanticNames.CONSTRUCTOR_CALL
@@ -89,4 +91,4 @@ internal class FunctionCallHighlighter(
private val KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME_CALLABLE_ID =
CallableId(StandardNames.BUILT_INS_PACKAGE_FQ_NAME, Name.identifier("suspend"))
}
-}
\ No newline at end of file
+}
diff --git a/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/AbstractK2HighlightingMetaInfoWithExtensionTest.kt b/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/AbstractK2HighlightingMetaInfoWithExtensionTest.kt
new file mode 100644
index 000000000000..193d468f58d4
--- /dev/null
+++ b/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/AbstractK2HighlightingMetaInfoWithExtensionTest.kt
@@ -0,0 +1,48 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.kotlin.idea.k2.highlighting
+
+import com.intellij.codeInsight.daemon.impl.HighlightInfoType
+import com.intellij.openapi.extensions.ExtensionPoint
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.annotations.annotations
+import org.jetbrains.kotlin.analysis.api.calls.KtCall
+import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall
+import org.jetbrains.kotlin.analysis.api.calls.symbol
+import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightInfoTypeSemanticNames
+import org.jetbrains.kotlin.idea.highlighting.KotlinCallHighlighterExtension
+
+object KotlinCallHighlighterExtensionForTest : KotlinCallHighlighterExtension {
+ private enum class HighlightType(val annotationName: String) {
+ SUSPEND("MySuspend"),
+ DYNAMIC("MyDynamic"),
+ EXTENSION("MyExtension"),
+ }
+
+ context(KtAnalysisSession)
+ override fun highlightCall(elementToHighlight: PsiElement, call: KtCall): HighlightInfoType? {
+ if (call !is KtCallableMemberCall<*, *>) return null
+ val highlightType = call.partiallyAppliedSymbol.symbol.annotations.firstNotNullOfOrNull { annotation ->
+ HighlightType.values().singleOrNull { it.annotationName == annotation.classId?.shortClassName?.asString() }
+ } ?: return null
+ return when (highlightType) {
+ HighlightType.SUSPEND -> KotlinHighlightInfoTypeSemanticNames.SUSPEND_FUNCTION_CALL
+ HighlightType.DYNAMIC -> KotlinHighlightInfoTypeSemanticNames.DYNAMIC_FUNCTION_CALL
+ HighlightType.EXTENSION -> KotlinHighlightInfoTypeSemanticNames.EXTENSION_FUNCTION_CALL
+ }
+ }
+}
+
+abstract class AbstractK2HighlightingMetaInfoWithExtensionTest : AbstractK2HighlightingMetaInfoTest() {
+ override fun setUp() {
+ super.setUp()
+ project.extensionArea.registerExtensionPoint(
+ KotlinCallHighlighterExtension.EP_NAME.name,
+ KotlinCallHighlighterExtension::class.java.name,
+ ExtensionPoint.Kind.INTERFACE,
+ true
+ )
+ project.extensionArea.getExtensionPoint(KotlinCallHighlighterExtension.EP_NAME)
+ .registerExtension(KotlinCallHighlighterExtensionForTest, testRootDisposable)
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/K2HighlightingMetaInfoWithExtensionTestGenerated.java b/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/K2HighlightingMetaInfoWithExtensionTestGenerated.java
new file mode 100644
index 000000000000..5a3b88a8f7ee
--- /dev/null
+++ b/plugins/kotlin/highlighting/test/org/jetbrains/kotlin/idea/k2/highlighting/K2HighlightingMetaInfoWithExtensionTestGenerated.java
@@ -0,0 +1,30 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+
+package org.jetbrains.kotlin.idea.k2.highlighting;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners;
+import org.jetbrains.kotlin.idea.test.KotlinTestUtils;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.jetbrains.kotlin.idea.base.test.TestRoot;
+import org.junit.runner.RunWith;
+
+/**
+ * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}.
+ * DO NOT MODIFY MANUALLY.
+ */
+@SuppressWarnings("all")
+@TestRoot("highlighting")
+@TestDataPath("$CONTENT_ROOT")
+@RunWith(JUnit3RunnerWithInners.class)
+@TestMetadata("../idea/tests/testData/highlighterMetaInfoWithExtension")
+public class K2HighlightingMetaInfoWithExtensionTestGenerated extends AbstractK2HighlightingMetaInfoWithExtensionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("Functions.kt")
+ public void testFunctions() throws Exception {
+ runTest("../idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt");
+ }
+}
diff --git a/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt b/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt
new file mode 100644
index 000000000000..2f3cbf2648df
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt
@@ -0,0 +1,52 @@
+// FIR_IDENTICAL
+// WITH_STDLIB
+// CHECK_SYMBOL_NAMES
+// HIGHLIGHTER_ATTRIBUTES_KEY
+fun global() {
+ fun inner() {
+
+ }
+ inner()
+}
+
+fun Int.ext() {
+}
+
+infix fun Int.fif(y: Int) {
+ this * y
+}
+
+open class Container {
+ open fun member() {
+ global()
+ 5.ext()
+ member()
+ 5 fif 6
+ }
+}
+
+fun foo() {
+ suspend {
+
+ }
+}
+
+annotation class MySuspend
+annotation class MyDynamic
+annotation class MyExtension
+
+@MySuspend
+fun suspendFunction() {
+}
+@MyDynamic
+fun dynamicFunction() {
+}
+@MyExtension
+fun extensionFunction() {
+}
+
+fun testFunctionsWithSpecialAnnotations() {
+ suspendFunction()
+ dynamicFunction()
+ extensionFunction()
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt.highlighting b/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt.highlighting
new file mode 100644
index 000000000000..21ce24c7f2ff
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/highlighterMetaInfoWithExtension/Functions.kt.highlighting
@@ -0,0 +1,52 @@
+// FIR_IDENTICAL
+// WITH_STDLIB
+// CHECK_SYMBOL_NAMES
+// HIGHLIGHTER_ATTRIBUTES_KEY
+fun global() {
+ fun inner() {
+
+ }
+ inner()
+}
+
+fun Int.ext() {
+}
+
+infix fun Int.fif(y: Int) {
+ this * y
+}
+
+open class Container {
+ open fun member() {
+ global()
+ 5.ext()
+ member()
+ 5 fif 6
+ }
+}
+
+fun foo() {
+ suspend {
+
+ }
+}
+
+annotation class MySuspend
+annotation class MyDynamic
+annotation class MyExtension
+
+@MySuspend
+fun suspendFunction() {
+}
+@MyDynamic
+fun dynamicFunction() {
+}
+@MyExtension
+fun extensionFunction() {
+}
+
+fun testFunctionsWithSpecialAnnotations() {
+ suspendFunction()
+ dynamicFunction()
+ extensionFunction()
+}
\ No newline at end of file
diff --git a/plugins/kotlin/plugin/k2/resources/META-INF/highlighting-k2.xml b/plugins/kotlin/plugin/k2/resources/META-INF/highlighting-k2.xml
index 4a5585e42e40..9a1d4998c94b 100644
--- a/plugins/kotlin/plugin/k2/resources/META-INF/highlighting-k2.xml
+++ b/plugins/kotlin/plugin/k2/resources/META-INF/highlighting-k2.xml
@@ -1,4 +1,11 @@
+
+
+
+
diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2HighlighterTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2HighlighterTests.kt
index c95814e62249..5d68ce8f1449 100644
--- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2HighlighterTests.kt
+++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2HighlighterTests.kt
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.fir.testGenerator
import org.jetbrains.kotlin.idea.k2.highlighting.AbstractK2BundledCompilerPluginsHighlightingMetaInfoTest
import org.jetbrains.kotlin.idea.k2.highlighting.AbstractK2HighlightingMetaInfoTest
+import org.jetbrains.kotlin.idea.k2.highlighting.AbstractK2HighlightingMetaInfoWithExtensionTest
import org.jetbrains.kotlin.idea.k2.highlighting.AbstractOutsiderHighlightingTest
import org.jetbrains.kotlin.testGenerator.model.*
@@ -15,6 +16,10 @@ internal fun MutableTWorkspace.generateK2HighlighterTests() {
testClass {
model("highlighterMetaInfoWithBundledCompilerPlugins")
}
+
+ testClass {
+ model("highlighterMetaInfoWithExtension")
+ }
}
testGroup("highlighting") {