From 287e6fe067e15d099181f8593ce2de6e4bcf5df8 Mon Sep 17 00:00:00 2001 From: Vojtech Balik Date: Mon, 26 May 2025 16:18:28 +0200 Subject: [PATCH] [kotlin] IJPL-166464 add support for exclude list configuration for Kotlin parameter name hints GitOrigin-RevId: 73cac11669f137cc1d07e3b352e16fd8cdca1cd3 --- .../resources/kotlin.code-insight.k2.xml | 18 +- .../hints/KtLambdasHintsProvider.kt | 9 + .../hints/KtParameterHintsProvider.kt | 209 ++++++++++-------- 3 files changed, 146 insertions(+), 90 deletions(-) diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml b/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml index 332a12269987..f5589e3c9d2d 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml @@ -143,8 +143,8 @@ defaultValue="true" restartRequired="true"/> - + + + + + + + + diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtLambdasHintsProvider.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtLambdasHintsProvider.kt index f456272c6038..f9698f9df4a2 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtLambdasHintsProvider.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtLambdasHintsProvider.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull import org.jetbrains.kotlin.analysis.api.resolution.symbol import org.jetbrains.kotlin.analysis.api.symbols.KaAnonymousFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol import org.jetbrains.kotlin.idea.codeInsight.hints.SHOW_IMPLICIT_RECEIVERS_AND_PARAMS import org.jetbrains.kotlin.idea.codeInsight.hints.SHOW_RETURN_EXPRESSIONS import org.jetbrains.kotlin.idea.codeInsight.hints.isFollowedByNewLine @@ -181,3 +182,11 @@ class KtLambdasHintsProvider : AbstractKtInlayHintsProvider() { } } } + +context(KaSession) +internal fun KaFunctionSymbol.isExcludeListed(excludeListMatchers: List): Boolean { + val callableFqName = callableId?.asSingleFqName()?.asString() ?: return false + val parameterNames = valueParameters.map { it.name.asString() } + return excludeListMatchers.any { it.isMatching(callableFqName, parameterNames) } +} + diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtParameterHintsProvider.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtParameterHintsProvider.kt index 354481192147..5722c7c65c00 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtParameterHintsProvider.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/KtParameterHintsProvider.kt @@ -1,9 +1,18 @@ // Copyright 2000-2024 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.codeinsight.hints +import com.intellij.codeInsight.CodeInsightBundle +import com.intellij.codeInsight.hints.ExcludeListDialog import com.intellij.codeInsight.hints.declarative.* -import com.intellij.codeInsight.hints.filtering.Matcher -import com.intellij.codeInsight.hints.filtering.MatcherConstructor +import com.intellij.codeInsight.hints.parameters.AbstractDeclarativeParameterHintsCustomSettingsProvider +import com.intellij.codeInsight.hints.parameters.ParameterHintsExcludeListConfigProvider +import com.intellij.codeInsight.hints.parameters.ParameterHintsExcludeListService +import com.intellij.lang.Language +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.util.NlsActions import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace @@ -19,6 +28,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolOrigin import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol import org.jetbrains.kotlin.analysis.api.types.KaFunctionType +import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.codeInsight.hints.SHOW_COMPILED_PARAMETERS import org.jetbrains.kotlin.idea.codeInsight.hints.SHOW_EXCLUDED_PARAMETERS import org.jetbrains.kotlin.idea.codeinsights.impl.base.ArgumentNameCommentInfo @@ -29,84 +39,9 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.siblings import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.kotlin.utils.addToStdlib.ifFalse class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { - private val excludeListMatchers: List by lazy { - setOf( - "*listOf", "*setOf", "*arrayOf", "*ListOf", "*SetOf", "*ArrayOf", "*assert*(*)", "*mapOf", "*MapOf", - "kotlin.require*(*)", "kotlin.check*(*)", "*contains*(value)", "*containsKey(key)", "kotlin.lazyOf(value)", - "*SequenceBuilder.resume(value)", "*SequenceBuilder.yield(value)", - - /* Gradle DSL especially annoying hints */ - "org.gradle.api.Project.property(propertyName)", - "org.gradle.api.Project.hasProperty(propertyName)", - "org.gradle.api.Project.findProperty(propertyName)", - "org.gradle.api.Project.file(path)", - "org.gradle.api.Project.uri(path)", - "jvmArgs(arguments)", - "org.gradle.kotlin.dsl.DependencyHandlerScope.*(notation)", - "org.gradle.kotlin.dsl.PluginDependenciesSpecScope.*(*)", - "org.gradle.kotlin.dsl.*(dependencyNotation)", - "org.gradle.api.tasks.util.*include(includes)", - "org.gradle.api.tasks.util.*exclude(excludes)", - "org.gradle.kotlin.dsl.kotlin(module)", - "org.gradle.kotlin.dsl.kotlin(module,version)", - "org.gradle.kotlin.dsl.project(path,configuration)", - "org.gradle.api.provider.Property.set(value)", - "org.gradle.api.plugins.ObjectConfigurationAction.plugin(pluginId)", - - /* copied from com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider.defaultBlackList */ - // TODO: IJPL-166464 should provide API like InlayParameterHintsProvider#getBlackListDependencyLanguage - - "(begin*, end*)", - "(start*, end*)", - "(first*, last*)", - "(first*, second*)", - "(from*, to*)", - "(min*, max*)", - "(key, value)", - "(format, arg*)", - "(message)", - "(message, error)", - - "*Exception", - - "*.set*(*)", - "*.add(*)", - "*.set(*,*)", - "*.get(*)", - "*.create(*)", - "*.getProperty(*)", - "*.setProperty(*,*)", - "*.print(*)", - "*.println(*)", - "*.append(*)", - "*.charAt(*)", - "*.indexOf(*)", - "*.contains(*)", - "*.startsWith(*)", - "*.endsWith(*)", - "*.equals(*)", - "*.equal(*)", - "*.compareTo(*)", - "*.compare(*,*)", - - "java.lang.Math.*", - "org.slf4j.Logger.*", - - "*.singleton(*)", - "*.singletonList(*)", - - "*.Set.of", - "*.ImmutableList.of", - "*.ImmutableMultiset.of", - "*.ImmutableSortedMultiset.of", - "*.ImmutableSortedSet.of", - "*.Arrays.asList" - - ).mapNotNull { MatcherConstructor.createMatcher(it) } - } - override fun collectFromElement( element: PsiElement, sink: InlayTreeSink @@ -128,14 +63,28 @@ class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { val functionSymbol: KaFunctionSymbol = functionCall.symbol val valueParameters: List = functionSymbol.valueParameters - val excludeListed = functionSymbol.isExcludeListed(excludeListMatchers) + val excludeListed: Boolean + val contextMenuPayloads: List? + val callableFqName = functionSymbol.callableId?.asSingleFqName()?.asString() + if (callableFqName != null) { + val parameterNames = valueParameters.map { it.name.asString() } + excludeListed = isExcludeListed(callableFqName, parameterNames) + contextMenuPayloads = excludeListed.ifFalse { + createAddToExcludeListActionPayloads( + callableFqName, callableFqName + "(" + parameterNames.joinToString(", ") + ")", KotlinLanguage.INSTANCE + ) + } + } else { + excludeListed = false + contextMenuPayloads = null + } sink.whenOptionEnabled(SHOW_EXCLUDED_PARAMETERS.name) { if (excludeListed) { val valueParametersWithNames = calculateValueParametersWithNames(functionSymbol, callElement, valueParameters) ?: return@whenOptionEnabled - collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, sink) + collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, contextMenuPayloads, sink) } } @@ -151,10 +100,10 @@ class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { if (compiledSource) { sink.whenOptionEnabled(SHOW_COMPILED_PARAMETERS.name) { - collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, sink) + collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, contextMenuPayloads, sink) } } else { - collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, sink) + collectFromParameters(functionCall.argumentMapping, valueParametersWithNames, contextMenuPayloads, sink) } } @@ -192,6 +141,7 @@ class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { private fun collectFromParameters( args: Map>, valueParametersWithNames: List>, + contextMenuPayloads: List?, sink: InlayTreeSink ) { for ((symbol, name) in valueParametersWithNames) { @@ -215,7 +165,11 @@ class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { name.takeUnless(Name::isSpecial)?.asString()?.let { stringName -> val element = arg.getParentOfType(true, KtValueArgumentList::class.java) ?: arg - sink.addPresentation(InlineInlayPosition(element.startOffset, true), hintFormat = HintFormat.default) { + sink.addPresentation( + InlineInlayPosition(element.startOffset, true), + payloads = contextMenuPayloads, + hintFormat = HintFormat.default + ) { if (symbol.isVararg) text(Typography.ellipsis.toString()) text(stringName, symbol.psi?.createSmartPointer()?.let { @@ -261,8 +215,87 @@ class KtParameterHintsProvider : AbstractKtInlayHintsProvider() { } context(KaSession) -internal fun KaFunctionSymbol.isExcludeListed(excludeListMatchers: List): Boolean { - val callableFqName = callableId?.asSingleFqName()?.asString() ?: return false - val parameterNames = valueParameters.map { it.name.asString() } - return excludeListMatchers.any { it.isMatching(callableFqName, parameterNames) } -} \ No newline at end of file +internal fun isExcludeListed(callableFqName: String, parameterNames: List): Boolean { + return ParameterHintsExcludeListService.getInstance().isExcluded( + callableFqName, + parameterNames, + KotlinLanguage.INSTANCE + ) +} + +class KtParameterHintsExcludeListConfigProvider : ParameterHintsExcludeListConfigProvider { + override fun getDefaultExcludeList(): Set = setOf( + "*listOf", "*setOf", "*arrayOf", "*ListOf", "*SetOf", "*ArrayOf", "*assert*(*)", "*mapOf", "*MapOf", + "kotlin.require*(*)", "kotlin.check*(*)", "*contains*(value)", "*containsKey(key)", "kotlin.lazyOf(value)", + "*SequenceBuilder.resume(value)", "*SequenceBuilder.yield(value)", + + /* Gradle DSL especially annoying hints */ + "org.gradle.api.Project.property(propertyName)", + "org.gradle.api.Project.hasProperty(propertyName)", + "org.gradle.api.Project.findProperty(propertyName)", + "org.gradle.api.Project.file(path)", + "org.gradle.api.Project.uri(path)", + "jvmArgs(arguments)", + "org.gradle.kotlin.dsl.DependencyHandlerScope.*(notation)", + "org.gradle.kotlin.dsl.PluginDependenciesSpecScope.*(*)", + "org.gradle.kotlin.dsl.*(dependencyNotation)", + // TODO '*' wildcard is only supported at the start or end of the method name + // "org.gradle.api.tasks.util.*include(includes)", + // "org.gradle.api.tasks.util.*exclude(excludes)", + "org.gradle.kotlin.dsl.kotlin(module)", + "org.gradle.kotlin.dsl.kotlin(module,version)", + "org.gradle.kotlin.dsl.project(path,configuration)", + "org.gradle.api.provider.Property.set(value)", + "org.gradle.api.plugins.ObjectConfigurationAction.plugin(pluginId)", + ) + + override fun getExcludeListDependencyLanguage(): Language = JavaLanguage.INSTANCE +} + +class KtParameterHintsCustomSettingsProvider : AbstractDeclarativeParameterHintsCustomSettingsProvider() + +private const val METHOD_NAME: String = "addToExcludeList.name" +private const val PATTERN_TO_ADD: String = "addToExcludeList.pattern" +private const val LANG_ID: String = "addToExcludeList.lang" + +fun createAddToExcludeListActionPayloads(methodName: String, patternToAdd: String, language: Language?): List = buildList { + add(InlayPayload(METHOD_NAME, StringInlayActionPayload(methodName))) + add(InlayPayload(PATTERN_TO_ADD, StringInlayActionPayload(patternToAdd))) + if (language != null) { + add(InlayPayload(LANG_ID, StringInlayActionPayload(language.id))) + } +} + +class KtAddToExcludeListAction : AnAction() { + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val methodName = e.getInlayPayloads()?.getStringPayload(METHOD_NAME) + + if (methodName == null) { + e.presentation.isEnabledAndVisible = false + } else { + e.presentation.isEnabledAndVisible = true + e.presentation.text = getDisableHintText(methodName) + } + } + + override fun actionPerformed(e: AnActionEvent) { + val payloads = e.getInlayPayloads() ?: return + val patternToAdd = payloads.getStringPayload(PATTERN_TO_ADD)?: return + val lang = payloads.getStringPayload(LANG_ID)?.let { Language.findLanguageByID(it) } ?: return + val config = ParameterHintsExcludeListService.getInstance().getConfig(lang) ?: return + ExcludeListDialog(config, patternToAdd).show() + } + + + @NlsActions.ActionText + private fun getDisableHintText(methodName: String): String = + CodeInsightBundle.message("inlay.hints.show.settings", methodName) +} + +private fun AnActionEvent.getInlayPayloads(): Map? = + getData(InlayHintsProvider.INLAY_PAYLOADS) + +private fun Map.getStringPayload(key: String): String? = + (get(key) as? StringInlayActionPayload)?.text \ No newline at end of file