Add "Throwable not thrown" Inspection
#KT-11629 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
e0aeb8f7ec
commit
20aa8ebdb0
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+128
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user