Do not report "no reflection" inspection where reflection is not used
#KT-7059 Fixed
This commit is contained in:
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
|||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.name.isSubpackageOf
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.scopes.JetScope
|
import org.jetbrains.kotlin.resolve.scopes.JetScope
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.TypeUtils.makeStarProjection
|
import org.jetbrains.kotlin.types.TypeUtils.makeStarProjection
|
||||||
@@ -125,4 +127,12 @@ public class ReflectionTypes(private val module: ModuleDescriptor) {
|
|||||||
arguments.add(TypeProjectionImpl(returnType))
|
arguments.add(TypeProjectionImpl(returnType))
|
||||||
return JetTypeImpl(annotations, classDescriptor.getTypeConstructor(), false, arguments, classDescriptor.getMemberScope(arguments))
|
return JetTypeImpl(annotations, classDescriptor.getTypeConstructor(), false, arguments, classDescriptor.getMemberScope(arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
public fun isReflectionType(type: JetType): Boolean {
|
||||||
|
val descriptor = type.getConstructor().getDeclarationDescriptor() ?: return false
|
||||||
|
val fqName = DescriptorUtils.getFqName(descriptor)
|
||||||
|
return fqName.isSafe() && fqName.toSafe().isSubpackageOf(KOTLIN_REFLECT_FQ_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.VfsUtil
|
|||||||
import com.intellij.openapi.vfs.VfsUtilCore
|
import com.intellij.openapi.vfs.VfsUtilCore
|
||||||
import com.intellij.psi.PsiElementVisitor
|
import com.intellij.psi.PsiElementVisitor
|
||||||
import org.jetbrains.kotlin.idea.JetBundle
|
import org.jetbrains.kotlin.idea.JetBundle
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
|
||||||
import org.jetbrains.kotlin.idea.configuration.ConfigureKotlinInProjectUtils
|
import org.jetbrains.kotlin.idea.configuration.ConfigureKotlinInProjectUtils
|
||||||
import org.jetbrains.kotlin.idea.configuration.KotlinJavaModuleConfigurator
|
import org.jetbrains.kotlin.idea.configuration.KotlinJavaModuleConfigurator
|
||||||
@@ -38,7 +39,9 @@ import org.jetbrains.kotlin.load.java.JvmAbi
|
|||||||
import org.jetbrains.kotlin.psi.JetDoubleColonExpression
|
import org.jetbrains.kotlin.psi.JetDoubleColonExpression
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
import org.jetbrains.kotlin.psi.JetFile
|
||||||
import org.jetbrains.kotlin.psi.JetVisitorVoid
|
import org.jetbrains.kotlin.psi.JetVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
||||||
|
import org.jetbrains.kotlin.types.reflect.ReflectionTypes
|
||||||
import org.jetbrains.kotlin.utils.PathUtil
|
import org.jetbrains.kotlin.utils.PathUtil
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||||
@@ -49,7 +52,7 @@ public class ReflectionNotFoundInspection : AbstractKotlinInspection() {
|
|||||||
|
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
val file = holder.getFile()
|
val file = holder.getFile()
|
||||||
val reportProblem =
|
val noReflectionInClassPath =
|
||||||
file is JetFile &&
|
file is JetFile &&
|
||||||
ProjectRootsUtil.isInProjectSource(file) &&
|
ProjectRootsUtil.isInProjectSource(file) &&
|
||||||
file.findModuleDescriptor().findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) == null
|
file.findModuleDescriptor().findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) == null
|
||||||
@@ -70,14 +73,20 @@ public class ReflectionNotFoundInspection : AbstractKotlinInspection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitDoubleColonExpression(expression: JetDoubleColonExpression) {
|
override fun visitDoubleColonExpression(expression: JetDoubleColonExpression) {
|
||||||
if (reportProblem) {
|
if (!noReflectionInClassPath) return
|
||||||
holder.registerProblem(
|
|
||||||
expression.getDoubleColonTokenReference(),
|
val expectedType = expression.analyze().get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression)
|
||||||
JetBundle.message("reflection.not.found"),
|
if (expectedType != null && !ReflectionTypes.isReflectionType(expectedType)) return
|
||||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
|
||||||
*(createQuickFix().singletonOrEmptyList().copyToArray())
|
// If a callable reference is used where a KFunction/KProperty/... expected, we should report that usage as dangerous
|
||||||
)
|
// because reflection features will fail without kotlin-reflect.jar in the classpath.
|
||||||
}
|
// If it's only used as a Function however (for example, "list.map(::function)"), we should not report anything
|
||||||
|
holder.registerProblem(
|
||||||
|
expression.getDoubleColonTokenReference(),
|
||||||
|
JetBundle.message("reflection.not.found"),
|
||||||
|
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||||
|
*(createQuickFix().singletonOrEmptyList().copyToArray())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
import kotlin.reflect.KFunction0
|
||||||
|
|
||||||
|
fun foo() {}
|
||||||
|
|
||||||
|
fun bar(f: () -> Unit) = f()
|
||||||
|
|
||||||
|
// Inspection should be reported here because '::foo' may be used as a reflection object
|
||||||
|
val p1 = ::foo // the type is KFunction0 by default
|
||||||
|
val p2: KFunction0<Unit> = ::foo // the expected type is KFunction0
|
||||||
|
|
||||||
|
// But shouldn't be reported here
|
||||||
|
val p3 = bar(::foo) // the expected type is Function0, '::foo' is used as an ordinary function
|
||||||
|
val p4: Any = ::foo // the expected type is Any
|
||||||
|
val p5: UnresolvedClass = ::foo // an error, another warning would be useless
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<problems>
|
||||||
|
<problem>
|
||||||
|
<file>functionReference.kt</file>
|
||||||
|
<line>12</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/functionReference.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Reflection not found</problem_class>
|
||||||
|
<description>Reflection not found in the classpath</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>functionReference.kt</file>
|
||||||
|
<line>13</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/functionReference.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Reflection not found</problem_class>
|
||||||
|
<description>Reflection not found in the classpath</description>
|
||||||
|
</problem>
|
||||||
|
</problems>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ReflectionNotFoundInspection
|
||||||
@@ -81,6 +81,12 @@ public class JetInspectionTestGenerated extends AbstractJetInspectionTest {
|
|||||||
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$"));
|
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("reflectionNotFound/inspectionData/inspections.test")
|
||||||
|
public void testReflectionNotFound_inspectionData_Inspections_test() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("spelling/inspectionData/inspections.test")
|
@TestMetadata("spelling/inspectionData/inspections.test")
|
||||||
public void testSpelling_inspectionData_Inspections_test() throws Exception {
|
public void testSpelling_inspectionData_Inspections_test() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/spelling/inspectionData/inspections.test");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/spelling/inspectionData/inspections.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user