diff --git a/idea/resources/inspectionDescriptions/UsePropertyAccessSyntax.html b/idea/resources/inspectionDescriptions/UsePropertyAccessSyntax.html
new file mode 100644
index 00000000000..718fb306473
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/UsePropertyAccessSyntax.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports calls to java get and set methods that can be replaced with use of Kotlin synthetic properties.
+
+
diff --git a/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/after.kt.template b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/after.kt.template
new file mode 100644
index 00000000000..3351cf05bec
--- /dev/null
+++ b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/after.kt.template
@@ -0,0 +1,3 @@
+fun foo(thread: Thread) {
+ thread.daemon = true
+}
diff --git a/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/before.kt.template b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/before.kt.template
new file mode 100644
index 00000000000..17249916001
--- /dev/null
+++ b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/before.kt.template
@@ -0,0 +1,3 @@
+fun foo(thread: Thread) {
+ thread.setDaemon(true)
+}
diff --git a/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/description.html b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/description.html
new file mode 100644
index 00000000000..cca81724012
--- /dev/null
+++ b/idea/resources/intentionDescriptions/UsePropertyAccessSyntaxIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This inspection replaces calls to java get and set methods with use of Kotlin synthetic properties.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index cc30188b4b7..8894006e50b 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -942,6 +942,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention
+ Kotlin
+
+
+
+
(UsePropertyAccessSyntaxIntention())
+
+class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntention(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()
+ .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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/.intention b/idea/testData/intentions/usePropertyAccessSyntax/.intention
new file mode 100644
index 00000000000..07c59e83a91
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt
new file mode 100644
index 00000000000..18d4bff3b10
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+import java.io.File
+
+class MyFile : File("file")
+
+fun foo(file: MyFile) {
+ file.getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt.after
new file mode 100644
index 00000000000..1581334f1b5
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstance.kt.after
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+import java.io.File
+
+class MyFile : File("file")
+
+fun foo(file: MyFile) {
+ file.absolutePath
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt
new file mode 100644
index 00000000000..dcec7f3b695
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt.after
new file mode 100644
index 00000000000..89f43a93045
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/accessThroughKotlinClassInstanceWithOverride.kt.after
@@ -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
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/conflict1.kt b/idea/testData/intentions/usePropertyAccessSyntax/conflict1.kt
new file mode 100644
index 00000000000..24a31ad2bf1
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/conflict1.kt
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+// IS_APPLICABLE: false
+import java.io.File
+
+fun File.foo(absolutePath: String) {
+ getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/conflict2.kt b/idea/testData/intentions/usePropertyAccessSyntax/conflict2.kt
new file mode 100644
index 00000000000..872ab35fa8f
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/conflict2.kt
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+// IS_APPLICABLE: false
+import java.io.File
+
+val File.absolutePath: String get() = ""
+
+fun foo(file: File) {
+ file.getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/get.kt b/idea/testData/intentions/usePropertyAccessSyntax/get.kt
new file mode 100644
index 00000000000..c675ee0285e
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/get.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun foo(file: File) {
+ file.getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/get.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/get.kt.after
new file mode 100644
index 00000000000..f0bfca56602
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/get.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun foo(file: File) {
+ file.absolutePath
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt b/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt
new file mode 100644
index 00000000000..cccad1ce70e
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun File.foo() {
+ getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt.after
new file mode 100644
index 00000000000..1dba46cb7c2
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/getImplicitReceiver.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun File.foo() {
+ absolutePath
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt b/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt
new file mode 100644
index 00000000000..43cb61ce3f3
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun foo(file: File?) {
+ file?.getAbsolutePath()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt.after
new file mode 100644
index 00000000000..214787cebcb
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/getSafeCall.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+import java.io.File
+
+fun foo(file: File?) {
+ file?.absolutePath
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/set.kt b/idea/testData/intentions/usePropertyAccessSyntax/set.kt
new file mode 100644
index 00000000000..1a5be1d26aa
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/set.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun foo(thread: Thread) {
+ thread.setName("name")
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/set.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/set.kt.after
new file mode 100644
index 00000000000..3a7238cc003
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/set.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun foo(thread: Thread) {
+ thread.name = "name"
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt b/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt
new file mode 100644
index 00000000000..c35a7e6f944
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun Thread.foo() {
+ setName("name")
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt.after
new file mode 100644
index 00000000000..a639d2fcc57
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/setImplicitReceiver.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun Thread.foo() {
+ name = "name"
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt b/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt
new file mode 100644
index 00000000000..2c44ee53f9b
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun foo(thread: Thread?) {
+ thread?.setName("name")
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt.after
new file mode 100644
index 00000000000..15254f25621
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/setSafeCall.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun foo(thread: Thread?) {
+ thread?.name = "name"
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/usePropertyAccessSyntax/superCall.kt b/idea/testData/intentions/usePropertyAccessSyntax/superCall.kt
new file mode 100644
index 00000000000..a28fb98d88a
--- /dev/null
+++ b/idea/testData/intentions/usePropertyAccessSyntax/superCall.kt
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+// IS_APPLICABLE: false
+import java.io.File
+
+class MyFile : File("file") {
+ override fun getCanonicalFile(): File {
+ return super.getCanonicalFile()
+ }
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index f068e1782f7..67130b4b574 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -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);
+ }
+ }
}