Intention&inspection to use synthetic property instead of get/set method call
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection reports calls to java get and set methods that can be replaced with use of Kotlin synthetic properties.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(thread: Thread) {
|
||||||
|
<spot>thread.daemon = true</spot>
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(thread: Thread) {
|
||||||
|
<spot>thread.setDaemon(true)</spot>
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection replaces calls to java get and set methods with use of Kotlin synthetic properties.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -942,6 +942,11 @@
|
|||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention</className>
|
||||||
|
<category>Kotlin</category>
|
||||||
|
</intentionAction>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
||||||
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
||||||
groupName="Kotlin"
|
groupName="Kotlin"
|
||||||
@@ -1026,6 +1031,13 @@
|
|||||||
level="WEAK WARNING"
|
level="WEAK WARNING"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxInspection"
|
||||||
|
displayName="Use property access syntax"
|
||||||
|
groupName="Kotlin"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection"
|
||||||
displayName="Unused Symbol"
|
displayName="Unused Symbol"
|
||||||
groupName="Kotlin"
|
groupName="Kotlin"
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.intentions
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
|
||||||
|
import org.jetbrains.kotlin.idea.core.getResolutionScope
|
||||||
|
import org.jetbrains.kotlin.idea.core.isVisible
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
|
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||||
|
import org.jetbrains.kotlin.renderer.render
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.JetScope
|
||||||
|
import org.jetbrains.kotlin.synthetic.SyntheticExtensionPropertyDescriptor
|
||||||
|
|
||||||
|
class UsePropertyAccessSyntaxInspection : IntentionBasedInspection<JetCallExpression>(UsePropertyAccessSyntaxIntention())
|
||||||
|
|
||||||
|
class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntention<JetCallExpression>(javaClass(), "Use property access syntax") {
|
||||||
|
override fun isApplicableTo(element: JetCallExpression): Boolean {
|
||||||
|
if (element.getQualifiedExpressionForSelector()?.getReceiverExpression() is JetSuperExpression) return false // cannot call extensions on "super"
|
||||||
|
|
||||||
|
return findExtensionPropertyToUse(element) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyTo(element: JetCallExpression, editor: Editor) {
|
||||||
|
val propertyName = findExtensionPropertyToUse(element)!!.getName()
|
||||||
|
val arguments = element.getValueArguments()
|
||||||
|
when (arguments.size()) {
|
||||||
|
0 -> replaceWithPropertyGet(element, propertyName)
|
||||||
|
1 -> replaceWithPropertySet(element, propertyName, arguments.single())
|
||||||
|
else -> error("More than one argument in call to accessor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findExtensionPropertyToUse(callExpression: JetCallExpression): PropertyDescriptor? {
|
||||||
|
val callee = callExpression.getCalleeExpression() as? JetSimpleNameExpression ?: return null
|
||||||
|
|
||||||
|
val bindingContext = callExpression.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return null
|
||||||
|
if (!resolvedCall.getStatus().isSuccess()) return null
|
||||||
|
|
||||||
|
val function = resolvedCall.getResultingDescriptor() as? FunctionDescriptor ?: return null
|
||||||
|
val resolutionScope = callExpression.getResolutionScope(bindingContext, callExpression.getResolutionFacade())
|
||||||
|
val property = findSyntheticProperty(function, resolutionScope) ?: return null
|
||||||
|
|
||||||
|
val moduleDescriptor = callExpression.getResolutionFacade().findModuleDescriptor(callExpression)
|
||||||
|
val inDescriptor = resolutionScope.getContainingDeclaration()
|
||||||
|
|
||||||
|
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
|
||||||
|
return if (descriptor is DeclarationDescriptorWithVisibility)
|
||||||
|
descriptor.isVisible(inDescriptor, bindingContext, callee)
|
||||||
|
else
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val referenceVariantsHelper = ReferenceVariantsHelper(bindingContext, moduleDescriptor, callExpression.getProject(), ::isVisible)
|
||||||
|
val propertyName = property.getName()
|
||||||
|
val accessibleVariables = referenceVariantsHelper.getReferenceVariants(callee, DescriptorKindFilter.VARIABLES, false, { it == propertyName })
|
||||||
|
if (property !in accessibleVariables) return null // shadowed by something else
|
||||||
|
|
||||||
|
return property
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSyntheticProperty(function: FunctionDescriptor, resolutionScope: JetScope): SyntheticExtensionPropertyDescriptor? {
|
||||||
|
findSyntheticPropertyNoOverriddenCheck(function, resolutionScope)?.let { return it }
|
||||||
|
|
||||||
|
for (overridden in function.getOverriddenDescriptors()) {
|
||||||
|
findSyntheticProperty(overridden, resolutionScope)?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSyntheticPropertyNoOverriddenCheck(function: FunctionDescriptor, resolutionScope: JetScope): SyntheticExtensionPropertyDescriptor? {
|
||||||
|
val owner = function.getContainingDeclaration()
|
||||||
|
if (owner !is JavaClassDescriptor) return null
|
||||||
|
|
||||||
|
return resolutionScope.getSyntheticExtensionProperties(owner.getDefaultType())
|
||||||
|
.filterIsInstance<SyntheticExtensionPropertyDescriptor>()
|
||||||
|
.firstOrNull { function == it.getMethod || function == it.setMethod }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceWithPropertyGet(callExpression: JetCallExpression, propertyName: Name) {
|
||||||
|
val newExpression = JetPsiFactory(callExpression).createExpression(propertyName.render())
|
||||||
|
callExpression.replace(newExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: what if it was used as expression (of type Unit)?
|
||||||
|
private fun replaceWithPropertySet(callExpression: JetCallExpression, propertyName: Name, argument: JetValueArgument) {
|
||||||
|
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector()
|
||||||
|
if (qualifiedExpression != null) {
|
||||||
|
val pattern = when (qualifiedExpression) {
|
||||||
|
is JetDotQualifiedExpression -> "$0.$1=$2"
|
||||||
|
is JetSafeQualifiedExpression -> "$0?.$1=$2"
|
||||||
|
else -> error(qualifiedExpression) //TODO: make it sealed?
|
||||||
|
}
|
||||||
|
val newExpression = JetPsiFactory(callExpression).createExpressionByPattern(
|
||||||
|
pattern,
|
||||||
|
qualifiedExpression.getReceiverExpression(),
|
||||||
|
propertyName,
|
||||||
|
argument.getArgumentExpression()!!
|
||||||
|
)
|
||||||
|
qualifiedExpression.replace(newExpression)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val newExpression = JetPsiFactory(callExpression).createExpressionByPattern("$0=$1", propertyName, argument.getArgumentExpression()!!)
|
||||||
|
callExpression.replace(newExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MyFile : File("file")
|
||||||
|
|
||||||
|
fun foo(file: MyFile) {
|
||||||
|
file.getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MyFile : File("file")
|
||||||
|
|
||||||
|
fun foo(file: MyFile) {
|
||||||
|
file.absolutePath<caret>
|
||||||
|
}
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MyFile : File("file") {
|
||||||
|
override fun getCanonicalFile(): File {
|
||||||
|
return super.getCanonicalFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(file: MyFile) {
|
||||||
|
file.getCanonicalFile()<caret>
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MyFile : File("file") {
|
||||||
|
override fun getCanonicalFile(): File {
|
||||||
|
return super.getCanonicalFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(file: MyFile) {
|
||||||
|
file.canonicalFile<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun File.foo(absolutePath: String) {
|
||||||
|
getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
val File.absolutePath: String get() = ""
|
||||||
|
|
||||||
|
fun foo(file: File) {
|
||||||
|
file.getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun foo(file: File) {
|
||||||
|
file.getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun foo(file: File) {
|
||||||
|
file.absolutePath<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun File.foo() {
|
||||||
|
getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun File.foo() {
|
||||||
|
absolutePath<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun foo(file: File?) {
|
||||||
|
file?.getAbsolutePath()<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun foo(file: File?) {
|
||||||
|
file?.absolutePath<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(thread: Thread) {
|
||||||
|
thread.setName("name")<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(thread: Thread) {
|
||||||
|
thread.name = "name"<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun Thread.foo() {
|
||||||
|
setName("name")<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun Thread.foo() {
|
||||||
|
name = "name"<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(thread: Thread?) {
|
||||||
|
thread?.setName("name")<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(thread: Thread?) {
|
||||||
|
thread?.name = "name"<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MyFile : File("file") {
|
||||||
|
override fun getCanonicalFile(): File {
|
||||||
|
return super.getCanonicalFile()<caret>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7382,4 +7382,79 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/intentions/usePropertyAccessSyntax")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class UsePropertyAccessSyntax extends AbstractIntentionTest {
|
||||||
|
@TestMetadata("accessThroughKotlinClassInstance.kt")
|
||||||
|
public void testAccessThroughKotlinClassInstance() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("accessThroughKotlinClassInstanceWithOverride.kt")
|
||||||
|
public void testAccessThroughKotlinClassInstanceWithOverride() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInUsePropertyAccessSyntax() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/usePropertyAccessSyntax"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("conflict1.kt")
|
||||||
|
public void testConflict1() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/conflict1.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("conflict2.kt")
|
||||||
|
public void testConflict2() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/conflict2.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("get.kt")
|
||||||
|
public void testGet() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/get.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("getImplicitReceiver.kt")
|
||||||
|
public void testGetImplicitReceiver() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("getSafeCall.kt")
|
||||||
|
public void testGetSafeCall() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("set.kt")
|
||||||
|
public void testSet() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/set.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("setImplicitReceiver.kt")
|
||||||
|
public void testSetImplicitReceiver() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("setSafeCall.kt")
|
||||||
|
public void testSetSafeCall() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("superCall.kt")
|
||||||
|
public void testSuperCall() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/superCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user