[devkit] IJPL-237218 Add an inspection that warns about JComponent.setToolTipText(String) usage

`com.intellij.ide.HelpTooltipKt.setToolTipText(HtmlChunk)` should be used instead.
Also, provide simple quick-fixes.


(cherry picked from commit 3b084a7e22ca0389feafb7c940a1d554b982549c)

IJ-CR-196509

GitOrigin-RevId: 83f52545a3ed3b33da0edc1197e59144b20d02af
This commit is contained in:
Konstantin.Hudyakov
2026-03-18 19:25:11 +00:00
committed by intellij-monorepo-bot
parent 4e71849228
commit 16d1a6aa83
23 changed files with 513 additions and 0 deletions
@@ -13,5 +13,6 @@ import javax.swing.JComponent
* prefer using [HtmlChunk.text] to avoid accidental HTML injections.
*/
fun JComponent.setToolTipText(html: HtmlChunk?) {
@Suppress("UseHtmlChunkToolTip")
this.toolTipText = html?.toString()
}
@@ -0,0 +1,23 @@
<html>
<body>
Reports usages of <code>JComponent.setToolTipText(String)</code>.
<p>
Passing raw strings may cause accidental HTML injections because IntelliJ Platform interprets HTML markup when renders the tooltip text.
<code>com.intellij.ide.HelpTooltipKt.setToolTipText(HtmlChunk)</code> should be used instead of
<code>JComponent.setToolTipText(String)</code>.
Use <code>HtmlChunk.text()</code> to safely escape text, especially in cases when text can contain user input
or project content (for example, file names).
Or use <code>HtmlChunk.raw()</code> if the tooltip supposes to contain HTML.
</p>
<p>
Example:
</p>
<pre><code lang="kotlin">
// Bad — raw string may contain HTML injection:
component.setToolTipText(text)
// Good — text is escaped:
component.setToolTipText(HtmlChunk.text(text))
</code></pre>
</body>
</html>
@@ -253,6 +253,12 @@
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.UseIntelliJVirtualThreadsInspection"
key="inspection.use.intellij.virtual.threads.display.name"/>
<localInspection language="UAST" shortName="UseHtmlChunkToolTip"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.code"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.UseHtmlChunkToolTipInspection"
key="inspection.use.html.chunk.tooltip.display.name"/>
<localInspection language="UAST" shortName="UseDPIAwareInsets"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.code"
@@ -769,6 +775,9 @@
<cancellationCheckInLoopsFixProvider
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaCancellationCheckInLoopsFixProvider"/>
<useHtmlChunkToolTipFixProvider
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaUseHtmlChunkToolTipFixProvider"/>
<visitorProviderForRBCInspection
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.CallingMethodShouldBeRequiresBlockingContextInspection$VisitorProviderForJava"/>
@@ -830,6 +839,12 @@
<with attribute="implementationClass"
implements="org.jetbrains.idea.devkit.inspections.quickfix.CancellationCheckInLoopsFixProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="DevKit.lang.useHtmlChunkToolTipFixProvider"
beanClass="com.intellij.lang.LanguageExtensionPoint"
dynamic="true">
<with attribute="implementationClass"
implements="org.jetbrains.idea.devkit.inspections.quickfix.UseHtmlChunkToolTipFixProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="DevKit.lang.visitorProviderForRBCInspection"
beanClass="com.intellij.lang.LanguageExtensionPoint"
dynamic="true">
@@ -850,6 +850,11 @@ inspection.use.intellij.virtual.threads.display.name=Use 'IntelliJVirtualThreads
inspection.use.intellij.virtual.threads.message=Use 'IntelliJVirtualThreads.ofVirtual()' instead of 'Thread.ofVirtual()'
inspection.use.intellij.virtual.threads.fix.family.name=Replace with 'IntelliJVirtualThreads.ofVirtual()'
inspection.use.html.chunk.tooltip.display.name=Use 'setToolTipText(HtmlChunk)' to avoid HTML injections
inspections.use.html.chunk.tooltip.message='JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'
inspections.use.html.chunk.tooltip.fix.wrap.text.family.name=Wrap with 'HtmlChunk.text()'
inspections.use.html.chunk.tooltip.fix.wrap.raw.family.name=Wrap with 'HtmlChunk.raw()'
category.platform.title=IntelliJ Platform
category.platform.description=APIs provided by IntelliJ Platform modules
category.plugins.title=Plugins
@@ -0,0 +1,85 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.CommonClassNames
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.InheritanceUtil
import com.intellij.uast.UastHintedVisitorAdapter
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.quickfix.UseHtmlChunkToolTipFixProviders
import org.jetbrains.uast.UBinaryExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.ULiteralExpression
import org.jetbrains.uast.UastBinaryOperator
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
/**
* Reports usages of [javax.swing.JComponent.setToolTipText] with a [String] argument
* and suggests using [com.intellij.ide.setToolTipText] to prevent accidental HTML injections.
*
* Detects both explicit method calls (`comp.setToolTipText(text)`) and
* Kotlin property-style setters (`comp.toolTipText = text`).
*/
internal class UseHtmlChunkToolTipInspection : DevKitUastInspectionBase() {
override fun isAllowed(holder: ProblemsHolder): Boolean {
return DevKitInspectionUtil.isAllowed(holder.file) &&
DevKitInspectionUtil.isClassAvailable(holder, HTML_CHUNK_FQN) &&
DevKitInspectionUtil.isClassAvailable(holder, HELP_TOOLTIP_KT_FQN)
}
override fun buildInternalVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return UastHintedVisitorAdapter.create(holder.file.language, object : AbstractUastNonRecursiveVisitor() {
override fun visitCallExpression(node: UCallExpression): Boolean {
if (node.methodName != SET_TIP_TEXT_METHOD_NAME) return super.visitCallExpression(node)
val method = node.resolve() ?: return super.visitCallExpression(node)
if (!isSetToolTipTextOnJComponent(method)) return super.visitCallExpression(node)
if (isNullLiteral(node.valueArguments.firstOrNull())) return super.visitCallExpression(node)
val psi = node.sourcePsi ?: return super.visitCallExpression(node)
registerProblem(psi)
return super.visitCallExpression(node)
}
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
if (node.operator != UastBinaryOperator.ASSIGN) return super.visitBinaryExpression(node)
val method = node.resolveOperator() ?: return super.visitBinaryExpression(node)
if (method.name != SET_TIP_TEXT_METHOD_NAME) return super.visitBinaryExpression(node)
if (!isSetToolTipTextOnJComponent(method)) return super.visitBinaryExpression(node)
if (isNullLiteral(node.rightOperand)) return super.visitBinaryExpression(node)
val psi = node.sourcePsi ?: return super.visitBinaryExpression(node)
registerProblem(psi)
return super.visitBinaryExpression(node)
}
private fun registerProblem(psi: com.intellij.psi.PsiElement) {
val fixProvider = UseHtmlChunkToolTipFixProviders.forLanguage(psi.language)
val fixes = fixProvider?.createFixes(psi) ?: LocalQuickFix.EMPTY_ARRAY
holder.registerProblem(psi, DevKitBundle.message("inspections.use.html.chunk.tooltip.message"), *fixes)
}
}, HINTS)
}
}
private const val SET_TIP_TEXT_METHOD_NAME = "setToolTipText"
private const val JCOMPONENT_FQN = "javax.swing.JComponent"
private const val HTML_CHUNK_FQN = "com.intellij.openapi.util.text.HtmlChunk"
private const val HELP_TOOLTIP_KT_FQN = "com.intellij.ide.HelpTooltipKt"
private val HINTS: Array<Class<out UElement>> = arrayOf(UCallExpression::class.java, UBinaryExpression::class.java)
private fun isNullLiteral(expr: UExpression?): Boolean {
return expr is ULiteralExpression && expr.isNull
}
private fun isSetToolTipTextOnJComponent(method: PsiMethod): Boolean {
val clazz = method.containingClass ?: return false
if (!InheritanceUtil.isInheritor(clazz, JCOMPONENT_FQN)) return false
val params = method.parameterList.parameters
return params.size == 1 && params[0].type.canonicalText == CommonClassNames.JAVA_LANG_STRING
}
@@ -0,0 +1,65 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections.quickfix
import com.intellij.codeInsight.intention.PriorityAction
import com.intellij.codeInsight.intention.PriorityAction.Priority
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.IntellijInternalApi
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.idea.devkit.DevKitBundle
private val EP_NAME = ExtensionPointName.create<UseHtmlChunkToolTipFixProvider>("DevKit.lang.useHtmlChunkToolTipFixProvider")
internal object UseHtmlChunkToolTipFixProviders : LanguageExtension<UseHtmlChunkToolTipFixProvider>(EP_NAME.name)
@IntellijInternalApi
@ApiStatus.Internal
interface UseHtmlChunkToolTipFixProvider {
fun createFixes(element: PsiElement): Array<LocalQuickFix>
}
private const val SET_TOOLTIP_TEXT_METHOD_FQN = "com.intellij.ide.HelpTooltipKt.setToolTipText"
private const val HTML_CHUNK_FQN = "com.intellij.openapi.util.text.HtmlChunk"
internal class JavaUseHtmlChunkToolTipFixProvider : UseHtmlChunkToolTipFixProvider {
override fun createFixes(element: PsiElement): Array<LocalQuickFix> {
return arrayOf(
JavaWrapWithHtmlChunkFix("text", "inspections.use.html.chunk.tooltip.fix.wrap.text.family.name", Priority.TOP),
JavaWrapWithHtmlChunkFix("raw", "inspections.use.html.chunk.tooltip.fix.wrap.raw.family.name", Priority.HIGH),
)
}
}
/**
* Replaces `comp.setToolTipText(arg)` with `HelpTooltipKt.setToolTipText(comp, HtmlChunk.text/raw(arg))`.
*/
private class JavaWrapWithHtmlChunkFix(
private val wrapMethodName: String,
private val familyNameKey: String,
private val fixPriority: Priority,
) : LocalQuickFix, PriorityAction {
override fun getPriority(): Priority = fixPriority
@IntentionFamilyName
override fun getFamilyName(): String = DevKitBundle.message(familyNameKey)
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val callExpr = descriptor.psiElement as? PsiMethodCallExpression ?: return
val qualifierExpr = callExpr.methodExpression.qualifierExpression ?: return
val arg = callExpr.argumentList.expressions.firstOrNull() ?: return
val newText = "$SET_TOOLTIP_TEXT_METHOD_FQN(${qualifierExpr.text}, $HTML_CHUNK_FQN.$wrapMethodName(${arg.text}))"
val newExpr = PsiElementFactory.getInstance(project).createExpressionFromText(newText, callExpr)
val replaced = callExpr.replace(newExpr)
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced)
}
}
@@ -0,0 +1,11 @@
import javax.swing.*;
class SetToolTipTextWithString {
void test(JComponent comp) {
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.setToolTipText("hello")</warning>;
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.setToolTipText(getString())</warning>;
comp.setToolTipText(null);
}
String getString() { return ""; }
}
@@ -0,0 +1,7 @@
import javax.swing.*;
class SetToolTipTextWithStringFix {
void test(JComponent comp) {
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.setToolTipText(<caret>"hello")</warning>;
}
}
@@ -0,0 +1,7 @@
import javax.swing.*;
class SetToolTipTextWithStringFixRaw {
void test(JComponent comp) {
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.setToolTipText(<caret>"hello")</warning>;
}
}
@@ -0,0 +1,10 @@
import com.intellij.ide.HelpTooltipKt;
import com.intellij.openapi.util.text.HtmlChunk;
import javax.swing.*;
class SetToolTipTextWithStringFixRaw {
void test(JComponent comp) {
HelpTooltipKt.setToolTipText(comp, HtmlChunk.raw("hello"));
}
}
@@ -0,0 +1,10 @@
import com.intellij.ide.HelpTooltipKt;
import com.intellij.openapi.util.text.HtmlChunk;
import javax.swing.*;
class SetToolTipTextWithStringFix {
void test(JComponent comp) {
HelpTooltipKt.setToolTipText(comp, HtmlChunk.text("hello"));
}
}
@@ -0,0 +1,27 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
import org.jetbrains.idea.devkit.inspections.quickfix.UseHtmlChunkToolTipInspectionTestBase
@TestDataPath($$"$CONTENT_ROOT/testData/inspections/useHtmlChunkToolTip")
class UseHtmlChunkToolTipInspectionTest : UseHtmlChunkToolTipInspectionTestBase() {
override fun getBasePath(): String {
return DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/useHtmlChunkToolTip"
}
override fun getFileExtension(): String = "java"
fun testSetToolTipTextWithString() {
doTest()
}
fun testSetToolTipTextWithStringFix() {
doTest("Wrap with 'HtmlChunk.text()'")
}
fun testSetToolTipTextWithStringFixRaw() {
doTest("Wrap with 'HtmlChunk.raw()'")
}
}
@@ -0,0 +1,52 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.k2.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.inspections.quickfix.UseHtmlChunkToolTipInspectionTestBase
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
import org.jetbrains.kotlin.idea.test.ExpectedPluginModeProvider
import org.jetbrains.kotlin.idea.test.setUpWithKotlinPlugin
@TestDataPath("\$CONTENT_ROOT/testData/inspections/useHtmlChunkToolTip")
class KtUseHtmlChunkToolTipInspectionTest : UseHtmlChunkToolTipInspectionTestBase(), ExpectedPluginModeProvider {
override val pluginMode: KotlinPluginMode = KotlinPluginMode.K2
override fun setUp() {
setUpWithKotlinPlugin { super.setUp() }
}
override fun getBasePath(): String {
return DevkitKtTestsUtil.TESTDATA_PATH + "inspections/useHtmlChunkToolTip"
}
override fun getFileExtension(): String = "kt"
override fun addSetToolTipTextExtensionStub() {
myFixture.addFileToProject(
"src/com/intellij/ide/HelpTooltip.kt",
"""
package com.intellij.ide
import javax.swing.JComponent
import com.intellij.openapi.util.text.HtmlChunk
fun JComponent.setToolTipText(html: HtmlChunk?) {}
""".trimIndent()
)
}
fun testSetToolTipTextWithString() {
doTest()
}
fun testSetToolTipTextWithStringFix() {
doTest("Wrap with 'HtmlChunk.text()'")
}
fun testSetToolTipTextWithStringFixRaw() {
doTest("Wrap with 'HtmlChunk.raw()'")
}
fun testSetToolTipTextPropertyStyleFix() {
doTest("Wrap with 'HtmlChunk.text()'")
}
}
@@ -0,0 +1,9 @@
@file:Suppress(<warning descr="[ERROR_SUPPRESSION] Suppression of error 'MISSING_DEPENDENCY_SUPERCLASS' might compile and work, but the compiler behavior is UNSPECIFIED and WILL NOT BE PRESERVED. Please report your use case to the Kotlin issue tracker instead: https://kotl.in/issue">"MISSING_DEPENDENCY_SUPERCLASS"</warning>) // mockSDK misses some superclasses
import javax.swing.JComponent
class SetToolTipTextPropertyStyleFix {
fun test(comp: JComponent) {
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'"><caret>comp.toolTipText = "hello"</warning>
}
}
@@ -0,0 +1,11 @@
@file:Suppress("MISSING_DEPENDENCY_SUPERCLASS") // mockSDK misses some superclasses
import com.intellij.ide.setToolTipText
import com.intellij.openapi.util.text.HtmlChunk
import javax.swing.JComponent
class SetToolTipTextPropertyStyleFix {
fun test(comp: JComponent) {
comp.setToolTipText(HtmlChunk.text("hello"))
}
}
@@ -0,0 +1,15 @@
@file:Suppress(<warning descr="[ERROR_SUPPRESSION] Suppression of error 'MISSING_DEPENDENCY_SUPERCLASS' might compile and work, but the compiler behavior is UNSPECIFIED and WILL NOT BE PRESERVED. Please report your use case to the Kotlin issue tracker instead: https://kotl.in/issue">"MISSING_DEPENDENCY_SUPERCLASS"</warning>) // mockSDK misses some superclasses
import javax.swing.JComponent
class SetToolTipTextWithString {
fun test(comp: JComponent) {
comp.<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">setToolTipText("hello")</warning>
comp.setToolTipText(null)
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.toolTipText = "hello"</warning>
<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">comp.toolTipText = getString()</warning>
comp.toolTipText = null
}
fun getString(): String = ""
}
@@ -0,0 +1,9 @@
@file:Suppress(<warning descr="[ERROR_SUPPRESSION] Suppression of error 'MISSING_DEPENDENCY_SUPERCLASS' might compile and work, but the compiler behavior is UNSPECIFIED and WILL NOT BE PRESERVED. Please report your use case to the Kotlin issue tracker instead: https://kotl.in/issue">"MISSING_DEPENDENCY_SUPERCLASS"</warning>) // mockSDK misses some superclasses
import javax.swing.JComponent
class SetToolTipTextWithStringFix {
fun test(comp: JComponent) {
comp.<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">setToolTipText(<caret>"hello")</warning>
}
}
@@ -0,0 +1,9 @@
@file:Suppress(<warning descr="[ERROR_SUPPRESSION] Suppression of error 'MISSING_DEPENDENCY_SUPERCLASS' might compile and work, but the compiler behavior is UNSPECIFIED and WILL NOT BE PRESERVED. Please report your use case to the Kotlin issue tracker instead: https://kotl.in/issue">"MISSING_DEPENDENCY_SUPERCLASS"</warning>) // mockSDK misses some superclasses
import javax.swing.JComponent
class SetToolTipTextWithStringFixRaw {
fun test(comp: JComponent) {
comp.<warning descr="'JComponent.setToolTipText(String)' used instead of safe 'setToolTipText(HtmlChunk)'">setToolTipText(<caret>"hello")</warning>
}
}
@@ -0,0 +1,11 @@
@file:Suppress("MISSING_DEPENDENCY_SUPERCLASS") // mockSDK misses some superclasses
import com.intellij.ide.setToolTipText
import com.intellij.openapi.util.text.HtmlChunk
import javax.swing.JComponent
class SetToolTipTextWithStringFixRaw {
fun test(comp: JComponent) {
comp.setToolTipText(HtmlChunk.raw("hello"))
}
}
@@ -0,0 +1,11 @@
@file:Suppress("MISSING_DEPENDENCY_SUPERCLASS") // mockSDK misses some superclasses
import com.intellij.ide.setToolTipText
import com.intellij.openapi.util.text.HtmlChunk
import javax.swing.JComponent
class SetToolTipTextWithStringFix {
fun test(comp: JComponent) {
comp.setToolTipText(HtmlChunk.text("hello"))
}
}
@@ -0,0 +1,37 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections.quickfix
import org.jetbrains.idea.devkit.inspections.UseHtmlChunkToolTipInspection
abstract class UseHtmlChunkToolTipInspectionTestBase : LightDevKitInspectionFixTestBase() {
@Throws(Exception::class)
override fun setUp() {
super.setUp()
myFixture.enableInspections(UseHtmlChunkToolTipInspection())
myFixture.addClass(
"""package javax.swing;
|public class JComponent {
| public String getToolTipText() { return ""; }
| public void setToolTipText(String text) {}
|}""".trimMargin()
)
myFixture.addClass(
"""package com.intellij.openapi.util.text;
|public class HtmlChunk {
| public static HtmlChunk text(String text) { return new HtmlChunk(); }
| public static HtmlChunk raw(String text) { return new HtmlChunk(); }
|}""".trimMargin()
)
myFixture.addClass(
"""package com.intellij.ide;
|import javax.swing.JComponent;
|import com.intellij.openapi.util.text.HtmlChunk;
|public class HelpTooltipKt {
| public static void setToolTipText(JComponent comp, HtmlChunk html) {}
|}""".trimMargin()
)
addSetToolTipTextExtensionStub()
}
protected open fun addSetToolTipTextExtensionStub() {}
}
@@ -140,6 +140,9 @@
<cancellationCheckInLoopsFixProvider
language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtCancellationCheckInLoopsFixProvider"/>
<useHtmlChunkToolTipFixProvider
language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtUseHtmlChunkToolTipFixProvider"/>
<visitorProviderForRBCInspection
language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtCallingFunctionShouldBeRequiresBlockingContextVisitorProvider"/>
@@ -0,0 +1,80 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.kotlin.inspections
import com.intellij.codeInsight.intention.PriorityAction
import com.intellij.codeInsight.intention.PriorityAction.Priority
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.quickfix.UseHtmlChunkToolTipFixProvider
import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
import org.jetbrains.kotlin.idea.base.psi.imports.addImport
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
internal class KtUseHtmlChunkToolTipFixProvider : UseHtmlChunkToolTipFixProvider {
override fun createFixes(element: PsiElement): Array<LocalQuickFix> {
return arrayOf(
KtWrapWithHtmlChunkFix("text", "inspections.use.html.chunk.tooltip.fix.wrap.text.family.name", Priority.TOP),
KtWrapWithHtmlChunkFix("raw", "inspections.use.html.chunk.tooltip.fix.wrap.raw.family.name", Priority.HIGH),
)
}
}
private const val HTML_CHUNK_FQN = "com.intellij.openapi.util.text.HtmlChunk"
private val SET_TOOL_TIP_TEXT_EXTENSION_FQN = FqName("com.intellij.ide.setToolTipText")
private class KtWrapWithHtmlChunkFix(
private val wrapMethodName: String,
private val familyNameKey: String,
private val fixPriority: Priority,
) : LocalQuickFix, PriorityAction {
override fun getPriority(): Priority = fixPriority
@IntentionFamilyName
override fun getFamilyName(): String = DevKitBundle.message(familyNameKey)
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement
val ktFile = element.containingFile as? KtFile ?: return
val psiFactory = KtPsiFactory(project)
// Case 1: Method call — comp.setToolTipText(arg)
val dotCall = element as? KtDotQualifiedExpression
?: (element.parent as? KtDotQualifiedExpression)?.takeIf { it.selectorExpression is KtCallExpression }
if (dotCall != null) {
val call = dotCall.selectorExpression as? KtCallExpression ?: return
val receiverText = dotCall.receiverExpression.text
val argText = call.valueArguments.firstOrNull()?.getArgumentExpression()?.text ?: return
replaceAndShortenReferences(ktFile, psiFactory, dotCall, receiverText, argText)
return
}
// Case 2: Property assignment — comp.toolTipText = arg
val binaryExpr = element as? KtBinaryExpression ?: return
val left = binaryExpr.left as? KtDotQualifiedExpression ?: return
val receiverText = left.receiverExpression.text
val argText = binaryExpr.right?.text ?: return
replaceAndShortenReferences(ktFile, psiFactory, binaryExpr, receiverText, argText)
}
private fun replaceAndShortenReferences(
ktFile: KtFile,
psiFactory: KtPsiFactory,
nodeToReplace: PsiElement,
receiverText: String,
argText: String,
) {
val newExpr = psiFactory.createExpression("$receiverText.setToolTipText($HTML_CHUNK_FQN.$wrapMethodName($argText))")
val replaced = nodeToReplace.replace(newExpr)
ShortenReferencesFacility.getInstance().shorten(replaced as org.jetbrains.kotlin.psi.KtElement)
ktFile.addImport(SET_TOOL_TIP_TEXT_EXTENSION_FQN)
}
}