mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
4e71849228
commit
16d1a6aa83
@@ -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)
|
||||
}
|
||||
}
|
||||
+11
@@ -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 ""; }
|
||||
}
|
||||
+7
@@ -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>;
|
||||
}
|
||||
}
|
||||
+7
@@ -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>;
|
||||
}
|
||||
}
|
||||
+10
@@ -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"));
|
||||
}
|
||||
}
|
||||
+10
@@ -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"));
|
||||
}
|
||||
}
|
||||
+27
@@ -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()'")
|
||||
}
|
||||
}
|
||||
+52
@@ -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()'")
|
||||
}
|
||||
}
|
||||
+9
@@ -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>
|
||||
}
|
||||
}
|
||||
+11
@@ -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"))
|
||||
}
|
||||
}
|
||||
+15
@@ -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 = ""
|
||||
}
|
||||
+9
@@ -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>
|
||||
}
|
||||
}
|
||||
+9
@@ -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>
|
||||
}
|
||||
}
|
||||
+11
@@ -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"))
|
||||
}
|
||||
}
|
||||
+11
@@ -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"))
|
||||
}
|
||||
}
|
||||
+37
@@ -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"/>
|
||||
|
||||
+80
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user