mirror of
https://github.com/jlengrand/detekt.git
synced 2026-03-10 08:11:23 +00:00
@@ -5,7 +5,7 @@ plugins {
|
||||
dependencies {
|
||||
val version = object {
|
||||
val spek = "2.0.15"
|
||||
val ktlint = "0.40.0"
|
||||
val ktlint = "0.41.0"
|
||||
}
|
||||
|
||||
constraints {
|
||||
|
||||
@@ -254,6 +254,8 @@ formatting:
|
||||
ArgumentListWrapping:
|
||||
active: false
|
||||
autoCorrect: true
|
||||
indentSize: 4
|
||||
maxLineLength: 120
|
||||
ChainWrapping:
|
||||
active: true
|
||||
autoCorrect: true
|
||||
@@ -272,7 +274,7 @@ formatting:
|
||||
ImportOrdering:
|
||||
active: false
|
||||
autoCorrect: true
|
||||
layout: 'idea'
|
||||
layout: '*,java.**,javax.**,kotlin.**,^'
|
||||
Indentation:
|
||||
active: false
|
||||
autoCorrect: true
|
||||
@@ -281,6 +283,7 @@ formatting:
|
||||
MaximumLineLength:
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
ignoreBackTickedIdentifier: false
|
||||
ModifierOrdering:
|
||||
active: true
|
||||
autoCorrect: true
|
||||
@@ -329,6 +332,7 @@ formatting:
|
||||
active: true
|
||||
autoCorrect: true
|
||||
indentSize: 4
|
||||
maxLineLength: 120
|
||||
SpacingAroundAngleBrackets:
|
||||
active: false
|
||||
autoCorrect: true
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.gitlab.arturbosch.detekt.formatting
|
||||
|
||||
import com.pinterest.ktlint.core.KtLint
|
||||
import com.pinterest.ktlint.core.api.FeatureInAlphaState
|
||||
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
|
||||
import io.github.detekt.psi.fileName
|
||||
import io.github.detekt.psi.toFilePath
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
@@ -23,6 +25,7 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
/**
|
||||
* Rule to detect formatting violations.
|
||||
*/
|
||||
@OptIn(FeatureInAlphaState::class)
|
||||
abstract class FormattingRule(config: Config) : Rule(config) {
|
||||
|
||||
abstract val wrapping: com.pinterest.ktlint.core.Rule
|
||||
@@ -49,21 +52,26 @@ abstract class FormattingRule(config: Config) : Rule(config) {
|
||||
val oldEditorConfig = root.node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)
|
||||
root.node.putUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY, oldEditorConfig.copy(overrides))
|
||||
}
|
||||
overrideEditorConfigProperties()?.let { overrides ->
|
||||
val oldUserData = root.node.getUserData(KtLint.EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY)
|
||||
val newUserData = if (oldUserData != null) {
|
||||
oldUserData + overrides
|
||||
} else {
|
||||
overrides
|
||||
val editorConfigProperties = overrideEditorConfigProperties()
|
||||
|
||||
if (!editorConfigProperties.isNullOrEmpty()) {
|
||||
val userData = (root.node.getUserData(KtLint.EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY) ?: emptyMap())
|
||||
.toMutableMap()
|
||||
editorConfigProperties.forEach { (editorConfigProperty, defaultValue) ->
|
||||
userData[editorConfigProperty.type.name] = Property.builder()
|
||||
.name(editorConfigProperty.type.name)
|
||||
.type(editorConfigProperty.type)
|
||||
.value(defaultValue)
|
||||
.build()
|
||||
}
|
||||
root.node.putUserData(KtLint.EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY, newUserData)
|
||||
root.node.putUserData(KtLint.EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY, userData)
|
||||
}
|
||||
root.node.putUserData(KtLint.FILE_PATH_USER_DATA_KEY, root.name)
|
||||
}
|
||||
|
||||
open fun overrideEditorConfig(): Map<String, Any>? = null
|
||||
|
||||
open fun overrideEditorConfigProperties(): Map<String, Property>? = null
|
||||
open fun overrideEditorConfigProperties(): Map<UsesEditorConfigProperties.EditorConfigProperty<*>, String>? = null
|
||||
|
||||
fun apply(node: ASTNode) {
|
||||
if (ruleShouldOnlyRunOnFileNode(node)) {
|
||||
|
||||
@@ -2,15 +2,37 @@ package io.gitlab.arturbosch.detekt.formatting.wrappers
|
||||
|
||||
import com.pinterest.ktlint.ruleset.experimental.ArgumentListWrappingRule
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.formatting.ANDROID_MAX_LINE_LENGTH
|
||||
import io.gitlab.arturbosch.detekt.formatting.DEFAULT_IDEA_LINE_LENGTH
|
||||
import io.gitlab.arturbosch.detekt.formatting.DEFAULT_INDENT
|
||||
import io.gitlab.arturbosch.detekt.formatting.FormattingRule
|
||||
import io.gitlab.arturbosch.detekt.formatting.INDENT_SIZE_KEY
|
||||
import io.gitlab.arturbosch.detekt.formatting.MAX_LINE_LENGTH_KEY
|
||||
|
||||
/**
|
||||
* See <a href="https://ktlint.github.io">ktlint-website</a> for documentation.
|
||||
*
|
||||
* @configuration indentSize - indentation size (default: `4`)
|
||||
* @configuration maxLineLength - maximum line length (default: `120`)
|
||||
*
|
||||
* @autoCorrect since v1.0.0
|
||||
*/
|
||||
class ArgumentListWrapping(config: Config) : FormattingRule(config) {
|
||||
|
||||
override val wrapping = ArgumentListWrappingRule()
|
||||
override val issue = issueFor("Reports incorrect argument list wrapping")
|
||||
|
||||
private val indentSize = valueOrDefault(INDENT_SIZE, DEFAULT_INDENT)
|
||||
private val defaultMaxLineLength = if (isAndroid) ANDROID_MAX_LINE_LENGTH else DEFAULT_IDEA_LINE_LENGTH
|
||||
private val maxLineLength = valueOrDefault(MAX_LINE_LENGTH, defaultMaxLineLength)
|
||||
|
||||
override fun overrideEditorConfig() = mapOf(
|
||||
INDENT_SIZE_KEY to indentSize,
|
||||
MAX_LINE_LENGTH_KEY to maxLineLength
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val INDENT_SIZE = "indentSize"
|
||||
const val MAX_LINE_LENGTH = "maxLineLength"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package io.gitlab.arturbosch.detekt.formatting.wrappers
|
||||
|
||||
import com.pinterest.ktlint.core.api.FeatureInAlphaState
|
||||
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
|
||||
import com.pinterest.ktlint.ruleset.standard.FinalNewlineRule
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
|
||||
import io.gitlab.arturbosch.detekt.formatting.FormattingRule
|
||||
import io.gitlab.arturbosch.detekt.formatting.INSERT_FINAL_NEWLINE_KEY
|
||||
import org.ec4j.core.model.Property
|
||||
import org.ec4j.core.model.PropertyType
|
||||
|
||||
/**
|
||||
* See <a href="https://ktlint.github.io">ktlint-website</a> for documentation.
|
||||
@@ -25,13 +23,8 @@ class FinalNewline(config: Config) : FormattingRule(config) {
|
||||
|
||||
private val insertFinalNewline = valueOrDefault(INSERT_FINAL_NEWLINE, true)
|
||||
|
||||
// HACK! FinalNewlineRule.insertNewLineProperty is internal. Therefore we are building
|
||||
// our custom Property to override the editor config properties.
|
||||
// When FinalNewlineRule exits the alpha/beta state, hopefully we could remove this hack.
|
||||
override fun overrideEditorConfigProperties() = mapOf(
|
||||
INSERT_FINAL_NEWLINE_KEY to
|
||||
Property.builder().type(PropertyType.insert_final_newline).value(insertFinalNewline.toString()).build()
|
||||
)
|
||||
override fun overrideEditorConfigProperties(): Map<UsesEditorConfigProperties.EditorConfigProperty<*>, String> =
|
||||
mapOf(FinalNewlineRule.insertNewLineProperty to insertFinalNewline.toString())
|
||||
}
|
||||
|
||||
const val INSERT_FINAL_NEWLINE = "insertFinalNewLine"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package io.gitlab.arturbosch.detekt.formatting.wrappers
|
||||
|
||||
import com.pinterest.ktlint.core.api.FeatureInAlphaState
|
||||
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
|
||||
import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.formatting.FormattingRule
|
||||
import io.gitlab.arturbosch.detekt.formatting.KOTLIN_IMPORTS_LAYOUT_KEY
|
||||
import org.ec4j.core.model.Property
|
||||
|
||||
/**
|
||||
* See <a href="https://ktlint.github.io">ktlint-website</a> for documentation.
|
||||
*
|
||||
* For defining custom import layout patterns see: https://github.com/pinterest/ktlint/blob/cdf871b6f015359f9a6f02e15ef1b85a6c442437/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt
|
||||
* For defining import layout patterns see:
|
||||
* https://github.com/pinterest/ktlint/blob/a6ca5b2edf95cc70a138a9470cfb6c4fd5d9d3ce/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt
|
||||
*
|
||||
* @configuration layout - the import ordering layout; use 'ascii', 'idea' or define a custom one (default: `'idea'`)
|
||||
* @configuration layout - the import ordering layout; (default: `'*,java.**,javax.**,kotlin.**,^'`)
|
||||
*
|
||||
* @autoCorrect since v1.0.0
|
||||
*/
|
||||
@@ -24,19 +24,14 @@ class ImportOrdering(config: Config) : FormattingRule(config) {
|
||||
|
||||
private val layout: String = valueOrNull(LAYOUT_PATTERN) ?: chooseDefaultLayout()
|
||||
|
||||
private fun chooseDefaultLayout() = if (isAndroid) ASCII else IDEA
|
||||
private fun chooseDefaultLayout() = if (isAndroid) ASCII_PATTERN else IDEA_PATTERN
|
||||
|
||||
// HACK! ImportOrderingRule.ktlintCustomImportsLayoutProperty is internal. Therefore we are using
|
||||
// ImportOrderingRule.editorConfigProperties.first() to access it.
|
||||
// When ImportOrderingRule exits the alpha/beta state, hopefully we could remove this hack.
|
||||
override fun overrideEditorConfigProperties() = mapOf(
|
||||
KOTLIN_IMPORTS_LAYOUT_KEY to
|
||||
Property.builder().type(wrapping.editorConfigProperties.first().type).value(layout).build()
|
||||
)
|
||||
override fun overrideEditorConfigProperties(): Map<UsesEditorConfigProperties.EditorConfigProperty<*>, String> =
|
||||
mapOf(ImportOrderingRule.ideaImportsLayoutProperty to layout)
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_PATTERN = "layout"
|
||||
const val ASCII = "ascii"
|
||||
const val IDEA = "idea"
|
||||
const val ASCII_PATTERN = "*"
|
||||
const val IDEA_PATTERN = "*,java.**,javax.**,kotlin.**,^"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.gitlab.arturbosch.detekt.formatting.wrappers
|
||||
|
||||
import com.pinterest.ktlint.core.api.FeatureInAlphaState
|
||||
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
|
||||
import com.pinterest.ktlint.ruleset.standard.MaxLineLengthRule
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
|
||||
@@ -12,8 +14,10 @@ import io.gitlab.arturbosch.detekt.formatting.MAX_LINE_LENGTH_KEY
|
||||
* See <a href="https://ktlint.github.io">ktlint-website</a> for documentation.
|
||||
*
|
||||
* @configuration maxLineLength - maximum line length (default: `120`)
|
||||
* @configuration ignoreBackTickedIdentifier - ignore back ticked identifier (default: `false`)
|
||||
*/
|
||||
@ActiveByDefault(since = "1.0.0")
|
||||
@OptIn(FeatureInAlphaState::class)
|
||||
class MaximumLineLength(config: Config) : FormattingRule(config) {
|
||||
|
||||
override val wrapping = MaxLineLengthRule()
|
||||
@@ -27,10 +31,15 @@ class MaximumLineLength(config: Config) : FormattingRule(config) {
|
||||
else DEFAULT_IDEA_LINE_LENGTH
|
||||
|
||||
private val maxLineLength: Int = valueOrDefault(MAX_LINE_LENGTH, defaultMaxLineLength)
|
||||
private val ignoreBackTickedIdentifier = valueOrDefault(IGNORE_BACK_TICKED_IDENTIFIER, false)
|
||||
|
||||
override fun overrideEditorConfig() = mapOf(MAX_LINE_LENGTH_KEY to maxLineLength)
|
||||
|
||||
override fun overrideEditorConfigProperties(): Map<UsesEditorConfigProperties.EditorConfigProperty<*>, String> =
|
||||
mapOf(MaxLineLengthRule.ignoreBackTickedIdentifierProperty to ignoreBackTickedIdentifier.toString())
|
||||
|
||||
companion object {
|
||||
const val MAX_LINE_LENGTH = "maxLineLength"
|
||||
const val IGNORE_BACK_TICKED_IDENTIFIER = "ignoreBackTickedIdentifier"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@ package io.gitlab.arturbosch.detekt.formatting.wrappers
|
||||
import com.pinterest.ktlint.ruleset.standard.ParameterListWrappingRule
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
|
||||
import io.gitlab.arturbosch.detekt.formatting.ANDROID_MAX_LINE_LENGTH
|
||||
import io.gitlab.arturbosch.detekt.formatting.DEFAULT_IDEA_LINE_LENGTH
|
||||
import io.gitlab.arturbosch.detekt.formatting.DEFAULT_INDENT
|
||||
import io.gitlab.arturbosch.detekt.formatting.FormattingRule
|
||||
import io.gitlab.arturbosch.detekt.formatting.INDENT_SIZE_KEY
|
||||
import io.gitlab.arturbosch.detekt.formatting.MAX_LINE_LENGTH_KEY
|
||||
|
||||
/**
|
||||
* See <a href="https://ktlint.github.io">ktlint-website</a> for documentation.
|
||||
*
|
||||
* @configuration indentSize - indentation size (default: `4`)
|
||||
* @configuration maxLineLength - maximum line length (default: `120`)
|
||||
*
|
||||
* @autoCorrect since v1.0.0
|
||||
*/
|
||||
@@ -21,10 +25,16 @@ class ParameterListWrapping(config: Config) : FormattingRule(config) {
|
||||
override val issue = issueFor("Detects mis-aligned parameter lists")
|
||||
|
||||
private val indentSize = valueOrDefault(INDENT_SIZE, DEFAULT_INDENT)
|
||||
private val defaultMaxLineLength = if (isAndroid) ANDROID_MAX_LINE_LENGTH else DEFAULT_IDEA_LINE_LENGTH
|
||||
private val maxLineLength = valueOrDefault(MAX_LINE_LENGTH, defaultMaxLineLength)
|
||||
|
||||
override fun overrideEditorConfig() = mapOf(INDENT_SIZE_KEY to indentSize)
|
||||
override fun overrideEditorConfig() = mapOf(
|
||||
INDENT_SIZE_KEY to indentSize,
|
||||
MAX_LINE_LENGTH_KEY to maxLineLength
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val INDENT_SIZE = "indentSize"
|
||||
const val MAX_LINE_LENGTH = "maxLineLength"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.gitlab.arturbosch.detekt.formatting
|
||||
|
||||
import io.gitlab.arturbosch.detekt.api.Config
|
||||
import io.gitlab.arturbosch.detekt.formatting.wrappers.ArgumentListWrapping
|
||||
import io.gitlab.arturbosch.detekt.test.TestConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
class ArgumentListWrappingSpec : Spek({
|
||||
|
||||
describe("ArgumentListWrapping rule") {
|
||||
|
||||
it("reports wrong argument wrapping") {
|
||||
val code = """
|
||||
val x = f(
|
||||
1,
|
||||
2, 3
|
||||
)
|
||||
""".trimIndent()
|
||||
assertThat(ArgumentListWrapping(Config.empty).lint(code)).hasSize(1)
|
||||
}
|
||||
|
||||
it("does not report correct argument list wrapping") {
|
||||
val code = """
|
||||
val x = f(
|
||||
1,
|
||||
2,
|
||||
3
|
||||
)
|
||||
""".trimIndent()
|
||||
assertThat(ArgumentListWrapping(Config.empty).lint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("does not report when overriding an indentation level config of 1") {
|
||||
val code = """
|
||||
val x = f(
|
||||
1,
|
||||
2,
|
||||
3
|
||||
)
|
||||
""".trimIndent()
|
||||
val config = TestConfig(ArgumentListWrapping.INDENT_SIZE to "1")
|
||||
assertThat(ArgumentListWrapping(config).lint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("reports when max line length is exceeded") {
|
||||
val code = """
|
||||
val x = f(1111, 2222, 3333)
|
||||
""".trimIndent()
|
||||
val config = TestConfig(ArgumentListWrapping.MAX_LINE_LENGTH to "10")
|
||||
assertThat(ArgumentListWrapping(config).lint(code)).hasSize(4)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -53,13 +53,15 @@ class ImportOrderingSpec : Spek({
|
||||
""".trimIndent()
|
||||
|
||||
it("passes for alphabetical order") {
|
||||
val findings = ImportOrdering(TestConfig("layout" to "ascii")).lint(negativeCase)
|
||||
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
|
||||
.lint(negativeCase)
|
||||
|
||||
assertThat(findings).isEmpty()
|
||||
}
|
||||
|
||||
it("fails for non alphabetical order") {
|
||||
val findings = ImportOrdering(TestConfig("layout" to "ascii")).lint(positiveCase)
|
||||
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
|
||||
.lint(positiveCase)
|
||||
|
||||
assertThat(findings).hasSize(1)
|
||||
}
|
||||
|
||||
@@ -57,5 +57,20 @@ class MaximumLineLengthSpec : Spek({
|
||||
assertThat(findings[0].entity.location.source.line).isEqualTo(8)
|
||||
assertThat(findings[1].entity.location.source.line).isEqualTo(14)
|
||||
}
|
||||
|
||||
it("does not report back ticked line which exceeds the threshold") {
|
||||
val code = """
|
||||
package home.test
|
||||
fun `this is a test method that has more than 30 characters inside the back ticks`() {
|
||||
}
|
||||
""".trimIndent()
|
||||
val findings = MaximumLineLength(
|
||||
TestConfig(
|
||||
MaximumLineLength.MAX_LINE_LENGTH to "30",
|
||||
MaximumLineLength.IGNORE_BACK_TICKED_IDENTIFIER to "true"
|
||||
)
|
||||
).lint(code)
|
||||
assertThat(findings).isEmpty()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,7 +15,11 @@ class ParameterListWrappingSpec : Spek({
|
||||
|
||||
describe("indent size equals 1") {
|
||||
|
||||
val code = "fun f(\n a: Int\n) {}"
|
||||
val code = """
|
||||
fun f(
|
||||
a: Int
|
||||
) {}
|
||||
""".trimIndent()
|
||||
|
||||
it("reports wrong indent size") {
|
||||
assertThat(subject.lint(code)).hasSize(1)
|
||||
@@ -28,8 +32,20 @@ class ParameterListWrappingSpec : Spek({
|
||||
}
|
||||
|
||||
it("does not report correct ParameterListWrapping level") {
|
||||
val code = "fun f(\n a: Int\n) {}"
|
||||
val code = """
|
||||
fun f(
|
||||
a: Int
|
||||
) {}
|
||||
""".trimIndent()
|
||||
assertThat(subject.lint(code)).isEmpty()
|
||||
}
|
||||
|
||||
it("reports when max line length is exceeded") {
|
||||
val code = """
|
||||
fun f(a: Int, b: Int, c: Int) {}
|
||||
""".trimIndent()
|
||||
val config = TestConfig(ParameterListWrapping.MAX_LINE_LENGTH to "10")
|
||||
assertThat(ParameterListWrapping(config).lint(code)).hasSize(4)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -163,14 +163,7 @@ private fun FlowOrInteractiveContent.summary(
|
||||
private class SUMMARY(
|
||||
initialAttributes: Map<String, String>,
|
||||
override val consumer: TagConsumer<*>
|
||||
) : HTMLTag(
|
||||
"summary",
|
||||
consumer,
|
||||
initialAttributes,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
),
|
||||
) : HTMLTag("summary", consumer, initialAttributes, null, false, false),
|
||||
CommonAttributeGroupFacadeFlowInteractiveContent
|
||||
|
||||
private fun TextLocation.length(): Int = end - start
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Remove the suppression when https://github.com/pinterest/ktlint/issues/1125 is fixed
|
||||
@file:Suppress("SpacingBetweenDeclarationsWithAnnotations")
|
||||
|
||||
package io.gitlab.arturbosch.detekt.rules
|
||||
|
||||
import io.github.detekt.test.utils.createEnvironment
|
||||
@@ -8,7 +11,7 @@ import org.spekframework.spek2.lifecycle.CachingMode
|
||||
fun Root.setupKotlinEnvironment() {
|
||||
val wrapper by memoized(CachingMode.SCOPE, { createEnvironment() }, { it.dispose() })
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
// name is used for delegation
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val env: KotlinCoreEnvironment by memoized(CachingMode.EACH_GROUP) { wrapper.env }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user