Allow removing trivial property accessor body when the accessor can not be fully deleted in redundant getter/setter inspections

This commit is contained in:
Ilya Kirillov
2019-05-29 13:51:08 +03:00
parent 95a6f59c62
commit 8fa9b9923f
14 changed files with 116 additions and 37 deletions
@@ -238,7 +238,7 @@ object PositioningStrategies {
is KtPropertyAccessor -> { is KtPropertyAccessor -> {
val endOfSignatureElement = val endOfSignatureElement =
element.returnTypeReference element.returnTypeReference
?: element.rightParenthesis?.psi ?: element.rightParenthesis
?: element.namePlaceholder ?: element.namePlaceholder
return markRange(element, endOfSignatureElement) return markRange(element, endOfSignatureElement)
@@ -157,8 +157,13 @@ public class KtPropertyAccessor extends KtDeclarationStub<KotlinPropertyAccessor
} }
@Nullable @Nullable
public ASTNode getRightParenthesis() { public PsiElement getRightParenthesis() {
return getNode().findChildByType(KtTokens.RPAR); return findChildByType(KtTokens.RPAR);
}
@Nullable
public PsiElement getLeftParenthesis() {
return findChildByType(KtTokens.LPAR);
} }
@Nullable @Nullable
@@ -41,7 +41,7 @@ class KotlinPropertySetterParametersFixer : SmartEnterProcessorWithFixers.Fixer<
val doc = editor.document val doc = editor.document
val parameterOffset = (psiElement.leftParenthesis?.startOffset ?: return) + 1 val parameterOffset = (psiElement.leftParenthesis?.node?.startOffset ?: return) + 1
if (parameter?.text.isNullOrBlank()) { if (parameter?.text.isNullOrBlank()) {
if (psiElement.rightParenthesis == null) { if (psiElement.rightParenthesis == null) {
@@ -10,7 +10,9 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -31,21 +33,33 @@ class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
private fun KtPropertyAccessor.isRedundantGetter(): Boolean { private fun KtPropertyAccessor.isRedundantGetter(): Boolean {
if (!isGetter) return false if (!isGetter) return false
if (hasModifier(KtTokens.EXTERNAL_KEYWORD)) return false val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (annotationEntries.isNotEmpty()) return false if (expression.isBackingFieldReferenceTo(property)) return true
val expression = bodyExpression ?: return true
if (expression is KtNameReferenceExpression) {
return expression.isFieldText()
}
if (expression is KtBlockExpression) { if (expression is KtBlockExpression) {
val statement = expression.statements.takeIf { it.size == 1 }?.firstOrNull() ?: return false val statement = expression.statements.singleOrNull() ?: return false
val returnExpression = statement as? KtReturnExpression ?: return false val returnExpression = statement as? KtReturnExpression ?: return false
return returnExpression.returnedExpression?.isFieldText() == true return returnExpression.returnedExpression?.isBackingFieldReferenceTo(property) == true
} }
return false return false
} }
private fun KtExpression.isFieldText(): Boolean = this.textMatches("field") fun KtExpression.isBackingFieldReferenceTo(property: KtProperty) =
this is KtNameReferenceExpression
&& text == KtTokens.FIELD_KEYWORD.value
&& property.isAncestor(this)
fun KtPropertyAccessor.canBeCompletelyDeleted(): Boolean {
if (modifierList == null) return true
if (annotationEntries.isNotEmpty()) return false
if (hasModifier(KtTokens.EXTERNAL_KEYWORD)) return false
return visibilityModifierTypeOrDefault() == property.visibilityModifierTypeOrDefault()
}
fun KtPropertyAccessor.deleteBody() {
val leftParenthesis = leftParenthesis ?: return
deleteChildRange(leftParenthesis, lastChild)
}
private class RemoveRedundantGetterFix : LocalQuickFix { private class RemoveRedundantGetterFix : LocalQuickFix {
override fun getName() = "Remove redundant getter" override fun getName() = "Remove redundant getter"
@@ -60,7 +74,10 @@ private class RemoveRedundantGetterFix : LocalQuickFix {
if (accessorTypeReference != null && property.typeReference == null && property.initializer == null) { if (accessorTypeReference != null && property.typeReference == null && property.initializer == null) {
property.typeReference = accessorTypeReference property.typeReference = accessorTypeReference
} }
if (accessor.canBeCompletelyDeleted()) {
accessor.delete() accessor.delete()
} else {
accessor.deleteBody()
}
} }
} }
@@ -32,35 +32,18 @@ class RedundantSetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
private fun KtPropertyAccessor.isRedundantSetter(): Boolean { private fun KtPropertyAccessor.isRedundantSetter(): Boolean {
if (!isSetter) return false if (!isSetter) return false
if (annotationEntries.isNotEmpty()) return false val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (hasLowerVisibilityThanProperty()) return false
val expression = bodyExpression ?: return true
if (expression is KtBlockExpression) { if (expression is KtBlockExpression) {
val statement = expression.statements.takeIf { it.size == 1 }?.firstOrNull() ?: return false val statement = expression.statements.singleOrNull() ?: return false
val parameter = valueParameters.takeIf { it.size == 1 }?.firstOrNull() ?: return false val parameter = valueParameters.singleOrNull() ?: return false
val binaryExpression = statement as? KtBinaryExpression ?: return false val binaryExpression = statement as? KtBinaryExpression ?: return false
return binaryExpression.operationToken == KtTokens.EQ return binaryExpression.operationToken == KtTokens.EQ
&& binaryExpression.left?.isFieldText() == true && binaryExpression.left?.isBackingFieldReferenceTo(property) == true
&& binaryExpression.right?.mainReference?.resolve() == parameter && binaryExpression.right?.mainReference?.resolve() == parameter
} }
return false return false
} }
private fun KtPropertyAccessor.hasLowerVisibilityThanProperty(): Boolean {
val p = property
return when {
p.hasModifier(KtTokens.PRIVATE_KEYWORD) ->
false
p.hasModifier(KtTokens.PROTECTED_KEYWORD) ->
hasModifier(KtTokens.PRIVATE_KEYWORD)
p.hasModifier(KtTokens.INTERNAL_KEYWORD) ->
hasModifier(KtTokens.PRIVATE_KEYWORD) || hasModifier(KtTokens.PROTECTED_KEYWORD)
else ->
hasModifier(KtTokens.PRIVATE_KEYWORD) || hasModifier(KtTokens.PROTECTED_KEYWORD) || hasModifier(KtTokens.INTERNAL_KEYWORD)
}
}
private fun KtExpression.isFieldText(): Boolean = this.textMatches("field")
private class RemoveRedundantSetterFix : LocalQuickFix { private class RemoveRedundantSetterFix : LocalQuickFix {
override fun getName() = "Remove redundant setter" override fun getName() = "Remove redundant setter"
@@ -69,6 +52,11 @@ private class RemoveRedundantSetterFix : LocalQuickFix {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return
accessor.delete()
if (accessor.canBeCompletelyDeleted()) {
accessor.delete()
} else {
accessor.deleteBody()
}
} }
} }
@@ -0,0 +1,8 @@
// PROBLEM: none
class Foo {
val foo: String = ""
@Deprecated("") <caret>get() {
1 + 2
return field
}
}
@@ -0,0 +1,6 @@
class Foo {
val foo: String = ""
@Deprecated("") <caret>get() {
return field
}
}
@@ -0,0 +1,4 @@
class Foo {
val foo: String = ""
@Deprecated("") <caret>get
}
@@ -0,0 +1,4 @@
class Foo {
val foo: String = ""
@Deprecated("") <caret>get() = field
}
@@ -0,0 +1,4 @@
class Foo {
val foo: String = ""
@Deprecated("") get
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class Foo {
var foo: String = ""
@Deprecated("") <caret>set(foo) {
1 + 2
field = foo
}
}
@@ -0,0 +1,6 @@
class Foo {
var foo: String = ""
@Deprecated("") <caret>set(x) {
field = x
}
}
@@ -0,0 +1,4 @@
class Foo {
var foo: String = ""
@Deprecated("") set
}
@@ -5741,6 +5741,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testOnlyReturnFieldBody() throws Exception { public void testOnlyReturnFieldBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantGetter/onlyReturnFieldBody.kt"); runTest("idea/testData/inspectionsLocal/redundantGetter/onlyReturnFieldBody.kt");
} }
@TestMetadata("withAnnotaionAndNonTrivialBlockBody.kt")
public void testWithAnnotaionAndNonTrivialBlockBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantGetter/withAnnotaionAndNonTrivialBlockBody.kt");
}
@TestMetadata("withAnnotaionAndTrivialBlockBody.kt")
public void testWithAnnotaionAndTrivialBlockBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantGetter/withAnnotaionAndTrivialBlockBody.kt");
}
@TestMetadata("withAnnotaionAndTrivialExpressionBody.kt")
public void testWithAnnotaionAndTrivialExpressionBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantGetter/withAnnotaionAndTrivialExpressionBody.kt");
}
} }
@TestMetadata("idea/testData/inspectionsLocal/redundantLambdaArrow") @TestMetadata("idea/testData/inspectionsLocal/redundantLambdaArrow")
@@ -6370,6 +6385,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testSameVisibility3() throws Exception { public void testSameVisibility3() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantSetter/sameVisibility3.kt"); runTest("idea/testData/inspectionsLocal/redundantSetter/sameVisibility3.kt");
} }
@TestMetadata("withAnnotaionAndNonTrivialBlockBody.kt")
public void testWithAnnotaionAndNonTrivialBlockBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantSetter/withAnnotaionAndNonTrivialBlockBody.kt");
}
@TestMetadata("withAnnotaionAndTrivialBlockBody.kt")
public void testWithAnnotaionAndTrivialBlockBody() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantSetter/withAnnotaionAndTrivialBlockBody.kt");
}
} }
@TestMetadata("idea/testData/inspectionsLocal/redundantSuspend") @TestMetadata("idea/testData/inspectionsLocal/redundantSuspend")