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>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
||||
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
||||
groupName="Kotlin"
|
||||
@@ -1026,6 +1031,13 @@
|
||||
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"
|
||||
displayName="Unused Symbol"
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@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