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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-29
@@ -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)) })
|
||||
}
|
||||
|
||||
+67
@@ -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
-7
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user