Introduct "Redundant 'with' call" inspection #KT-6633 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-07-12 17:19:48 +09:00
committed by Natalia Selezneva
parent 58becffb1f
commit 4c34ced1fa
40 changed files with 493 additions and 0 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports a redundant <b>with</b> function call that don't access anything from the receiver.
</body>
</html>
+9
View File
@@ -2944,6 +2944,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2941,6 +2941,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2944,6 +2944,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2945,6 +2945,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2944,6 +2944,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2944,6 +2944,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -2941,6 +2941,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantWithInspection"
displayName="Redundant 'with' call"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2018 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.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.moveCaret
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.util.getThisReceiverOwner
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class RedundantWithInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
callExpressionVisitor(fun(callExpression) {
val callee = callExpression.calleeExpression ?: return
if (callee.text != "with") return
val valueArguments = callExpression.valueArguments
if (valueArguments.size != 2) return
val receiver = valueArguments[0].getArgumentExpression() ?: return
val lambda = valueArguments[1].lambdaExpression() ?: return
val lambdaBody = lambda.bodyExpression ?: return
val context = callExpression.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
if (lambdaBody.statements.size > 1 && context[BindingContext.USED_AS_EXPRESSION, callExpression] == true) return
if (callExpression.getResolvedCall(context)?.resultingDescriptor?.fqNameSafe != FqName("kotlin.with")) return
val lambdaDescriptor = context[BindingContext.FUNCTION, lambda.functionLiteral] ?: return
val lambdaExtensionReceiver = lambdaDescriptor.extensionReceiverParameter
var used = false
lambda.functionLiteral.acceptChildren(object : KtVisitorVoid() {
override fun visitKtElement(element: KtElement) {
if (used) return
element.acceptChildren(this)
if (element is KtReturnExpression && element.getLabelName() == "with") {
used = true
return
}
val resolvedCall = element.getResolvedCall(context) ?: return
if (isUsageOfReceiver(resolvedCall, context)) {
used = true
} else if (resolvedCall is VariableAsFunctionResolvedCall && isUsageOfReceiver(resolvedCall.variableCall, context)) {
used = true
}
}
private fun isUsageOfReceiver(resolvedCall: ResolvedCall<*>, bindingContext: BindingContext): Boolean {
// As receiver of call
if (resolvedCall.dispatchReceiver.getThisReceiverOwner(bindingContext) == lambdaDescriptor ||
resolvedCall.extensionReceiver.getThisReceiverOwner(bindingContext) == lambdaDescriptor
) {
return true
}
// As explicit "this"
if (resolvedCall.candidateDescriptor == lambdaExtensionReceiver) {
return true
}
return false
}
})
if (!used) {
val quickfix = when (receiver) {
is KtSimpleNameExpression, is KtStringTemplateExpression, is KtConstantExpression -> RemoveRedundantWithFix()
else -> null
}
holder.registerProblem(
callee,
"Redundant 'with' call",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
quickfix
)
}
})
}
private fun KtValueArgument.lambdaExpression(): KtLambdaExpression? =
(this as? KtLambdaArgument)?.getLambdaExpression() ?: this.getArgumentExpression() as? KtLambdaExpression
private class RemoveRedundantWithFix : LocalQuickFix {
override fun getName() = "Remove redundant 'with' call"
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val callExpression = descriptor.psiElement.parent as? KtCallExpression ?: return
val lambdaBody = callExpression.valueArguments.getOrNull(1)?.lambdaExpression()?.bodyExpression ?: return
val replaced = callExpression.replaced(lambdaBody)
replaced.findExistingEditor()?.moveCaret(replaced.startOffset)
}
}
+10
View File
@@ -0,0 +1,10 @@
fun test() {
with(foo()) {
println("test")
}
}
fun foo(): String {
println("foo")
return ""
}
@@ -0,0 +1,9 @@
<problems>
<problem>
<file>callReceiver.kt</file>
<line>2</line>
<entry_point TYPE="file" FQNAME="temp:///src/callReceiver.kt" />
<problem_class severity="WEAK WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Redundant 'with' call</problem_class>
<description>Redundant 'with' call</description>
</problem>
</problems>
@@ -0,0 +1,2 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantWithInspection
// WITH_RUNTIME
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.RedundantWithInspection
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test() {
return <caret>with (1) {
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test() {
return
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test() {
<caret>with ("") {
with ("a") {
this
}
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test() {
<caret>with ("a") {
this
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
this
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
this.length
}
}
@@ -0,0 +1,12 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
val c = MyClass()
<caret>with(c) {
println(f())
}
}
class MyClass {
fun f(): String = ""
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
length
}
}
@@ -0,0 +1,8 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
val b = 1 == <caret>with ("") {
println()
1
}
}
@@ -0,0 +1,6 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() = <caret>with("") {
println()
1
}
@@ -0,0 +1,8 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
val i = <caret>with ("") {
println()
1
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
val a: Int
get() = <caret>with("") {
println()
1
}
@@ -0,0 +1,8 @@
// PROBLEM: none
// WITH_RUNTIME
fun test(): Int {
return <caret>with ("") {
println()
1
}
}
@@ -0,0 +1,10 @@
// PROBLEM: none
// WITH_RUNTIME
fun foo(i: Int) {}
fun test() {
foo(<caret>with("") {
println()
1
})
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
return@with
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test(): String {
<caret>with("") {
return this@with
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
this@with
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
<caret>with ("") {
this@with.length
}
}
@@ -0,0 +1,12 @@
// PROBLEM: none
// WITH_RUNTIME
fun test() {
val c = MyClass()
<caret>with(c) {
println(f())
}
}
class MyClass {
val f: () -> String = { "" }
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun test() {
<caret>with ("") {
println("1")
println("2")
}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test() {
<caret>println("1")
println("2")
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun test(s: String) {
with<caret> (s, {
println("1")
println("2")
})
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(s: String) {
<caret>println("1")
println("2")
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(): Int {
return <caret>with (1) {
2
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(): Int {
return 2
}
@@ -304,6 +304,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
runTest("idea/testData/inspections/redundantVisibilityModifier/inspectionData/inspections.test");
}
@TestMetadata("redundantWith/inspectionData/inspections.test")
public void testRedundantWith_inspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/redundantWith/inspectionData/inspections.test");
}
@TestMetadata("reformat/inspectionData/inspections.test")
public void testReformat_inspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/reformat/inspectionData/inspections.test");
@@ -3779,6 +3779,119 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/redundantWith")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class RedundantWith extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInRedundantWith() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantWith"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("emptyExpressionInReturn.kt")
public void testEmptyExpressionInReturn() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/emptyExpressionInReturn.kt");
}
@TestMetadata("nested.kt")
public void testNested() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/nested.kt");
}
@TestMetadata("notApplicable_explicitThis.kt")
public void testNotApplicable_explicitThis() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_explicitThis.kt");
}
@TestMetadata("notApplicable_explicitThis2.kt")
public void testNotApplicable_explicitThis2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_explicitThis2.kt");
}
@TestMetadata("notApplicable_functionCall.kt")
public void testNotApplicable_functionCall() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_functionCall.kt");
}
@TestMetadata("notApplicable_implicitThis.kt")
public void testNotApplicable_implicitThis() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_implicitThis.kt");
}
@TestMetadata("notApplicable_inBinaryExpression.kt")
public void testNotApplicable_inBinaryExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inBinaryExpression.kt");
}
@TestMetadata("notApplicable_inFunctionBody.kt")
public void testNotApplicable_inFunctionBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inFunctionBody.kt");
}
@TestMetadata("notApplicable_inProperty.kt")
public void testNotApplicable_inProperty() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inProperty.kt");
}
@TestMetadata("notApplicable_inPropertyGetter.kt")
public void testNotApplicable_inPropertyGetter() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inPropertyGetter.kt");
}
@TestMetadata("notApplicable_inReturn.kt")
public void testNotApplicable_inReturn() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inReturn.kt");
}
@TestMetadata("notApplicable_inValueArgument.kt")
public void testNotApplicable_inValueArgument() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_inValueArgument.kt");
}
@TestMetadata("notApplicable_labeledReturn.kt")
public void testNotApplicable_labeledReturn() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_labeledReturn.kt");
}
@TestMetadata("notApplicable_labeledReturn2.kt")
public void testNotApplicable_labeledReturn2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_labeledReturn2.kt");
}
@TestMetadata("notApplicable_labeledThis.kt")
public void testNotApplicable_labeledThis() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_labeledThis.kt");
}
@TestMetadata("notApplicable_labeledThis2.kt")
public void testNotApplicable_labeledThis2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_labeledThis2.kt");
}
@TestMetadata("notApplicable_variableFunctionCall.kt")
public void testNotApplicable_variableFunctionCall() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/notApplicable_variableFunctionCall.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/simple.kt");
}
@TestMetadata("simple2.kt")
public void testSimple2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/simple2.kt");
}
@TestMetadata("singleExpressionInReturn.kt")
public void testSingleExpressionInReturn() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantWith/singleExpressionInReturn.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/removeRedundantBackticks")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)