Find Usages: Look for as-property usages of Java methods
This commit is contained in:
committed by
Alexey Sedunov
parent
7e9222f70a
commit
9c360ef05a
@@ -1047,7 +1047,7 @@ public class DescriptorResolver {
|
||||
else if (property.isVar()) {
|
||||
Annotations setterAnnotations = annotationSplitter.getAnnotationsForTarget(PROPERTY_SETTER);
|
||||
setterDescriptor = DescriptorFactory.createSetter(propertyDescriptor, setterAnnotations, !property.hasDelegate(),
|
||||
/* isExternal = */ false);
|
||||
/* isExternal = */ false, propertyDescriptor.getSource());
|
||||
}
|
||||
|
||||
if (!property.isVar()) {
|
||||
|
||||
+2
-2
@@ -76,7 +76,7 @@ public class JavaPropertyDescriptor extends PropertyDescriptorImpl implements Ja
|
||||
if (getter != null) {
|
||||
newGetter = new PropertyGetterDescriptorImpl(
|
||||
enhanced, getter.getAnnotations(), getter.getModality(), getter.getVisibility(),
|
||||
getter.hasBody(), getter.isDefault(), getter.isExternal(), getKind(), getter, SourceElement.NO_SOURCE);
|
||||
getter.hasBody(), getter.isDefault(), getter.isExternal(), getKind(), getter, getter.getSource());
|
||||
newGetter.initialize(enhancedReturnType);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class JavaPropertyDescriptor extends PropertyDescriptorImpl implements Ja
|
||||
if (setter != null) {
|
||||
newSetter = new PropertySetterDescriptorImpl(
|
||||
enhanced, setter.getAnnotations(), setter.getModality(), setter.getVisibility(),
|
||||
setter.hasBody(), setter.isDefault(), setter.isExternal(), getKind(), setter, SourceElement.NO_SOURCE);
|
||||
setter.hasBody(), setter.isDefault(), setter.isExternal(), getKind(), setter, setter.getSource());
|
||||
newSetter.initialize(setter.getValueParameters().get(0));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -322,7 +322,7 @@ public class LazyJavaClassMemberScope(
|
||||
|
||||
val setter = setterMethod?.let { setterMethod ->
|
||||
DescriptorFactory.createSetter(propertyDescriptor, setterMethod.annotations, /* isDefault = */false,
|
||||
/* isExternal = */ false, setterMethod.visibility)
|
||||
/* isExternal = */ false, setterMethod.visibility, setterMethod.source)
|
||||
}
|
||||
|
||||
return propertyDescriptor.apply { initialize(getter, setter) }
|
||||
|
||||
@@ -50,17 +50,7 @@ public class DescriptorFactory {
|
||||
@NotNull PropertyDescriptor propertyDescriptor,
|
||||
@NotNull Annotations annotations
|
||||
) {
|
||||
return createSetter(propertyDescriptor, annotations, true, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PropertySetterDescriptorImpl createSetter(
|
||||
@NotNull PropertyDescriptor propertyDescriptor,
|
||||
@NotNull Annotations annotations,
|
||||
boolean isDefault,
|
||||
boolean isExternal
|
||||
) {
|
||||
return createSetter(propertyDescriptor, annotations, isDefault, isExternal, propertyDescriptor.getVisibility());
|
||||
return createSetter(propertyDescriptor, annotations, true, false, propertyDescriptor.getSource());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -69,12 +59,24 @@ public class DescriptorFactory {
|
||||
@NotNull Annotations annotations,
|
||||
boolean isDefault,
|
||||
boolean isExternal,
|
||||
@NotNull Visibility visibility
|
||||
@NotNull SourceElement sourceElement
|
||||
) {
|
||||
return createSetter(propertyDescriptor, annotations, isDefault, isExternal, propertyDescriptor.getVisibility(), sourceElement);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PropertySetterDescriptorImpl createSetter(
|
||||
@NotNull PropertyDescriptor propertyDescriptor,
|
||||
@NotNull Annotations annotations,
|
||||
boolean isDefault,
|
||||
boolean isExternal,
|
||||
@NotNull Visibility visibility,
|
||||
@NotNull SourceElement sourceElement
|
||||
) {
|
||||
PropertySetterDescriptorImpl setterDescriptor =
|
||||
new PropertySetterDescriptorImpl(propertyDescriptor, annotations, propertyDescriptor.getModality(),
|
||||
visibility, !isDefault, isDefault, isExternal,
|
||||
CallableMemberDescriptor.Kind.DECLARATION, null, propertyDescriptor.getSource());
|
||||
CallableMemberDescriptor.Kind.DECLARATION, null, sourceElement);
|
||||
setterDescriptor.initializeDefault();
|
||||
return setterDescriptor;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,15 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
|
||||
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.lexer.KtToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.isOneSegmentFQN
|
||||
@@ -36,10 +39,37 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
|
||||
class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleReference<KtSimpleNameExpression>(expression) {
|
||||
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
|
||||
return SmartList<DeclarationDescriptor>().apply {
|
||||
// Replace Java property with its accessor(s)
|
||||
for (descriptor in super.getTargetDescriptors(context)) {
|
||||
val sizeBefore = size
|
||||
|
||||
if (descriptor !is JavaPropertyDescriptor) {
|
||||
add(descriptor)
|
||||
continue
|
||||
}
|
||||
|
||||
val readWriteAccess = expression.readWriteAccess(true)
|
||||
descriptor.getter?.let {
|
||||
if (readWriteAccess.isRead) add(it)
|
||||
}
|
||||
descriptor.setter?.let {
|
||||
if (readWriteAccess.isWrite) add(it)
|
||||
}
|
||||
|
||||
if (size == sizeBefore) {
|
||||
add(descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isReferenceTo(element: PsiElement?): Boolean {
|
||||
if (element != null) {
|
||||
if (!canBeReferenceTo(element)) return false
|
||||
|
||||
@@ -148,8 +148,8 @@ val KtElement.mainReference: KtReference?
|
||||
|
||||
// ----------- Read/write access -----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public enum class ReferenceAccess {
|
||||
READ, WRITE, READ_WRITE
|
||||
public enum class ReferenceAccess(val isRead: Boolean, val isWrite: Boolean) {
|
||||
READ(true, false), WRITE(false, true), READ_WRITE(true, true)
|
||||
}
|
||||
|
||||
public fun KtExpression.readWriteAccess(useResolveForReadWrite: Boolean): ReferenceAccess {
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.search
|
||||
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
public fun SearchScope.and(otherScope: SearchScope): SearchScope = intersectWith(otherScope)
|
||||
public fun SearchScope.or(otherScope: SearchScope): SearchScope = union(otherScope)
|
||||
@@ -30,4 +33,19 @@ public fun Project.allScope(): GlobalSearchScope = GlobalSearchScope.allScope(th
|
||||
|
||||
public fun Project.projectScope(): GlobalSearchScope = GlobalSearchScope.projectScope(this)
|
||||
|
||||
public fun PsiFile.fileScope(): GlobalSearchScope = GlobalSearchScope.fileScope(this)
|
||||
public fun PsiFile.fileScope(): GlobalSearchScope = GlobalSearchScope.fileScope(this)
|
||||
|
||||
public fun SearchScope.restrictToKotlinSources(): SearchScope {
|
||||
return when (this) {
|
||||
is GlobalSearchScope -> GlobalSearchScope.getScopeRestrictedByFileTypes(this, KotlinFileType.INSTANCE)
|
||||
is LocalSearchScope -> {
|
||||
val ktElements = scope.filter { it.containingFile is KtFile }
|
||||
when (ktElements.size) {
|
||||
0 -> GlobalSearchScope.EMPTY_SCOPE
|
||||
scope.size -> this
|
||||
else -> LocalSearchScope(ktElements.toTypedArray())
|
||||
}
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,10 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.util.MethodSignatureUtil
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightElement
|
||||
import org.jetbrains.kotlin.asJava.KotlinNoOriginLightMethod
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
@@ -206,21 +208,28 @@ fun PsiReference.isUsageInContainingDeclaration(declaration: KtNamedDeclaration)
|
||||
}
|
||||
|
||||
fun PsiReference.isCallableOverrideUsage(declaration: KtNamedDeclaration): Boolean {
|
||||
val toDescriptor: (KtDeclaration) -> DeclarationDescriptor? = { declaration ->
|
||||
val toDescriptor: (KtDeclaration) -> CallableDescriptor? = { declaration ->
|
||||
if (declaration is KtParameter) {
|
||||
// we don't treat parameters in overriding method as "override" here (overriding parameters usages are searched optionally and via searching of overriding methods first)
|
||||
if (declaration.hasValOrVar()) declaration.propertyDescriptor else null
|
||||
}
|
||||
else {
|
||||
declaration.descriptor
|
||||
declaration.descriptor as? CallableDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
val descriptor = toDescriptor(declaration) ?: return false
|
||||
val targetDescriptor = toDescriptor(declaration) ?: return false
|
||||
|
||||
return checkUsageVsOriginalDescriptor(descriptor, toDescriptor) { usageDescriptor, targetDescriptor ->
|
||||
usageDescriptor is CallableDescriptor
|
||||
&& targetDescriptor is CallableDescriptor
|
||||
&& OverrideResolver.overrides(usageDescriptor, targetDescriptor)
|
||||
return unwrappedTargets.any {
|
||||
when (it) {
|
||||
is KtDeclaration -> {
|
||||
val usageDescriptor = toDescriptor(it)
|
||||
usageDescriptor != null && OverrideResolver.overrides(usageDescriptor, targetDescriptor)
|
||||
}
|
||||
is PsiMethod -> {
|
||||
declaration.toLightMethods().any { superMethod -> MethodSignatureUtil.isSuperMethod(superMethod, it) }
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public fun runReadAction<T>(action: () -> T): T {
|
||||
return ApplicationManager.getApplication().runReadAction<T>(action)
|
||||
}
|
||||
|
||||
public fun <T> Project.runReadActionInSmartMode(action: () -> T): T {
|
||||
if (ApplicationManager.getApplication().isReadAccessAllowed) return action()
|
||||
return DumbService.getInstance(this).runReadActionInSmartMode<T>(action)
|
||||
}
|
||||
|
||||
public fun runWriteAction<T>(action: () -> T): T {
|
||||
return ApplicationManager.getApplication().runWriteAction<T>(action)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.findUsages
|
||||
|
||||
import com.intellij.find.findUsages.FindUsagesOptions
|
||||
import com.intellij.find.findUsages.JavaClassFindUsagesOptions
|
||||
import com.intellij.find.findUsages.JavaMethodFindUsagesOptions
|
||||
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions
|
||||
@@ -35,6 +36,8 @@ public class KotlinClassFindUsagesOptions(project: Project) : JavaClassFindUsage
|
||||
|
||||
public interface KotlinCallableFindUsagesOptions {
|
||||
public var searchOverrides: Boolean
|
||||
|
||||
fun toJavaOptions(project: Project): FindUsagesOptions?
|
||||
}
|
||||
|
||||
public class KotlinFunctionFindUsagesOptions(project: Project): KotlinCallableFindUsagesOptions, JavaMethodFindUsagesOptions(project) {
|
||||
@@ -43,37 +46,36 @@ public class KotlinFunctionFindUsagesOptions(project: Project): KotlinCallableFi
|
||||
set(value: Boolean) {
|
||||
isOverridingMethods = value
|
||||
}
|
||||
}
|
||||
|
||||
fun KotlinFunctionFindUsagesOptions.toJavaMethodOptions(project: Project): JavaMethodFindUsagesOptions {
|
||||
val javaOptions = JavaMethodFindUsagesOptions(project)
|
||||
javaOptions.fastTrack = fastTrack
|
||||
javaOptions.isCheckDeepInheritance = isCheckDeepInheritance
|
||||
javaOptions.isImplementingMethods = isImplementingMethods
|
||||
javaOptions.isIncludeInherited = isIncludeInherited
|
||||
javaOptions.isIncludeOverloadUsages = isIncludeOverloadUsages
|
||||
javaOptions.isOverridingMethods = isOverridingMethods
|
||||
javaOptions.isSearchForTextOccurrences = isSearchForTextOccurrences
|
||||
javaOptions.isSkipImportStatements = isSkipImportStatements
|
||||
javaOptions.isUsages = isUsages
|
||||
javaOptions.searchScope = searchScope
|
||||
|
||||
return javaOptions
|
||||
}
|
||||
override fun toJavaOptions(project: Project): FindUsagesOptions? {
|
||||
val javaOptions = JavaMethodFindUsagesOptions(project)
|
||||
javaOptions.fastTrack = fastTrack
|
||||
javaOptions.isCheckDeepInheritance = isCheckDeepInheritance
|
||||
javaOptions.isImplementingMethods = isImplementingMethods
|
||||
javaOptions.isIncludeInherited = isIncludeInherited
|
||||
javaOptions.isIncludeOverloadUsages = isIncludeOverloadUsages
|
||||
javaOptions.isOverridingMethods = isOverridingMethods
|
||||
javaOptions.isSearchForTextOccurrences = isSearchForTextOccurrences
|
||||
javaOptions.isSkipImportStatements = isSkipImportStatements
|
||||
javaOptions.isUsages = isUsages
|
||||
javaOptions.searchScope = searchScope
|
||||
|
||||
fun KotlinPropertyFindUsagesOptions.toJavaVariableOptions(project: Project): JavaVariableFindUsagesOptions {
|
||||
val javaOptions = JavaVariableFindUsagesOptions(project)
|
||||
javaOptions.fastTrack = fastTrack
|
||||
javaOptions.isSearchForTextOccurrences = isSearchForTextOccurrences
|
||||
javaOptions.isSkipImportStatements = isSkipImportStatements
|
||||
javaOptions.isReadAccess = isReadAccess
|
||||
javaOptions.isWriteAccess = isWriteAccess
|
||||
javaOptions.isUsages = isUsages
|
||||
javaOptions.searchScope = searchScope
|
||||
return javaOptions
|
||||
return javaOptions
|
||||
}
|
||||
}
|
||||
|
||||
public class KotlinPropertyFindUsagesOptions(project: Project): KotlinCallableFindUsagesOptions, JavaVariableFindUsagesOptions(project) {
|
||||
override var searchOverrides: Boolean = false
|
||||
}
|
||||
|
||||
override fun toJavaOptions(project: Project): JavaVariableFindUsagesOptions {
|
||||
val javaOptions = JavaVariableFindUsagesOptions(project)
|
||||
javaOptions.fastTrack = fastTrack
|
||||
javaOptions.isSearchForTextOccurrences = isSearchForTextOccurrences
|
||||
javaOptions.isSkipImportStatements = isSkipImportStatements
|
||||
javaOptions.isReadAccess = isReadAccess
|
||||
javaOptions.isWriteAccess = isWriteAccess
|
||||
javaOptions.isUsages = isUsages
|
||||
javaOptions.searchScope = searchScope
|
||||
return javaOptions
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -47,15 +47,9 @@ class DelegatingFindMemberUsagesHandler(
|
||||
is KtNamedDeclaration ->
|
||||
HandlerAndOptions(KotlinFindMemberUsagesHandler.getInstance(element, elementsToSearch, factory), options)
|
||||
|
||||
is PsiMethod ->
|
||||
/* Can't have KotlinPropertyFindUsagesOptions here since Kotlin properties do not override java methods, so
|
||||
* elementsToSearch contains property declarations only */
|
||||
is PsiMethod, is PsiParameter ->
|
||||
HandlerAndOptions(JavaFindUsagesHandler(element, elementsToSearch.toTypedArray(), factory.javaHandlerFactory),
|
||||
(options as KotlinFunctionFindUsagesOptions?)?.toJavaMethodOptions(project))
|
||||
|
||||
is PsiParameter ->
|
||||
HandlerAndOptions(JavaFindUsagesHandler(element, elementsToSearch.toTypedArray(), factory.javaHandlerFactory),
|
||||
(options as KotlinPropertyFindUsagesOptions?)?.toJavaVariableOptions(project))
|
||||
(options as KotlinCallableFindUsagesOptions?)?.toJavaOptions(project))
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
+7
-12
@@ -21,13 +21,11 @@ import com.intellij.find.findUsages.AbstractFindUsagesDialog
|
||||
import com.intellij.find.findUsages.FindUsagesOptions
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.*
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinCallableFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
||||
@@ -128,17 +126,14 @@ public abstract class KotlinFindMemberUsagesHandler<T : KtNamedDeclaration>
|
||||
scope = options.searchScope,
|
||||
kotlinOptions = createKotlinReferencesSearchOptions(options))
|
||||
|
||||
val query = applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))
|
||||
if (!query.forEach(referenceProcessor)) return false
|
||||
|
||||
val psiMethods = when (element) {
|
||||
is PsiMethod -> listOf(element)
|
||||
is KtFunction -> runReadAction { LightClassUtil.getLightClassMethods(element) }
|
||||
else -> listOf<PsiMethod>()
|
||||
with(applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))) {
|
||||
if (!forEach(referenceProcessor)) return false
|
||||
}
|
||||
for (psiMethod in psiMethods) {
|
||||
val query = applyQueryFilters(element, options, MethodReferencesSearch.search(psiMethod, options.searchScope, true))
|
||||
if (!query.forEach(referenceProcessor)) return false
|
||||
|
||||
for (psiMethod in runReadAction { element.toLightMethods() }) {
|
||||
with(applyQueryFilters(element, options, MethodReferencesSearch.search(psiMethod, options.searchScope, true))) {
|
||||
if (!forEach(referenceProcessor)) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-12
@@ -16,31 +16,72 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.search.ideaExtensions
|
||||
|
||||
import com.intellij.openapi.application.QueryExecutorBase
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.search.MethodTextOccurrenceProcessor
|
||||
import com.intellij.psi.impl.search.MethodUsagesSearcher
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.UsageSearchContext
|
||||
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightMethod
|
||||
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.readWriteAccess
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadActionInSmartMode
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.java.getPropertyNamesCandidatesByAccessorName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
public class KotlinOverridingMethodReferenceSearcher : MethodUsagesSearcher() {
|
||||
override fun processQuery(p: MethodReferencesSearch.SearchParameters, consumer: Processor<PsiReference>) {
|
||||
super.processQuery(p, consumer)
|
||||
|
||||
val method = p.method
|
||||
p.project.runReadActionInSmartMode {
|
||||
val containingClass = method.containingClass ?: return@runReadActionInSmartMode
|
||||
|
||||
val searchScope = p.effectiveSearchScope
|
||||
.intersectWith(method.useScope)
|
||||
.restrictToKotlinSources()
|
||||
if (searchScope === GlobalSearchScope.EMPTY_SCOPE) return@runReadActionInSmartMode
|
||||
|
||||
for (name in getPropertyNamesCandidatesByAccessorName(Name.guess(method.name))) {
|
||||
p.optimizer.searchWord(
|
||||
name.asString(),
|
||||
searchScope,
|
||||
UsageSearchContext.IN_CODE,
|
||||
true,
|
||||
method,
|
||||
getTextOccurrenceProcessor(arrayOf(method), containingClass, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTextOccurrenceProcessor(methods: Array<out PsiMethod>,
|
||||
aClass: PsiClass,
|
||||
strictSignatureSearch: Boolean): MethodTextOccurrenceProcessor? {
|
||||
strictSignatureSearch: Boolean): MethodTextOccurrenceProcessor {
|
||||
return object: MethodTextOccurrenceProcessor(aClass, strictSignatureSearch, *methods) {
|
||||
override fun processInexactReference(ref: PsiReference, refElement: PsiElement?, method: PsiMethod, consumer: Processor<PsiReference>): Boolean {
|
||||
if (refElement !is KtCallableDeclaration) return true
|
||||
return refElement.getRepresentativeLightMethod()?.let { super.processInexactReference(ref, it, method, consumer) } ?: true
|
||||
|
||||
var lightMethods = refElement.toLightMethods()
|
||||
.filterNot { it.hasModifierProperty(PsiModifier.FINAL) }
|
||||
.ifEmpty { return true }
|
||||
if (refElement is KtProperty || refElement is KtParameter) {
|
||||
val isGetter = JvmAbi.isGetterName(method.name)
|
||||
if (ref is KtSimpleNameReference) {
|
||||
val readWriteAccess = ref.expression.readWriteAccess(true)
|
||||
if (readWriteAccess.isRead != isGetter && readWriteAccess.isWrite == isGetter) return true
|
||||
}
|
||||
lightMethods = lightMethods.filter { JvmAbi.isGetterName(it.name) == isGetter }
|
||||
}
|
||||
|
||||
return lightMethods.all { super.processInexactReference(ref, it, method, consumer) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-11
@@ -19,14 +19,12 @@ package org.jetbrains.kotlin.idea.search.ideaExtensions
|
||||
import com.intellij.openapi.application.QueryExecutorBase
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.search.UsageSearchContext
|
||||
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
@@ -37,7 +35,7 @@ public class KotlinPropertyAccessorsReferenceSearcher() : QueryExecutorBase<PsiR
|
||||
val method = queryParameters.getMethod()
|
||||
val propertyName = propertyName(method) ?: return
|
||||
|
||||
val onlyKotlinFiles = restrictToKotlinSources(queryParameters.getEffectiveSearchScope())
|
||||
val onlyKotlinFiles = queryParameters.getEffectiveSearchScope().restrictToKotlinSources()
|
||||
|
||||
queryParameters.getOptimizer()!!.searchWord(
|
||||
propertyName,
|
||||
@@ -58,11 +56,4 @@ public class KotlinPropertyAccessorsReferenceSearcher() : QueryExecutorBase<PsiR
|
||||
val property = SyntheticJavaPropertyDescriptor.findByGetterOrSetter(functionDescriptor, syntheticExtensionsScope) ?: return null
|
||||
return property.getName().asString()
|
||||
}
|
||||
|
||||
private fun restrictToKotlinSources(originalScope: SearchScope): SearchScope {
|
||||
return when (originalScope) {
|
||||
is GlobalSearchScope -> GlobalSearchScope.getScopeRestrictedByFileTypes(originalScope, KotlinFileType.INSTANCE)
|
||||
else -> originalScope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// PSI_ELEMENT: com.intellij.psi.PsiMethod
|
||||
// OPTIONS: usages
|
||||
public class J extends A {
|
||||
@Override
|
||||
public int <caret>getP() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setP(int value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
static void test() {
|
||||
new A().getP();
|
||||
new A().setP(1);
|
||||
|
||||
new AA().getP();
|
||||
new AA().setP(1);
|
||||
|
||||
new J().getP();
|
||||
new J().setP(1);
|
||||
|
||||
new B().getP();
|
||||
new B().setP(1);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
open class A {
|
||||
open var p: Int = 1
|
||||
}
|
||||
|
||||
class AA : A() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
class B : J() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A().p
|
||||
A().p = 1
|
||||
|
||||
val t = AA().p
|
||||
AA().p = 1
|
||||
|
||||
val t = J().p
|
||||
J().p = 1
|
||||
|
||||
val t = B().p
|
||||
B().p = 1
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[javaPropertyGetterUsagesKJ.0.java] Unclassified usage (23: 17) new J().getP();
|
||||
[javaPropertyGetterUsagesKJ.0.java] Unclassified usage (26: 17) new B().getP();
|
||||
[javaPropertyGetterUsagesKJ.1.kt] Value read (20: 17) val t = J().p
|
||||
[javaPropertyGetterUsagesKJ.1.kt] Value read (23: 17) val t = B().p
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// PSI_ELEMENT: com.intellij.psi.PsiMethod
|
||||
// OPTIONS: usages
|
||||
public class J extends A {
|
||||
@Override
|
||||
public int getP() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void <caret>setP(int value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
static void test() {
|
||||
new A().getP();
|
||||
new A().setP(1);
|
||||
|
||||
new AA().getP();
|
||||
new AA().setP(1);
|
||||
|
||||
new J().getP();
|
||||
new J().setP(1);
|
||||
|
||||
new B().getP();
|
||||
new B().setP(1);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
open class A {
|
||||
open var p: Int = 1
|
||||
}
|
||||
|
||||
class AA : A() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
class B : J() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A().p
|
||||
A().p = 1
|
||||
|
||||
val t = AA().p
|
||||
AA().p = 1
|
||||
|
||||
val t = J().p
|
||||
J().p = 1
|
||||
|
||||
val t = B().p
|
||||
B().p = 1
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[javaPropertySetterUsagesKJ.0.java] Unclassified usage (24: 17) new J().setP(1);
|
||||
[javaPropertySetterUsagesKJ.0.java] Unclassified usage (27: 17) new B().setP(1);
|
||||
[javaPropertySetterUsagesKJ.1.kt] Value write (21: 9) J().p = 1
|
||||
[javaPropertySetterUsagesKJ.1.kt] Value write (24: 9) B().p = 1
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
// OPTIONS: usages
|
||||
// FIND_BY_REF
|
||||
|
||||
class A(val n: Int) {
|
||||
override fun <caret>equals(other: Any?): Boolean = other is A && other.n == n
|
||||
override fun equals(other: Any?): Boolean = other is A && other.n <caret>== n
|
||||
}
|
||||
|
||||
fun test() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Function call (10: 10) A(0) != A(1)
|
||||
Function call (11: 10) A(0) equals A(1)
|
||||
Function call (5: 71) override fun equals(other: Any?): Boolean = other is A && other.n == n
|
||||
Function call (9: 10) A(0) == A(1)
|
||||
Function call (10: 10) A(0) == A(1)
|
||||
Function call (11: 10) A(0) != A(1)
|
||||
Function call (12: 10) A(0) equals A(1)
|
||||
Function call (6: 71) override fun equals(other: Any?): Boolean = other is A && other.n == n
|
||||
+2
-2
@@ -6,11 +6,11 @@ interface A {
|
||||
}
|
||||
|
||||
class B: A {
|
||||
override fun <caret>foo() {} // Find usages gives no results
|
||||
override fun foo() {} // Find usages gives no results
|
||||
}
|
||||
|
||||
fun main(a: A) {
|
||||
a.foo()
|
||||
a.<caret>foo()
|
||||
}
|
||||
|
||||
// for KT-3769 Find usages gives no result for overrides
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtProperty
|
||||
// OPTIONS: usages
|
||||
open class A {
|
||||
open var <caret>p: Int = 1
|
||||
}
|
||||
|
||||
class AA : A() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
class B : J() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A().p
|
||||
A().p = 1
|
||||
|
||||
val t = AA().p
|
||||
AA().p = 1
|
||||
|
||||
val t = J().p
|
||||
J().p = 1
|
||||
|
||||
val t = B().p
|
||||
B().p = 1
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
public class J extends A {
|
||||
@Override
|
||||
public int getP() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setP(int value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
static void test() {
|
||||
new A().getP();
|
||||
new A().setP(1);
|
||||
|
||||
new AA().getP();
|
||||
new AA().setP(1);
|
||||
|
||||
new J().getP();
|
||||
new J().setP(1);
|
||||
|
||||
new B().getP();
|
||||
new B().setP(1);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
[javaPropertyUsagesK.0.kt] Value read (16: 17) val t = A().p
|
||||
[javaPropertyUsagesK.0.kt] Value read (19: 18) val t = AA().p
|
||||
[javaPropertyUsagesK.0.kt] Value read (22: 17) val t = J().p
|
||||
[javaPropertyUsagesK.0.kt] Value read (25: 17) val t = B().p
|
||||
[javaPropertyUsagesK.0.kt] Value write (17: 9) A().p = 1
|
||||
[javaPropertyUsagesK.0.kt] Value write (20: 10) AA().p = 1
|
||||
[javaPropertyUsagesK.0.kt] Value write (23: 9) J().p = 1
|
||||
[javaPropertyUsagesK.0.kt] Value write (26: 9) B().p = 1
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (15: 17) new A().getP();
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (16: 17) new A().setP(1);
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (18: 18) new AA().getP();
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (19: 18) new AA().setP(1);
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (21: 17) new J().getP();
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (22: 17) new J().setP(1);
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (24: 17) new B().getP();
|
||||
[javaPropertyUsagesK.1.java] Unclassified usage (25: 17) new B().setP(1);
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtProperty
|
||||
// OPTIONS: usages
|
||||
open class A {
|
||||
open var p: Int = 1
|
||||
}
|
||||
|
||||
class AA : A() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
class B : J() {
|
||||
override var <caret>p: Int = 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A().p
|
||||
A().p = 1
|
||||
|
||||
val t = AA().p
|
||||
AA().p = 1
|
||||
|
||||
val t = J().p
|
||||
J().p = 1
|
||||
|
||||
val t = B().p
|
||||
B().p = 1
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
public class J extends A {
|
||||
@Override
|
||||
public int getP() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setP(int value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
static void test() {
|
||||
new A().getP();
|
||||
new A().setP(1);
|
||||
|
||||
new AA().getP();
|
||||
new AA().setP(1);
|
||||
|
||||
new J().getP();
|
||||
new J().setP(1);
|
||||
|
||||
new B().getP();
|
||||
new B().setP(1);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[javaPropertyUsagesKJK.0.kt] Value read (25: 17) val t = B().p
|
||||
[javaPropertyUsagesKJK.0.kt] Value write (26: 9) B().p = 1
|
||||
[javaPropertyUsagesKJK.1.java] Unclassified usage (24: 17) new B().getP();
|
||||
[javaPropertyUsagesKJK.1.java] Unclassified usage (25: 17) new B().setP(1);
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtProperty
|
||||
// OPTIONS: usages
|
||||
open class A {
|
||||
open var p: Int = 1
|
||||
}
|
||||
|
||||
class AA : A() {
|
||||
override var <caret>p: Int = 1
|
||||
}
|
||||
|
||||
class B : J() {
|
||||
override var p: Int = 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A().p
|
||||
A().p = 1
|
||||
|
||||
val t = AA().p
|
||||
AA().p = 1
|
||||
|
||||
val t = J().p
|
||||
J().p = 1
|
||||
|
||||
val t = B().p
|
||||
B().p = 1
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
public class J extends A {
|
||||
@Override
|
||||
public int getP() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setP(int value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
static void test() {
|
||||
new A().getP();
|
||||
new A().setP(1);
|
||||
|
||||
new AA().getP();
|
||||
new AA().setP(1);
|
||||
|
||||
new J().getP();
|
||||
new J().setP(1);
|
||||
|
||||
new B().getP();
|
||||
new B().setP(1);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[javaPropertyUsagesKK.0.kt] Value read (19: 18) val t = AA().p
|
||||
[javaPropertyUsagesKK.0.kt] Value write (20: 10) AA().p = 1
|
||||
[javaPropertyUsagesKK.1.java] Unclassified usage (18: 18) new AA().getP();
|
||||
[javaPropertyUsagesKK.1.java] Unclassified usage (19: 18) new AA().setP(1);
|
||||
@@ -34,7 +34,6 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiPackage;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
@@ -53,6 +52,7 @@ import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinClassFindUsagesOptions;
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory;
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions;
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions;
|
||||
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase;
|
||||
@@ -433,8 +433,17 @@ public abstract class AbstractJetFindUsagesTest extends JetLightCodeInsightFixtu
|
||||
|
||||
protected Collection<UsageInfo> findUsages(@NotNull PsiElement targetElement, @Nullable FindUsagesOptions options) {
|
||||
Project project = getProject();
|
||||
FindUsagesHandler handler =
|
||||
((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager().getFindUsagesHandler(targetElement, false);
|
||||
|
||||
FindUsagesHandler handler;
|
||||
if (targetElement instanceof PsiMember) {
|
||||
handler = new JavaFindUsagesHandler(targetElement, new JavaFindUsagesHandlerFactory(project));
|
||||
}
|
||||
else if (targetElement instanceof KtDeclaration) {
|
||||
handler = new KotlinFindUsagesHandlerFactory(project).createFindUsagesHandlerNoQuestions(targetElement);
|
||||
}
|
||||
else {
|
||||
handler = ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager().getFindUsagesHandler(targetElement, false);
|
||||
}
|
||||
assert handler != null : "Cannot find handler for: " + targetElement;
|
||||
|
||||
if (options == null) {
|
||||
|
||||
@@ -685,6 +685,33 @@ public class JetFindUsagesTestGenerated extends AbstractJetFindUsagesTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/findUsages/kotlin/findJavaPropertyUsages")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class FindJavaPropertyUsages extends AbstractJetFindUsagesTest {
|
||||
public void testAllFilesPresentInFindJavaPropertyUsages() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/kotlin/findJavaPropertyUsages"), Pattern.compile("^(.+)\\.0\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("javaPropertyUsagesK.0.kt")
|
||||
public void testJavaPropertyUsagesK() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/findJavaPropertyUsages/javaPropertyUsagesK.0.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaPropertyUsagesKJK.0.kt")
|
||||
public void testJavaPropertyUsagesKJK() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/findJavaPropertyUsages/javaPropertyUsagesKJK.0.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaPropertyUsagesKK.0.kt")
|
||||
public void testJavaPropertyUsagesKK() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/findJavaPropertyUsages/javaPropertyUsagesKK.0.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/findUsages/kotlin/findObjectUsages")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@@ -1341,6 +1368,27 @@ public class JetFindUsagesTestGenerated extends AbstractJetFindUsagesTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/findUsages/java/findJavaPropertyUsages")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class FindJavaPropertyUsages extends AbstractJetFindUsagesTest {
|
||||
public void testAllFilesPresentInFindJavaPropertyUsages() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/java/findJavaPropertyUsages"), Pattern.compile("^(.+)\\.0\\.java$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("javaPropertyGetterUsagesKJ.0.java")
|
||||
public void testJavaPropertyGetterUsagesKJ() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/java/findJavaPropertyUsages/javaPropertyGetterUsagesKJ.0.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaPropertySetterUsagesKJ.0.java")
|
||||
public void testJavaPropertySetterUsagesKJ() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/java/findJavaPropertyUsages/javaPropertySetterUsagesKJ.0.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/findUsages/propertyFiles")
|
||||
|
||||
Reference in New Issue
Block a user