Reference resolve from synthetic extension usages to get/set-methods

This commit is contained in:
Valentin Kipyatkov
2015-07-09 17:26:46 +03:00
parent 2ec6f50402
commit 73dd4a214e
10 changed files with 257 additions and 204 deletions
@@ -130,7 +130,7 @@ public fun JetElement.getQualifiedExpressionForSelector(): JetQualifiedExpressio
return if (parent is JetQualifiedExpression && parent.getSelectorExpression() == this) parent else null
}
public fun JetElement.getQualifiedExpressionForSelectorOrThis(): JetElement {
public fun JetExpression.getQualifiedExpressionForSelectorOrThis(): JetExpression {
return getQualifiedExpressionForSelector() ?: this
}
@@ -16,34 +16,81 @@
package org.jetbrains.kotlin.idea.references
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.refactoring.fqName.changeQualifiedName
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.lexer.JetToken
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.resolve.BindingContext
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.psiUtil.*
import com.intellij.psi.impl.light.LightElement
import com.intellij.openapi.extensions.Extensions
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.idea.refactoring.fqName.changeQualifiedName
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.lexer.JetToken
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.plugin.references.SimpleNameReferenceExtension
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.synthetic.SyntheticExtensionPropertyDescriptor
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.constant
public class JetSimpleNameReference(
jetSimpleNameExpression: JetSimpleNameExpression
) : JetSimpleReference<JetSimpleNameExpression>(jetSimpleNameExpression) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
val targets = super.getTargetDescriptors(context)
if (targets.none { it is SyntheticExtensionPropertyDescriptor }) return targets
val newTargets = SmartList<DeclarationDescriptor>()
for (target in targets) {
if (!(target !is SyntheticExtensionPropertyDescriptor)) {
val access = access()
if (access == Access.READ || access == Access.READ_WRITE) {
newTargets.add(target.getMethod)
}
if (access == Access.WRITE || access == Access.READ_WRITE) {
newTargets.addIfNotNull(target.setMethod)
}
}
else {
newTargets.add(target)
}
}
return newTargets
}
//TODO: there should be some common util for that
private enum class Access {
READ, WRITE, READ_WRITE
}
private fun access(): Access {
var expression = myElement.getQualifiedExpressionForSelectorOrThis()
while (expression.getParent() is JetParenthesizedExpression) {
expression = expression.getParent() as JetParenthesizedExpression
}
val assignment = expression.getAssignmentByLHS()
if (assignment != null) {
return if (assignment.getOperationToken() == JetTokens.EQ) Access.WRITE else Access.READ_WRITE
}
return if ((expression.getParent() as? JetUnaryExpression)?.getOperationToken() in constant { setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) })
Access.READ_WRITE
else
Access.READ
}
override fun isReferenceTo(element: PsiElement?): Boolean {
if (element != null) {
if (!canBeReferenceTo(element)) return false
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
@@ -82,7 +83,7 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
override fun copy(parameter: Parameter) = RenameReplacement(parameter)
override fun invoke(e: JetElement): JetElement {
var expressionToReplace = (e.getParent() as? JetThisExpression ?: e).getQualifiedExpressionForSelectorOrThis()
var expressionToReplace = (e.getParent() as? JetThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it }
val parameterName = JetPsiUtil.unquoteIdentifier(parameter.nameForRef)
val replacingName =
if (e.getText().startsWith('`') || !KotlinNameSuggester.isIdentifier(parameterName)) "`$parameterName`" else parameterName
@@ -0,0 +1,4 @@
class JavaClass {
public int getSomething() { return 1; }
public void setSomething(int value) {}
}
+28
View File
@@ -0,0 +1,28 @@
fun JavaClass.foo(javaClass: JavaClass) {
print(javaClass.<caret>something)
javaClass.<caret>something = 1
javaClass.<caret>something += 1
javaClass.<caret>something++
--javaClass.<caret>something
<caret>something++
(<caret>something)++
(<caret>something) = 1
(javaClass.<caret>something) = 1
}
// MULTIRESOLVE
// REF1: (in JavaClass).getSomething()
// REF2: (in JavaClass).setSomething(int)
// REF3: (in JavaClass).getSomething()
// REF3: (in JavaClass).setSomething(int)
// REF4: (in JavaClass).getSomething()
// REF4: (in JavaClass).setSomething(int)
// REF5: (in JavaClass).getSomething()
// REF5: (in JavaClass).setSomething(int)
// REF6: (in JavaClass).getSomething()
// REF6: (in JavaClass).setSomething(int)
// REF7: (in JavaClass).getSomething()
// REF7: (in JavaClass).setSomething(int)
// REF8: (in JavaClass).setSomething(int)
// REF9: (in JavaClass).setSomething(int)
@@ -34,7 +34,7 @@ public abstract class AbstractReferenceResolveInLibrarySourcesTest : JetLightCod
fixture.configureByFile(path)
val expectedResolveData = AbstractReferenceResolveTest.readResolveData(fixture.getFile()!!.getText())
val expectedResolveData = AbstractReferenceResolveTest.readResolveData(fixture.getFile()!!.getText(), 0)
val gotoData = NavigationTestUtils.invokeGotoImplementations(fixture.getEditor(), fixture.getFile())!!
Assert.assertEquals("Single target expected for origianl file", 1, gotoData.targets.size())
@@ -1,164 +0,0 @@
/*
* 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.resolve;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.idea.test.KotlinLightPlatformCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.ReferenceUtils;
import org.jetbrains.kotlin.test.util.UtilPackage;
import org.junit.Assert;
import java.util.List;
public abstract class AbstractReferenceResolveTest extends KotlinLightPlatformCodeInsightFixtureTestCase {
public static class ExpectedResolveData {
private final Boolean shouldBeUnresolved;
private final String referenceToString;
public ExpectedResolveData(Boolean shouldBeUnresolved, String referenceToString) {
this.shouldBeUnresolved = shouldBeUnresolved;
this.referenceToString = referenceToString;
}
public boolean shouldBeUnresolved() {
return shouldBeUnresolved;
}
public String getReferenceString() {
return referenceToString;
}
}
public static final String MULTIRESOLVE = "MULTIRESOLVE";
public static final String REF_EMPTY = "REF_EMPTY";
protected void doTest(@NotNull String path) {
assert path.endsWith(".kt") : path;
UtilPackage.configureWithExtraFile(myFixture, path, ".Data");
performChecks();
}
protected void performChecks() {
if (InTextDirectivesUtils.isDirectiveDefined(myFixture.getFile().getText(), MULTIRESOLVE)) {
doMultiResolveTest();
}
else {
doSingleResolveTest();
}
}
protected void doSingleResolveTest() {
ExpectedResolveData expectedResolveData = readResolveData(myFixture.getFile().getText());
int offset = myFixture.getEditor().getCaretModel().getOffset();
PsiReference psiReference = myFixture.getFile().findReferenceAt(offset);
checkReferenceResolve(expectedResolveData, offset, psiReference);
}
protected void doMultiResolveTest() {
List<String> expectedReferences = ReferenceUtils.getExpectedReferences(myFixture.getFile().getText());
PsiReference psiReference = myFixture.getFile().findReferenceAt(myFixture.getEditor().getCaretModel().getOffset());
assertTrue(psiReference instanceof PsiPolyVariantReference);
PsiPolyVariantReference variantReference = (PsiPolyVariantReference) psiReference;
ResolveResult[] results = variantReference.multiResolve(true);
List<String> actualResolvedTo = Lists.newArrayList();
for (ResolveResult result : results) {
PsiElement resolvedToElement = result.getElement();
assertNotNull(resolvedToElement);
actualResolvedTo.add(ReferenceUtils.renderAsGotoImplementation(resolvedToElement));
}
assertOrderedEquals(Ordering.natural().sortedCopy(actualResolvedTo), Ordering.natural().sortedCopy(expectedReferences));
}
@NotNull
public static ExpectedResolveData readResolveData(String fileText) {
boolean shouldBeUnresolved = InTextDirectivesUtils.isDirectiveDefined(fileText, REF_EMPTY);
List<String> refs = ReferenceUtils.getExpectedReferences(fileText);
String referenceToString;
if (shouldBeUnresolved) {
Assert.assertTrue("REF: directives will be ignored for " + REF_EMPTY + " test: " + refs, refs.isEmpty());
referenceToString = "<empty>";
}
else {
assertTrue("Must be a single ref: " + refs + ".\n" +
"Use " + MULTIRESOLVE + " if you need multiple refs\n" +
"Use "+ REF_EMPTY + " for an unresolved reference",
refs.size() == 1);
referenceToString = refs.get(0);
Assert.assertNotNull("Test data wasn't found, use \"// REF: \" directive", referenceToString);
}
return new ExpectedResolveData(shouldBeUnresolved, referenceToString);
}
public static void checkReferenceResolve(ExpectedResolveData expectedResolveData, int offset, PsiReference psiReference) {
if (psiReference != null) {
PsiElement resolvedTo = psiReference.resolve();
if (resolvedTo != null) {
String resolvedToElementStr = ReferenceUtils.renderAsGotoImplementation(resolvedTo);
String notEqualMessage = String.format("Found reference to '%s', but '%s' was expected",
resolvedToElementStr, expectedResolveData.getReferenceString());
assertEquals(notEqualMessage, expectedResolveData.getReferenceString(), resolvedToElementStr);
}
else {
if (!expectedResolveData.shouldBeUnresolved()) {
Assert.assertNull(
String.format("Element %s wasn't resolved to anything, but %s was expected",
psiReference, expectedResolveData.getReferenceString()),
expectedResolveData.getReferenceString());
}
}
}
else {
Assert.assertNull(
String.format("No reference found at offset: %s, but one resolved to %s was expected",
offset, expectedResolveData.getReferenceString()),
expectedResolveData.getReferenceString());
}
}
@Nullable
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE;
}
@NotNull
@Override
protected String getTestDataPath() {
return "./";
}
}
@@ -0,0 +1,151 @@
/*
* 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.resolve
import com.google.common.collect.Lists
import com.google.common.collect.Ordering
import com.intellij.psi.PsiPolyVariantReference
import com.intellij.psi.PsiReference
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinLightPlatformCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.ReferenceUtils
import org.jetbrains.kotlin.test.util.configureWithExtraFile
import org.junit.Assert
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
public abstract class AbstractReferenceResolveTest : KotlinLightPlatformCodeInsightFixtureTestCase() {
public class ExpectedResolveData(private val shouldBeUnresolved: Boolean?, public val referenceString: String) {
public fun shouldBeUnresolved(): Boolean {
return shouldBeUnresolved!!
}
}
protected open fun doTest(path: String) {
assert(path.endsWith(".kt")) { path }
myFixture.configureWithExtraFile(path, ".Data")
performChecks()
}
protected fun performChecks() {
if (InTextDirectivesUtils.isDirectiveDefined(myFixture.getFile().getText(), MULTIRESOLVE)) {
doMultiResolveTest()
}
else {
doSingleResolveTest()
}
}
protected fun doSingleResolveTest() {
forEachCaret { index, offset ->
val expectedResolveData = readResolveData(myFixture.getFile().getText(), index)
val psiReference = myFixture.getFile().findReferenceAt(offset)
checkReferenceResolve(expectedResolveData, offset, psiReference)
}
}
protected fun doMultiResolveTest() {
forEachCaret { index, offset ->
val expectedReferences = getExpectedReferences(myFixture.getFile().getText(), index)
val psiReference = myFixture.getFile().findReferenceAt(offset)
assertTrue(psiReference is PsiPolyVariantReference)
psiReference as PsiPolyVariantReference
val results = psiReference.multiResolve(true)
val actualResolvedTo = Lists.newArrayList<String>()
for (result in results) {
actualResolvedTo.add(ReferenceUtils.renderAsGotoImplementation(result.getElement()!!))
}
UsefulTestCase.assertOrderedEquals("Not matching for reference #$index", actualResolvedTo.sort(), expectedReferences.sort())
}
}
private fun forEachCaret(action: (index: Int, offset: Int) -> Unit) {
val offsets = myFixture.getEditor().getCaretModel().getAllCarets().map { it.getOffset() }
val singleCaret = offsets.size() == 1
for ((index, offset) in offsets.withIndex()) {
action(if (singleCaret) -1 else index + 1, offset)
}
}
override fun getProjectDescriptor(): LightProjectDescriptor? = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
override fun getTestDataPath() = "./"
companion object {
public val MULTIRESOLVE: String = "MULTIRESOLVE"
public val REF_EMPTY: String = "REF_EMPTY"
public fun readResolveData(fileText: String, index: Int): ExpectedResolveData {
val shouldBeUnresolved = InTextDirectivesUtils.isDirectiveDefined(fileText, REF_EMPTY)
val refs = getExpectedReferences(fileText, index)
val referenceToString: String
if (shouldBeUnresolved) {
Assert.assertTrue("REF: directives will be ignored for $REF_EMPTY test: $refs", refs.isEmpty())
referenceToString = "<empty>"
}
else {
assertTrue(refs.size() == 1, "Must be a single ref: $refs.\nUse $MULTIRESOLVE if you need multiple refs\nUse $REF_EMPTY for an unresolved reference")
referenceToString = refs.get(0)
Assert.assertNotNull("Test data wasn't found, use \"// REF: \" directive", referenceToString)
}
return ExpectedResolveData(shouldBeUnresolved, referenceToString)
}
// purpose of this helper is to deal with the case when navigation element is a file
// see ReferenceResolveInJavaTestGenerated.testPackageFacade()
private fun getExpectedReferences(text: String, index: Int): List<String> {
val prefix = if (index > 0) "// REF$index:" else "// REF:"
val prefixes = InTextDirectivesUtils.findLinesWithPrefixesRemoved(text, prefix)
return prefixes.map {
val replaced = it.replace("<test dir>", PluginTestCaseBase.getTestDataPathBase())
PathUtil.toSystemDependentName(replaced).replace("//", "/") //happens on Unix
}
}
public fun checkReferenceResolve(expectedResolveData: ExpectedResolveData, offset: Int, psiReference: PsiReference?) {
if (psiReference != null) {
val resolvedTo = psiReference.resolve()
if (resolvedTo != null) {
val resolvedToElementStr = ReferenceUtils.renderAsGotoImplementation(resolvedTo)
assertEquals(expectedResolveData.referenceString, resolvedToElementStr, "Found reference to '$resolvedToElementStr', but '${expectedResolveData.referenceString}' was expected")
}
else {
if (!expectedResolveData.shouldBeUnresolved()) {
assertNull(expectedResolveData.referenceString, "Element $psiReference wasn't resolved to anything, but ${expectedResolveData.referenceString} was expected")
}
}
}
else {
assertNull(expectedResolveData.referenceString, "No reference found at offset: $offset, but one resolved to ${expectedResolveData.referenceString} was expected")
}
}
}
}
@@ -347,6 +347,12 @@ public class ReferenceResolveTestGenerated extends AbstractReferenceResolveTest
doTest(fileName);
}
@TestMetadata("SyntheticProperty.kt")
public void testSyntheticProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/references/SyntheticProperty.kt");
doTest(fileName);
}
@TestMetadata("TypeParameterInAnonymousObject.kt")
public void testTypeParameterInAnonymousObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/references/TypeParameterInAnonymousObject.kt");
@@ -21,17 +21,11 @@ import com.intellij.navigation.NavigationItem;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.PathUtil;
import kotlin.KotlinPackage;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import org.jetbrains.kotlin.psi.JetClass;
import org.jetbrains.kotlin.psi.JetObjectDeclaration;
import org.junit.Assert;
import java.util.List;
public final class ReferenceUtils {
private ReferenceUtils() {
}
@@ -60,18 +54,4 @@ public final class ReferenceUtils {
? presentableText
: locationString + "." + presentableText;
}
// purpose of this helper is to deal with the case when navigation element is a file
// see ReferenceResolveInJavaTestGenerated.testPackageFacade()
@NotNull
public static List<String> getExpectedReferences(@NotNull String text) {
List<String> prefixes = InTextDirectivesUtils.findLinesWithPrefixesRemoved(text, "// REF:");
return KotlinPackage.map(prefixes, new Function1<String, String>() {
@Override
public String invoke(String s) {
String replaced = s.replace("<test dir>", PluginTestCaseBase.getTestDataPathBase());
return PathUtil.toSystemDependentName(replaced).replace("//", "/"); //happens on Unix
}
});
}
}