[FIR] Support several annotation class diagnostics
This commit is contained in:
committed by
Mikhail Glukhikh
parent
c112d37ac1
commit
80cd26c9df
+23
@@ -0,0 +1,23 @@
|
||||
class Foo
|
||||
|
||||
enum class Enum {
|
||||
Bar
|
||||
}
|
||||
|
||||
annotation class Ann1(
|
||||
val p1: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Int><!>,
|
||||
val p2: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Int?><!>,
|
||||
val p3: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<IntArray><!>,
|
||||
val p4: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Foo><!>,
|
||||
val p5: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Foo<!>,
|
||||
vararg val p6: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Class<*><!>
|
||||
)
|
||||
|
||||
annotation class Ann2(
|
||||
val p1: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Int?<!>,
|
||||
val p2: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>String?<!>,
|
||||
val p3: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>IntArray?<!>,
|
||||
val p4: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Array<Int>?<!>,
|
||||
val p5: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Ann1?<!>,
|
||||
val p6: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Enum?<!>
|
||||
)
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
FILE: typeOfAnnotationMember.kt
|
||||
public final class Foo : R|kotlin/Any| {
|
||||
public constructor(): R|Foo| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
}
|
||||
public final enum class Enum : R|kotlin/Enum<Enum>| {
|
||||
private constructor(): R|Enum| {
|
||||
super<R|kotlin/Enum<Enum>|>()
|
||||
}
|
||||
|
||||
public final static enum entry Bar: R|Enum|
|
||||
public final static fun values(): R|kotlin/Array<Enum>| {
|
||||
}
|
||||
|
||||
public final static fun valueOf(value: R|kotlin/String|): R|Enum| {
|
||||
}
|
||||
|
||||
}
|
||||
public final annotation class Ann1 : R|kotlin/Annotation| {
|
||||
public constructor(p1: R|kotlin/Array<kotlin/Int>|, p2: R|kotlin/Array<kotlin/Int?>|, p3: R|kotlin/Array<kotlin/IntArray>|, p4: R|kotlin/Array<Foo>|, p5: R|Foo|, vararg p6: R|kotlin/Array<out java/lang/Class<*>>|): R|Ann1| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final val p1: R|kotlin/Array<kotlin/Int>| = R|<local>/p1|
|
||||
public get(): R|kotlin/Array<kotlin/Int>|
|
||||
|
||||
public final val p2: R|kotlin/Array<kotlin/Int?>| = R|<local>/p2|
|
||||
public get(): R|kotlin/Array<kotlin/Int?>|
|
||||
|
||||
public final val p3: R|kotlin/Array<kotlin/IntArray>| = R|<local>/p3|
|
||||
public get(): R|kotlin/Array<kotlin/IntArray>|
|
||||
|
||||
public final val p4: R|kotlin/Array<Foo>| = R|<local>/p4|
|
||||
public get(): R|kotlin/Array<Foo>|
|
||||
|
||||
public final val p5: R|Foo| = R|<local>/p5|
|
||||
public get(): R|Foo|
|
||||
|
||||
public final val p6: R|kotlin/Array<out java/lang/Class<*>>| = R|<local>/p6|
|
||||
public get(): R|kotlin/Array<out java/lang/Class<*>>|
|
||||
|
||||
}
|
||||
public final annotation class Ann2 : R|kotlin/Annotation| {
|
||||
public constructor(p1: R|kotlin/Int?|, p2: R|kotlin/String?|, p3: R|kotlin/IntArray?|, p4: R|kotlin/Array<kotlin/Int>?|, p5: R|Ann1?|, p6: R|Enum?|): R|Ann2| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final val p1: R|kotlin/Int?| = R|<local>/p1|
|
||||
public get(): R|kotlin/Int?|
|
||||
|
||||
public final val p2: R|kotlin/String?| = R|<local>/p2|
|
||||
public get(): R|kotlin/String?|
|
||||
|
||||
public final val p3: R|kotlin/IntArray?| = R|<local>/p3|
|
||||
public get(): R|kotlin/IntArray?|
|
||||
|
||||
public final val p4: R|kotlin/Array<kotlin/Int>?| = R|<local>/p4|
|
||||
public get(): R|kotlin/Array<kotlin/Int>?|
|
||||
|
||||
public final val p5: R|Ann1?| = R|<local>/p5|
|
||||
public get(): R|Ann1?|
|
||||
|
||||
public final val p6: R|Enum?| = R|<local>/p6|
|
||||
public get(): R|Enum?|
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ annotation class WithString(val s: String)
|
||||
|
||||
annotation class Complex(val wi: WithInt, val ws: WithString)
|
||||
|
||||
annotation class VeryComplex(val f: Float, val d: Double, val b: Boolean, val l: Long, val n: Int?)
|
||||
annotation class VeryComplex(val f: Float, val d: Double, val b: Boolean, val l: Long, val n: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Int?<!>)
|
||||
|
||||
// FILE: main.kt
|
||||
|
||||
|
||||
Generated
+5
@@ -871,6 +871,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("typeOfAnnotationMember.kt")
|
||||
public void testTypeOfAnnotationMember() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("valOnAnnotationParameter.kt")
|
||||
public void testValOnAnnotationParameter() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
|
||||
|
||||
+5
@@ -871,6 +871,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("typeOfAnnotationMember.kt")
|
||||
public void testTypeOfAnnotationMember() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("valOnAnnotationParameter.kt")
|
||||
public void testValOnAnnotationParameter() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
|
||||
|
||||
+1
-1
@@ -11,10 +11,10 @@ import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||
|
||||
object DeclarationCheckers {
|
||||
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf(
|
||||
FirAnnotationClassDeclarationChecker,
|
||||
FirModifierChecker
|
||||
)
|
||||
val MEMBER_DECLARATIONS: List<FirDeclarationChecker<FirMemberDeclaration>> = DECLARATIONS + listOf(
|
||||
FirAnnotationClassDeclarationChecker,
|
||||
FirInfixFunctionDeclarationChecker,
|
||||
FirExposedVisibilityChecker
|
||||
)
|
||||
|
||||
+91
-1
@@ -9,6 +9,7 @@ import com.intellij.lang.LighterASTNode
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind.ANNOTATION_CLASS
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
|
||||
@@ -16,11 +17,19 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.FirLightSourceElement
|
||||
import org.jetbrains.kotlin.fir.FirPsiSourceElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveArrayTypeByElementType
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveTypes
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.unsignedTypes
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.KtNodeTypes.FUN
|
||||
import org.jetbrains.kotlin.KtNodeTypes.VALUE_PARAMETER
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.VAL_KEYWORD
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.VAR_KEYWORD
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
|
||||
object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclaration>() {
|
||||
@@ -28,13 +37,15 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
|
||||
if (declaration !is FirRegularClass) return
|
||||
if (declaration.classKind != ANNOTATION_CLASS) return
|
||||
if (declaration.isLocal) reporter.report(declaration.source, FirErrors.LOCAL_ANNOTATION_CLASS_ERROR)
|
||||
|
||||
for (it in declaration.declarations) {
|
||||
when {
|
||||
it is FirConstructor && it.isPrimary -> {
|
||||
for (parameter in it.valueParameters)
|
||||
for (parameter in it.valueParameters) {
|
||||
when (val parameterSourceElement = parameter.source) {
|
||||
is FirPsiSourceElement<*> -> {
|
||||
val parameterPsiElement = parameterSourceElement.psi as KtParameter
|
||||
|
||||
if (!parameterPsiElement.hasValOrVar())
|
||||
reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
|
||||
else if (parameterPsiElement.isMutable)
|
||||
@@ -43,12 +54,52 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
|
||||
is FirLightSourceElement -> {
|
||||
val kidsRef = Ref<Array<LighterASTNode?>>()
|
||||
parameterSourceElement.tree.getChildren(parameterSourceElement.element, kidsRef)
|
||||
|
||||
if (kidsRef.get().any { it?.tokenType == VAR_KEYWORD })
|
||||
reporter.report(parameterSourceElement, FirErrors.VAR_ANNOTATION_PARAMETER)
|
||||
else if (kidsRef.get().all { it?.tokenType != VAL_KEYWORD })
|
||||
reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
|
||||
}
|
||||
}
|
||||
|
||||
val typeRef = parameter.returnTypeRef
|
||||
val coneType = typeRef.coneTypeSafe<ConeLookupTagBasedType>()
|
||||
val classId = coneType?.classId
|
||||
|
||||
if (coneType != null) when {
|
||||
classId == ClassId.fromString("<error>") -> {
|
||||
// TODO: replace with UNRESOLVED_REFERENCE check
|
||||
}
|
||||
coneType.isNullable -> {
|
||||
reporter.report(typeRef.source, FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER)
|
||||
}
|
||||
classId in primitiveTypes -> {
|
||||
// DO NOTHING: primitives are allowed as annotation class parameter
|
||||
}
|
||||
classId in unsignedTypes -> {
|
||||
// TODO: replace with EXPERIMENTAL_UNSIGNED_LITERALS check
|
||||
}
|
||||
classId == StandardClassIds.KClass -> {
|
||||
// DO NOTHING: KClass is allowed
|
||||
}
|
||||
classId == StandardClassIds.String -> {
|
||||
// DO NOTHING: String is allowed
|
||||
}
|
||||
classId in primitiveArrayTypeByElementType.values -> {
|
||||
// DO NOTHING: primitive arrays are allowed
|
||||
}
|
||||
classId == StandardClassIds.Array -> {
|
||||
if (!isAllowedArray(typeRef, context.session))
|
||||
reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
|
||||
}
|
||||
isAllowedClassKind(coneType, context.session) -> {
|
||||
// DO NOTHING: annotation or enum classes are allowed
|
||||
}
|
||||
else -> {
|
||||
reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it is FirRegularClass -> {
|
||||
// DO NOTHING: nested annotation classes are allowed in 1.3+
|
||||
@@ -65,7 +116,46 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAllowedClassKind(cone: ConeLookupTagBasedType, session: FirSession): Boolean {
|
||||
val typeRefClassKind = (cone.lookupTag.toSymbol(session)
|
||||
?.fir as? FirRegularClass)
|
||||
?.classKind
|
||||
?: return false
|
||||
|
||||
return typeRefClassKind == ANNOTATION_CLASS || typeRefClassKind == ENUM_CLASS
|
||||
}
|
||||
|
||||
private fun isAllowedArray(typeRef: FirTypeRef, session: FirSession): Boolean {
|
||||
val typeArguments = typeRef.coneTypeUnsafe<ConeKotlinType>().typeArguments
|
||||
|
||||
if (typeArguments.size != 1) return false
|
||||
|
||||
val arrayType = (typeArguments[0] as? ConeKotlinTypeProjection)
|
||||
?.type
|
||||
?: return false
|
||||
|
||||
if (arrayType.isNullable) return false
|
||||
|
||||
val arrayTypeClassId = arrayType.classId
|
||||
|
||||
when {
|
||||
arrayTypeClassId == StandardClassIds.KClass -> {
|
||||
// KClass is allowed
|
||||
return true
|
||||
}
|
||||
arrayTypeClassId == StandardClassIds.String -> {
|
||||
// String is allowed
|
||||
return true
|
||||
}
|
||||
isAllowedClassKind(arrayType as ConeLookupTagBasedType, session) -> {
|
||||
// annotation or enum classes are allowed
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private inline fun <reified T : FirSourceElement, P : PsiElement> DiagnosticReporter.report(
|
||||
|
||||
@@ -54,8 +54,11 @@ object FirErrors {
|
||||
val NON_PRIVATE_CONSTRUCTOR_IN_SEALED by existing<FirSourceElement, PsiElement>(Errors.NON_PRIVATE_CONSTRUCTOR_IN_SEALED)
|
||||
|
||||
val ANNOTATION_CLASS_MEMBER by existing<FirSourceElement, PsiElement>(Errors.ANNOTATION_CLASS_MEMBER)
|
||||
val ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT by existing<FirSourceElement, KtExpression>(Errors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT)
|
||||
val LOCAL_ANNOTATION_CLASS_ERROR by existing<FirSourceElement, KtClassOrObject>(Errors.LOCAL_ANNOTATION_CLASS_ERROR)
|
||||
val MISSING_VAL_ON_ANNOTATION_PARAMETER by existing<FirSourceElement, KtParameter>(Errors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
|
||||
val NULLABLE_TYPE_OF_ANNOTATION_MEMBER by existing<FirSourceElement, KtTypeReference>(Errors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER)
|
||||
val INVALID_TYPE_OF_ANNOTATION_MEMBER by existing<FirSourceElement, KtTypeReference>(Errors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
|
||||
val VAR_ANNOTATION_PARAMETER by existing<FirSourceElement, KtParameter>(Errors.VAR_ANNOTATION_PARAMETER)
|
||||
|
||||
// Exposed visibility group
|
||||
|
||||
+22
-22
@@ -25,11 +25,11 @@ annotation class Ann4(val p1: IntArray,
|
||||
|
||||
annotation class Ann5(val p1: MyEnum)
|
||||
|
||||
annotation class Ann6(val p: Class<*>)
|
||||
annotation class Ann6(val p: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Class<*><!>)
|
||||
annotation class Ann7(val p: RetentionPolicy)
|
||||
|
||||
annotation class Ann8(val p1: Array<String>,
|
||||
val p2: Array<Class<*>>,
|
||||
val p2: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Class<*>><!>,
|
||||
val p3: Array<MyEnum>,
|
||||
val p4: Array<Ann1>)
|
||||
|
||||
@@ -39,32 +39,32 @@ annotation class Ann9(
|
||||
|
||||
|
||||
// INCORRECT
|
||||
annotation class InAnn1(val p1: Int?,
|
||||
val p3: Short?,
|
||||
val p4: Long?,
|
||||
val p5: Double?,
|
||||
val p6: Float?,
|
||||
val p7: Char?,
|
||||
val p8: Boolean?)
|
||||
annotation class InAnn1(val p1: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Int?<!>,
|
||||
val p3: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Short?<!>,
|
||||
val p4: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Long?<!>,
|
||||
val p5: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Double?<!>,
|
||||
val p6: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Float?<!>,
|
||||
val p7: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Char?<!>,
|
||||
val p8: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Boolean?<!>)
|
||||
|
||||
annotation class InAnn4(val p1: Array<Int>,
|
||||
val p2: Array<Int>?)
|
||||
annotation class InAnn4(val p1: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Int><!>,
|
||||
val p2: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Array<Int>?<!>)
|
||||
|
||||
annotation class InAnn6(val p: Class<*>?)
|
||||
annotation class InAnn7(val p: RetentionPolicy?)
|
||||
annotation class InAnn8(val p1: Array<Int>,
|
||||
val p2: Array<Int?>,
|
||||
val p3: Array<MyClass>,
|
||||
val p4: Array<IntArray>)
|
||||
annotation class InAnn6(val p: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Class<*>?<!>)
|
||||
annotation class InAnn7(val p: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>RetentionPolicy?<!>)
|
||||
annotation class InAnn8(val p1: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Int><!>,
|
||||
val p2: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<Int?><!>,
|
||||
val p3: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<MyClass><!>,
|
||||
val p4: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<IntArray><!>)
|
||||
|
||||
annotation class InAnn9(val p: MyClass)
|
||||
annotation class InAnn9(val p: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>MyClass<!>)
|
||||
|
||||
annotation class InAnn10(val p1: String?)
|
||||
annotation class InAnn11(val p1: Ann1?)
|
||||
annotation class InAnn12(val p1: MyEnum?)
|
||||
annotation class InAnn10(val p1: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>String?<!>)
|
||||
annotation class InAnn11(val p1: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>Ann1?<!>)
|
||||
annotation class InAnn12(val p1: <!NULLABLE_TYPE_OF_ANNOTATION_MEMBER!>MyEnum?<!>)
|
||||
|
||||
annotation class InAnn13(vararg val p1: String,
|
||||
vararg val p2: Class<*>,
|
||||
vararg val p2: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Class<*><!>,
|
||||
vararg val p3: MyEnum,
|
||||
vararg val p4: Ann1,
|
||||
vararg val p5: Int)
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@ import kotlin.reflect.KClass
|
||||
inline class MyInt(val x: Int)
|
||||
inline class MyString(val x: String)
|
||||
|
||||
annotation class Ann1(val a: MyInt)
|
||||
annotation class Ann2(val a: Array<MyString>)
|
||||
annotation class Ann3(vararg val a: MyInt)
|
||||
annotation class Ann1(val a: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>MyInt<!>)
|
||||
annotation class Ann2(val a: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Array<MyString><!>)
|
||||
annotation class Ann3(vararg val a: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>MyInt<!>)
|
||||
|
||||
annotation class Ann4(val a: KClass<MyInt>)
|
||||
Vendored
+1
-1
@@ -18,4 +18,4 @@ class B(vararg val s: Foo) {
|
||||
constructor(a: Int, vararg s: Foo) : this(*s)
|
||||
}
|
||||
|
||||
annotation class Ann(vararg val f: Foo)
|
||||
annotation class Ann(vararg val f: <!INVALID_TYPE_OF_ANNOTATION_MEMBER!>Foo<!>)
|
||||
Reference in New Issue
Block a user