Public utility to detect read/write access for an expression

Implemented ReadWriteAccessDetector which gives read/write access in Highlight Usages and Find Usages
+ More correct UsageType detection
This commit is contained in:
Valentin Kipyatkov
2015-08-17 22:01:15 +03:00
parent 1fd0578b9e
commit 5e97387e8d
13 changed files with 178 additions and 74 deletions
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.addToStdlib.constant
import java.util.ArrayList
import java.util.Collections
import kotlin.test.assertTrue
@@ -307,6 +308,29 @@ public inline fun <reified T : JetElement, R> flatMapDescendantsOfTypeVisitor(ac
return forEachDescendantOfTypeVisitor<T> { accumulator.addAll(map(it)) }
}
// ----------- Read/write access -----------------------------------------------------------------------------------------------------------------------
public enum class ReferenceAccess {
READ, WRITE, READ_WRITE
}
public fun JetExpression.readWriteAccess(): ReferenceAccess {
var expression = getQualifiedExpressionForSelectorOrThis()
while (expression.parent is JetParenthesizedExpression) {
expression = expression.parent as JetParenthesizedExpression
}
val assignment = expression.getAssignmentByLHS()
if (assignment != null) {
return if (assignment.operationToken == JetTokens.EQ) ReferenceAccess.WRITE else ReferenceAccess.READ_WRITE
}
return if ((expression.parent as? JetUnaryExpression)?.operationToken in constant { setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) })
ReferenceAccess.READ_WRITE
else
ReferenceAccess.READ
}
// ----------- Other -----------------------------------------------------------------------------------------------------------------------
public fun JetClassOrObject.effectiveDeclarations(): List<JetDeclaration> {
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.idea.findUsages
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
@@ -33,12 +30,12 @@ import com.intellij.psi.PsiReferenceExpression
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.findUsages.UsageTypeEnum.*
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.idea.references.JetArrayAccessReference
import org.jetbrains.kotlin.idea.references.JetInvokeFunctionReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.unwrappedTargets
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
public object UsageTypeUtils {
@@ -149,15 +146,9 @@ public object UsageTypeUtils {
}
}
return when {
(refExpr.getParentOfTypesAndPredicate(false, javaClass<JetBinaryExpression>()) { JetPsiUtil.isAssignment(it) })
?.getLeft().isAncestor(refExpr) ->
WRITE
refExpr.getNonStrictParentOfType<JetSimpleNameExpression>() != null ->
READ
else -> null
return when (refExpr.readWriteAccess()) {
ReferenceAccess.READ -> READ
ReferenceAccess.WRITE, ReferenceAccess.READ_WRITE -> WRITE
}
}
@@ -23,9 +23,8 @@ import org.jetbrains.kotlin.idea.kdoc.KDocReference
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.utils.addToStdlib.constant
import org.jetbrains.kotlin.psi.psiUtil.ReferenceAccess
import org.jetbrains.kotlin.psi.psiUtil.readWriteAccess
public class JetReferenceContributor() : PsiReferenceContributor() {
public override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
@@ -37,10 +36,10 @@ public class JetReferenceContributor() : PsiReferenceContributor() {
registerMultiProvider(javaClass<JetNameReferenceExpression>()) {
if (it.getReferencedNameElementType() != JetTokens.IDENTIFIER) return@registerMultiProvider emptyArray()
when (it.access()) {
Access.READ -> arrayOf(SyntheticPropertyAccessorReference.Getter(it))
Access.WRITE -> arrayOf(SyntheticPropertyAccessorReference.Setter(it))
Access.READ_WRITE -> arrayOf(SyntheticPropertyAccessorReference.Getter(it), SyntheticPropertyAccessorReference.Setter(it))
when (it.readWriteAccess()) {
ReferenceAccess.READ -> arrayOf(SyntheticPropertyAccessorReference.Getter(it))
ReferenceAccess.WRITE -> arrayOf(SyntheticPropertyAccessorReference.Setter(it))
ReferenceAccess.READ_WRITE -> arrayOf(SyntheticPropertyAccessorReference.Getter(it), SyntheticPropertyAccessorReference.Setter(it))
}
}
@@ -74,28 +73,6 @@ public class JetReferenceContributor() : PsiReferenceContributor() {
}
}
//TODO: there should be some common util for that
private enum class Access {
READ, WRITE, READ_WRITE
}
private fun JetSimpleNameExpression.access(): Access {
var expression = 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
}
private fun <E : JetElement> PsiReferenceRegistrar.registerProvider(elementClass: Class<E>, factory: (E) -> JetReference) {
registerMultiProvider(elementClass, { arrayOf(factory(it)) })
}
@@ -0,0 +1,67 @@
/*
* 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.search.ideaExtensions
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.ReferenceAccess
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.readWriteAccess
public class KotlinReadWriteAccessDetector : ReadWriteAccessDetector() {
override fun isReadWriteAccessible(element: PsiElement) = element is JetVariableDeclaration || element is JetParameter
override fun isDeclarationWriteAccess(element: PsiElement) = isReadWriteAccessible(element)
override fun getReferenceAccess(referencedElement: PsiElement, reference: PsiReference): ReadWriteAccessDetector.Access {
if (!isReadWriteAccessible(referencedElement)) {
return ReadWriteAccessDetector.Access.Read
}
val refTarget = reference.resolve()
if (refTarget is KotlinLightMethod) {
val origin = refTarget.getOrigin()
val declaration: JetNamedDeclaration = when (origin) {
is JetPropertyAccessor -> origin.getNonStrictParentOfType<JetProperty>()
is JetProperty, is JetParameter -> origin as JetNamedDeclaration
else -> null
} ?: return ReadWriteAccessDetector.Access.ReadWrite
return when (refTarget.name) {
JvmAbi.getterName(declaration.name!!) -> return ReadWriteAccessDetector.Access.Read
JvmAbi.setterName(declaration.name!!) -> return ReadWriteAccessDetector.Access.Write
else -> ReadWriteAccessDetector.Access.ReadWrite
}
}
return getExpressionAccess(reference.element)
}
override fun getExpressionAccess(expression: PsiElement): ReadWriteAccessDetector.Access {
if (expression !is JetExpression) return ReadWriteAccessDetector.Access.Read
return when (expression.readWriteAccess()) {
ReferenceAccess.READ -> ReadWriteAccessDetector.Access.Read
ReferenceAccess.WRITE -> ReadWriteAccessDetector.Access.Write
ReferenceAccess.READ_WRITE -> ReadWriteAccessDetector.Access.ReadWrite
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.search.usagesSearch
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.references.matchesTarget
import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT
import org.jetbrains.kotlin.idea.search.and
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.False
import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.True
import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope
@@ -237,8 +239,6 @@ class FunctionUsagesSearchHelper(
}
}
val isPropertyReadOnlyUsage = (PsiReference::isPropertyReadOnlyUsage).searchFilter
// Used for JetProperty and JetParameter
class PropertyUsagesSearchHelper(
public val readUsages: Boolean = true,
@@ -294,12 +294,21 @@ class PropertyUsagesSearchHelper(
}
override fun makeFilter(target: UsagesSearchTarget<JetNamedDeclaration>): UsagesSearchFilter {
val readWriteFilter = when {
readUsages && writeUsages -> True
readUsages -> isPropertyReadOnlyUsage
writeUsages -> !isPropertyReadOnlyUsage
else -> False
val readWriteFilter = object: UsagesSearchFilter {
val detector = KotlinReadWriteAccessDetector()
override fun accepts(ref: PsiReference, item: UsagesSearchRequestItem): Boolean {
if (readUsages == writeUsages) return readUsages
val access = detector.getReferenceAccess(target.element, ref)
return when (access) {
ReadWriteAccessDetector.Access.Read -> readUsages
ReadWriteAccessDetector.Access.Write -> writeUsages
ReadWriteAccessDetector.Access.ReadWrite -> true
}
}
}
var result = isTargetOrOverrideUsage and readWriteFilter and isFilteredImport
if (!namedArgumentUsages) {
result = result and !(PsiReference::isNamedArgumentUsage).searchFilter
@@ -22,18 +22,14 @@ import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchScope
import org.jetbrains.kotlin.asJava.KotlinLightElement
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.asJava.KotlinNoOriginLightMethod
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.findUsages.UsageTypeEnum
import org.jetbrains.kotlin.idea.findUsages.UsageTypeUtils
import org.jetbrains.kotlin.idea.references.unwrappedTargets
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
@@ -226,23 +222,3 @@ fun PsiReference.isCallableOverrideUsage(declaration: JetNamedDeclaration): Bool
&& OverrideResolver.overrides(usageDescriptor, targetDescriptor)
}
}
// Check if reference resolves to property getter
// Works for JetProperty and JetParameter
fun PsiReference.isPropertyReadOnlyUsage(): Boolean {
if (UsageTypeUtils.getUsageType(getElement()) == UsageTypeEnum.READ) return true
val refTarget = resolve()
if (refTarget is KotlinLightMethod) {
val origin = refTarget.getOrigin()
val declaration: JetNamedDeclaration? = when (origin) {
is JetPropertyAccessor -> origin.getNonStrictParentOfType<JetProperty>()
is JetProperty, is JetParameter -> origin as JetNamedDeclaration
else -> null
}
return declaration != null && refTarget.getName() == JvmAbi.getterName(declaration.getName()!!)
}
return false
}
+2
View File
@@ -516,6 +516,8 @@
<methodReferencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinConstructorDelegationCallReferenceSearcher"/>
<methodReferencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinOverridingMethodReferenceSearcher"/>
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>
<exceptionFilter implementation="org.jetbrains.kotlin.idea.filters.JetExceptionFilterFactory" order="first"/>
<applicationService serviceInterface="org.jetbrains.kotlin.idea.editor.JetEditorOptions"
@@ -1,2 +1,2 @@
Value read (2: 21) print(javaClass.something)
Value read (4: 15) javaClass.something++
Value write (4: 15) javaClass.something++
@@ -0,0 +1,13 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.JetProperty
// OPTIONS: usages
// OPTIONS: skipWrite
fun foo() {
var <caret>v = 1
v = 2
print(v)
++v
v--
print(-v)
v += 1
v -= 1
}
@@ -0,0 +1,6 @@
Value read (10: 12) print(-v)
Value read (7: 11) print(v)
Value write (11: 5) v += 1
Value write (12: 5) v -= 1
Value write (8: 7) ++v
Value write (9: 5) v--
@@ -0,0 +1,13 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.JetProperty
// OPTIONS: usages
// OPTIONS: skipRead
fun foo() {
var <caret>v = 1
v = 2
print(v)
++v
v--
print(-v)
v += 1
v -= 1
}
@@ -0,0 +1,5 @@
Value write (11: 5) v += 1
Value write (12: 5) v -= 1
Value write (6: 5) v = 2
Value write (8: 7) ++v
Value write (9: 5) v--
@@ -1026,6 +1026,27 @@ public class JetFindUsagesTestGenerated extends AbstractJetFindUsagesTest {
doTest(fileName);
}
}
@TestMetadata("idea/testData/findUsages/kotlin/variable")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Variable extends AbstractJetFindUsagesTest {
public void testAllFilesPresentInVariable() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/kotlin/variable"), Pattern.compile("^(.+)\\.0\\.kt$"), true);
}
@TestMetadata("readAccess.0.kt")
public void testReadAccess() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/variable/readAccess.0.kt");
doTest(fileName);
}
@TestMetadata("writeAccess.0.kt")
public void testWriteAccess() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/variable/writeAccess.0.kt");
doTest(fileName);
}
}
}
@TestMetadata("idea/testData/findUsages/java")