Has platform type inspection is now intention-based

(cherry picked from commit 7cc6b9b)
This commit is contained in:
Mikhail Glukhikh
2016-06-20 17:38:12 +03:00
committed by Mikhail Glukhikh
parent a68e8e0db5
commit 70becfbd86
5 changed files with 54 additions and 57 deletions
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
abstract class IntentionBasedInspection<TElement : PsiElement>(
val intentions: List<IntentionBasedInspection.IntentionData<TElement>>,
protected val problemText: String?,
protected open val problemText: String?,
protected val elementType: Class<TElement>
) : AbstractKotlinInspection() {
@@ -17,74 +17,49 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isFlexible
import org.jetbrains.kotlin.psi.psiUtil.getStartOffsetIn
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.types.isNullabilityFlexible
import javax.swing.JComponent
class HasPlatformTypeInspection(
val intention: SpecifyTypeExplicitlyIntention = SpecifyTypeExplicitlyIntention(),
@JvmField var publicAPIOnly: Boolean = true
) : AbstractKotlinInspection() {
) : IntentionBasedInspection<KtCallableDeclaration>(
intention,
{ intention.dangerousFlexibleTypeOrNull(it, publicAPIOnly) != null }
) {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : KtVisitorVoid() {
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
super.visitDeclaration(declaration)
override val problemHighlightType = ProblemHighlightType.WEAK_WARNING
val declType = when (declaration) {
is KtFunction -> if (declaration.isLocal || declaration.hasDeclaredReturnType()) return else "Function"
is KtProperty -> if (declaration.isLocal || declaration.typeReference != null) return else "Property"
else -> return
}
override val problemText = "Declaration has platform type. Make the type explicit to prevent subtle bugs."
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)
val callable = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? CallableDescriptor ?: return
if (publicAPIOnly && !callable.visibility.isPublicAPI) return
val returnType = callable.returnType ?: return
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() }
if (type.isNullabilityFlexible()) {
val expression = element.node.findChildByType(KtTokens.EQ)?.psi?.getNextSiblingIgnoringWhitespaceAndComments()
if (expression != null) {
return listOf(IntentionWrapper(AddExclExclCallFix(expression), element.containingFile))
}
}
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? {
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.isResolvableInScope
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.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.startOffset
@@ -47,6 +48,28 @@ class SpecifyTypeExplicitlyIntention :
SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Specify type explicitly"),
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? {
if (element.containingFile is KtCodeFragment) return null
if (element is KtFunctionLiteral) return null // TODO: should KtFunctionLiteral be KtCallableDeclaration at all?
@@ -5,7 +5,7 @@
<module>light_idea_test_case</module>
<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>
<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>
<file>function.kt</file>
@@ -13,7 +13,7 @@
<module>light_idea_test_case</module>
<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>
<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>
<file>property1.kt</file>
@@ -21,7 +21,7 @@
<module>light_idea_test_case</module>
<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>
<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>
<file>property2.kt</file>
@@ -29,6 +29,6 @@
<module>light_idea_test_case</module>
<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>
<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>
</problems>
@@ -3,6 +3,5 @@
// ACTION: Convert to block body
// ACTION: Create test
// ACTION: Specify return type explicitly
// ACTION: Specify return type explicitly
fun foo<caret>() = arrayOf(java.lang.String.valueOf(1))