[kotlin] IJPL-166464 add support for exclude list configuration for Kotlin parameter name hints

GitOrigin-RevId: 73cac11669f137cc1d07e3b352e16fd8cdca1cd3
This commit is contained in:
Vojtech Balik
2025-05-26 16:18:28 +02:00
committed by intellij-monorepo-bot
parent 356725b73f
commit 287e6fe067
3 changed files with 146 additions and 90 deletions

View File

@@ -143,8 +143,8 @@
defaultValue="true"
restartRequired="true"/>
<registryKey key="kotlin.k2.auto.import.from.subclass.objects.enabled"
defaultValue="true"
<registryKey key="kotlin.k2.auto.import.from.subclass.objects.enabled"
defaultValue="true"
description="Enable auto-import of declarations inherited by objects in K2 Mode of Kotlin Plugin"/>
<registryKey key="kotlin.k2.auto.import.mismatched.arguments.factory.enabled"
@@ -253,6 +253,15 @@
descriptionKey="inlay.kotlin.parameters.hints.compiled"/>
</codeInsight.declarativeInlayProvider>
<codeInsight.declarativeInlayProviderCustomSettingsProvider
language="kotlin"
providerId="kotlin.parameters.hints"
implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.hints.KtParameterHintsCustomSettingsProvider"/>
<codeInsight.parameterHintsExcludeListConfigProvider
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.hints.KtParameterHintsExcludeListConfigProvider"/>
<codeInsight.declarativeInlayProvider
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.hints.KtCallChainHintsProvider"
@@ -328,6 +337,11 @@
class="org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinGenerateToStringAction"/>
<add-to-group group-id="GenerateGroup" anchor="first"/>
</group>
<action id="KtAddToExcludeListAction"
class="org.jetbrains.kotlin.idea.k2.codeinsight.hints.KtAddToExcludeListAction">
<add-to-group group-id="InlayMenu"/>
</action>
</actions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<codeinsight.quickfix.registrar implementation="org.jetbrains.kotlin.idea.k2.codeinsight.quickFixes.createFromUsage.K2CreateFromUsageQuickFixesRegistrar"/>

View File

@@ -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<Matcher>): Boolean {
val callableFqName = callableId?.asSingleFqName()?.asString() ?: return false
val parameterNames = valueParameters.map { it.name.asString() }
return excludeListMatchers.any { it.isMatching(callableFqName, parameterNames) }
}

View File

@@ -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<Matcher> 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<KaValueParameterSymbol> = functionSymbol.valueParameters
val excludeListed = functionSymbol.isExcludeListed(excludeListMatchers)
val excludeListed: Boolean
val contextMenuPayloads: List<InlayPayload>?
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<KtExpression, KaVariableSignature<KaValueParameterSymbol>>,
valueParametersWithNames: List<Pair<KaValueParameterSymbol, Name?>>,
contextMenuPayloads: List<InlayPayload>?,
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<KtValueArgument>(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<Matcher>): Boolean {
val callableFqName = callableId?.asSingleFqName()?.asString() ?: return false
val parameterNames = valueParameters.map { it.name.asString() }
return excludeListMatchers.any { it.isMatching(callableFqName, parameterNames) }
}
internal fun isExcludeListed(callableFqName: String, parameterNames: List<String>): Boolean {
return ParameterHintsExcludeListService.getInstance().isExcluded(
callableFqName,
parameterNames,
KotlinLanguage.INSTANCE
)
}
class KtParameterHintsExcludeListConfigProvider : ParameterHintsExcludeListConfigProvider {
override fun getDefaultExcludeList(): Set<String> = 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<InlayPayload> = 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<String, InlayActionPayload>? =
getData(InlayHintsProvider.INLAY_PAYLOADS)
private fun Map<String, InlayActionPayload>.getStringPayload(key: String): String? =
(get(key) as? StringInlayActionPayload)?.text