Refactoring of repeatable on retention SOURCE check (correct additional checker usage)
This commit is contained in:
@@ -185,7 +185,7 @@ public abstract class AnnotationCodegen {
|
||||
private void generateTargetAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
|
||||
String descriptor = Type.getType(Target.class).getDescriptor();
|
||||
if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return;
|
||||
Set<KotlinTarget> targets = AnnotationChecker.INSTANCE$.possibleTargetSet(classDescriptor);
|
||||
Set<KotlinTarget> targets = AnnotationChecker.Companion.possibleTargetSet(classDescriptor);
|
||||
Set<ElementType> javaTargets;
|
||||
if (targets == null) {
|
||||
javaTargets = getJavaTargetList(classDescriptor);
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public object JvmPlatformConfigurator : PlatformConfigurator(
|
||||
|
||||
additionalSymbolUsageValidators = listOf(),
|
||||
|
||||
additionalAnnotationChecker = RepeatableAnnotationChecker
|
||||
additionalAnnotationCheckers = listOf(RepeatableAnnotationChecker)
|
||||
) {
|
||||
|
||||
override fun configure(container: StorageComponentContainer) {
|
||||
|
||||
@@ -29,9 +29,7 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isRepeatableAnnotation
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
|
||||
|
||||
public object AnnotationChecker {
|
||||
|
||||
public var additionalChecker: AdditionalAnnotationChecker? = null
|
||||
public class AnnotationChecker(private val additionalCheckers: Iterable<AdditionalAnnotationChecker>) {
|
||||
|
||||
public fun check(annotated: JetAnnotated, trace: BindingTrace, descriptor: ClassDescriptor? = null) {
|
||||
if (annotated is JetTypeParameter) return // TODO: support type parameter annotations
|
||||
@@ -71,25 +69,7 @@ public object AnnotationChecker {
|
||||
trace.report(Errors.REPEATED_ANNOTATION.on(entry));
|
||||
}
|
||||
}
|
||||
additionalChecker?.checkEntries(entries, actualTargets, trace)
|
||||
}
|
||||
|
||||
public fun possibleTargetSet(classDescriptor: ClassDescriptor): Set<KotlinTarget>? {
|
||||
val targetEntryDescriptor = classDescriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.target)
|
||||
?: return null
|
||||
val valueArguments = targetEntryDescriptor.allValueArguments
|
||||
val valueArgument = valueArguments.entrySet().firstOrNull()?.getValue() as? ArrayValue ?: return null
|
||||
return valueArgument.value.filterIsInstance<EnumValue>().map {
|
||||
KotlinTarget.valueOrNull(it.value.name.asString())
|
||||
}.filterNotNull().toSet()
|
||||
}
|
||||
|
||||
private fun possibleTargetSet(entry: JetAnnotationEntry, trace: BindingTrace): Set<KotlinTarget> {
|
||||
val descriptor = trace.get(BindingContext.ANNOTATION, entry) ?: return KotlinTarget.DEFAULT_TARGET_SET
|
||||
// For descriptor with error type, all targets are considered as possible
|
||||
if (descriptor.type.isError) return KotlinTarget.ALL_TARGET_SET
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(descriptor.type) ?: return KotlinTarget.DEFAULT_TARGET_SET
|
||||
return possibleTargetSet(classDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
|
||||
additionalCheckers.forEach { it.checkEntries(entries, actualTargets, trace) }
|
||||
}
|
||||
|
||||
private fun checkAnnotationEntry(entry: JetAnnotationEntry, actualTargets: List<KotlinTarget>, trace: BindingTrace) {
|
||||
@@ -98,39 +78,60 @@ public object AnnotationChecker {
|
||||
trace.report(Errors.WRONG_ANNOTATION_TARGET.on(entry, actualTargets.firstOrNull()?.description ?: "unidentified target"))
|
||||
}
|
||||
|
||||
public fun getActualTargetList(annotated: JetElement, descriptor: ClassDescriptor?): List<KotlinTarget> {
|
||||
return when (annotated) {
|
||||
is JetClassOrObject -> descriptor?.let { KotlinTarget.classActualTargets(it) } ?: listOf(CLASSIFIER)
|
||||
is JetProperty ->
|
||||
if (annotated.isLocal) {
|
||||
listOf(LOCAL_VARIABLE)
|
||||
}
|
||||
else if (annotated.parent is JetClassOrObject || annotated.parent is JetClassBody) {
|
||||
listOf(MEMBER_PROPERTY, PROPERTY, FIELD)
|
||||
}
|
||||
else {
|
||||
listOf(TOP_LEVEL_PROPERTY, PROPERTY, FIELD)
|
||||
}
|
||||
is JetParameter -> if (annotated.hasValOrVar()) listOf(PROPERTY_PARAMETER, MEMBER_PROPERTY, PROPERTY, FIELD) else listOf(VALUE_PARAMETER)
|
||||
is JetConstructor<*> -> listOf(CONSTRUCTOR)
|
||||
is JetFunction ->
|
||||
if (annotated.isLocal) {
|
||||
listOf(LOCAL_FUNCTION, FUNCTION)
|
||||
}
|
||||
else if (annotated.parent is JetClassOrObject || annotated.parent is JetClassBody) {
|
||||
listOf(MEMBER_FUNCTION, FUNCTION)
|
||||
}
|
||||
else {
|
||||
listOf(TOP_LEVEL_FUNCTION, FUNCTION)
|
||||
}
|
||||
is JetPropertyAccessor -> if (annotated.isGetter) listOf(PROPERTY_GETTER) else listOf(PROPERTY_SETTER)
|
||||
is JetPackageDirective -> listOf(PACKAGE)
|
||||
is JetTypeReference -> listOf(TYPE)
|
||||
is JetFile -> listOf(FILE)
|
||||
is JetTypeParameter -> listOf(TYPE_PARAMETER)
|
||||
is JetTypeProjection -> if (annotated.projectionKind == JetProjectionKind.STAR) listOf(STAR_PROJECTION) else listOf(TYPE_PROJECTION)
|
||||
is JetClassInitializer -> listOf(INITIALIZER)
|
||||
else -> listOf()
|
||||
companion object {
|
||||
|
||||
private fun possibleTargetSet(entry: JetAnnotationEntry, trace: BindingTrace): Set<KotlinTarget> {
|
||||
val descriptor = trace.get(BindingContext.ANNOTATION, entry) ?: return KotlinTarget.DEFAULT_TARGET_SET
|
||||
// For descriptor with error type, all targets are considered as possible
|
||||
if (descriptor.type.isError) return KotlinTarget.ALL_TARGET_SET
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(descriptor.type) ?: return KotlinTarget.DEFAULT_TARGET_SET
|
||||
return possibleTargetSet(classDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
|
||||
}
|
||||
|
||||
public fun possibleTargetSet(classDescriptor: ClassDescriptor): Set<KotlinTarget>? {
|
||||
val targetEntryDescriptor = classDescriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.target)
|
||||
?: return null
|
||||
val valueArguments = targetEntryDescriptor.allValueArguments
|
||||
val valueArgument = valueArguments.entrySet().firstOrNull()?.getValue() as? ArrayValue ?: return null
|
||||
return valueArgument.value.filterIsInstance<EnumValue>().map {
|
||||
KotlinTarget.valueOrNull(it.value.name.asString())
|
||||
}.filterNotNull().toSet()
|
||||
}
|
||||
|
||||
public fun getActualTargetList(annotated: JetElement, descriptor: ClassDescriptor?): List<KotlinTarget> {
|
||||
return when (annotated) {
|
||||
is JetClassOrObject -> descriptor?.let { KotlinTarget.classActualTargets(it) } ?: listOf(CLASSIFIER)
|
||||
is JetProperty ->
|
||||
if (annotated.isLocal) {
|
||||
listOf(LOCAL_VARIABLE)
|
||||
}
|
||||
else if (annotated.parent is JetClassOrObject || annotated.parent is JetClassBody) {
|
||||
listOf(MEMBER_PROPERTY, PROPERTY, FIELD)
|
||||
}
|
||||
else {
|
||||
listOf(TOP_LEVEL_PROPERTY, PROPERTY, FIELD)
|
||||
}
|
||||
is JetParameter -> if (annotated.hasValOrVar()) listOf(PROPERTY_PARAMETER, MEMBER_PROPERTY, PROPERTY, FIELD) else listOf(VALUE_PARAMETER)
|
||||
is JetConstructor<*> -> listOf(CONSTRUCTOR)
|
||||
is JetFunction ->
|
||||
if (annotated.isLocal) {
|
||||
listOf(LOCAL_FUNCTION, FUNCTION)
|
||||
}
|
||||
else if (annotated.parent is JetClassOrObject || annotated.parent is JetClassBody) {
|
||||
listOf(MEMBER_FUNCTION, FUNCTION)
|
||||
}
|
||||
else {
|
||||
listOf(TOP_LEVEL_FUNCTION, FUNCTION)
|
||||
}
|
||||
is JetPropertyAccessor -> if (annotated.isGetter) listOf(PROPERTY_GETTER) else listOf(PROPERTY_SETTER)
|
||||
is JetPackageDirective -> listOf(PACKAGE)
|
||||
is JetTypeReference -> listOf(TYPE)
|
||||
is JetFile -> listOf(FILE)
|
||||
is JetTypeParameter -> listOf(TYPE_PARAMETER)
|
||||
is JetTypeProjection -> if (annotated.projectionKind == JetProjectionKind.STAR) listOf(STAR_PROJECTION) else listOf(TYPE_PROJECTION)
|
||||
is JetClassInitializer -> listOf(INITIALIZER)
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE;
|
||||
|
||||
public class BodyResolver {
|
||||
@NotNull private final ScriptBodyResolver scriptBodyResolverResolver;
|
||||
@NotNull private final ModifiersChecker modifiersChecker;
|
||||
@NotNull private final AnnotationChecker annotationChecker;
|
||||
@NotNull private final ExpressionTypingServices expressionTypingServices;
|
||||
@NotNull private final CallResolver callResolver;
|
||||
@NotNull private final ObservableBindingTrace trace;
|
||||
@@ -78,7 +78,7 @@ public class BodyResolver {
|
||||
@NotNull ScriptBodyResolver scriptBodyResolverResolver,
|
||||
@NotNull BindingTrace trace,
|
||||
@NotNull ValueParameterResolver valueParameterResolver,
|
||||
@NotNull ModifiersChecker modifiersChecker
|
||||
@NotNull AnnotationChecker annotationChecker
|
||||
) {
|
||||
this.annotationResolver = annotationResolver;
|
||||
this.bodyResolveCache = bodyResolveCache;
|
||||
@@ -89,7 +89,7 @@ public class BodyResolver {
|
||||
this.expressionTypingServices = expressionTypingServices;
|
||||
this.functionAnalyzerExtension = functionAnalyzerExtension;
|
||||
this.scriptBodyResolverResolver = scriptBodyResolverResolver;
|
||||
this.modifiersChecker = modifiersChecker;
|
||||
this.annotationChecker = annotationChecker;
|
||||
this.trace = new ObservableBindingTrace(trace);
|
||||
this.valueParameterResolver = valueParameterResolver;
|
||||
}
|
||||
@@ -507,7 +507,7 @@ public class BodyResolver {
|
||||
}
|
||||
|
||||
private void processModifiersOnInitializer(@NotNull JetModifierListOwner owner, @NotNull JetScope scope) {
|
||||
AnnotationChecker.INSTANCE$.check(owner, trace, null);
|
||||
annotationChecker.check(owner, trace, null);
|
||||
ModifierCheckerCore.INSTANCE$.check(owner, trace, null);
|
||||
JetModifierList modifierList = owner.getModifierList();
|
||||
if (modifierList == null) return;
|
||||
|
||||
@@ -47,21 +47,24 @@ public class DeclarationsChecker {
|
||||
@NotNull private final BindingTrace trace;
|
||||
@NotNull private final ModifiersChecker.ModifiersCheckingProcedure modifiersChecker;
|
||||
@NotNull private final DescriptorResolver descriptorResolver;
|
||||
@NotNull private final AnnotationChecker annotationChecker;
|
||||
|
||||
public DeclarationsChecker(
|
||||
@NotNull DescriptorResolver descriptorResolver,
|
||||
@NotNull ModifiersChecker modifiersChecker,
|
||||
@NotNull AnnotationChecker annotationChecker,
|
||||
@NotNull BindingTrace trace
|
||||
) {
|
||||
this.descriptorResolver = descriptorResolver;
|
||||
this.modifiersChecker = modifiersChecker.withTrace(trace);
|
||||
this.annotationChecker = annotationChecker;
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
public void process(@NotNull BodiesResolveContext bodiesResolveContext) {
|
||||
for (JetFile file : bodiesResolveContext.getFiles()) {
|
||||
checkModifiersAndAnnotationsInPackageDirective(file);
|
||||
AnnotationChecker.INSTANCE$.check(file, trace, null);
|
||||
annotationChecker.check(file, trace, null);
|
||||
}
|
||||
|
||||
Map<JetClassOrObject, ClassDescriptorWithResolutionScopes> classes = bodiesResolveContext.getDeclaredClasses();
|
||||
@@ -133,7 +136,7 @@ public class DeclarationsChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
AnnotationChecker.INSTANCE$.check(packageDirective, trace, null);
|
||||
annotationChecker.check(packageDirective, trace, null);
|
||||
ModifierCheckerCore.INSTANCE$.check(packageDirective, trace, null);
|
||||
}
|
||||
|
||||
@@ -279,7 +282,7 @@ public class DeclarationsChecker {
|
||||
if (typeParameter != null) {
|
||||
DescriptorResolver.checkConflictingUpperBounds(trace, typeParameter, jetTypeParameter);
|
||||
}
|
||||
AnnotationChecker.INSTANCE$.check(jetTypeParameter, trace, null);
|
||||
annotationChecker.check(jetTypeParameter, trace, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ public class ModifiersChecker {
|
||||
checkPlatformNameApplicability(descriptor);
|
||||
runDeclarationCheckers(modifierListOwner, descriptor);
|
||||
ClassDescriptor classDescriptor = descriptor instanceof ClassDescriptor ? (ClassDescriptor) descriptor : null;
|
||||
AnnotationChecker.INSTANCE$.check(modifierListOwner, trace, classDescriptor);
|
||||
annotationChecker.check(modifierListOwner, trace, classDescriptor);
|
||||
}
|
||||
|
||||
public void checkModifiersForLocalDeclaration(
|
||||
@@ -201,7 +201,7 @@ public class ModifiersChecker {
|
||||
) {
|
||||
checkPlatformNameApplicability(descriptor);
|
||||
runDeclarationCheckers(modifierListOwner, descriptor);
|
||||
AnnotationChecker.INSTANCE$.check(modifierListOwner, trace,
|
||||
annotationChecker.check(modifierListOwner, trace,
|
||||
descriptor instanceof ClassDescriptor ? (ClassDescriptor) descriptor : null);
|
||||
ModifierCheckerCore.INSTANCE$.check(modifierListOwner, trace, descriptor);
|
||||
}
|
||||
@@ -285,10 +285,14 @@ public class ModifiersChecker {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final AnnotationChecker annotationChecker;
|
||||
|
||||
@NotNull
|
||||
private final Iterable<DeclarationChecker> declarationCheckers;
|
||||
|
||||
public ModifiersChecker(@NotNull Iterable<DeclarationChecker> declarationCheckers) {
|
||||
public ModifiersChecker(@NotNull AnnotationChecker annotationChecker, @NotNull Iterable<DeclarationChecker> declarationCheckers) {
|
||||
this.annotationChecker = annotationChecker;
|
||||
this.declarationCheckers = declarationCheckers;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public abstract class TargetPlatform(
|
||||
public abstract val platformConfigurator: PlatformConfigurator
|
||||
|
||||
public object Default : TargetPlatform("Default") {
|
||||
override val platformConfigurator = PlatformConfigurator(DynamicTypesSettings(), listOf(), listOf(), listOf(), listOf())
|
||||
override val platformConfigurator = PlatformConfigurator(DynamicTypesSettings(), listOf(), listOf(), listOf(), listOf(), listOf())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public open class PlatformConfigurator(
|
||||
additionalCallCheckers: List<CallChecker>,
|
||||
additionalTypeCheckers: List<AdditionalTypeChecker>,
|
||||
additionalSymbolUsageValidators: List<SymbolUsageValidator>,
|
||||
private val additionalAnnotationChecker: AdditionalAnnotationChecker? = null
|
||||
private val additionalAnnotationCheckers: List<AdditionalAnnotationChecker>
|
||||
) {
|
||||
|
||||
private val declarationCheckers: List<DeclarationChecker> = DEFAULT_DECLARATION_CHECKERS + additionalDeclarationCheckers
|
||||
@@ -64,7 +64,7 @@ public open class PlatformConfigurator(
|
||||
callCheckers.forEach { useInstance(it) }
|
||||
typeCheckers.forEach { useInstance(it) }
|
||||
useInstance(symbolUsageValidator)
|
||||
additionalAnnotationCheckers.forEach { useInstance(it) }
|
||||
}
|
||||
AnnotationChecker.additionalChecker = additionalAnnotationChecker
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -49,14 +49,17 @@ public class ExpressionTypingServices {
|
||||
private final ExpressionTypingFacade expressionTypingFacade;
|
||||
private final ExpressionTypingComponents expressionTypingComponents;
|
||||
|
||||
@NotNull AnnotationChecker annotationChecker;
|
||||
@NotNull private final StatementFilter statementFilter;
|
||||
|
||||
public ExpressionTypingServices(
|
||||
@NotNull ExpressionTypingComponents components,
|
||||
@NotNull AnnotationChecker annotationChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
@NotNull ExpressionTypingVisitorDispatcher.ForDeclarations facade
|
||||
) {
|
||||
this.expressionTypingComponents = components;
|
||||
this.annotationChecker = annotationChecker;
|
||||
this.statementFilter = statementFilter;
|
||||
this.expressionTypingFacade = facade;
|
||||
}
|
||||
@@ -227,7 +230,8 @@ public class ExpressionTypingServices {
|
||||
return TypeInfoFactoryPackage.createTypeInfo(expressionTypingComponents.builtIns.getUnitType(), context);
|
||||
}
|
||||
|
||||
ExpressionTypingInternals blockLevelVisitor = new ExpressionTypingVisitorDispatcher.ForBlock(expressionTypingComponents, scope);
|
||||
ExpressionTypingInternals blockLevelVisitor = new ExpressionTypingVisitorDispatcher.ForBlock(
|
||||
expressionTypingComponents, annotationChecker, scope);
|
||||
ExpressionTypingContext newContext = context.replaceScope(scope).replaceExpectedType(NO_EXPECTED_TYPE);
|
||||
|
||||
JetTypeInfo result = TypeInfoFactoryPackage.noTypeInfo(context);
|
||||
@@ -260,7 +264,7 @@ public class ExpressionTypingServices {
|
||||
newContext = newContext.replaceDataFlowInfo(newDataFlowInfo);
|
||||
// We take current data flow info if jump there is not possible
|
||||
}
|
||||
blockLevelVisitor = new ExpressionTypingVisitorDispatcher.ForBlock(expressionTypingComponents, scope);
|
||||
blockLevelVisitor = new ExpressionTypingVisitorDispatcher.ForBlock(expressionTypingComponents, annotationChecker, scope);
|
||||
}
|
||||
return result.replaceJumpOutPossible(jumpOutPossible).replaceJumpFlowInfo(beforeJumpInfo);
|
||||
}
|
||||
|
||||
+9
-5
@@ -46,8 +46,8 @@ public abstract class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTy
|
||||
private static final Logger LOG = Logger.getInstance(ExpressionTypingVisitor.class);
|
||||
|
||||
public static class ForDeclarations extends ExpressionTypingVisitorDispatcher {
|
||||
public ForDeclarations(@NotNull ExpressionTypingComponents components) {
|
||||
super(components);
|
||||
public ForDeclarations(@NotNull ExpressionTypingComponents components, @NotNull AnnotationChecker annotationChecker) {
|
||||
super(components, annotationChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,9 +64,10 @@ public abstract class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTy
|
||||
|
||||
public ForBlock(
|
||||
@NotNull ExpressionTypingComponents components,
|
||||
@NotNull AnnotationChecker annotationChecker,
|
||||
@NotNull WritableScope writableScope
|
||||
) {
|
||||
super(components);
|
||||
super(components, annotationChecker);
|
||||
this.visitorForBlock = new ExpressionTypingVisitorForStatements(
|
||||
this, writableScope, basic, controlStructures, patterns, functions
|
||||
);
|
||||
@@ -79,15 +80,18 @@ public abstract class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTy
|
||||
}
|
||||
|
||||
private final ExpressionTypingComponents components;
|
||||
@NotNull private final AnnotationChecker annotationChecker;
|
||||
protected final BasicExpressionTypingVisitor basic;
|
||||
protected final FunctionsTypingVisitor functions;
|
||||
protected final ControlStructureTypingVisitor controlStructures;
|
||||
protected final PatternMatchingTypingVisitor patterns;
|
||||
|
||||
private ExpressionTypingVisitorDispatcher(
|
||||
@NotNull ExpressionTypingComponents components
|
||||
@NotNull ExpressionTypingComponents components,
|
||||
@NotNull AnnotationChecker annotationChecker
|
||||
) {
|
||||
this.components = components;
|
||||
this.annotationChecker = annotationChecker;
|
||||
this.basic = new BasicExpressionTypingVisitor(this);
|
||||
this.controlStructures = new ControlStructureTypingVisitor(this);
|
||||
this.patterns = new PatternMatchingTypingVisitor(this);
|
||||
@@ -128,7 +132,7 @@ public abstract class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTy
|
||||
@NotNull
|
||||
public final JetTypeInfo getTypeInfo(@NotNull JetExpression expression, ExpressionTypingContext context) {
|
||||
JetTypeInfo result = getTypeInfo(expression, context, this);
|
||||
AnnotationChecker.INSTANCE$.checkExpression(expression, context.trace);
|
||||
annotationChecker.checkExpression(expression, context.trace);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ public object JsPlatformConfigurator : PlatformConfigurator(
|
||||
additionalDeclarationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), ClassDeclarationChecker()),
|
||||
additionalCallCheckers = listOf(),
|
||||
additionalTypeCheckers = listOf(),
|
||||
additionalSymbolUsageValidators = listOf()
|
||||
additionalSymbolUsageValidators = listOf(),
|
||||
additionalAnnotationCheckers = listOf()
|
||||
) {
|
||||
override fun configure(container: StorageComponentContainer) {
|
||||
super.configure(container)
|
||||
|
||||
Reference in New Issue
Block a user