FIR checker: introduce member function checker
This commit is contained in:
committed by
Mikhail Glukhikh
parent
5594af0d70
commit
39df3e2b0a
+1
-1
@@ -13,7 +13,7 @@ interface Your<R> {
|
|||||||
|
|
||||||
object My : Your<Double> {
|
object My : Your<Double> {
|
||||||
fun <T> T.bar() {}
|
fun <T> T.bar() {}
|
||||||
fun baz()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun baz()<!>
|
||||||
fun Boolean.gau() {}
|
fun Boolean.gau() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ private class Private {
|
|||||||
|
|
||||||
fun withLocals() {
|
fun withLocals() {
|
||||||
class Local {
|
class Local {
|
||||||
private fun bar()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>private fun bar()<!>
|
||||||
|
|
||||||
fun baz() {
|
fun baz() {
|
||||||
bar()
|
bar()
|
||||||
|
|||||||
+1
-2
@@ -16,8 +16,7 @@ FILE: RedundantModalityModifierChecker.kt
|
|||||||
|
|
||||||
public abstract fun foo(): R|kotlin/Unit|
|
public abstract fun foo(): R|kotlin/Unit|
|
||||||
|
|
||||||
private final fun bar(): R|kotlin/Unit| {
|
private final fun bar(): R|kotlin/Unit|
|
||||||
}
|
|
||||||
|
|
||||||
public open fun goo(): R|kotlin/Unit| {
|
public open fun goo(): R|kotlin/Unit| {
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
-4
@@ -9,14 +9,14 @@ interface Interface {
|
|||||||
get() = 42<!>
|
get() = 42<!>
|
||||||
<!REDUNDANT_MODALITY_MODIFIER{LT}!>// Redundant
|
<!REDUNDANT_MODALITY_MODIFIER{LT}!>// Redundant
|
||||||
<!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> fun foo()<!>
|
<!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> fun foo()<!>
|
||||||
// error
|
<!PRIVATE_FUNCTION_WITH_NO_BODY{LT}!>// error
|
||||||
private final fun bar() {}
|
<!PRIVATE_FUNCTION_WITH_NO_BODY{PSI}!>private<!> final fun bar()<!>
|
||||||
|
|
||||||
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>open<!> fun goo() {}<!>
|
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>open<!> fun goo() {}<!>
|
||||||
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> fun tar()<!>
|
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> fun tar()<!>
|
||||||
|
|
||||||
// error
|
<!ABSTRACT_FUNCTION_WITH_BODY{LT}!>// error
|
||||||
abstract fun too() {}
|
<!ABSTRACT_FUNCTION_WITH_BODY{PSI}!>abstract<!> fun too() {}<!>
|
||||||
}
|
}
|
||||||
interface B {
|
interface B {
|
||||||
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> var bar: Unit<!>
|
<!REDUNDANT_MODALITY_MODIFIER{LT}!><!REDUNDANT_MODALITY_MODIFIER{PSI}!>abstract<!> var bar: Unit<!>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ class A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class B {
|
abstract class B {
|
||||||
<!EXPOSED_FUNCTION_RETURN_TYPE{LT}!>fun <!EXPOSED_FUNCTION_RETURN_TYPE{PSI}!>foo<!>(str: String): A.InnerA<!>
|
<!EXPOSED_FUNCTION_RETURN_TYPE{LT}, NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun <!EXPOSED_FUNCTION_RETURN_TYPE{PSI}!>foo<!>(str: String): A.InnerA<!>
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Some {
|
private enum class Some {
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
|
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.extended.report
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
|
||||||
|
// See old FE's [DeclarationsChecker]
|
||||||
|
object FirMemberFunctionChecker : FirRegularClassChecker() {
|
||||||
|
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
|
for (member in declaration.declarations) {
|
||||||
|
if (member is FirSimpleFunction) {
|
||||||
|
checkFunction(declaration, member, context, reporter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkFunction(
|
||||||
|
containingDeclaration: FirRegularClass,
|
||||||
|
function: FirSimpleFunction,
|
||||||
|
context: CheckerContext,
|
||||||
|
reporter: DiagnosticReporter
|
||||||
|
) {
|
||||||
|
val source = function.source ?: return
|
||||||
|
if (source.kind is FirFakeSourceElementKind) return
|
||||||
|
// If multiple (potentially conflicting) modality modifiers are specified, not all modifiers are recorded at `status`.
|
||||||
|
// So, our source of truth should be the full modifier list retrieved from the source.
|
||||||
|
val modifierList = with(FirModifierList) { source.getModifierList() }
|
||||||
|
val isAbstract = function.isAbstract || modifierList?.modifiers?.any { it.token == KtTokens.ABSTRACT_KEYWORD } == true
|
||||||
|
if (isAbstract) {
|
||||||
|
if (!containingDeclaration.canHaveAbstractDeclaration) {
|
||||||
|
reporter.report(source, FirErrors.ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS)
|
||||||
|
}
|
||||||
|
if (function.hasBody) {
|
||||||
|
reporter.report(source, FirErrors.ABSTRACT_FUNCTION_WITH_BODY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isInsideExpectClass = isInsideExpectClass(containingDeclaration, context)
|
||||||
|
val isOpen = function.isOpen || modifierList?.modifiers?.any { it.token == KtTokens.OPEN_KEYWORD } == true
|
||||||
|
val isExternal = function.isExternal || modifierList?.modifiers?.any { it.token == KtTokens.EXTERNAL_KEYWORD } == true
|
||||||
|
if (!function.hasBody) {
|
||||||
|
if (containingDeclaration.isInterface) {
|
||||||
|
if (Visibilities.isPrivate(function.visibility)) {
|
||||||
|
reporter.report(source, FirErrors.PRIVATE_FUNCTION_WITH_NO_BODY)
|
||||||
|
}
|
||||||
|
if (!isInsideExpectClass && !isAbstract && isOpen) {
|
||||||
|
reporter.report(source, FirErrors.REDUNDANT_OPEN_IN_INTERFACE)
|
||||||
|
}
|
||||||
|
} else if (!isInsideExpectClass && !isAbstract && !isExternal) {
|
||||||
|
// TODO: we need to check if modifiers of the function already get some errors, e.g., INCOMPATIBLE_MODIFIERS
|
||||||
|
reporter.report(FirErrors.NON_ABSTRACT_FUNCTION_WITH_NO_BODY.on(source, function.symbol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInsideExpectClass(containingDeclaration: FirRegularClass, context: CheckerContext): Boolean =
|
||||||
|
// Note that the class that contains the currently visiting function is *not* in the context's containing declarations *yet*.
|
||||||
|
containingDeclaration.isExpect || context.containingDeclarations.asReversed().any { it is FirRegularClass && it.isExpect }
|
||||||
|
|
||||||
|
}
|
||||||
+8
-4
@@ -14,10 +14,7 @@ import org.jetbrains.kotlin.fir.declarations.FirClass
|
|||||||
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
@@ -111,6 +108,7 @@ object FirErrors {
|
|||||||
val REDUNDANT_MODIFIER by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
val REDUNDANT_MODIFIER by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||||
val DEPRECATED_MODIFIER_PAIR by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
val DEPRECATED_MODIFIER_PAIR by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||||
val INCOMPATIBLE_MODIFIERS by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
val INCOMPATIBLE_MODIFIERS by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||||
|
val REDUNDANT_OPEN_IN_INTERFACE by warning0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
||||||
|
|
||||||
// Applicability
|
// Applicability
|
||||||
val NONE_APPLICABLE by error1<FirSourceElement, PsiElement, Collection<AbstractFirBasedSymbol<*>>>()
|
val NONE_APPLICABLE by error1<FirSourceElement, PsiElement, Collection<AbstractFirBasedSymbol<*>>>()
|
||||||
@@ -149,6 +147,12 @@ object FirErrors {
|
|||||||
val LOCAL_OBJECT_NOT_ALLOWED by error1<FirSourceElement, KtNamedDeclaration, Name>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
val LOCAL_OBJECT_NOT_ALLOWED by error1<FirSourceElement, KtNamedDeclaration, Name>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||||
val LOCAL_INTERFACE_NOT_ALLOWED by error1<FirSourceElement, KtNamedDeclaration, Name>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
val LOCAL_INTERFACE_NOT_ALLOWED by error1<FirSourceElement, KtNamedDeclaration, Name>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
val ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
||||||
|
val ABSTRACT_FUNCTION_WITH_BODY by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
||||||
|
val NON_ABSTRACT_FUNCTION_WITH_NO_BODY by error1<FirSourceElement, PsiElement, FirFunctionSymbol<*>>()
|
||||||
|
val PRIVATE_FUNCTION_WITH_NO_BODY by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||||
|
|
||||||
// Properties & accessors
|
// Properties & accessors
|
||||||
val ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
val ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
||||||
val PRIVATE_PROPERTY_IN_INTERFACE by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
val PRIVATE_PROPERTY_IN_INTERFACE by error0<FirSourceElement, KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||||
|
|||||||
+1
@@ -47,6 +47,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
FirSupertypeInitializedWithoutPrimaryConstructor,
|
FirSupertypeInitializedWithoutPrimaryConstructor,
|
||||||
FirTypeParametersInObjectChecker,
|
FirTypeParametersInObjectChecker,
|
||||||
FirTypeMismatchOnOverrideChecker,
|
FirTypeMismatchOnOverrideChecker,
|
||||||
|
FirMemberFunctionChecker,
|
||||||
FirMemberPropertyChecker,
|
FirMemberPropertyChecker,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ abstract class MyAbstractClass() {
|
|||||||
abstract val e3: Int = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>0<!>; <!ABSTRACT_PROPERTY_WITH_GETTER!>get() = a<!>
|
abstract val e3: Int = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>0<!>; <!ABSTRACT_PROPERTY_WITH_GETTER!>get() = a<!>
|
||||||
|
|
||||||
//methods
|
//methods
|
||||||
fun f()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun f()<!>
|
||||||
fun g() {}
|
fun g() {}
|
||||||
abstract fun h()
|
abstract fun h()
|
||||||
abstract fun j() {}
|
<!ABSTRACT_FUNCTION_WITH_BODY!>abstract<!> fun j() {}
|
||||||
|
|
||||||
//property accessors
|
//property accessors
|
||||||
var i: Int abstract get abstract set
|
var i: Int abstract get abstract set
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ class MyClass() {
|
|||||||
<!ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS!>abstract<!> val e3: Int = 0; get() = a
|
<!ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS!>abstract<!> val e3: Int = 0; get() = a
|
||||||
|
|
||||||
//methods
|
//methods
|
||||||
fun f()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun f()<!>
|
||||||
fun g() {}
|
fun g() {}
|
||||||
abstract fun h()
|
<!ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS!>abstract<!> fun h()
|
||||||
abstract fun j() {}
|
<!ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS, ABSTRACT_FUNCTION_WITH_BODY!>abstract<!> fun j() {}
|
||||||
|
|
||||||
//property accessors
|
//property accessors
|
||||||
var i: Int abstract get abstract set
|
var i: Int abstract get abstract set
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface MyTrait {
|
|||||||
fun f()
|
fun f()
|
||||||
fun g() {}
|
fun g() {}
|
||||||
abstract fun h()
|
abstract fun h()
|
||||||
abstract fun j() {}
|
<!ABSTRACT_FUNCTION_WITH_BODY!>abstract<!> fun j() {}
|
||||||
|
|
||||||
//property accessors
|
//property accessors
|
||||||
var i: Int abstract get abstract set
|
var i: Int abstract get abstract set
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ interface T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
final fun foo()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>final fun foo()<!>
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,10 @@ enum class MyEnum() {
|
|||||||
abstract val e3: Int = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>0<!>; <!ABSTRACT_PROPERTY_WITH_GETTER!>get() = a<!>
|
abstract val e3: Int = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>0<!>; <!ABSTRACT_PROPERTY_WITH_GETTER!>get() = a<!>
|
||||||
|
|
||||||
//methods
|
//methods
|
||||||
fun f()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun f()<!>
|
||||||
fun g() {}
|
fun g() {}
|
||||||
abstract fun h()
|
abstract fun h()
|
||||||
abstract fun j() {}
|
<!ABSTRACT_FUNCTION_WITH_BODY!>abstract<!> fun j() {}
|
||||||
|
|
||||||
//property accessors
|
//property accessors
|
||||||
var i: Int abstract get abstract set
|
var i: Int abstract get abstract set
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface My {
|
|||||||
final val y: Int
|
final val y: Int
|
||||||
final val yy: Int
|
final val yy: Int
|
||||||
get() = 1
|
get() = 1
|
||||||
private fun foo(): Int
|
<!PRIVATE_FUNCTION_WITH_NO_BODY!>private<!> fun foo(): Int
|
||||||
// ok
|
// ok
|
||||||
private fun bar() = 42
|
private fun bar() = 42
|
||||||
}
|
}
|
||||||
|
|||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
// !LANGUAGE: +MultiPlatformProjects
|
|
||||||
// MODULE: m1-common
|
|
||||||
// FILE: common.kt
|
|
||||||
|
|
||||||
interface Foo {
|
|
||||||
fun foo()
|
|
||||||
}
|
|
||||||
|
|
||||||
expect class NonAbstractClass : Foo {
|
|
||||||
abstract fun bar()
|
|
||||||
|
|
||||||
<!ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS!>abstract<!> val baz: Int
|
|
||||||
|
|
||||||
abstract override fun foo()
|
|
||||||
}
|
|
||||||
|
|
||||||
expect abstract class AbstractClass : Foo {
|
|
||||||
abstract fun bar()
|
|
||||||
|
|
||||||
abstract val baz: Int
|
|
||||||
|
|
||||||
abstract override fun foo()
|
|
||||||
}
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// !LANGUAGE: +MultiPlatformProjects
|
// !LANGUAGE: +MultiPlatformProjects
|
||||||
// MODULE: m1-common
|
// MODULE: m1-common
|
||||||
// FILE: common.kt
|
// FILE: common.kt
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// FILE: common.kt
|
// FILE: common.kt
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
expect fun bar(): String
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>expect fun bar(): String<!>
|
||||||
}
|
}
|
||||||
|
|
||||||
// MODULE: m1-jvm(m1-common)
|
// MODULE: m1-jvm(m1-common)
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ class Outer expect constructor() {
|
|||||||
|
|
||||||
expect init {}
|
expect init {}
|
||||||
|
|
||||||
expect fun foo()
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>expect fun foo()<!>
|
||||||
expect val bar: Int
|
expect val bar: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ interface Test<in I, out O> {
|
|||||||
fun internal_fun(i: O) : I
|
fun internal_fun(i: O) : I
|
||||||
public fun public_fun(i: O) : I
|
public fun public_fun(i: O) : I
|
||||||
protected fun protected_fun(i: O) : I
|
protected fun protected_fun(i: O) : I
|
||||||
private fun private_fun(i: O) : I
|
<!PRIVATE_FUNCTION_WITH_NO_BODY!>private<!> fun private_fun(i: O) : I
|
||||||
}
|
}
|
||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
// TESTCASE NUMBER: 1
|
// TESTCASE NUMBER: 1
|
||||||
|
|
||||||
abstract class Base() {
|
abstract class Base() {
|
||||||
abstract fun foo() = {}
|
<!ABSTRACT_FUNCTION_WITH_BODY!>abstract<!> fun foo() = {}
|
||||||
fun boo() : Unit
|
<!NON_ABSTRACT_FUNCTION_WITH_NO_BODY!>fun boo() : Unit<!>
|
||||||
abstract val a = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>""<!>
|
abstract val a = <!ABSTRACT_PROPERTY_WITH_INITIALIZER!>""<!>
|
||||||
val b
|
val b
|
||||||
var d
|
var d
|
||||||
|
|||||||
Reference in New Issue
Block a user