Add "Throwable not thrown" Inspection

#KT-11629 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-01-21 18:25:08 +09:00
committed by Mikhail Glukhikh
parent e0aeb8f7ec
commit 20aa8ebdb0
28 changed files with 412 additions and 0 deletions
@@ -3279,6 +3279,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinThrowableNotThrownInspection"
displayName="Throwable not thrown"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,6 @@
<html>
<body>
This inspection reports creating of <b>Throwable</b> and subclasses, where the created <b>Throwable</b> is never actually thrown.
Also reports function calls which return <b>Throwable</b> instances, where the result of the function call is not thrown.
</body>
</html>
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.search.searches.ReferencesSearch
import com.siyeh.ig.psiutils.TestUtils
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.isNothing
class KotlinThrowableNotThrownInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(callExpression) {
val calleeExpression = callExpression.calleeExpression ?: return
if (!calleeExpression.text.let { it.contains("Exception") || it.contains("Error") }) return
if (TestUtils.isInTestSourceContent(callExpression)) return
val resultingDescriptor = callExpression.resolveToCall()?.resultingDescriptor ?: return
val type = resultingDescriptor.returnType ?: return
if (type.isNothing() || type.isNullable()) return
val classDescriptor = type.constructor.declarationDescriptor as? ClassDescriptor ?: return
if (!classDescriptor.isSubclassOf(DefaultBuiltIns.Instance.throwable)) return
if (callExpression.isUsed()) return
val description = if (resultingDescriptor is ConstructorDescriptor) {
"Throwable instance '${calleeExpression.text}' is not thrown"
} else {
"Result of '${calleeExpression.text}' call is not thrown"
}
holder.registerProblem(calleeExpression, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
})
private fun KtExpression.isUsed(): Boolean {
if (!isUsedAsExpression(analyze(BodyResolveMode.PARTIAL_WITH_CFA))) return false
val property = getParentOfTypes(
true,
KtThrowExpression::class.java,
KtReturnExpression::class.java,
KtProperty::class.java
) as? KtProperty ?: return true
return !property.isLocal || ReferencesSearch.search(property).any()
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.KotlinThrowableNotThrownInspection
@@ -0,0 +1,7 @@
// PROBLEM: Throwable instance 'FooException' is not thrown
// FIX: none
class FooException : RuntimeException()
fun test() {
<caret>FooException()
}
@@ -0,0 +1,9 @@
// PROBLEM: Result of 'createError' call is not thrown
// FIX: none
class FooException : RuntimeException()
fun createError() = FooException()
fun test() {
createError<caret>()
}
@@ -0,0 +1,8 @@
// PROBLEM: none
fun foo() {
throw RuntimeException()
}
fun test() {
<caret>foo()
}
@@ -0,0 +1,6 @@
// PROBLEM: none
class FooException : Exception()
class Test {
val e = <caret>FooException()
}
+13
View File
@@ -0,0 +1,13 @@
// PROBLEM: Throwable instance 'FooException' is not thrown
// FIX: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
if (i == 1) {
<caret>FooException()
} else {
createException()
}
}
@@ -0,0 +1,12 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
throw if (i == 1) {
<caret>FooException()
} else {
createException()
}
}
@@ -0,0 +1,13 @@
// PROBLEM: Throwable instance 'FooException' is not thrown
// FIX: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
val e = if (i == 1) {
<caret>FooException()
} else {
createException()
}
}
@@ -0,0 +1,13 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
val e = if (i == 1) {
<caret>FooException()
} else {
createException()
}
throw e
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class FooException : RuntimeException()
fun test(i: Int) {
val x = if (i == 1) {
throw <caret>FooException()
} else {
0
}
}
@@ -0,0 +1,8 @@
// PROBLEM: none
fun foo(): RuntimeException? {
return RuntimeException()
}
fun test() {
<caret>foo()
}
@@ -0,0 +1,7 @@
// PROBLEM: Throwable instance 'FooException' is not thrown
// FIX: none
class FooException : Exception()
fun test() {
val e = <caret>FooException()
}
@@ -0,0 +1,7 @@
// PROBLEM: none
class FooException : Exception()
fun test() {
val e = <caret>FooException()
throw e
}
@@ -0,0 +1,7 @@
// PROBLEM: none
class FooException : Exception()
fun test() {
val e = <caret>FooException()
val e2 = e
}
@@ -0,0 +1,6 @@
// PROBLEM: none
class FooException : RuntimeException()
fun test(i: Int?) {
val x = i ?: throw <caret>FooException()
}
@@ -0,0 +1,9 @@
// PROBLEM: none
class FooException : RuntimeException()
class BarException : RuntimeException()
fun test(i: Int?): RuntimeException {
val x = i ?: return <caret>FooException()
return BarException()
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(): FooException {
return <caret>createException()
}
@@ -0,0 +1,6 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test() = <caret>createException()
@@ -0,0 +1,6 @@
// PROBLEM: none
class FooException : RuntimeException()
fun test() {
throw <caret>FooException()
}
@@ -0,0 +1,12 @@
// PROBLEM: Result of 'createException' call is not thrown
// FIX: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
when (i) {
1 -> FooException()
else -> <caret>createException()
}
}
@@ -0,0 +1,11 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
throw when (i) {
1 -> FooException()
else -> <caret>createException()
}
}
@@ -0,0 +1,12 @@
// PROBLEM: Result of 'createException' call is not thrown
// FIX: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
val e = when (i) {
1 -> FooException()
else -> <caret>createException()
}
}
@@ -0,0 +1,12 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
val e = when (i) {
1 -> FooException()
else -> <caret>createException()
}
throw e
}
@@ -0,0 +1,11 @@
// PROBLEM: none
class FooException : RuntimeException()
fun createException() = FooException()
fun test(i: Int) {
val x = when (i) {
1 -> 10
else -> throw <caret>createException()
}
}
@@ -7763,6 +7763,134 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/throwableNotThrown")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ThrowableNotThrown extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInThrowableNotThrown() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/throwableNotThrown"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("basic.kt")
public void testBasic() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/basic.kt");
}
@TestMetadata("basic2.kt")
public void testBasic2() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/basic2.kt");
}
@TestMetadata("basic3.kt")
public void testBasic3() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/basic3.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/field.kt");
}
@TestMetadata("if.kt")
public void testIf() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/if.kt");
}
@TestMetadata("if2.kt")
public void testIf2() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/if2.kt");
}
@TestMetadata("if3.kt")
public void testIf3() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/if3.kt");
}
@TestMetadata("if4.kt")
public void testIf4() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/if4.kt");
}
@TestMetadata("if5.kt")
public void testIf5() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/if5.kt");
}
@TestMetadata("nullable.kt")
public void testNullable() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/nullable.kt");
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/property.kt");
}
@TestMetadata("property2.kt")
public void testProperty2() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/property2.kt");
}
@TestMetadata("property3.kt")
public void testProperty3() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/property3.kt");
}
@TestMetadata("property4.kt")
public void testProperty4() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/property4.kt");
}
@TestMetadata("property5.kt")
public void testProperty5() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/property5.kt");
}
@TestMetadata("return.kt")
public void testReturn() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/return.kt");
}
@TestMetadata("return2.kt")
public void testReturn2() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/return2.kt");
}
@TestMetadata("throw.kt")
public void testThrow() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/throw.kt");
}
@TestMetadata("when.kt")
public void testWhen() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/when.kt");
}
@TestMetadata("when2.kt")
public void testWhen2() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/when2.kt");
}
@TestMetadata("when3.kt")
public void testWhen3() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/when3.kt");
}
@TestMetadata("when4.kt")
public void testWhen4() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/when4.kt");
}
@TestMetadata("when5.kt")
public void testWhen5() throws Exception {
runTest("idea/testData/inspectionsLocal/throwableNotThrown/when5.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/unlabeledReturnInsideLambda")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)