Add intentions to convert lazy <--> ordinary property

#KT-23501 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-04-17 10:02:02 +03:00
committed by Mikhail Glukhikh
parent a2205cfc98
commit fbd992f8c7
55 changed files with 280 additions and 0 deletions
@@ -278,6 +278,14 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
bodyExpression.replace(expression)
return setter
}
fun createPropertyDelegate(expression: KtExpression): KtPropertyDelegate {
val property = createProperty("val x by lazy { 1 }")
val delegate = property.delegate!!
val delegateExpression = delegate.expression!!
delegateExpression.replace(expression)
return delegate
}
fun createDestructuringDeclaration(text: String): KtDestructuringDeclaration {
return createFunction("fun foo() {$text}").bodyBlockExpression!!.statements.first() as KtDestructuringDeclaration
+10
View File
@@ -1663,6 +1663,16 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertOrdinaryPropertyToLazyIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertLazyPropertyToOrdinaryIntention</className>
<category>Kotlin</category>
</intentionAction>
<lang.inspectionSuppressor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinInspectionSuppressor"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
@@ -0,0 +1 @@
val x: String = "Hello"
@@ -0,0 +1 @@
val x: String by lazy { "Hello" }
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts a lazy property / variable to an ordinary property / variable.
</body>
</html>
@@ -0,0 +1 @@
val x: String by lazy { "Hello" }
@@ -0,0 +1 @@
val x: String = "Hello"
@@ -0,0 +1,6 @@
<html>
<body>
This intention converts a ordinary property / variable to a lazy property / variable.
</body>
</html>
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiComment
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
class ConvertLazyPropertyToOrdinaryIntention : SelfTargetingIntention<KtProperty>(
KtProperty::class.java, "Convert to ordinary property"
) {
override fun isApplicableTo(element: KtProperty, caretOffset: Int): Boolean {
val delegateExpression = element.delegate?.expression as? KtCallExpression ?: return false
val lambdaBody = delegateExpression.functionLiteral()?.bodyExpression ?: return false
if (lambdaBody.statements.isEmpty()) return false
return delegateExpression.isCalling(FqName("kotlin.lazy"))
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val delegate = element.delegate ?: return
val delegateExpression = delegate.expression as? KtCallExpression ?: return
val functionLiteral = delegateExpression.functionLiteral() ?: return
element.initializer = functionLiteral.singleStatement() ?: KtPsiFactory(element).createExpression("run ${functionLiteral.text}")
delegate.delete()
}
private fun KtCallExpression.functionLiteral(): KtFunctionLiteral? {
return lambdaArguments.singleOrNull()?.getLambdaExpression()?.functionLiteral
}
private fun KtFunctionLiteral.singleStatement(): KtExpression? {
val body = this.bodyExpression ?: return null
if (body.allChildren.any { it is PsiComment }) return null
return body.statements.singleOrNull()
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
class ConvertOrdinaryPropertyToLazyIntention : SelfTargetingIntention<KtProperty>(
KtProperty::class.java, "Convert to lazy property"
) {
override fun isApplicableTo(element: KtProperty, caretOffset: Int): Boolean {
return !element.isVar && element.initializer != null && element.getter == null && !element.isLocal
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val initializer = element.initializer ?: return
val psiFactory = KtPsiFactory(element)
val newExpression = if (initializer is KtCallExpression && initializer.isCalling(FqName("kotlin.run"))) {
initializer.calleeExpression?.replace(psiFactory.createExpression("lazy"))
initializer
} else {
psiFactory.createExpressionByPattern("lazy { $0 }", initializer)
}
element.addAfter(psiFactory.createPropertyDelegate(newExpression), initializer)
element.initializer = null
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertLazyPropertyToOrdinaryIntention
@@ -0,0 +1,5 @@
// WITH_RUNTIME
<caret>val x by lazy {
// comment
1
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val x = run {
// comment
1
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
<caret>val x by lazy {
foo()
1
}
fun foo() {}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
val x = run {
foo()
1
}
fun foo() {}
@@ -0,0 +1,3 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
<caret>val x by lazy { }
@@ -0,0 +1,2 @@
// WITH_RUNTIME
<caret>val x: Int by lazy { 1 }
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val x: Int = 1
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val x by lazy {
1
}<caret>
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val x = 1
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
// WITH_RUNTIME
<caret>var x: Int by lazy { 1 }
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
// WITH_RUNTIME
var x: Int = 1
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertOrdinaryPropertyToLazyIntention
@@ -0,0 +1,2 @@
// WITH_RUNTIME
<caret>val x: Int = 1
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val x: Int by lazy { 1 }
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
class Foo {
lateinit <caret>var x: String
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
val x = run {
foo()
3
}<caret>
fun foo() {}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
val x by lazy {
foo()
3
}
fun foo() {}
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
<caret>var x: Int = 1
@@ -1,5 +1,6 @@
// "Add annotation target" "false"
// WITH_RUNTIME
// ACTION: Convert to ordinary property
// ACTION: Introduce import alias
// ACTION: Make internal
// ACTION: Make private
@@ -1,5 +1,6 @@
// "Add annotation target" "false"
// WITH_RUNTIME
// ACTION: Convert to ordinary property
// ACTION: Introduce import alias
// ACTION: Make internal
// ACTION: Make private
+1
View File
@@ -1,4 +1,5 @@
// "Add non-null asserted (!!) call" "false"
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Add 'toString()' call
// ACTION: Change type of 'x' to 'String?'
@@ -1,4 +1,5 @@
// "class org.jetbrains.kotlin.idea.quickfix.ImportFix" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create class 'SomeTest'
// ACTION: Rename reference
@@ -1,6 +1,7 @@
// "class org.jetbrains.kotlin.idea.quickfix.ImportFix" "false"
// ERROR: Unresolved reference: SomeTest
// ERROR: Expression expected, but a package name found
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create class 'SomeTest'
// ACTION: Replace safe access expression with 'if' expression
@@ -1,4 +1,5 @@
// "class org.jetbrains.kotlin.idea.quickfix.ImportFix" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create class 'SomeClass'
// ACTION: Create function 'SomeClass'
@@ -1,4 +1,5 @@
// "class org.jetbrains.kotlin.idea.quickfix.ImportFix" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create class 'ExcludedClass'
// ACTION: Create function 'ExcludedClass'
@@ -1,4 +1,5 @@
// "class org.jetbrains.kotlin.idea.quickfix.ImportFix" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create function 'someFunction'
// ACTION: Rename reference
@@ -1,4 +1,5 @@
// "Create local variable 'foo'" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create parameter 'foo'
// ACTION: Create property 'foo'
@@ -1,4 +1,5 @@
// "Create local variable 'foo'" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create property 'foo'
// ACTION: Rename reference
@@ -1,4 +1,5 @@
// "Create parameter 'foo'" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create property 'foo'
// ACTION: Rename reference
@@ -1,4 +1,5 @@
// "Create parameter 'foo'" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create property 'foo'
// ACTION: Rename reference
@@ -1,4 +1,5 @@
// "Create parameter 'foo'" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Create property 'foo'
// ACTION: Rename reference
@@ -1,4 +1,5 @@
// "Make bar internal" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Add names to call arguments
// ACTION: Move to constructor
@@ -1,4 +1,5 @@
// "Make 'MyClass' open" "false"
// ACTION: Convert to lazy property
// ACTION: Make 'y' not open
// ACTION: Add 'const' modifier
// ACTION: Make internal
+1
View File
@@ -1,4 +1,5 @@
// "Make bar suspend" "false"
// ACTION: Convert to lazy property
// ACTION: Convert property initializer to getter
// ACTION: Introduce import alias
// ERROR: Suspend function 'foo' should be called only from a coroutine or another suspend function
@@ -1,6 +1,7 @@
// "Replace '@JvmField' with 'const'" "false"
// WITH_RUNTIME
// ERROR: JvmField has no effect on a private property
// ACTION: Convert to lazy property
// ACTION: Make internal
// ACTION: Make protected
// ACTION: Make public
@@ -1,6 +1,7 @@
// "Replace '@JvmField' with 'const'" "false"
// WITH_RUNTIME
// ERROR: JvmField has no effect on a private property
// ACTION: Convert to lazy property
// ACTION: Make internal
// ACTION: Make public
// ACTION: Remove explicit type specification
@@ -1,6 +1,7 @@
// "Replace '@JvmField' with 'const'" "false"
// WITH_RUNTIME
// ERROR: JvmField has no effect on a private property
// ACTION: Convert to lazy property
// ACTION: Make internal
// ACTION: Make public
// ACTION: Remove explicit type specification
@@ -1,6 +1,7 @@
// "Replace '@JvmField' with 'const'" "false"
// WITH_RUNTIME
// ERROR: JvmField has no effect on a private property
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Make internal
// ACTION: Make public
@@ -1,5 +1,6 @@
// "Typo: Change to..." "false"
// TOOL: com.intellij.spellchecker.inspections.SpellCheckingInspection
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Convert property initializer to getter
// ACTION: To raw string literal
+1
View File
@@ -1,6 +1,7 @@
// "Cast expression '1' to 'UInt'" "false"
// WITH_RUNTIME
// ERROR: Conversion of signed constants to unsigned ones is prohibited
// ACTION: Convert to lazy property
// ACTION: Change parameter 'u' type of function 'takeUInt' to 'Int'
// ACTION: Convert property initializer to getter
// ACTION: Change to '1u'
@@ -1,4 +1,5 @@
// "Change to '1'" "false"
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Change type of 'a' to 'Double'
// ACTION: Convert expression to 'Int'
@@ -1,4 +1,5 @@
// "Change to '10000000000000000000L'" "false"
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Change type of 'a' to 'Double'
// ACTION: Convert expression to 'Long'
@@ -1,4 +1,5 @@
// "Change to '65000'" "false"
// ACTION: Convert to lazy property
// ACTION: Add 'const' modifier
// ACTION: Change type of 'a' to 'Double'
// ACTION: Convert expression to 'Short'
@@ -5049,6 +5049,49 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertLazyPropertyToOrdinary")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertLazyPropertyToOrdinary extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInConvertLazyPropertyToOrdinary() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertLazyPropertyToOrdinary"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("comment.kt")
public void testComment() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/comment.kt");
}
@TestMetadata("multiStatement.kt")
public void testMultiStatement() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/multiStatement.kt");
}
@TestMetadata("noStatement.kt")
public void testNoStatement() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/noStatement.kt");
}
@TestMetadata("singleStatement.kt")
public void testSingleStatement() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/singleStatement.kt");
}
@TestMetadata("singleStatement2.kt")
public void testSingleStatement2() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/singleStatement2.kt");
}
@TestMetadata("var.kt")
public void testVar() throws Exception {
runTest("idea/testData/intentions/convertLazyPropertyToOrdinary/var.kt");
}
}
@TestMetadata("idea/testData/intentions/convertLineCommentToBlockComment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -5248,6 +5291,39 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertOrdinaryPropertyToLazy")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertOrdinaryPropertyToLazy extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInConvertOrdinaryPropertyToLazy() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertOrdinaryPropertyToLazy"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("basic.kt")
public void testBasic() throws Exception {
runTest("idea/testData/intentions/convertOrdinaryPropertyToLazy/basic.kt");
}
@TestMetadata("noInitializer.kt")
public void testNoInitializer() throws Exception {
runTest("idea/testData/intentions/convertOrdinaryPropertyToLazy/noInitializer.kt");
}
@TestMetadata("run.kt")
public void testRun() throws Exception {
runTest("idea/testData/intentions/convertOrdinaryPropertyToLazy/run.kt");
}
@TestMetadata("var.kt")
public void testVar() throws Exception {
runTest("idea/testData/intentions/convertOrdinaryPropertyToLazy/var.kt");
}
}
@TestMetadata("idea/testData/intentions/convertParameterToReceiver")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)