Kotlin Uast: Support object literals
This commit is contained in:
@@ -859,9 +859,6 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
owner = null;
|
owner = null;
|
||||||
}
|
}
|
||||||
} else if (owner.startsWith("java/")) { //$NON-NLS-1$
|
} else if (owner.startsWith("java/")) { //$NON-NLS-1$
|
||||||
if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) {
|
|
||||||
checkSimpleDateFormat(context, method, node, minSdk);
|
|
||||||
}
|
|
||||||
// Already inlined; see comment above
|
// Already inlined; see comment above
|
||||||
owner = null;
|
owner = null;
|
||||||
} else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
|
} else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ class UastModifier(val name: String) {
|
|||||||
val STATIC = UastModifier("static")
|
val STATIC = UastModifier("static")
|
||||||
@JvmField
|
@JvmField
|
||||||
val FINAL = UastModifier("final")
|
val FINAL = UastModifier("final")
|
||||||
|
@JvmField
|
||||||
|
val VARARG = UastModifier("vararg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.uast.kinds.KotlinSpecialExpressionKinds
|
||||||
import org.jetbrains.kotlin.uast.lint.PropertyAsCallAndroidUastAdditionalChecker
|
import org.jetbrains.kotlin.uast.lint.PropertyAsCallAndroidUastAdditionalChecker
|
||||||
import org.jetbrains.uast.*
|
import org.jetbrains.uast.*
|
||||||
|
|
||||||
@@ -57,6 +58,12 @@ internal object KotlinConverter : UastConverter {
|
|||||||
annotations = element.entries.map { KotlinUAnnotation(it, this) }
|
annotations = element.entries.map { KotlinUAnnotation(it, this) }
|
||||||
}
|
}
|
||||||
is KtDeclaration -> convert(element, parent)
|
is KtDeclaration -> convert(element, parent)
|
||||||
|
is KtParameterList -> KotlinUDeclarationsExpression(parent).apply {
|
||||||
|
declarations = element.parameters.map { convert(it, this) }
|
||||||
|
}
|
||||||
|
is KtClassBody -> KotlinUSpecialExpressionList(element, KotlinSpecialExpressionKinds.CLASS_BODY, parent).apply {
|
||||||
|
expressions = emptyList()
|
||||||
|
}
|
||||||
is KtImportDirective -> KotlinUImportStatement(element, parent)
|
is KtImportDirective -> KotlinUImportStatement(element, parent)
|
||||||
is KtCatchClause -> KotlinUCatchClause(element, parent)
|
is KtCatchClause -> KotlinUCatchClause(element, parent)
|
||||||
is KtExpression -> KotlinConverter.convert(element, parent)
|
is KtExpression -> KotlinConverter.convert(element, parent)
|
||||||
@@ -121,6 +128,7 @@ internal object KotlinConverter : UastConverter {
|
|||||||
}
|
}
|
||||||
declarations = listOf(tempAssignment) + destructuringAssignments
|
declarations = listOf(tempAssignment) + destructuringAssignments
|
||||||
}
|
}
|
||||||
|
is KtObjectLiteralExpression -> KotlinUObjectLiteralExpression(expression, parent)
|
||||||
is KtStringTemplateEntry -> convertOrEmpty(expression.expression, parent)
|
is KtStringTemplateEntry -> convertOrEmpty(expression.expression, parent)
|
||||||
is KtDotQualifiedExpression -> KotlinUQualifiedExpression(expression, parent)
|
is KtDotQualifiedExpression -> KotlinUQualifiedExpression(expression, parent)
|
||||||
is KtSafeQualifiedExpression -> KotlinUSafeQualifiedExpression(expression, parent)
|
is KtSafeQualifiedExpression -> KotlinUSafeQualifiedExpression(expression, parent)
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.uast
|
package org.jetbrains.kotlin.uast
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
||||||
import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
|
import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
|
||||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.name.SpecialNames
|
import org.jetbrains.kotlin.name.SpecialNames
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
@@ -40,12 +40,22 @@ import org.jetbrains.uast.psi.PsiElementBacked
|
|||||||
|
|
||||||
class KotlinUClass(
|
class KotlinUClass(
|
||||||
override val psi: KtClassOrObject,
|
override val psi: KtClassOrObject,
|
||||||
override val parent: UElement
|
override val parent: UElement,
|
||||||
|
override val isAnonymous: Boolean = false
|
||||||
) : UClass, PsiElementBacked {
|
) : UClass, PsiElementBacked {
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = psi.name.orAnonymous()
|
get() = psi.name.orAnonymous()
|
||||||
|
|
||||||
override val nameElement by lz { KotlinConverter.asSimpleReference(psi.nameIdentifier, this) }
|
override val nameElement by lz {
|
||||||
|
fun objectKeyword() = try { // !! in getObjectKeyword()
|
||||||
|
if (psi is KtObjectDeclaration && psi.isObjectLiteral()) psi.getObjectKeyword() else null
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val namePsiElement = psi.nameIdentifier ?: objectKeyword()
|
||||||
|
KotlinConverter.asSimpleReference(namePsiElement, this)
|
||||||
|
}
|
||||||
|
|
||||||
override val fqName: String?
|
override val fqName: String?
|
||||||
get() = psi.fqName?.asString()
|
get() = psi.fqName?.asString()
|
||||||
@@ -75,25 +85,37 @@ class KotlinUClass(
|
|||||||
(psi as? KtClass)?.getCompanionObjects()?.map { KotlinConverter.convert(it, this) } ?: emptyList()
|
(psi as? KtClass)?.getCompanionObjects()?.map { KotlinConverter.convert(it, this) } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isAnonymous = false
|
|
||||||
|
|
||||||
override val internalName by lz {
|
override val internalName by lz {
|
||||||
val descriptor = resolveToDescriptor() ?: return@lz null
|
val descriptor = resolveToDescriptor() ?: return@lz null
|
||||||
val typeMapper = JetTypeMapper(BindingContext.EMPTY, ClassBuilderMode.LIGHT_CLASSES, NoResolveFileClassesProvider, null,
|
val typeMapper = KotlinTypeMapper(BindingContext.EMPTY, ClassBuilderMode.LIGHT_CLASSES, NoResolveFileClassesProvider, null,
|
||||||
IncompatibleClassTracker.DoNothing, JvmAbi.DEFAULT_MODULE_NAME)
|
IncompatibleClassTracker.DoNothing, JvmAbi.DEFAULT_MODULE_NAME)
|
||||||
typeMapper.mapClass(descriptor).internalName
|
typeMapper.mapClass(descriptor).internalName
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSuperClass(context: UastContext): UClass? {
|
override fun getSuperClass(context: UastContext): UClass? {
|
||||||
val superClass = resolveToDescriptor()?.getSuperClassOrAny() ?: return null
|
val descriptor = resolveToDescriptor() ?: return null
|
||||||
val source = DescriptorToSourceUtilsIde.getAnyDeclaration(psi.project, superClass) ?: return null
|
if (KotlinBuiltIns.isAny(descriptor)) return null
|
||||||
|
val source = descriptor.getSuperClassOrAny().toSource(psi.project) ?: return null
|
||||||
return context.convert(source) as? UClass
|
return context.convert(source) as? UClass
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasModifier(modifier: UastModifier) = psi.hasModifier(modifier)
|
override fun hasModifier(modifier: UastModifier) = psi.hasModifier(modifier)
|
||||||
|
|
||||||
override val declarations by lz {
|
override val declarations by lz {
|
||||||
val primaryConstructor = psi.getPrimaryConstructor()?.let { KotlinConverter.convert(it, this) }
|
val primaryConstructor = if (psi is KtObjectDeclaration && psi.isObjectLiteral()) {
|
||||||
|
val obj = psi
|
||||||
|
object : KotlinObjectLiteralConstructorUFunction(obj, this) {
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
psi.getPrimaryConstructor()?.let { KotlinConverter.convert(it, this) } ?: run {
|
||||||
|
if (psi.getSecondaryConstructors().isEmpty())
|
||||||
|
KotlinDefaultPrimaryConstructorUFunction(psi, this)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val anonymousInitializers = psi.getAnonymousInitializers().map { KotlinConverter.convert(it, this) }.filterNotNull()
|
val anonymousInitializers = psi.getAnonymousInitializers().map { KotlinConverter.convert(it, this) }.filterNotNull()
|
||||||
val declarations = psi.declarations.map { KotlinConverter.convert(it, this) }.filterNotNull()
|
val declarations = psi.declarations.map { KotlinConverter.convert(it, this) }.filterNotNull()
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.uast
|
package org.jetbrains.kotlin.uast
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
@@ -125,4 +126,103 @@ class KotlinAnonymousInitializerUFunction(
|
|||||||
|
|
||||||
override val annotations: List<UAnnotation>
|
override val annotations: List<UAnnotation>
|
||||||
get() = emptyList()
|
get() = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class KotlinDefaultPrimaryConstructorUFunction(
|
||||||
|
override val psi: KtClassOrObject,
|
||||||
|
override val parent: UClass
|
||||||
|
) : UFunction, PsiElementBacked, NoModifiers, NoAnnotations {
|
||||||
|
override val kind: UastFunctionKind
|
||||||
|
get() = UastFunctionKind.CONSTRUCTOR
|
||||||
|
|
||||||
|
override val nameElement by lz { psi.nameIdentifier?.let { KotlinPsiElementStub(it, this) } }
|
||||||
|
override val name: String
|
||||||
|
get() = "<init>"
|
||||||
|
|
||||||
|
override val valueParameters: List<UVariable>
|
||||||
|
get() = emptyList()
|
||||||
|
override val valueParameterCount: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
override val typeParameters: List<UTypeReference>
|
||||||
|
get() = emptyList()
|
||||||
|
override val typeParameterCount: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
override val returnType: UType?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override val body: UExpression = EmptyExpression(this)
|
||||||
|
|
||||||
|
override val visibility: UastVisibility
|
||||||
|
get() = parent.visibility
|
||||||
|
|
||||||
|
override fun getSuperFunctions(context: UastContext) = emptyList<UFunction>()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class KotlinObjectLiteralConstructorUFunction(
|
||||||
|
override val psi: KtObjectDeclaration,
|
||||||
|
override val parent: UClass
|
||||||
|
) : UFunction, PsiElementBacked, NoModifiers, NoAnnotations {
|
||||||
|
private val resolvedCall by lz {
|
||||||
|
val bindingContext = psi.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, psi] as? ClassDescriptor
|
||||||
|
val primaryConstructor = descriptor?.unsubstitutedPrimaryConstructor ?: return@lz null
|
||||||
|
bindingContext[BindingContext.CONSTRUCTOR_RESOLVED_DELEGATION_CALL, primaryConstructor]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val kind: UastFunctionKind
|
||||||
|
get() = UastFunctionKind.CONSTRUCTOR
|
||||||
|
|
||||||
|
override val nameElement by lz { psi.nameIdentifier?.let { KotlinPsiElementStub(it, this) } }
|
||||||
|
override val name: String
|
||||||
|
get() = "<init>"
|
||||||
|
|
||||||
|
|
||||||
|
override val valueParameters by lz {
|
||||||
|
val params = resolvedCall?.valueArguments?.keys ?: return@lz emptyList<UVariable>()
|
||||||
|
params.map { param ->
|
||||||
|
object : UVariable {
|
||||||
|
override val initializer: UExpression?
|
||||||
|
get() = null
|
||||||
|
override val kind: UastVariableKind
|
||||||
|
get() = UastVariableKind.VALUE_PARAMETER
|
||||||
|
override val type: UType
|
||||||
|
get() = KotlinConverter.convert(param.type, psi.project, this)
|
||||||
|
override val nameElement: UElement?
|
||||||
|
get() = null
|
||||||
|
override val parent: UElement?
|
||||||
|
get() = this@KotlinObjectLiteralConstructorUFunction
|
||||||
|
override val name: String
|
||||||
|
get() = param.name.asString()
|
||||||
|
|
||||||
|
override fun hasModifier(modifier: UastModifier) = when(modifier) {
|
||||||
|
UastModifier.VARARG -> param.varargElementType != null
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override val annotations: List<UAnnotation>
|
||||||
|
get() = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val valueParameterCount: Int
|
||||||
|
get() = resolvedCall?.valueArgumentsByIndex?.size ?: 0
|
||||||
|
|
||||||
|
override val typeParameters: List<UTypeReference>
|
||||||
|
get() = emptyList()
|
||||||
|
|
||||||
|
override val typeParameterCount: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
override val returnType: UType?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override val body: UExpression = EmptyExpression(this)
|
||||||
|
|
||||||
|
override val visibility: UastVisibility
|
||||||
|
get() = parent.visibility
|
||||||
|
|
||||||
|
override fun getSuperFunctions(context: UastContext) = emptyList<UFunction>()
|
||||||
}
|
}
|
||||||
+61
@@ -95,4 +95,65 @@ class KotlinUComponentFunctionCallExpression(
|
|||||||
override val functionNameElement = null
|
override val functionNameElement = null
|
||||||
override val kind = UastCallKind.FUNCTION_CALL
|
override val kind = UastCallKind.FUNCTION_CALL
|
||||||
override fun resolve(context: UastContext) = null
|
override fun resolve(context: UastContext) = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class KotlinUSuperConstructorCallExpression(
|
||||||
|
override val psi: KtObjectDeclaration,
|
||||||
|
override val parent: UElement,
|
||||||
|
val resolvedCall: () -> ResolvedCall<*>?
|
||||||
|
) : UCallExpression, PsiElementBacked, NoEvaluate {
|
||||||
|
override val functionReference: USimpleReferenceExpression?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override val classReference: USimpleReferenceExpression by lz {
|
||||||
|
val referenceParent = this
|
||||||
|
object : USimpleReferenceExpression, PsiElementBacked, NoEvaluate {
|
||||||
|
override val parent: UElement?
|
||||||
|
get() = referenceParent
|
||||||
|
|
||||||
|
override val identifier: String
|
||||||
|
get() = resolvedCall()?.resultingDescriptor?.name?.asString().orAnonymous()
|
||||||
|
|
||||||
|
override fun resolve(context: UastContext): UDeclaration? {
|
||||||
|
val descriptor = resolvedCall()?.resultingDescriptor ?: return null
|
||||||
|
val source = DescriptorToSourceUtilsIde.getAnyDeclaration(psi.project, descriptor) ?: return null
|
||||||
|
return context.convert(source) as? UDeclaration
|
||||||
|
}
|
||||||
|
|
||||||
|
override val psi: PsiElement
|
||||||
|
get() = this@KotlinUSuperConstructorCallExpression.psi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val functionName: String?
|
||||||
|
get() = "<init>"
|
||||||
|
|
||||||
|
override val functionNameElement by lz {
|
||||||
|
try {
|
||||||
|
KotlinPsiElementStub(psi.getObjectKeyword(), this)
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val valueArgumentCount: Int
|
||||||
|
get() = resolvedCall()?.valueArguments?.size ?: 0
|
||||||
|
|
||||||
|
override val valueArguments by lz {
|
||||||
|
resolvedCall()?.call?.valueArguments?.map { KotlinConverter.convertOrEmpty(it.getArgumentExpression(), this) } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val typeArgumentCount: Int
|
||||||
|
get() = 0
|
||||||
|
override val typeArguments: List<UType>
|
||||||
|
get() = emptyList()
|
||||||
|
|
||||||
|
override val kind: UastCallKind
|
||||||
|
get() = UastCallKind.CONSTRUCTOR_CALL
|
||||||
|
|
||||||
|
override fun resolve(context: UastContext): UFunction? {
|
||||||
|
val descriptor = resolvedCall()?.resultingDescriptor ?: return null
|
||||||
|
val source = DescriptorToSourceUtilsIde.getAnyDeclaration(psi.project, descriptor) ?: return null
|
||||||
|
return context.convert(source) as? UFunction
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.uast
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.psi.KtObjectLiteralExpression
|
||||||
|
import org.jetbrains.uast.NoEvaluate
|
||||||
|
import org.jetbrains.uast.UElement
|
||||||
|
import org.jetbrains.uast.UObjectLiteralExpression
|
||||||
|
import org.jetbrains.uast.psi.PsiElementBacked
|
||||||
|
|
||||||
|
class KotlinUObjectLiteralExpression(
|
||||||
|
override val psi: KtObjectLiteralExpression,
|
||||||
|
override val parent: UElement
|
||||||
|
) : UObjectLiteralExpression, PsiElementBacked, KotlinTypeHelper, NoEvaluate {
|
||||||
|
override val declaration by lz { KotlinUClass(psi.objectDeclaration, this, true) }
|
||||||
|
}
|
||||||
+1
-1
@@ -29,7 +29,7 @@ import org.jetbrains.uast.UastSpecialExpressionKind
|
|||||||
import org.jetbrains.uast.psi.PsiElementBacked
|
import org.jetbrains.uast.psi.PsiElementBacked
|
||||||
|
|
||||||
open class KotlinUSpecialExpressionList(
|
open class KotlinUSpecialExpressionList(
|
||||||
override val psi: PsiElement,
|
override val psi: PsiElement?,
|
||||||
override val kind: UastSpecialExpressionKind, // original element
|
override val kind: UastSpecialExpressionKind, // original element
|
||||||
override val parent: UElement
|
override val parent: UElement
|
||||||
) : USpecialExpressionList, PsiElementBacked, KotlinTypeHelper, KotlinEvaluateHelper {
|
) : USpecialExpressionList, PsiElementBacked, KotlinTypeHelper, KotlinEvaluateHelper {
|
||||||
|
|||||||
+4
-1
@@ -26,5 +26,8 @@ object KotlinSpecialExpressionKinds {
|
|||||||
val WHEN_ENTRY = UastSpecialExpressionKind("when_entry")
|
val WHEN_ENTRY = UastSpecialExpressionKind("when_entry")
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val DESTRUCTURING = UastSpecialExpressionKind("destructuring")
|
val CLASS_BODY = UastSpecialExpressionKind("class_body")
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val VARARG = UastSpecialExpressionKind("vararg")
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user