Intention&inspection to use synthetic property instead of get/set method call

This commit is contained in:
Valentin Kipyatkov
2015-07-08 23:12:44 +03:00
parent a014f5c8db
commit bfdc74ce74
27 changed files with 365 additions and 0 deletions
@@ -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>
@@ -0,0 +1,3 @@
fun foo(thread: Thread) {
<spot>thread.daemon = true</spot>
}
@@ -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>
+12
View File
@@ -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
@@ -0,0 +1,8 @@
// WITH_RUNTIME
import java.io.File
class MyFile : File("file")
fun foo(file: MyFile) {
file.getAbsolutePath()<caret>
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
import java.io.File
class MyFile : File("file")
fun foo(file: MyFile) {
file.absolutePath<caret>
}
@@ -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>
}
@@ -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);
}
}
}