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"
|
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"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<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")
|
@TestMetadata("idea/testData/inspectionsLocal/unlabeledReturnInsideLambda")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user