Has platform type inspection is now intention-based
(cherry picked from commit 7cc6b9b)
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a68e8e0db5
commit
70becfbd86
+1
-1
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
|||||||
|
|
||||||
abstract class IntentionBasedInspection<TElement : PsiElement>(
|
abstract class IntentionBasedInspection<TElement : PsiElement>(
|
||||||
val intentions: List<IntentionBasedInspection.IntentionData<TElement>>,
|
val intentions: List<IntentionBasedInspection.IntentionData<TElement>>,
|
||||||
protected val problemText: String?,
|
protected open val problemText: String?,
|
||||||
protected val elementType: Class<TElement>
|
protected val elementType: Class<TElement>
|
||||||
) : AbstractKotlinInspection() {
|
) : AbstractKotlinInspection() {
|
||||||
|
|
||||||
|
|||||||
@@ -17,74 +17,49 @@
|
|||||||
package org.jetbrains.kotlin.idea.inspections
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
import com.intellij.codeInspection.IntentionWrapper
|
import com.intellij.codeInspection.IntentionWrapper
|
||||||
|
import com.intellij.codeInspection.LocalQuickFix
|
||||||
import com.intellij.codeInspection.ProblemHighlightType
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
import com.intellij.codeInspection.ProblemsHolder
|
|
||||||
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
|
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
|
||||||
import com.intellij.psi.PsiElementVisitor
|
import com.intellij.openapi.util.TextRange
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
||||||
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.psi.psiUtil.getStartOffsetIn
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
|
||||||
import org.jetbrains.kotlin.types.isFlexible
|
|
||||||
import org.jetbrains.kotlin.types.isNullabilityFlexible
|
import org.jetbrains.kotlin.types.isNullabilityFlexible
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
|
|
||||||
class HasPlatformTypeInspection(
|
class HasPlatformTypeInspection(
|
||||||
|
val intention: SpecifyTypeExplicitlyIntention = SpecifyTypeExplicitlyIntention(),
|
||||||
@JvmField var publicAPIOnly: Boolean = true
|
@JvmField var publicAPIOnly: Boolean = true
|
||||||
) : AbstractKotlinInspection() {
|
) : IntentionBasedInspection<KtCallableDeclaration>(
|
||||||
|
intention,
|
||||||
|
{ intention.dangerousFlexibleTypeOrNull(it, publicAPIOnly) != null }
|
||||||
|
) {
|
||||||
|
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
override val problemHighlightType = ProblemHighlightType.WEAK_WARNING
|
||||||
return object : KtVisitorVoid() {
|
|
||||||
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
|
|
||||||
super.visitDeclaration(declaration)
|
|
||||||
|
|
||||||
val declType = when (declaration) {
|
override val problemText = "Declaration has platform type. Make the type explicit to prevent subtle bugs."
|
||||||
is KtFunction -> if (declaration.isLocal || declaration.hasDeclaredReturnType()) return else "Function"
|
|
||||||
is KtProperty -> if (declaration.isLocal || declaration.typeReference != null) return else "Property"
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (declaration.containingClassOrObject?.isLocal() ?: false) return
|
override fun additionalFixes(element: KtCallableDeclaration): List<LocalQuickFix>? {
|
||||||
|
val type = intention.dangerousFlexibleTypeOrNull(element, publicAPIOnly) ?: return null
|
||||||
|
|
||||||
val context = declaration.analyze(BodyResolveMode.PARTIAL)
|
if (type.isNullabilityFlexible()) {
|
||||||
val callable = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? CallableDescriptor ?: return
|
val expression = element.node.findChildByType(KtTokens.EQ)?.psi?.getNextSiblingIgnoringWhitespaceAndComments()
|
||||||
if (publicAPIOnly && !callable.visibility.isPublicAPI) return
|
if (expression != null) {
|
||||||
val returnType = callable.returnType ?: return
|
return listOf(IntentionWrapper(AddExclExclCallFix(expression), element.containingFile))
|
||||||
if (!returnType.isFlexibleRecursive()) return
|
|
||||||
|
|
||||||
val fixes = mutableListOf(IntentionWrapper(SpecifyTypeExplicitlyIntention(), declaration.containingFile))
|
|
||||||
|
|
||||||
if (returnType.isNullabilityFlexible()) {
|
|
||||||
val expression = declaration.node.findChildByType(KtTokens.EQ)?.psi?.getNextSiblingIgnoringWhitespaceAndComments()
|
|
||||||
if (expression != null) {
|
|
||||||
fixes += IntentionWrapper(AddExclExclCallFix(expression), declaration.containingFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val nameElement = declaration.nameIdentifier ?: return
|
|
||||||
val problemDescriptor = holder.manager.createProblemDescriptor(
|
|
||||||
nameElement,
|
|
||||||
nameElement,
|
|
||||||
"$declType has platform type. Make the type explicit to prevent subtle bugs.",
|
|
||||||
ProblemHighlightType.WEAK_WARNING,
|
|
||||||
isOnTheFly,
|
|
||||||
*fixes.toTypedArray()
|
|
||||||
)
|
|
||||||
holder.registerProblem(problemDescriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KotlinType.isFlexibleRecursive(): Boolean {
|
|
||||||
if (isFlexible()) return true
|
|
||||||
return arguments.any { it.type.isFlexibleRecursive() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inspectionRange(element: KtCallableDeclaration) = element.nameIdentifier?.let {
|
||||||
|
val start = it.getStartOffsetIn(element)
|
||||||
|
TextRange(start, start + it.endOffset - it.startOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createOptionsPanel(): JComponent? {
|
override fun createOptionsPanel(): JComponent? {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
|
|||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
@@ -47,6 +48,28 @@ class SpecifyTypeExplicitlyIntention :
|
|||||||
SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Specify type explicitly"),
|
SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Specify type explicitly"),
|
||||||
LowPriorityAction {
|
LowPriorityAction {
|
||||||
|
|
||||||
|
private fun KotlinType.isFlexibleRecursive(): Boolean {
|
||||||
|
if (isFlexible()) return true
|
||||||
|
return arguments.any { it.type.isFlexibleRecursive() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dangerousFlexibleTypeOrNull(declaration: KtCallableDeclaration, publicAPIOnly: Boolean): KotlinType? {
|
||||||
|
when (declaration) {
|
||||||
|
is KtFunction -> if (declaration.isLocal || declaration.hasDeclaredReturnType()) return null
|
||||||
|
is KtProperty -> if (declaration.isLocal || declaration.typeReference != null) return null
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declaration.containingClassOrObject?.isLocal() ?: false) return null
|
||||||
|
|
||||||
|
val context = declaration.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val callable = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? CallableDescriptor ?: return null
|
||||||
|
if (publicAPIOnly && !callable.visibility.isPublicAPI) return null
|
||||||
|
val type = callable.returnType ?: return null
|
||||||
|
if (!type.isFlexibleRecursive()) return null
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
override fun applicabilityRange(element: KtCallableDeclaration): TextRange? {
|
override fun applicabilityRange(element: KtCallableDeclaration): TextRange? {
|
||||||
if (element.containingFile is KtCodeFragment) return null
|
if (element.containingFile is KtCodeFragment) return null
|
||||||
if (element is KtFunctionLiteral) return null // TODO: should KtFunctionLiteral be KtCallableDeclaration at all?
|
if (element is KtFunctionLiteral) return null // TODO: should KtFunctionLiteral be KtCallableDeclaration at all?
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/function.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/function.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
||||||
<description>Function has platform type. Make the type explicit to prevent subtle bugs.</description>
|
<description>Declaration has platform type. Make the type explicit to prevent subtle bugs.</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>function.kt</file>
|
<file>function.kt</file>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/function.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/function.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function or property has platform type</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function or property has platform type</problem_class>
|
||||||
<description>Function has platform type. Make the type explicit to prevent subtle bugs.</description>
|
<description>Declaration has platform type. Make the type explicit to prevent subtle bugs.</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>property1.kt</file>
|
<file>property1.kt</file>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/property1.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/property1.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
||||||
<description>Property has platform type. Make the type explicit to prevent subtle bugs.</description>
|
<description>Declaration has platform type. Make the type explicit to prevent subtle bugs.</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>property2.kt</file>
|
<file>property2.kt</file>
|
||||||
@@ -29,6 +29,6 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/property2.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/property2.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Function, property or variable has platform type.</problem_class>
|
||||||
<description>Property has platform type. Make the type explicit to prevent subtle bugs.</description>
|
<description>Declaration has platform type. Make the type explicit to prevent subtle bugs.</description>
|
||||||
</problem>
|
</problem>
|
||||||
</problems>
|
</problems>
|
||||||
@@ -3,6 +3,5 @@
|
|||||||
// ACTION: Convert to block body
|
// ACTION: Convert to block body
|
||||||
// ACTION: Create test
|
// ACTION: Create test
|
||||||
// ACTION: Specify return type explicitly
|
// ACTION: Specify return type explicitly
|
||||||
// ACTION: Specify return type explicitly
|
|
||||||
|
|
||||||
fun foo<caret>() = arrayOf(java.lang.String.valueOf(1))
|
fun foo<caret>() = arrayOf(java.lang.String.valueOf(1))
|
||||||
Reference in New Issue
Block a user