DeprecatedSymbolUsageFix: more correct generation of "let" for safe call

This commit is contained in:
Valentin Kipyatkov
2015-05-18 20:41:56 +03:00
parent df850d7035
commit 348d09866a
11 changed files with 203 additions and 8 deletions
@@ -45,11 +45,10 @@ public class JetNameSuggester {
}
private static void addName(ArrayList<String> result, @Nullable String name, JetNameValidator validator) {
if (name == null) return;
if ("class".equals(name)) name = "clazz";
if (!isIdentifier(name)) return;
String newName = validator.validateName(name);
if (newName == null) return;
result.add(newName);
result.add(validator.validateName(name));
}
/**
@@ -93,8 +92,13 @@ public class JetNameSuggester {
}
public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator) {
return suggestNamesForExpression(expression, validator, null);
}
public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator, @Nullable String defaultName) {
ArrayList<String> result = new ArrayList<String>();
addNamesForExpression(result, expression, validator);
if (result.isEmpty()) addName(result, defaultName, validator);
return ArrayUtil.toStringArray(result);
}
@@ -32,6 +32,8 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
@@ -97,9 +99,9 @@ public class DeprecatedSymbolUsageFix(
val bindingContext = element.analyze()
val resolvedCall = element.getResolvedCall(bindingContext)!!
val descriptor = resolvedCall.getResultingDescriptor()
val callElement = resolvedCall.getCall().getCallElement() as JetExpression
val qualifiedExpression = callElement.getParent() as? JetQualifiedExpression
val expressionToReplace = qualifiedExpression ?: callElement
val callExpression = resolvedCall.getCall().getCallElement() as JetExpression
val qualifiedExpression = callExpression.getParent() as? JetQualifiedExpression
val expressionToReplace = qualifiedExpression ?: callExpression
val USER_CODE_KEY = Key<Unit>("USER_CODE")
@@ -125,8 +127,19 @@ public class DeprecatedSymbolUsageFix(
}
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
expression = psiFactory.createExpressionByPattern("$0?.let { $1 }", explicitReceiver!!, expression)
thisReplacement = psiFactory.createExpression("it")
if (!isNameUsed("it", callExpression, expression)) {
expression = psiFactory.createExpressionByPattern("$0?.let { $1 }", explicitReceiver!!, expression)
thisReplacement = psiFactory.createExpression("it")
}
else {
val nameValidator = object : JetNameValidator() {
override fun validateInner(name: String) = !isNameUsed(name, callExpression, expression)
}
val name = JetNameSuggester.suggestNamesForExpression(explicitReceiver!!, nameValidator, "t").first()
val nameInCode = IdeDescriptorRenderers.SOURCE_CODE.renderName(Name.identifier(name))
expression = psiFactory.createExpressionByPattern("$0?.let { $nameInCode -> $1 }", explicitReceiver, expression)
thisReplacement = psiFactory.createExpression(nameInCode)
}
}
else {
expression = psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", explicitReceiver!!, expression)
@@ -301,6 +314,24 @@ public class DeprecatedSymbolUsageFix(
}
}
private fun isNameUsed(name: String, vararg inExpressions: JetExpression): Boolean {
var result = false
inExpressions.forEach {
it.accept(object : JetVisitorVoid(){
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
if (expression.getReferencedName() == name) {
result = true
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
}
return result
}
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null
@@ -0,0 +1,18 @@
// "Replace with 'c.newFun(this)'" "true"
class X {
@deprecated("", ReplaceWith("c.newFun(this)"))
fun oldFun(c: Char): Char = c.newFun(this)
}
fun Char.newFun(x: X): Char = this
fun foo(x: X?, p: Boolean, s: String) {
val chars = s.filter {
val v = if (p)
x?.<caret>oldFun(it)
else
null
v != 'a'
}
}
@@ -0,0 +1,18 @@
// "Replace with 'c.newFun(this)'" "true"
class X {
@deprecated("", ReplaceWith("c.newFun(this)"))
fun oldFun(c: Char): Char = c.newFun(this)
}
fun Char.newFun(x: X): Char = this
fun foo(x: X?, p: Boolean, s: String) {
val chars = s.filter {
val v = if (p)
x?.let { x -> it.newFun(x) }
else
null
v != 'a'
}
}
@@ -0,0 +1,14 @@
// "Replace with 's.filter { it != c }'" "true"
class X {
@deprecated("", ReplaceWith("s.filter { it != c }"))
fun oldFun(s: String): CharSequence = s.filter { it != c }
val c = 'x'
}
fun foo(x: X?, s: String) {
bar(x?.<caret>oldFun(s))
}
fun bar(s: CharSequence?){}
@@ -0,0 +1,14 @@
// "Replace with 's.filter { it != c }'" "true"
class X {
@deprecated("", ReplaceWith("s.filter { it != c }"))
fun oldFun(s: String): CharSequence = s.filter { it != c }
val c = 'x'
}
fun foo(x: X?, s: String) {
bar(x?.<caret>let { x -> s.filter { it != x.c } })
}
fun bar(s: CharSequence?){}
@@ -0,0 +1,20 @@
// "Replace with 'c1.newFun(this, c2)'" "true"
class X {
@deprecated("", ReplaceWith("c1.newFun(this, c2)"))
fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2)
val c: Char = 'a'
}
fun Char.newFun(x: X, c: Char): Char = this
fun foo(s: String, x: X) {
val chars = s.filter {
O.x?.<caret>oldFun(it, x.c) != 'a'
}
}
object O {
var x: X? = null
}
@@ -0,0 +1,20 @@
// "Replace with 'c1.newFun(this, c2)'" "true"
class X {
@deprecated("", ReplaceWith("c1.newFun(this, c2)"))
fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2)
val c: Char = 'a'
}
fun Char.newFun(x: X, c: Char): Char = this
fun foo(s: String, x: X) {
val chars = s.filter {
O.x?.<caret>let { x1 -> it.newFun(x1, x.c) } != 'a'
}
}
object O {
var x: X? = null
}
@@ -0,0 +1,16 @@
// "Replace with 'c1.newFun(this, c2)'" "true"
class X(val c: Char) {
@deprecated("", ReplaceWith("c1.newFun(this, c2)"))
fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2)
}
fun Char.newFun(x: X, c: Char): Char = this
fun foo(s: String, t: X) {
val chars = s.filter {
(X('a') + X('b'))?.<caret>oldFun(it, t.c) != 'a'
}
}
fun X.plus(x: X): X? = null
@@ -0,0 +1,16 @@
// "Replace with 'c1.newFun(this, c2)'" "true"
class X(val c: Char) {
@deprecated("", ReplaceWith("c1.newFun(this, c2)"))
fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2)
}
fun Char.newFun(x: X, c: Char): Char = this
fun foo(s: String, t: X) {
val chars = s.filter {
(X('a') + X('b'))?.let { t1 -> it.newFun(t1, t.c) } != 'a'
}
}
fun X.plus(x: X): X? = null
@@ -2896,6 +2896,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("changeThisSafeCallWithValue1Runtime.kt")
public void testChangeThisSafeCallWithValue1Runtime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt");
doTest(fileName);
}
@TestMetadata("changeThisSafeCallWithValue2Runtime.kt")
public void testChangeThisSafeCallWithValue2Runtime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt");
doTest(fileName);
}
@TestMetadata("changeThisSafeCallWithValue3Runtime.kt")
public void testChangeThisSafeCallWithValue3Runtime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt");
doTest(fileName);
}
@TestMetadata("changeThisSafeCallWithValue4Runtime.kt")
public void testChangeThisSafeCallWithValue4Runtime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt");
doTest(fileName);
}
@TestMetadata("changeThisSafeCallWithValueRuntime.kt")
public void testChangeThisSafeCallWithValueRuntime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValueRuntime.kt");