mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[gradle] resolve/complete configurations via DependencyHandler instance
This commit is contained in:
@@ -35,8 +35,6 @@
|
||||
<annotator language="Groovy" implementationClass="org.jetbrains.plugins.gradle.service.resolve.dsl.GradleDslAnnotator"/>
|
||||
<weigher key="completion" implementationClass="org.jetbrains.plugins.gradle.service.resolve.dsl.GradleCompletionWeigher"
|
||||
id="gradleWeigher" order="first"/>
|
||||
<completion.contributor language="Groovy" order="first"
|
||||
implementationClass="org.jetbrains.plugins.gradle.codeInsight.ConfigurationsCompletionContributor"/>
|
||||
<completion.contributor language="Groovy" order="first"
|
||||
implementationClass="org.jetbrains.plugins.gradle.codeInsight.PropertiesTasksCompletionContributor"/>
|
||||
|
||||
@@ -61,6 +59,7 @@
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSettingsScriptContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptMembersContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleProjectMembersContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleDependencyHandlerContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleNonCodeMembersContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleScriptContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.gradle.service.resolve.GradleSetterAsMethodContributor"/>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.gradle.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.completion.*
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.patterns.PlatformPatterns.psiElement
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.ProcessingContext
|
||||
import icons.GradleIcons
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleExtensionsContributor
|
||||
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightVariable
|
||||
import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure
|
||||
import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
|
||||
import java.lang.Double.MAX_VALUE
|
||||
|
||||
/**
|
||||
* @author Vladislav.Soroka
|
||||
*/
|
||||
class ConfigurationsCompletionContributor : AbstractGradleCompletionContributor() {
|
||||
|
||||
class ConfigurationsCompletionProvider(val isScriptClasspath: Boolean = false) : CompletionProvider<CompletionParameters>() {
|
||||
|
||||
override fun addCompletions(params: CompletionParameters,
|
||||
context: ProcessingContext,
|
||||
result: CompletionResultSet) {
|
||||
val position = params.position
|
||||
val extensionsData = GradleExtensionsContributor.getExtensionsFor(position) ?: return
|
||||
for (configuration in extensionsData.configurations.values) {
|
||||
if (isScriptClasspath != configuration.scriptClasspath) continue
|
||||
|
||||
val confVar = object : GrLightVariable(position.manager, configuration.name, GRADLE_API_CONFIGURATION, position) {
|
||||
override fun getNavigationElement(): PsiElement {
|
||||
val navigationElement = super.getNavigationElement()
|
||||
val description = if (isScriptClasspath && configuration.description == null) DEFAULT_SCRIPT_CLASSPATH_DESCRIPTION else configuration.description
|
||||
navigationElement.putUserData(NonCodeMembersHolder.DOCUMENTATION, description)
|
||||
return navigationElement
|
||||
}
|
||||
}
|
||||
val elementBuilder = LookupElementBuilder.create(confVar, configuration.name)
|
||||
.withIcon(GradleIcons.Gradle)
|
||||
.withTypeText("Configuration")
|
||||
result.addElement(PrioritizedLookupElement.withPriority(elementBuilder, MAX_VALUE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
extend(CompletionType.BASIC, PROJECT_DEPENDENCIES_PLACE_PATTERN, ConfigurationsCompletionProvider())
|
||||
extend(CompletionType.BASIC, SCRIPT_DEPENDENCIES_PLACE_PATTERN, ConfigurationsCompletionProvider(true))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DEFAULT_SCRIPT_CLASSPATH_DESCRIPTION = "The script classpath configuration used to compile and execute a build script. " +
|
||||
"This classpath is also used to load the plugins which the build script uses."
|
||||
|
||||
private val PROJECT_DEPENDENCIES_PLACE_PATTERN = psiElement()
|
||||
.and(GRADLE_FILE_PATTERN)
|
||||
.withParent(GrReferenceExpression::class.java)
|
||||
.withAncestor(4, groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "dependencies")))
|
||||
|
||||
private val SCRIPT_DEPENDENCIES_PLACE_PATTERN = psiElement()
|
||||
.and(GRADLE_FILE_PATTERN)
|
||||
.withParent(GrReferenceExpression::class.java)
|
||||
.withAncestor(4, groovyClosure().inMethod(psiMethod(GRADLE_API_SCRIPT_HANDLER, "dependencies")))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.gradle.service.resolve
|
||||
|
||||
import com.intellij.util.ProcessingContext
|
||||
import groovy.lang.Closure
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_PROJECT
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_SCRIPT_HANDLER
|
||||
@@ -16,12 +17,17 @@ import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
|
||||
*/
|
||||
class GradleBuildScriptContributor : GradleMethodContextContributor {
|
||||
companion object {
|
||||
val buildscriptClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_PROJECT, "buildscript"))
|
||||
val buildscriptClosure: GroovyClosurePattern = groovyClosure().inMethod(
|
||||
psiMethod(GRADLE_API_PROJECT, "buildscript")
|
||||
).inMethodResult(saveProjectType)
|
||||
}
|
||||
|
||||
override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? {
|
||||
if (buildscriptClosure.accepts(closure)) {
|
||||
return DelegatesToInfo(TypesUtil.createType(GRADLE_API_SCRIPT_HANDLER, closure), Closure.DELEGATE_FIRST)
|
||||
val context = ProcessingContext()
|
||||
if (buildscriptClosure.accepts(closure, context)) {
|
||||
val scriptHandler = TypesUtil.createType(GRADLE_API_SCRIPT_HANDLER, closure)
|
||||
val delegate = context.get(projectTypeKey)?.setType(scriptHandler, true) ?: scriptHandler
|
||||
return DelegatesToInfo(delegate, Closure.DELEGATE_FIRST)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.gradle.service.resolve
|
||||
|
||||
import com.intellij.patterns.PsiJavaPatterns.psiElement
|
||||
import com.intellij.patterns.StandardPatterns.or
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.ResolveState
|
||||
import com.intellij.psi.scope.ElementClassHint
|
||||
import com.intellij.psi.scope.PsiScopeProcessor
|
||||
import com.intellij.util.ProcessingContext
|
||||
import groovy.lang.Closure
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleResolverUtil.canBeMethodOf
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder
|
||||
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyClosurePattern
|
||||
import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyClosure
|
||||
import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
|
||||
|
||||
/**
|
||||
@@ -31,7 +21,7 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
|
||||
val dependenciesClosure: GroovyClosurePattern = groovyClosure().inMethod(or(
|
||||
psiMethod(GRADLE_API_PROJECT, "dependencies"),
|
||||
psiMethod(GRADLE_API_SCRIPT_HANDLER, "dependencies")
|
||||
))
|
||||
)).inMethodResult(saveProjectType)
|
||||
|
||||
val dependencyClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_DEPENDENCY_HANDLER, "add"))
|
||||
val moduleClosure: GroovyClosurePattern = groovyClosure().inMethod(psiMethod(GRADLE_API_DEPENDENCY_HANDLER, "module"))
|
||||
@@ -44,8 +34,13 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
|
||||
}
|
||||
|
||||
override fun getDelegatesToInfo(closure: GrClosableBlock): DelegatesToInfo? {
|
||||
val context = ProcessingContext()
|
||||
if (dependenciesClosure.accepts(closure, context)) {
|
||||
val dependencyHandler = TypesUtil.createType(GRADLE_API_DEPENDENCY_HANDLER, closure)
|
||||
val delegate = context.get(projectTypeKey)?.setType(dependencyHandler) ?: dependencyHandler
|
||||
return DelegatesToInfo(delegate, Closure.DELEGATE_FIRST)
|
||||
}
|
||||
val fqn = when {
|
||||
dependenciesClosure.accepts(closure) -> GRADLE_API_DEPENDENCY_HANDLER
|
||||
dependencyClosure.accepts(closure) -> GRADLE_API_ARTIFACTS_MODULE_DEPENDENCY
|
||||
moduleClosure.accepts(closure) -> GRADLE_API_ARTIFACTS_CLIENT_MODULE_DEPENDENCY
|
||||
componentsClosure.accepts(closure) -> GRADLE_API_COMPONENT_METADATA_HANDLER
|
||||
@@ -55,30 +50,4 @@ class GradleDependenciesContributor : GradleMethodContextContributor {
|
||||
}
|
||||
return DelegatesToInfo(TypesUtil.createType(fqn, closure), Closure.DELEGATE_FIRST)
|
||||
}
|
||||
|
||||
override fun process(methodCallInfo: List<String>, processor: PsiScopeProcessor, state: ResolveState, place: PsiElement): Boolean {
|
||||
val groovyPsiManager = GroovyPsiManager.getInstance(place.project)
|
||||
val methodName = methodCallInfo.firstOrNull() ?: return true
|
||||
|
||||
val classHint = processor.getHint(ElementClassHint.KEY)
|
||||
val shouldProcessMethods = ResolveUtil.shouldProcessMethods(classHint)
|
||||
if (shouldProcessMethods && place is GrReferenceExpression && psiElement().inside(dependenciesClosure).accepts(place)) {
|
||||
if (methodCallInfo.size == 2) {
|
||||
val resolveScope = place.getResolveScope()
|
||||
val psiClass = JavaPsiFacade.getInstance(place.project).findClass(GRADLE_API_DEPENDENCY_HANDLER, resolveScope) ?: return true
|
||||
if (canBeMethodOf(methodName, psiClass)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val returnClass = groovyPsiManager.createTypeByFQClassName(GRADLE_API_ARTIFACTS_DEPENDENCY, resolveScope)
|
||||
val wrappedBase = GrLightMethodBuilder(place.manager, methodName).apply {
|
||||
returnType = returnClass
|
||||
containingClass = psiClass
|
||||
}
|
||||
wrappedBase.addParameter("dependencyNotation", TypesUtil.getJavaLangObject(place).createArrayType())
|
||||
if (!processor.execute(wrappedBase, state)) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.gradle.service.resolve
|
||||
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.scope.PsiScopeProcessor
|
||||
import icons.GradleIcons
|
||||
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.GRADLE_API_DEPENDENCY_HANDLER
|
||||
import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings.GradleConfiguration
|
||||
import org.jetbrains.plugins.gradle.util.GradleBundle
|
||||
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.getName
|
||||
import org.jetbrains.plugins.groovy.lang.resolve.shouldProcessMethods
|
||||
|
||||
class GradleDependencyHandlerContributor : NonCodeMembersContributor() {
|
||||
|
||||
override fun getParentClassName(): String? = GRADLE_API_DEPENDENCY_HANDLER
|
||||
|
||||
override fun processDynamicElements(qualifierType: PsiType,
|
||||
clazz: PsiClass?,
|
||||
processor: PsiScopeProcessor,
|
||||
place: PsiElement,
|
||||
state: ResolveState) {
|
||||
if (qualifierType !is GradleProjectAwareType) return
|
||||
|
||||
if (clazz == null) return
|
||||
if (!processor.shouldProcessMethods()) return
|
||||
|
||||
val data = GradleExtensionsContributor.getExtensionsFor(place) ?: return
|
||||
val methodName = processor.getName(state)
|
||||
val manager = place.manager
|
||||
val objectVarargType = PsiEllipsisType(TypesUtil.getJavaLangObject(place))
|
||||
|
||||
val configurations = if (methodName == null) data.configurations.values else listOf(data.configurations[methodName] ?: return)
|
||||
for (configuration in configurations) {
|
||||
if (configuration.scriptClasspath != qualifierType.buildscript) continue
|
||||
val configurationName = configuration.name ?: continue
|
||||
val method = GrLightMethodBuilder(manager, configurationName).apply {
|
||||
methodKind = GradleArtifactHandlerContributor.ourMethodKind
|
||||
containingClass = clazz
|
||||
returnType = null
|
||||
addParameter("dependencyNotation", objectVarargType)
|
||||
setBaseIcon(GradleIcons.Gradle)
|
||||
putUserData(NonCodeMembersHolder.DOCUMENTATION, configuration.getDescription())
|
||||
}
|
||||
if (!processor.execute(method, state)) return
|
||||
}
|
||||
}
|
||||
|
||||
private fun GradleConfiguration.getDescription(): String? {
|
||||
if (description == null && scriptClasspath && name == "classpath") {
|
||||
return GradleBundle.message("gradle.codeInsigt.buildscript.classpath.description")
|
||||
}
|
||||
else {
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import com.intellij.psi.PsiType
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
|
||||
// TODO track exact project id to obtain project extension data
|
||||
class GradleProjectAwareType(
|
||||
private val delegate: PsiClassType
|
||||
class GradleProjectAwareType (
|
||||
private val delegate: PsiClassType,
|
||||
val buildscript: Boolean
|
||||
) : PsiClassType(LanguageLevel.HIGHEST) {
|
||||
|
||||
override fun isValid(): Boolean = delegate.isValid
|
||||
@@ -17,7 +18,7 @@ class GradleProjectAwareType(
|
||||
override fun resolve(): PsiClass? = delegate.resolve()
|
||||
override fun resolveGenerics(): ClassResolveResult = delegate.resolveGenerics()
|
||||
override fun getParameters(): Array<PsiType> = delegate.parameters
|
||||
override fun rawType(): PsiClassType = GradleProjectAwareType(delegate.rawType())
|
||||
override fun rawType(): PsiClassType = GradleProjectAwareType(delegate.rawType(), buildscript)
|
||||
|
||||
override fun getClassName(): String = delegate.className
|
||||
override fun getCanonicalText(): String = delegate.canonicalText
|
||||
@@ -27,5 +28,11 @@ class GradleProjectAwareType(
|
||||
override fun getLanguageLevel(): LanguageLevel = delegate.languageLevel
|
||||
override fun setLanguageLevel(languageLevel: LanguageLevel): PsiClassType = error("must not be called")
|
||||
|
||||
fun setType(delegate: PsiClassType): GradleProjectAwareType = GradleProjectAwareType(delegate)
|
||||
fun setType(delegate: PsiClassType): GradleProjectAwareType {
|
||||
return GradleProjectAwareType(delegate, buildscript)
|
||||
}
|
||||
|
||||
fun setType(delegate: PsiClassType, buildscript: Boolean): GradleProjectAwareType {
|
||||
return GradleProjectAwareType(delegate, buildscript)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ class GradleProjectCallTypeCalculator : GrCallTypeCalculator {
|
||||
private val methodNames = setOf(
|
||||
"getProject",
|
||||
"getArtifacts",
|
||||
"getTasks"
|
||||
"getTasks",
|
||||
"getDependencies"
|
||||
)
|
||||
|
||||
override fun getType(receiver: PsiType?, method: PsiMethod, arguments: Arguments?, context: PsiElement): PsiType? {
|
||||
|
||||
@@ -21,7 +21,7 @@ class GradleScriptMembersContributor : NonCodeMembersContributor() {
|
||||
place: PsiElement,
|
||||
state: ResolveState) {
|
||||
if (aClass == null || !aClass.isResolvedInGradleScript()) return
|
||||
val delegate = GradleProjectAwareType(createType(GRADLE_API_PROJECT, place.containingFile))
|
||||
val delegate = GradleProjectAwareType(createType(GRADLE_API_PROJECT, place.containingFile), false)
|
||||
delegate.processReceiverType(processor, state, place)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
|
||||
|
||||
@Override
|
||||
protected List<String> getParentCalls() {
|
||||
return super.getParentCalls() + 'buildscript'
|
||||
return []
|
||||
}
|
||||
|
||||
@Test
|
||||
void dependenciesTest() {
|
||||
importProject("")
|
||||
importProject("apply plugin: 'java'")
|
||||
new RunAll().append {
|
||||
'dependencies delegate'()
|
||||
} append {
|
||||
@@ -38,6 +38,14 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
|
||||
'modules delegate'()
|
||||
} append {
|
||||
'modules module delegate'()
|
||||
} append {
|
||||
'classpath configuration'()
|
||||
} append {
|
||||
'compile configuration'()
|
||||
} append {
|
||||
'buildscript classpath configuration'()
|
||||
} append {
|
||||
'buildscript compile configuration'()
|
||||
} run()
|
||||
}
|
||||
|
||||
@@ -96,4 +104,34 @@ class GradleDependenciesTest extends GradleHighlightingBaseTest implements Resol
|
||||
closureDelegateTest(GRADLE_API_COMPONENT_MODULE_METADATA_DETAILS, 1)
|
||||
}
|
||||
}
|
||||
|
||||
void 'classpath configuration'() {
|
||||
doTest('dependencies { <caret>classpath("hi") }') {
|
||||
resolveTest(null)
|
||||
}
|
||||
}
|
||||
|
||||
void 'compile configuration'() {
|
||||
doTest('dependencies { <caret>compile("hi") }') {
|
||||
methodTest(resolveTest(PsiMethod), "compile", GRADLE_API_DEPENDENCY_HANDLER)
|
||||
}
|
||||
}
|
||||
|
||||
void 'compile confiduration via property'() {
|
||||
doTest('dependencies.<caret>testCompile("hi")') {
|
||||
methodTest(resolveTest(PsiMethod), "testCompile", GRADLE_API_DEPENDENCY_HANDLER)
|
||||
}
|
||||
}
|
||||
|
||||
void 'buildscript classpath configuration'() {
|
||||
doTest('buildscript { dependencies { <caret>classpath("hi") } }') {
|
||||
methodTest(resolveTest(PsiMethod), "classpath", GRADLE_API_DEPENDENCY_HANDLER)
|
||||
}
|
||||
}
|
||||
|
||||
void 'buildscript compile configuration'() {
|
||||
doTest('buildscript { dependencies { <caret>compile("hi") } }') {
|
||||
resolveTest(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ gradle.codeInsight.action.apply_plugin.popup.title=Choose plugin
|
||||
gradle.codeInsight.action.add_maven_dependency.text=Add Maven artifact dependency
|
||||
gradle.codeInsight.action.add_maven_dependency.description=Add selected Maven artifact dependency to the project
|
||||
|
||||
gradle.codeInsigt.buildscript.classpath.description=The script classpath configuration used to compile and execute a build script. \
|
||||
This classpath is also used to load the plugins which the build script uses.
|
||||
|
||||
gradle.runner=Runner
|
||||
gradle.runner.toggle.tree.text.action.name=Toggle tasks executions/text mode
|
||||
gradle.preferred_test_runner.CHOOSE_PER_TEST=Choose per test
|
||||
|
||||
Reference in New Issue
Block a user