Move visibility check for toplevel private declarations to Visibilities.PRIVATE and fix it.
This commit is contained in:
@@ -432,7 +432,7 @@ public class JetFlowInformationProvider {
|
|||||||
if (Visibilities.isVisible(receiverValue, variableDescriptor, descriptor) && setterDescriptor != null
|
if (Visibilities.isVisible(receiverValue, variableDescriptor, descriptor) && setterDescriptor != null
|
||||||
&& !Visibilities.isVisible(receiverValue, setterDescriptor, descriptor)) {
|
&& !Visibilities.isVisible(receiverValue, setterDescriptor, descriptor)) {
|
||||||
report(Errors.INVISIBLE_SETTER.on(expression, variableDescriptor, setterDescriptor.getVisibility(),
|
report(Errors.INVISIBLE_SETTER.on(expression, variableDescriptor, setterDescriptor.getVisibility(),
|
||||||
variableDescriptor.getContainingDeclaration()), ctxt);
|
setterDescriptor), ctxt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ public interface Errors {
|
|||||||
DiagnosticFactory3<JetSimpleNameExpression, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_REFERENCE =
|
DiagnosticFactory3<JetSimpleNameExpression, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_REFERENCE =
|
||||||
DiagnosticFactory3.create(ERROR);
|
DiagnosticFactory3.create(ERROR);
|
||||||
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_MEMBER = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
|
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_MEMBER = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
|
||||||
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, JetFile> ACCESS_TO_PRIVATE_TOP_LEVEL_FROM_ANOTHER_FILE = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
|
|
||||||
|
|
||||||
DiagnosticFactory1<JetElement, Collection<ClassDescriptor>> PLATFORM_CLASS_MAPPED_TO_KOTLIN = DiagnosticFactory1.create(WARNING);
|
DiagnosticFactory1<JetElement, Collection<ClassDescriptor>> PLATFORM_CLASS_MAPPED_TO_KOTLIN = DiagnosticFactory1.create(WARNING);
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -109,8 +109,8 @@ public class DefaultErrorMessages {
|
|||||||
static {
|
static {
|
||||||
MAP.put(UNRESOLVED_REFERENCE, "Unresolved reference: {0}", ELEMENT_TEXT);
|
MAP.put(UNRESOLVED_REFERENCE, "Unresolved reference: {0}", ELEMENT_TEXT);
|
||||||
|
|
||||||
MAP.put(INVISIBLE_REFERENCE, "Cannot access ''{0}'': it is ''{1}'' in ''{2}''", NAME, TO_STRING, NAME);
|
MAP.put(INVISIBLE_REFERENCE, "Cannot access ''{0}'': it is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
|
||||||
MAP.put(INVISIBLE_MEMBER, "Cannot access ''{0}'': it is ''{1}'' in ''{2}''", NAME, TO_STRING, NAME);
|
MAP.put(INVISIBLE_MEMBER, "Cannot access ''{0}'': it is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
|
||||||
|
|
||||||
MAP.put(REDECLARATION, "Redeclaration: {0}", STRING);
|
MAP.put(REDECLARATION, "Redeclaration: {0}", STRING);
|
||||||
MAP.put(NAME_SHADOWING, "Name shadowed: {0}", STRING);
|
MAP.put(NAME_SHADOWING, "Name shadowed: {0}", STRING);
|
||||||
@@ -261,7 +261,7 @@ public class DefaultErrorMessages {
|
|||||||
|
|
||||||
MAP.put(VAL_REASSIGNMENT, "Val cannot be reassigned", NAME);
|
MAP.put(VAL_REASSIGNMENT, "Val cannot be reassigned", NAME);
|
||||||
MAP.put(SETTER_PROJECTED_OUT, "Setter for ''{0}'' is removed by type projection", NAME);
|
MAP.put(SETTER_PROJECTED_OUT, "Setter for ''{0}'' is removed by type projection", NAME);
|
||||||
MAP.put(INVISIBLE_SETTER, "Cannot assign to ''{0}'': the setter is ''{1}'' in ''{2}''", NAME, TO_STRING, NAME);
|
MAP.put(INVISIBLE_SETTER, "Cannot assign to ''{0}'': the setter is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
|
||||||
MAP.put(INITIALIZATION_BEFORE_DECLARATION, "Variable cannot be initialized before declaration", NAME);
|
MAP.put(INITIALIZATION_BEFORE_DECLARATION, "Variable cannot be initialized before declaration", NAME);
|
||||||
MAP.put(VARIABLE_EXPECTED, "Variable expected");
|
MAP.put(VARIABLE_EXPECTED, "Variable expected");
|
||||||
|
|
||||||
@@ -658,7 +658,6 @@ public class DefaultErrorMessages {
|
|||||||
MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Add ''crossinline'' modifier to parameter declaration ''{0}''", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES, SHORT_NAMES_IN_TYPES);
|
MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Add ''crossinline'' modifier to parameter declaration ''{0}''", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES, SHORT_NAMES_IN_TYPES);
|
||||||
MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME);
|
MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME);
|
||||||
MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled");
|
MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled");
|
||||||
MAP.put(ACCESS_TO_PRIVATE_TOP_LEVEL_FROM_ANOTHER_FILE, "Cannot access ''{0}'': it is ''{1}'' in ''{2}''", NAME, TO_STRING, RENDER_FILE);
|
|
||||||
|
|
||||||
MAP.setImmutable();
|
MAP.setImmutable();
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.newTa
|
|||||||
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.newText
|
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.newText
|
||||||
import org.jetbrains.kotlin.psi.JetClass
|
import org.jetbrains.kotlin.psi.JetClass
|
||||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
|
||||||
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.renderer.Renderer
|
import org.jetbrains.kotlin.renderer.Renderer
|
||||||
@@ -73,6 +72,15 @@ public object Renderers {
|
|||||||
|
|
||||||
public val NAME: Renderer<Named> = Renderer { it.getName().asString() }
|
public val NAME: Renderer<Named> = Renderer { it.getName().asString() }
|
||||||
|
|
||||||
|
public val NAME_OF_PARENT_OR_FILE: Renderer<DeclarationDescriptor> = Renderer {
|
||||||
|
if (DescriptorUtils.isTopLevelDeclaration(it) && it is DeclarationDescriptorWithVisibility && it.visibility == Visibilities.PRIVATE) {
|
||||||
|
"file"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"'" + it.containingDeclaration!!.name + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public val ELEMENT_TEXT: Renderer<PsiElement> = Renderer { it.getText() }
|
public val ELEMENT_TEXT: Renderer<PsiElement> = Renderer { it.getText() }
|
||||||
|
|
||||||
public val DECLARATION_NAME: Renderer<JetNamedDeclaration> = Renderer { it.getNameAsSafeName().asString() }
|
public val DECLARATION_NAME: Renderer<JetNamedDeclaration> = Renderer { it.getNameAsSafeName().asString() }
|
||||||
@@ -87,8 +95,6 @@ public object Renderers {
|
|||||||
|
|
||||||
public val RENDER_TYPE: Renderer<JetType> = Renderer { DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it) }
|
public val RENDER_TYPE: Renderer<JetType> = Renderer { DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it) }
|
||||||
|
|
||||||
public val RENDER_FILE: Renderer<JetFile> = Renderer { it.name }
|
|
||||||
|
|
||||||
public val RENDER_POSITION_VARIANCE: Renderer<Variance> = Renderer {
|
public val RENDER_POSITION_VARIANCE: Renderer<Variance> = Renderer {
|
||||||
variance: Variance ->
|
variance: Variance ->
|
||||||
when (variance) {
|
when (variance) {
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ public class QualifiedExpressionResolver(val symbolUsageValidator: SymbolUsageVa
|
|||||||
val visibleDescriptors = descriptors.filter { isVisible(it, shouldBeVisibleFrom, inImport) }
|
val visibleDescriptors = descriptors.filter { isVisible(it, shouldBeVisibleFrom, inImport) }
|
||||||
if (visibleDescriptors.isEmpty()) {
|
if (visibleDescriptors.isEmpty()) {
|
||||||
val descriptor = descriptors.first() as DeclarationDescriptorWithVisibility
|
val descriptor = descriptors.first() as DeclarationDescriptorWithVisibility
|
||||||
trace.report(Errors.INVISIBLE_REFERENCE.on(referenceExpression, descriptor, descriptor.visibility, descriptor.containingDeclaration!!))
|
trace.report(Errors.INVISIBLE_REFERENCE.on(referenceExpression, descriptor, descriptor.visibility, descriptor))
|
||||||
}
|
}
|
||||||
else if (visibleDescriptors.size() > 1) {
|
else if (visibleDescriptors.size() > 1) {
|
||||||
trace.record(BindingContext.AMBIGUOUS_REFERENCE_TARGET, referenceExpression, visibleDescriptors)
|
trace.record(BindingContext.AMBIGUOUS_REFERENCE_TARGET, referenceExpression, visibleDescriptors)
|
||||||
@@ -382,7 +382,7 @@ public class QualifiedExpressionResolver(val symbolUsageValidator: SymbolUsageVa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor is DeclarationDescriptorWithVisibility && !isVisible(descriptor, shouldBeVisibleFrom, inImport)) {
|
if (descriptor is DeclarationDescriptorWithVisibility && !isVisible(descriptor, shouldBeVisibleFrom, inImport)) {
|
||||||
trace.report(Errors.INVISIBLE_REFERENCE.on(referenceExpression, descriptor, descriptor.visibility, descriptor.containingDeclaration!!))
|
trace.report(Errors.INVISIBLE_REFERENCE.on(referenceExpression, descriptor, descriptor.visibility, descriptor))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQualifier) {
|
if (isQualifier) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.resolve
|
|||||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||||
import org.jetbrains.kotlin.container.useInstance
|
import org.jetbrains.kotlin.container.useInstance
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.*
|
import org.jetbrains.kotlin.resolve.calls.checkers.*
|
||||||
import org.jetbrains.kotlin.resolve.validation.AccessToPrivateTopLevelSymbolValidator
|
|
||||||
import org.jetbrains.kotlin.resolve.validation.DeprecatedSymbolValidator
|
import org.jetbrains.kotlin.resolve.validation.DeprecatedSymbolValidator
|
||||||
import org.jetbrains.kotlin.resolve.validation.OperatorValidator
|
import org.jetbrains.kotlin.resolve.validation.OperatorValidator
|
||||||
import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator
|
import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator
|
||||||
@@ -42,7 +41,7 @@ public abstract class TargetPlatform(
|
|||||||
private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker(), ConstModifierChecker, UnderscoreChecker, OperatorModifierChecker())
|
private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker(), ConstModifierChecker, UnderscoreChecker, OperatorModifierChecker())
|
||||||
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker())
|
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker())
|
||||||
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
||||||
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), AccessToPrivateTopLevelSymbolValidator(), OperatorValidator())
|
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator())
|
||||||
|
|
||||||
|
|
||||||
public open class PlatformConfigurator(
|
public open class PlatformConfigurator(
|
||||||
|
|||||||
+1
-1
@@ -194,7 +194,7 @@ public abstract class AbstractTracingStrategy implements TracingStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invisibleMember(@NotNull BindingTrace trace, @NotNull DeclarationDescriptorWithVisibility descriptor) {
|
public void invisibleMember(@NotNull BindingTrace trace, @NotNull DeclarationDescriptorWithVisibility descriptor) {
|
||||||
trace.report(INVISIBLE_MEMBER.on(call.getCallElement(), descriptor, descriptor.getVisibility(), descriptor.getContainingDeclaration()));
|
trace.report(INVISIBLE_MEMBER.on(call.getCallElement(), descriptor, descriptor.getVisibility(), descriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.validation
|
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
|
||||||
import org.jetbrains.kotlin.psi.JetElement
|
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
||||||
|
|
||||||
public class AccessToPrivateTopLevelSymbolValidator : SymbolUsageValidator {
|
|
||||||
override fun validateCall(targetDescriptor: CallableDescriptor, trace: BindingTrace, element: PsiElement) {
|
|
||||||
val descriptor =
|
|
||||||
if (DescriptorUtils.isTopLevelDeclaration(targetDescriptor)) targetDescriptor
|
|
||||||
else DescriptorUtils.getContainingClass(targetDescriptor)
|
|
||||||
|
|
||||||
descriptor?.let {
|
|
||||||
reportIfNeeded(it, trace, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun validateTypeUsage(targetDescriptor: ClassifierDescriptor, trace: BindingTrace, element: PsiElement) =
|
|
||||||
reportIfNeeded(targetDescriptor, trace, element)
|
|
||||||
|
|
||||||
private fun reportIfNeeded(descriptor: DeclarationDescriptor, trace: BindingTrace, element: PsiElement) {
|
|
||||||
if (descriptor !is DeclarationDescriptorWithVisibility ||
|
|
||||||
descriptor.visibility != Visibilities.PRIVATE ||
|
|
||||||
!DescriptorUtils.isTopLevelDeclaration(descriptor)) return
|
|
||||||
|
|
||||||
if (descriptor is PropertySetterDescriptor && descriptor.correspondingProperty.visibility == Visibilities.PRIVATE) return
|
|
||||||
|
|
||||||
if (element !is JetElement) return
|
|
||||||
|
|
||||||
DescriptorToSourceUtils.getContainingFile(descriptor)?.let {
|
|
||||||
val jetFile = element.containingFile as? JetFile ?: return
|
|
||||||
|
|
||||||
val currentPackageViewDescriptor = trace.bindingContext.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, jetFile)
|
|
||||||
if (currentPackageViewDescriptor != null && !DescriptorUtils.areInSameModule(currentPackageViewDescriptor, descriptor)) return
|
|
||||||
|
|
||||||
val packageFqName = DescriptorUtils.getParentOfType(descriptor, PackageFragmentDescriptor::class.java)?.fqName
|
|
||||||
if (packageFqName != currentPackageViewDescriptor?.fqName) return
|
|
||||||
|
|
||||||
if (jetFile != it) {
|
|
||||||
trace.report(Errors.ACCESS_TO_PRIVATE_TOP_LEVEL_FROM_ANOTHER_FILE.on(element, descriptor, descriptor.visibility, it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,6 +36,13 @@ public class Visibilities {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVisible(@NotNull ReceiverValue receiver, @NotNull DeclarationDescriptorWithVisibility what, @NotNull DeclarationDescriptor from) {
|
public boolean isVisible(@NotNull ReceiverValue receiver, @NotNull DeclarationDescriptorWithVisibility what, @NotNull DeclarationDescriptor from) {
|
||||||
|
if (DescriptorUtils.isTopLevelDeclaration(what)) {
|
||||||
|
SourceFile fromContainingFile = DescriptorUtils.getContainingSourceFile(from);
|
||||||
|
if (fromContainingFile != SourceFile.NO_SOURCE_FILE) {
|
||||||
|
return fromContainingFile.equals(DescriptorUtils.getContainingSourceFile(what));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DeclarationDescriptor parent = what;
|
DeclarationDescriptor parent = what;
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
parent = parent.getContainingDeclaration();
|
parent = parent.getContainingDeclaration();
|
||||||
|
|||||||
@@ -599,6 +599,12 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("topLevelPrivateValUsageAdded")
|
||||||
|
public void testTopLevelPrivateValUsageAdded() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/topLevelPrivateValUsageAdded/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("traitClassObjectConstantChanged")
|
@TestMetadata("traitClassObjectConstantChanged")
|
||||||
public void testTraitClassObjectConstantChanged() throws Exception {
|
public void testTraitClassObjectConstantChanged() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/traitClassObjectConstantChanged/");
|
String fileName = JetTestUtils.navigationMetadata("jps-plugin/testData/incremental/pureKotlin/traitClassObjectConstantChanged/");
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/META-INF/module.kotlin_module
|
||||||
|
out/production/module/test/TestPackage.class
|
||||||
|
out/production/module/test/UsageKt.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
|
COMPILATION FAILED
|
||||||
|
Cannot access 'foo': it is 'private' in file
|
||||||
|
|
||||||
|
|
||||||
|
Cleaning output files:
|
||||||
|
out/production/module/test/FooKt.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/foo.kt
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
private fun foo() = 1
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
println(foo())
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user