diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt index 0afc8807257..7890558dd0e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt @@ -32,7 +32,7 @@ import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention abstract class IntentionBasedInspection( val intentions: List>, - protected val problemText: String?, + protected open val problemText: String?, protected val elementType: Class ) : AbstractKotlinInspection() { diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt index 1bfc5ab4687..fb152e0b698 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt @@ -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( + 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? { + 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? { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt index a5757cb74d2..07636c882b4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt @@ -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::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? diff --git a/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml b/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml index 0edd710181a..e0ba834692c 100644 --- a/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml +++ b/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml @@ -5,7 +5,7 @@ light_idea_test_case Function, property or variable has platform type. - Function has platform type. Make the type explicit to prevent subtle bugs. + Declaration has platform type. Make the type explicit to prevent subtle bugs. function.kt @@ -13,7 +13,7 @@ light_idea_test_case Function or property has platform type - Function has platform type. Make the type explicit to prevent subtle bugs. + Declaration has platform type. Make the type explicit to prevent subtle bugs. property1.kt @@ -21,7 +21,7 @@ light_idea_test_case Function, property or variable has platform type. - Property has platform type. Make the type explicit to prevent subtle bugs. + Declaration has platform type. Make the type explicit to prevent subtle bugs. property2.kt @@ -29,6 +29,6 @@ light_idea_test_case Function, property or variable has platform type. - Property has platform type. Make the type explicit to prevent subtle bugs. + Declaration has platform type. Make the type explicit to prevent subtle bugs. \ No newline at end of file diff --git a/idea/testData/quickfix/platformTypesInspection/nestedNoAssertRuntime.kt b/idea/testData/quickfix/platformTypesInspection/nestedNoAssertRuntime.kt index ba9e42ef37b..b32956195ee 100644 --- a/idea/testData/quickfix/platformTypesInspection/nestedNoAssertRuntime.kt +++ b/idea/testData/quickfix/platformTypesInspection/nestedNoAssertRuntime.kt @@ -3,6 +3,5 @@ // ACTION: Convert to block body // ACTION: Create test // ACTION: Specify return type explicitly -// ACTION: Specify return type explicitly fun foo() = arrayOf(java.lang.String.valueOf(1)) \ No newline at end of file