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 -> {
val endOfSignatureElement =
element.returnTypeReference
?: element.rightParenthesis?.psi
?: element.rightParenthesis
?: element.namePlaceholder
return markRange(element, endOfSignatureElement)
@@ -157,8 +157,13 @@ public class KtPropertyAccessor extends KtDeclarationStub<KotlinPropertyAccessor
}
@Nullable
public ASTNode getRightParenthesis() {
return getNode().findChildByType(KtTokens.RPAR);
public PsiElement getRightParenthesis() {
return findChildByType(KtTokens.RPAR);
}
@Nullable
public PsiElement getLeftParenthesis() {
return findChildByType(KtTokens.LPAR);
}
@Nullable
@@ -41,7 +41,7 @@ class KotlinPropertySetterParametersFixer : SmartEnterProcessorWithFixers.Fixer<
val doc = editor.document
val parameterOffset = (psiElement.leftParenthesis?.startOffset ?: return) + 1
val parameterOffset = (psiElement.leftParenthesis?.node?.startOffset ?: return) + 1
if (parameter?.text.isNullOrBlank()) {
if (psiElement.rightParenthesis == null) {
@@ -10,7 +10,9 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.lexer.KtTokens
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.visibilityModifierTypeOrDefault
class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -31,21 +33,33 @@ class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
private fun KtPropertyAccessor.isRedundantGetter(): Boolean {
if (!isGetter) return false
if (hasModifier(KtTokens.EXTERNAL_KEYWORD)) return false
if (annotationEntries.isNotEmpty()) return false
val expression = bodyExpression ?: return true
if (expression is KtNameReferenceExpression) {
return expression.isFieldText()
}
val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (expression.isBackingFieldReferenceTo(property)) return true
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
return returnExpression.returnedExpression?.isFieldText() == true
return returnExpression.returnedExpression?.isBackingFieldReferenceTo(property) == true
}
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 {
override fun getName() = "Remove redundant getter"
@@ -60,7 +74,10 @@ private class RemoveRedundantGetterFix : LocalQuickFix {
if (accessorTypeReference != null && property.typeReference == null && property.initializer == null) {
property.typeReference = accessorTypeReference
}
accessor.delete()
if (accessor.canBeCompletelyDeleted()) {
accessor.delete()
} else {
accessor.deleteBody()
}
}
}
@@ -32,35 +32,18 @@ class RedundantSetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
private fun KtPropertyAccessor.isRedundantSetter(): Boolean {
if (!isSetter) return false
if (annotationEntries.isNotEmpty()) return false
if (hasLowerVisibilityThanProperty()) return false
val expression = bodyExpression ?: return true
val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (expression is KtBlockExpression) {
val statement = expression.statements.takeIf { it.size == 1 }?.firstOrNull() ?: return false
val parameter = valueParameters.takeIf { it.size == 1 }?.firstOrNull() ?: return false
val statement = expression.statements.singleOrNull() ?: return false
val parameter = valueParameters.singleOrNull() ?: return false
val binaryExpression = statement as? KtBinaryExpression ?: return false
return binaryExpression.operationToken == KtTokens.EQ
&& binaryExpression.left?.isFieldText() == true
&& binaryExpression.left?.isBackingFieldReferenceTo(property) == true
&& binaryExpression.right?.mainReference?.resolve() == parameter
}
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 {
override fun getName() = "Remove redundant setter"
@@ -69,6 +52,11 @@ private class RemoveRedundantSetterFix : LocalQuickFix {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
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 {
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")
@@ -6370,6 +6385,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testSameVisibility3() throws Exception {
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")