J2K converter: automatic generation of 'open' modifier for classes and methods
This commit is contained in:
committed by
valentin
parent
39cea5999c
commit
7e147d28c2
@@ -47,7 +47,8 @@ enum class AccessorKind {
|
||||
}
|
||||
|
||||
class ClassBodyConverter(private val psiClass: PsiClass,
|
||||
private val converter: Converter) {
|
||||
private val converter: Converter,
|
||||
private val isOpenClass: Boolean) {
|
||||
private val membersToRemove = HashSet<PsiMember>()
|
||||
private val fieldCorrections = HashMap<PsiField, FieldCorrectionInfo>()
|
||||
|
||||
@@ -110,7 +111,7 @@ class ClassBodyConverter(private val psiClass: PsiClass,
|
||||
membersToRemove: MutableSet<PsiMember>,
|
||||
constructorConverter: ConstructorConverter?): Member? {
|
||||
return when (member) {
|
||||
is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter)
|
||||
is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter, isOpenClass)
|
||||
is PsiField -> convertField(member, fieldCorrections[member])
|
||||
is PsiClass -> convertClass(member)
|
||||
is PsiClassInitializer -> convertInitializer(member)
|
||||
|
||||
@@ -148,7 +148,7 @@ class CodeConverter(public val converter: Converter,
|
||||
}
|
||||
|
||||
public fun convertAnonymousClassBody(anonymousClass: PsiAnonymousClass): AnonymousClassBody {
|
||||
return AnonymousClassBody(ClassBodyConverter(anonymousClass, converter).convertBody(),
|
||||
return AnonymousClassBody(ClassBodyConverter(anonymousClass, converter, false).convertBody(),
|
||||
anonymousClass.getBaseClassType().resolve()?.isInterface() ?: false).assignPrototype(anonymousClass)
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
private fun convertTopElement(element: PsiElement): Element? = when (element) {
|
||||
is PsiJavaFile -> convertFile(element)
|
||||
is PsiClass -> convertClass(element)
|
||||
is PsiMethod -> convertMethod(element, null, null)
|
||||
is PsiMethod -> convertMethod(element, null, null, false)
|
||||
is PsiField -> convertField(element, null)
|
||||
is PsiStatement -> createDefaultCodeConverter().convertStatement(element)
|
||||
is PsiExpression -> createDefaultCodeConverter().convertExpression(element)
|
||||
@@ -179,15 +179,19 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
val extendsTypes = convertToNotNullableTypes(psiClass.getExtendsListTypes())
|
||||
val name = psiClass.declarationIdentifier()
|
||||
|
||||
var classBody = ClassBodyConverter(psiClass, this).convertBody()
|
||||
|
||||
return when {
|
||||
psiClass.isInterface() -> Trait(name, annotations, modifiers, typeParameters, extendsTypes, listOf(), implementsTypes, classBody)
|
||||
psiClass.isInterface() -> {
|
||||
var classBody = ClassBodyConverter(psiClass, this, false).convertBody()
|
||||
Trait(name, annotations, modifiers, typeParameters, extendsTypes, listOf(), implementsTypes, classBody)
|
||||
}
|
||||
|
||||
psiClass.isEnum() -> Enum(name, annotations, modifiers, typeParameters, listOf(), listOf(), implementsTypes, classBody)
|
||||
psiClass.isEnum() -> {
|
||||
var classBody = ClassBodyConverter(psiClass, this, false).convertBody()
|
||||
Enum(name, annotations, modifiers, typeParameters, listOf(), listOf(), implementsTypes, classBody)
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (settings.openByDefault && !psiClass.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
if (needOpenModifier(psiClass)) {
|
||||
modifiers = modifiers.with(Modifier.OPEN)
|
||||
}
|
||||
|
||||
@@ -195,11 +199,20 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
modifiers = modifiers.with(Modifier.INNER)
|
||||
}
|
||||
|
||||
var classBody = ClassBodyConverter(psiClass, this, modifiers.contains(Modifier.OPEN)).convertBody()
|
||||
|
||||
Class(name, annotations, modifiers, typeParameters, extendsTypes, classBody.baseClassParams, implementsTypes, classBody)
|
||||
}
|
||||
}.assignPrototype(psiClass)
|
||||
}
|
||||
|
||||
private fun needOpenModifier(psiClass: PsiClass): Boolean {
|
||||
return if (settings.openByDefault)
|
||||
!psiClass.hasModifierProperty(PsiModifier.FINAL) && !psiClass.hasModifierProperty(PsiModifier.ABSTRACT)
|
||||
else
|
||||
referenceSearcher.hasInheritors(psiClass)
|
||||
}
|
||||
|
||||
private fun convertAnnotationType(psiClass: PsiClass): Class {
|
||||
val paramModifiers = Modifiers(listOf(Modifier.PUBLIC)).assignNoPrototype()
|
||||
val noBlankLinesInheritance = CommentsAndSpacesInheritance(blankLinesBefore = false)
|
||||
@@ -224,7 +237,7 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
val constructorSignature = PrimaryConstructorSignature(Annotations.Empty(), Modifiers.Empty(), parameterList).assignNoPrototype()
|
||||
|
||||
// to convert fields and nested types - they are not allowed in Kotlin but we convert them and let user refactor code
|
||||
var classBody = ClassBodyConverter(psiClass, this).convertBody()
|
||||
var classBody = ClassBodyConverter(psiClass, this, false).convertBody()
|
||||
classBody = ClassBody(constructorSignature, classBody.baseClassParams, classBody.members, classBody.classObjectMembers, listOf(), classBody.lBrace, classBody.rBrace)
|
||||
|
||||
val annotationAnnotation = Annotation(Identifier("annotation").assignNoPrototype(), listOf(), false, false).assignNoPrototype()
|
||||
@@ -299,11 +312,18 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
return if (convertedType == initializerType) null else convertedType
|
||||
}
|
||||
|
||||
public fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>?, constructorConverter: ConstructorConverter?): Member? {
|
||||
public fun convertMethod(method: PsiMethod,
|
||||
membersToRemove: MutableSet<PsiMember>?,
|
||||
constructorConverter: ConstructorConverter?,
|
||||
isInOpenClass: Boolean): Member? {
|
||||
val returnType = typeConverter.convertMethodReturnType(method)
|
||||
|
||||
val annotations = (convertAnnotations(method) + convertThrows(method)).assignNoPrototype()
|
||||
|
||||
var modifiers = convertModifiers(method)
|
||||
if (needOpenModifier(method, isInOpenClass, modifiers)) {
|
||||
modifiers = modifiers.with(Modifier.OPEN)
|
||||
}
|
||||
|
||||
val statementsToInsert = ArrayList<Statement>()
|
||||
for (parameter in method.getParameterList().getParameters()) {
|
||||
@@ -392,6 +412,19 @@ class Converter private(private val elementToConvert: PsiElement,
|
||||
return false
|
||||
}
|
||||
|
||||
private fun needOpenModifier(method: PsiMethod, isInOpenClass: Boolean, modifiers: Modifiers): Boolean {
|
||||
if (!isInOpenClass) return false
|
||||
if (modifiers.contains(Modifier.OVERRIDE) || modifiers.contains(Modifier.ABSTRACT)) return false
|
||||
if (settings.openByDefault) {
|
||||
return !method.hasModifierProperty(PsiModifier.FINAL)
|
||||
&& !method.hasModifierProperty(PsiModifier.PRIVATE)
|
||||
&& !method.hasModifierProperty(PsiModifier.STATIC)
|
||||
}
|
||||
else {
|
||||
return referenceSearcher.hasOverrides(method)
|
||||
}
|
||||
}
|
||||
|
||||
public fun convertCodeReferenceElement(element: PsiJavaCodeReferenceElement, hasExternalQualifier: Boolean, typeArgsConverted: List<Element>? = null): ReferenceElement {
|
||||
val typeArgs = typeArgsConverted ?: typeConverter.convertTypes(element.getTypeParameters())
|
||||
|
||||
|
||||
@@ -23,16 +23,22 @@ import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import java.util.Collections
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch
|
||||
|
||||
public trait ReferenceSearcher {
|
||||
fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression>
|
||||
fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression>
|
||||
fun hasInheritors(`class`: PsiClass): Boolean
|
||||
fun hasOverrides(method: PsiMethod): Boolean
|
||||
}
|
||||
|
||||
public object EmptyReferenceSearcher: ReferenceSearcher {
|
||||
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement) = listOf<PsiReferenceExpression>()
|
||||
override fun findMethodCalls(method: PsiMethod, scope: PsiElement) = listOf<PsiMethodCallExpression>()
|
||||
override fun hasInheritors(`class`: PsiClass) = false
|
||||
override fun hasOverrides(method: PsiMethod) = false
|
||||
}
|
||||
|
||||
public object IdeaReferenceSearcher : ReferenceSearcher {
|
||||
@@ -51,4 +57,8 @@ public object IdeaReferenceSearcher : ReferenceSearcher {
|
||||
}
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
override fun hasInheritors(`class`: PsiClass) = ClassInheritorsSearch.search(`class`, false).any()
|
||||
|
||||
override fun hasOverrides(method: PsiMethod) = OverridingMethodsSearch.search(method, false).any()
|
||||
}
|
||||
|
||||
+6
@@ -2162,6 +2162,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/tests/testData/fileOrElement/inheritance/classOneExtendsBaseWithZeroParamsNonEmptyConstructor.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("openModifier.java")
|
||||
public void testOpenModifier() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/tests/testData/fileOrElement/inheritance/openModifier.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("j2k/tests/testData/fileOrElement/isOperator")
|
||||
|
||||
+6
@@ -2162,6 +2162,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/tests/testData/fileOrElement/inheritance/classOneExtendsBaseWithZeroParamsNonEmptyConstructor.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("openModifier.java")
|
||||
public void testOpenModifier() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/tests/testData/fileOrElement/inheritance/openModifier.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("j2k/tests/testData/fileOrElement/isOperator")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base(o: Any, l: Int)
|
||||
open class Base(o: Any, l: Int)
|
||||
|
||||
class C(private val string: String) : Base(string, string.length())
|
||||
@@ -1,5 +1,4 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base(nested: Base.Nested) {
|
||||
open class Base(nested: Base.Nested) {
|
||||
|
||||
class Nested(p: Int) {
|
||||
class object {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
public class Base(x: Int) {
|
||||
public open class Base(x: Int) {
|
||||
public var x: Int = 42
|
||||
protected set
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
public class AAA {
|
||||
public open class AAA {
|
||||
public var x: Int = 42
|
||||
protected set
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base {
|
||||
open class Base {
|
||||
private val myFirst: String? = null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// ERROR: 'clone' in 'Base' is final and cannot be overridden
|
||||
// ERROR: 'finalize' in 'Base' is final and cannot be overridden
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: Unresolved reference: clone
|
||||
// ERROR: Unresolved reference: finalize
|
||||
package test
|
||||
@@ -29,7 +26,7 @@ class Test : Base() {
|
||||
}
|
||||
}
|
||||
|
||||
class Base {
|
||||
open class Base {
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
@@ -39,7 +36,7 @@ class Base {
|
||||
}
|
||||
|
||||
throws(javaClass<CloneNotSupportedException>())
|
||||
protected fun clone(): Any {
|
||||
protected open fun clone(): Any {
|
||||
return super.clone()
|
||||
}
|
||||
|
||||
@@ -48,7 +45,7 @@ class Base {
|
||||
}
|
||||
|
||||
throws(javaClass<Throwable>())
|
||||
protected fun finalize() {
|
||||
protected open fun finalize() {
|
||||
super.finalize()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// ERROR: 'a' in 'A' is final and cannot be overridden
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class A {
|
||||
fun a() {
|
||||
open class A {
|
||||
open fun a() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// ERROR: 'foo' in 'A' is final and cannot be overridden
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class A {
|
||||
fun foo() {
|
||||
open class A {
|
||||
open fun foo() {
|
||||
}
|
||||
}
|
||||
|
||||
class B : A() {
|
||||
open class B : A() {
|
||||
override fun foo() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: Unresolved reference: clone
|
||||
class Base
|
||||
open class Base
|
||||
|
||||
class X : Base() {
|
||||
override fun hashCode(): Int {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base {
|
||||
open class Base {
|
||||
override fun equals(o: Any?): Boolean {
|
||||
return super.equals(o)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class `$$$$$`
|
||||
|
||||
class `$`
|
||||
open class `$`
|
||||
|
||||
class `$$`(val `$$$`: `$$$$$`) : `$`() {
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base<T>(name: T)
|
||||
open class Base<T>(name: T)
|
||||
|
||||
class One<T, K>(name: T, private val mySecond: K) : Base<T>(name)
|
||||
@@ -1,4 +1,3 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base(name: String)
|
||||
open class Base(name: String)
|
||||
|
||||
class One(name: String, second: String) : Base(name)
|
||||
@@ -1,4 +1,3 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base
|
||||
open class Base
|
||||
|
||||
class One : Base()
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base(name: String)
|
||||
open class Base(name: String)
|
||||
|
||||
class One(name: String, private val mySecond: String) : Base(name)
|
||||
@@ -0,0 +1,48 @@
|
||||
import java.lang.Override;
|
||||
import java.lang.Void;
|
||||
|
||||
class A {
|
||||
public void f1(){}
|
||||
public void f2(){}
|
||||
private void f3(){}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
@Override
|
||||
public void f1() {
|
||||
super.f1();
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
@Override
|
||||
public void f1() {
|
||||
super.f1();
|
||||
}
|
||||
}
|
||||
|
||||
interface I {
|
||||
void f();
|
||||
}
|
||||
|
||||
class D implements I {
|
||||
@Override
|
||||
public void f() { }
|
||||
}
|
||||
|
||||
abstract class E {
|
||||
abstract void f1();
|
||||
void f2(){}
|
||||
void f3(){}
|
||||
}
|
||||
|
||||
class F extends E {
|
||||
@Override
|
||||
void f1() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void f2() {
|
||||
super.f2();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import java.lang.Void
|
||||
|
||||
open class A {
|
||||
public open fun f1() {
|
||||
}
|
||||
|
||||
public fun f2() {
|
||||
}
|
||||
|
||||
private fun f3() {
|
||||
}
|
||||
}
|
||||
|
||||
open class B : A() {
|
||||
override fun f1() {
|
||||
super.f1()
|
||||
}
|
||||
}
|
||||
|
||||
class C : B() {
|
||||
override fun f1() {
|
||||
super.f1()
|
||||
}
|
||||
}
|
||||
|
||||
trait I {
|
||||
public fun f()
|
||||
}
|
||||
|
||||
class D : I {
|
||||
override fun f() {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class E {
|
||||
abstract fun f1()
|
||||
open fun f2() {
|
||||
}
|
||||
|
||||
fun f3() {
|
||||
}
|
||||
}
|
||||
|
||||
class F : E() {
|
||||
override fun f1() {
|
||||
}
|
||||
|
||||
override fun f2() {
|
||||
super.f2()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: Property must be initialized or be abstract
|
||||
class Base {
|
||||
open class Base {
|
||||
inner class Nested
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
package test
|
||||
|
||||
class Base {
|
||||
open class Base {
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ERROR: 'test' in 'Base' is final and cannot be overridden
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
package com.voltvoodoo.saplo4j.model
|
||||
|
||||
import java.io.Serializable
|
||||
@@ -12,8 +10,8 @@ public class Language(protected var code: String) : Serializable {
|
||||
}
|
||||
|
||||
|
||||
class Base {
|
||||
fun test() {
|
||||
open class Base {
|
||||
open fun test() {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// ERROR: There's a cycle in the inheritance hierarchy for this type
|
||||
// ERROR: There's a cycle in the inheritance hierarchy for this type
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class A : B() {
|
||||
public fun foo(s: String) {
|
||||
open class A : B() {
|
||||
public open fun foo(s: String) {
|
||||
}
|
||||
}
|
||||
|
||||
class B : A() {
|
||||
public fun foo(s: String) {
|
||||
open class B : A() {
|
||||
public open fun foo(s: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// ERROR: 'foo' in 'Base' is final and cannot be overridden
|
||||
// ERROR: 'bar' in 'Base' is final and cannot be overridden
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
class Base {
|
||||
public fun foo(s: String?): String? {
|
||||
open class Base {
|
||||
public open fun foo(s: String?): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
public fun bar(s: String?): String? {
|
||||
public open fun bar(s: String?): String? {
|
||||
return if (s != null) s + 1 else null
|
||||
}
|
||||
|
||||
public fun zoo(o: Any): String {
|
||||
public open fun zoo(o: Any): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
package demo
|
||||
|
||||
trait WindowListener {
|
||||
public fun windowClosing()
|
||||
}
|
||||
|
||||
class WindowAdapter : WindowListener {
|
||||
open class WindowAdapter : WindowListener {
|
||||
override fun windowClosing() {
|
||||
}
|
||||
}
|
||||
|
||||
class Frame {
|
||||
open class Frame {
|
||||
public fun addWindowListener(listener: WindowListener) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// !openByDefault: true
|
||||
|
||||
open class A {
|
||||
open fun foo1() {
|
||||
open open fun foo1() {
|
||||
}
|
||||
|
||||
private fun foo2() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
package a.b
|
||||
|
||||
class Base {
|
||||
open class Base {
|
||||
fun foo() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//class
|
||||
class B {
|
||||
B(int i) {}
|
||||
int call() {return 1;}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class B(i: Int) {
|
||||
fun call(): Int {
|
||||
open class B(i: Int) {
|
||||
open fun call(): Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// ERROR: This type is final, so it cannot be inherited from
|
||||
package a.b
|
||||
|
||||
class Base {
|
||||
open class Base {
|
||||
fun foo() {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user