mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Make UsePropertyAccessSyntax to report references to Java getters
^KTIJ-23892 Fixed Review: https://jetbrains.team/p/ij/reviews/100206 I can afford to use `FrontendInternals` API because this API is already used in this intention GitOrigin-RevId: 5a24ea37951f679a01bc134a88b69313ca868b3a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
549b6c9d85
commit
615a2fbbc1
+82
-29
@@ -10,20 +10,23 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.profile.codeInspection.InspectionProjectProfileManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.idea.FrontendInternals
|
||||
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.base.psi.copied
|
||||
import org.jetbrains.kotlin.idea.base.psi.replaced
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.IntentionBasedInspection
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingOffsetIndependentIntention
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.NotPropertyListPanel
|
||||
import org.jetbrains.kotlin.idea.core.NotPropertiesService
|
||||
import org.jetbrains.kotlin.idea.base.psi.copied
|
||||
import org.jetbrains.kotlin.idea.base.psi.replaced
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingOffsetIndependentIntention
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.IntentionBasedInspection
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.resolve.dataFlowValueFactory
|
||||
import org.jetbrains.kotlin.idea.resolve.frontendService
|
||||
@@ -63,7 +66,7 @@ import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
|
||||
import javax.swing.JComponent
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class UsePropertyAccessSyntaxInspection : IntentionBasedInspection<KtCallExpression>(UsePropertyAccessSyntaxIntention::class),
|
||||
class UsePropertyAccessSyntaxInspection : IntentionBasedInspection<KtExpression>(UsePropertyAccessSyntaxIntention::class),
|
||||
CleanupLocalInspectionTool {
|
||||
|
||||
val fqNameList = NotPropertiesService.DEFAULT.map(::FqNameUnsafe).toMutableList()
|
||||
@@ -89,17 +92,22 @@ class UsePropertyAccessSyntaxInspection : IntentionBasedInspection<KtCallExpress
|
||||
return LabeledComponent.create(list, KotlinBundle.message("excluded.methods"))
|
||||
}
|
||||
|
||||
override fun inspectionTarget(element: KtCallExpression): PsiElement? {
|
||||
return element.calleeExpression
|
||||
}
|
||||
override fun inspectionTarget(element: KtExpression): PsiElement? =
|
||||
element.callOrReferenceOrNull(KtCallExpression::getCalleeExpression, KtCallableReferenceExpression::getCallableReference)
|
||||
|
||||
override fun inspectionProblemText(element: KtCallExpression): String {
|
||||
return when (element.valueArguments.size) {
|
||||
0 -> KotlinBundle.message("use.of.getter.method.instead.of.property.access.syntax")
|
||||
1 -> KotlinBundle.message("use.of.setter.method.instead.of.property.access.syntax")
|
||||
else -> error("getter or setter arg length can't be !in 0..1")
|
||||
}
|
||||
}
|
||||
override fun inspectionProblemText(element: KtExpression): String? =
|
||||
element.callOrReferenceOrNull(
|
||||
{
|
||||
when (it.valueArguments.size) {
|
||||
0 -> KotlinBundle.message("use.of.getter.method.instead.of.property.access.syntax")
|
||||
1 -> KotlinBundle.message("use.of.setter.method.instead.of.property.access.syntax")
|
||||
else -> error("getter or setter arg length can't be !in 0..1")
|
||||
}
|
||||
},
|
||||
{
|
||||
KotlinBundle.message("use.of.getter.method.instead.of.property.access.syntax")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class NotPropertiesServiceImpl(private val project: Project) : NotPropertiesService {
|
||||
@@ -114,26 +122,61 @@ class NotPropertiesServiceImpl(private val project: Project) : NotPropertiesServ
|
||||
}
|
||||
}
|
||||
|
||||
class UsePropertyAccessSyntaxIntention : SelfTargetingOffsetIndependentIntention<KtCallExpression>(
|
||||
KtCallExpression::class.java,
|
||||
/**
|
||||
* Affected tests:
|
||||
* [org.jetbrains.kotlin.idea.intentions.K1IntentionTestGenerated.UsePropertyAccessSyntax]
|
||||
* [org.jetbrains.kotlin.idea.inspections.LocalInspectionTestGenerated.UsePropertyAccessSyntax]
|
||||
* [org.jetbrains.kotlin.idea.inspections.MultiFileLocalInspectionTestGenerated]
|
||||
*/
|
||||
class UsePropertyAccessSyntaxIntention : SelfTargetingOffsetIndependentIntention<KtExpression>(
|
||||
KtExpression::class.java,
|
||||
KotlinBundle.lazyMessage("use.property.access.syntax")
|
||||
) {
|
||||
override fun isApplicableTo(element: KtCallExpression): Boolean = detectPropertyNameToUse(element) != null
|
||||
override fun isApplicableTo(element: KtExpression): Boolean =
|
||||
element.callOrReferenceOrNull(::detectPropertyNameToUseForCall, ::detectPropertyNameToUseForReference) != null
|
||||
|
||||
override fun applyTo(element: KtCallExpression, editor: Editor?) {
|
||||
val propertyName = detectPropertyNameToUse(element) ?: return
|
||||
override fun applyTo(element: KtExpression, editor: Editor?) {
|
||||
val propertyName = element.callOrReferenceOrNull(::detectPropertyNameToUseForCall, ::detectPropertyNameToUseForReference) ?: return
|
||||
runWriteActionIfPhysical(element) {
|
||||
applyTo(element, propertyName, reformat = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyTo(element: KtCallExpression, propertyName: Name, reformat: Boolean): KtExpression = when (element.valueArguments.size) {
|
||||
0 -> replaceWithPropertyGet(element, propertyName)
|
||||
1 -> replaceWithPropertySet(element, propertyName, reformat)
|
||||
else -> error("More than one argument in call to accessor")
|
||||
fun applyTo(element: KtExpression, propertyName: Name, reformat: Boolean): KtExpression =
|
||||
element.callOrReferenceOrNull(
|
||||
{
|
||||
when (it.valueArguments.size) {
|
||||
0 -> replaceWithPropertyGet(it, propertyName)
|
||||
1 -> replaceWithPropertySet(it, propertyName, reformat)
|
||||
else -> error("More than one argument in call to accessor")
|
||||
}
|
||||
},
|
||||
{
|
||||
replaceWithPropertyGet(it.callableReference, propertyName)
|
||||
}
|
||||
) ?: error("Can't parse $element (${element::class})")
|
||||
|
||||
private fun detectPropertyNameToUseForReference(referenceExpression: KtCallableReferenceExpression): Name? {
|
||||
if (!referenceExpression.languageVersionSettings.supportsFeature(LanguageFeature.ReferencesToSyntheticJavaProperties)) {
|
||||
return null
|
||||
}
|
||||
if (!referenceExpression.callableReference.getReferencedName().startsWith("get")) {
|
||||
// Suggest to convert only getters. Keep setters and is-getters untouched
|
||||
// Don't suggest replacing setters because setters and property references have different types
|
||||
// Don't suggest replacing is-getters because is-getter method reference and is-getter property references have the same syntax
|
||||
return null
|
||||
}
|
||||
|
||||
return referenceExpression.callableReference.resolveToCall()?.resultingDescriptor
|
||||
?.let { it as? FunctionDescriptor }
|
||||
?.let {
|
||||
@OptIn(FrontendInternals::class)
|
||||
findSyntheticProperty(it, referenceExpression.getResolutionFacade().getFrontendService(SyntheticScopes::class.java))
|
||||
}
|
||||
?.name
|
||||
}
|
||||
|
||||
fun detectPropertyNameToUse(callExpression: KtCallExpression): Name? {
|
||||
fun detectPropertyNameToUseForCall(callExpression: KtCallExpression): Name? {
|
||||
if (callExpression.getQualifiedExpressionForSelector()
|
||||
?.receiverExpression is KtSuperExpression
|
||||
) return null // cannot call extensions on "super"
|
||||
@@ -259,9 +302,9 @@ class UsePropertyAccessSyntaxIntention : SelfTargetingOffsetIndependentIntention
|
||||
return null
|
||||
}
|
||||
|
||||
private fun replaceWithPropertyGet(callExpression: KtCallExpression, propertyName: Name): KtExpression {
|
||||
val newExpression = KtPsiFactory(callExpression.project).createExpression(propertyName.render())
|
||||
return callExpression.replaced(newExpression)
|
||||
private fun replaceWithPropertyGet(oldElement: KtElement, propertyName: Name): KtExpression {
|
||||
val newExpression = KtPsiFactory(oldElement.project).createExpression(propertyName.render())
|
||||
return oldElement.replaced(newExpression)
|
||||
}
|
||||
|
||||
private fun KtCallExpression.convertExpressionBodyToBlockBodyIfPossible(): KtCallExpression {
|
||||
@@ -313,4 +356,14 @@ private val commonGetterLikePrefixes: Set<Regex> = setOf(
|
||||
"^getOr[A-Z]".toRegex(),
|
||||
"^getAnd[A-Z]".toRegex(),
|
||||
"^getIf[A-Z]".toRegex(),
|
||||
)
|
||||
)
|
||||
|
||||
private inline fun <T> KtExpression.callOrReferenceOrNull(
|
||||
call: (KtCallExpression) -> T,
|
||||
reference: (KtCallableReferenceExpression) -> T
|
||||
): T? =
|
||||
when {
|
||||
this is KtCallExpression -> call(this)
|
||||
this is KtSimpleNameExpression && parent is KtCallableReferenceExpression -> reference(parent as KtCallableReferenceExpression)
|
||||
else -> null
|
||||
}
|
||||
|
||||
+25
@@ -17707,6 +17707,31 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("referenceGetter0.kt")
|
||||
public void testReferenceGetter0() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/referenceGetter0.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("referenceGetter1.kt")
|
||||
public void testReferenceGetter1() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/referenceGetter1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("referenceGetterOldLv.kt")
|
||||
public void testReferenceGetterOldLv() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/referenceGetterOldLv.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("referenceIsGetter.kt")
|
||||
public void testReferenceIsGetter() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/referenceIsGetter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("referenceSetter.kt")
|
||||
public void testReferenceSetter() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/referenceSetter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("set.kt")
|
||||
public void testSet() throws Exception {
|
||||
runTest("testData/intentions/usePropertyAccessSyntax/set.kt");
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
fun main() {
|
||||
suppressUnused(Foo()::<caret>getFoo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: () -> Int): Any = foo
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
fun main() {
|
||||
suppressUnused(Foo()::foo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: () -> Int): Any = foo
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
fun main() {
|
||||
suppressUnused(Foo::<caret>getFoo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: (Foo) -> Int): Any = foo
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
fun main() {
|
||||
suppressUnused(Foo::foo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: (Foo) -> Int): Any = foo
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:-ReferencesToSyntheticJavaProperties
|
||||
// IS_APPLICABLE: false
|
||||
fun main() {
|
||||
suppressUnused(Foo::<caret>getFoo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: (Foo) -> Int): Any = foo
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Foo {
|
||||
public boolean isFoo() {return true;}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
// IS_APPLICABLE: false
|
||||
fun main() {
|
||||
suppressUnused(Foo::<caret>isFoo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: (Foo) -> Boolean): Any = foo
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
public class Foo {
|
||||
public int getFoo() {return 1;}
|
||||
public void setFoo(int foo) {}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+ReferencesToSyntheticJavaProperties
|
||||
// IS_APPLICABLE: false
|
||||
fun main() {
|
||||
suppressUnused(Foo::<caret>setFoo)
|
||||
}
|
||||
|
||||
fun suppressUnused(foo: (Foo, Int) -> Unit): Any = foo
|
||||
+1
-1
@@ -274,7 +274,7 @@ internal class J2KPostProcessingRegistrarImpl : J2KPostProcessingRegistrar {
|
||||
|
||||
override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? {
|
||||
if (element !is KtCallExpression) return null
|
||||
val propertyName = intention.detectPropertyNameToUse(element) ?: return null
|
||||
val propertyName = intention.detectPropertyNameToUseForCall(element) ?: return null
|
||||
return { intention.applyTo(element, propertyName, reformat = true) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user