diff --git a/README.md b/README.md
index e2c35703fe..c5dc41b7a4 100644
--- a/README.md
+++ b/README.md
@@ -374,7 +374,8 @@ Also see [the list of all rules supported by diKTat](info/available-rules.md).
Suppress warnings on individual code blocks
-In addition to enabling/disabling warning globally via config file (`enable = false`), you can suppress warnings by adding `@Suppress` annotation on individual code blocks
+In addition to enabling/disabling warning globally via config file (`enable = false`), you can suppress warnings
+by adding `@Suppress` annotation on individual code blocks or `@file:Suppress()` annotation on a file-level.
For example:
@@ -389,7 +390,35 @@ class SomeClass {
-Suppress groups of inspections
+Disable all inspections on selected code blocks
+Also you can suppress **all** warnings by adding `@Suppress("diktat")` annotation on individual code blocks.
+
+For example:
+
+``` kotlin
+@Suppress("diktat")
+class SomeClass {
+ fun methODTREE(): String {
+
+ }
+}
+```
+
+
+
+ignoreAnnotated: disable inspections on blocks with predefined annotation
+In the `diktat-analysis.yml` file for each inspection it is possible to define a list of annotations that will cause
+disabling of the inspection on that particular code block:
+
+```yaml
+- name: HEADER_NOT_BEFORE_PACKAGE
+ enabled: true
+ ignoreAnnotated: [MyAnnotation, Compose, Controller]
+```
+
+
+
+Suppress groups of inspections by chapters
It is easy to suppress even groups of inspections in diKTat.
These groups are linked to chapters of [Codestyle](info/guide/diktat-coding-convention.md).
diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt
index ee3c5a054b..0cd23a9369 100644
--- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt
+++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt
@@ -26,6 +26,12 @@ import kotlinx.serialization.decodeFromString
*/
const val DIKTAT_COMMON = "DIKTAT_COMMON"
+/**
+ * Common application name, that is used in plugins and can be used to Suppress all diktat inspections on the
+ * particular code block with @Suppress("diktat")
+ */
+const val DIKTAT = "diktat"
+
/**
* This interface represents individual inspection in rule set.
*/
@@ -41,12 +47,14 @@ interface Rule {
* @property name name of the rule
* @property enabled
* @property configuration a map of strings with configuration options
+ * @property ignoreAnnotated if a code block is marked with this annotations - it will not be checked by this rule
*/
@Serializable
data class RulesConfig(
val name: String,
val enabled: Boolean = true,
- val configuration: Map = emptyMap()
+ val configuration: Map = emptyMap(),
+ val ignoreAnnotated: Set = emptySet(),
)
/**
@@ -188,6 +196,20 @@ fun List.isRuleEnabled(rule: Rule): Boolean {
return ruleMatched?.enabled ?: true
}
+/**
+ * @param rule diktat inspection
+ * @param annotations set of annotations that are annotating a block of code
+ * @return true if the code block is marked with annotation that is in `ignored list` in the rule
+ */
+fun List.isAnnotatedWithIgnoredAnnotation(rule: Rule, annotations: Set): Boolean =
+ getRuleConfig(rule)
+ ?.ignoreAnnotated
+ ?.map { it.trim() }
+ ?.map { it.trim('"') }
+ ?.intersect(annotations)
+ ?.isNotEmpty()
+ ?: false
+
/**
* Parse string into KotlinVersion
*
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
index 2425f7be42..ee4bb67c64 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
@@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.constants
import org.cqfn.diktat.common.config.rules.Rule
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.isRuleEnabled
-import org.cqfn.diktat.ruleset.utils.hasSuppress
+import org.cqfn.diktat.ruleset.utils.isSuppressed
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
typealias EmitType = ((offset: Int,
@@ -232,7 +232,7 @@ enum class Warnings(
freeText: String,
offset: Int,
node: ASTNode) {
- if (isRuleFromActiveChapter(configs) && configs.isRuleEnabled(this) && !node.hasSuppress(name)) {
+ if (isRuleFromActiveChapter(configs) && configs.isRuleEnabled(this) && !node.isSuppressed(name, this, configs)) {
val trimmedFreeText = freeText
.lines()
.run { if (size > 1) "${first()}..." else first() }
@@ -248,7 +248,7 @@ enum class Warnings(
isFix: Boolean,
node: ASTNode,
autoFix: () -> Unit) {
- if (isRuleFromActiveChapter(configs) && configs.isRuleEnabled(this) && isFix && !node.hasSuppress(name)) {
+ if (isRuleFromActiveChapter(configs) && configs.isRuleEnabled(this) && isFix && !node.isSuppressed(name, this, configs)) {
autoFix()
}
}
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
index 8a5ef64524..2fb9170d58 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
@@ -11,6 +11,10 @@
package org.cqfn.diktat.ruleset.utils
+import org.cqfn.diktat.common.config.rules.DIKTAT
+import org.cqfn.diktat.common.config.rules.Rule
+import org.cqfn.diktat.common.config.rules.RulesConfig
+import org.cqfn.diktat.common.config.rules.isAnnotatedWithIgnoredAnnotation
import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming
import com.pinterest.ktlint.core.KtLint
@@ -508,21 +512,12 @@ fun ASTNode?.isAccessibleOutside(): Boolean =
* @param warningName a name of the warning which is checked
* @return boolean result
*/
-fun ASTNode.hasSuppress(warningName: String) = parent({ node ->
- val annotationNode = if (node.elementType != FILE) {
- node.findChildByType(MODIFIER_LIST) ?: node.findChildByType(ANNOTATED_EXPRESSION)
- } else {
- node.findChildByType(FILE_ANNOTATION_LIST)
- }
- annotationNode?.findAllDescendantsWithSpecificType(ANNOTATION_ENTRY)
- ?.map { it.psi as KtAnnotationEntry }
- ?.any {
- it.shortName.toString() == Suppress::class.simpleName &&
- it.valueArgumentList?.arguments
- ?.any { annotationName -> annotationName.text.trim('"', ' ') == warningName }
- ?: false
- } ?: false
-}, strict = false) != null
+fun ASTNode.isSuppressed(
+ warningName: String,
+ rule: Rule,
+ configs: List
+) =
+ this.parent(hasAnySuppressorForInspection(warningName, rule, configs), strict = false) != null
/**
* Checks node has `override` modifier
@@ -826,6 +821,15 @@ fun ASTNode.takeByChainOfTypes(vararg types: IElementType): ASTNode? {
return node
}
+private fun Collection.containSuppressWithName(name: String) =
+ this.any {
+ it.shortName.toString() == (Suppress::class.simpleName) &&
+ (it.valueArgumentList
+ ?.arguments
+ ?.any { annotation -> annotation.text.trim('"') == name }
+ ?: false)
+ }
+
private fun ASTNode.findOffsetByLine(line: Int, positionByOffset: (Int) -> Pair): Int {
val currentLine = this.getLineNumber()
val currentOffset = this.startOffset
@@ -967,6 +971,30 @@ fun doesLambdaContainIt(lambdaNode: ASTNode): Boolean {
return hasNoParameters(lambdaNode) && hasIt
}
+private fun hasAnySuppressorForInspection(
+ warningName: String,
+ rule: Rule,
+ configs: List
+) = { node: ASTNode ->
+ val annotationsForNode = if (node.elementType != FILE) {
+ node.findChildByType(MODIFIER_LIST) ?: node.findChildByType(ANNOTATED_EXPRESSION)
+ } else {
+ node.findChildByType(FILE_ANNOTATION_LIST)
+ }
+ ?.findAllDescendantsWithSpecificType(ANNOTATION_ENTRY)
+ ?.map { it.psi as KtAnnotationEntry }
+ ?: emptySet()
+
+ val foundSuppress = annotationsForNode.containSuppressWithName(warningName)
+
+ val foundIgnoredAnnotation =
+ configs.isAnnotatedWithIgnoredAnnotation(rule, annotationsForNode.map { it.shortName.toString() }.toSet())
+
+ val isCompletelyIgnoredBlock = annotationsForNode.containSuppressWithName(DIKTAT)
+
+ foundSuppress || foundIgnoredAnnotation || isCompletelyIgnoredBlock
+}
+
private fun hasNoParameters(lambdaNode: ASTNode): Boolean {
require(lambdaNode.elementType == LAMBDA_EXPRESSION) { "Method can be called only for lambda" }
return null == lambdaNode
diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
index 9e6da6fe0a..1d6301997f 100644
--- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
@@ -12,6 +12,8 @@
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
- name: CONSTANT_UPPERCASE
enabled: true
diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml
index 3164079e88..4fead92a14 100644
--- a/diktat-rules/src/main/resources/diktat-analysis.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis.yml
@@ -12,6 +12,8 @@
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
- name: CONSTANT_UPPERCASE
enabled: true
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/SuppressingTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/SuppressingTest.kt
new file mode 100644
index 0000000000..c1d5bc4ab1
--- /dev/null
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/SuppressingTest.kt
@@ -0,0 +1,87 @@
+package org.cqfn.diktat.util
+
+import org.cqfn.diktat.common.config.rules.RulesConfig
+import org.cqfn.diktat.ruleset.constants.Warnings.BACKTICKS_PROHIBITED
+import org.cqfn.diktat.ruleset.constants.Warnings.CLASS_NAME_INCORRECT
+import org.cqfn.diktat.ruleset.constants.Warnings.CONFUSING_IDENTIFIER_NAMING
+import org.cqfn.diktat.ruleset.constants.Warnings.CONSTANT_UPPERCASE
+import org.cqfn.diktat.ruleset.constants.Warnings.ENUM_VALUE
+import org.cqfn.diktat.ruleset.constants.Warnings.EXCEPTION_SUFFIX
+import org.cqfn.diktat.ruleset.constants.Warnings.FUNCTION_BOOLEAN_PREFIX
+import org.cqfn.diktat.ruleset.constants.Warnings.GENERIC_NAME
+import org.cqfn.diktat.ruleset.constants.Warnings.IDENTIFIER_LENGTH
+import org.cqfn.diktat.ruleset.constants.Warnings.OBJECT_NAME_INCORRECT
+import org.cqfn.diktat.ruleset.constants.Warnings.VARIABLE_HAS_PREFIX
+import org.cqfn.diktat.ruleset.constants.Warnings.VARIABLE_NAME_INCORRECT
+import org.cqfn.diktat.ruleset.constants.Warnings.VARIABLE_NAME_INCORRECT_FORMAT
+import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
+import org.cqfn.diktat.ruleset.rules.chapter1.IdentifierNaming
+
+import com.pinterest.ktlint.core.LintError
+import generated.WarningNames
+import org.junit.jupiter.api.Tag
+import org.junit.jupiter.api.Tags
+import org.junit.jupiter.api.Test
+
+class SuppressingTest : LintTestBase(::IdentifierNaming) {
+ private val ruleId: String = "$DIKTAT_RULE_SET_ID:${IdentifierNaming.NAME_ID}"
+ private val rulesConfigBooleanFunctions: List = listOf(
+ RulesConfig(IDENTIFIER_LENGTH.name, true, emptyMap(), setOf("MySuperSuppress"))
+ )
+
+ @Test
+ fun `checking that suppression with ignoredAnnotation works`() {
+ val code =
+ """
+ @MySuperSuppress()
+ fun foo() {
+ val a = 1
+ }
+ """.trimIndent()
+ lintMethod(code, rulesConfigList = rulesConfigBooleanFunctions)
+ }
+
+ @Test
+ fun `checking that suppression with ignore everything works`() {
+ val code =
+ """
+ @Suppress("diktat")
+ fun foo() {
+ val a = 1
+ }
+ """.trimIndent()
+ lintMethod(code)
+ }
+
+ @Test
+ fun `checking that suppression with a targeted inspection name works`() {
+ val code =
+ """
+ @Suppress("IDENTIFIER_LENGTH")
+ fun foo() {
+ val a = 1
+ }
+ """.trimIndent()
+ lintMethod(code)
+ }
+
+ @Test
+ fun `negative scenario for other annotation`() {
+ val code =
+ """
+ @MySuperSuppress111()
+ fun foo() {
+ val a = 1
+ }
+ """.trimIndent()
+ lintMethod(
+ code,
+ LintError(3,
+ 9,
+ ruleId,
+ "[IDENTIFIER_LENGTH] identifier's length is incorrect, it" +
+ " should be in range of [2, 64] symbols: a", false),
+ rulesConfigList = rulesConfigBooleanFunctions,
+ )
+ }
+}
diff --git a/examples/gradle-groovy-dsl/diktat-analysis.yml b/examples/gradle-groovy-dsl/diktat-analysis.yml
index c5945beb5b..464d7674d0 100644
--- a/examples/gradle-groovy-dsl/diktat-analysis.yml
+++ b/examples/gradle-groovy-dsl/diktat-analysis.yml
@@ -12,6 +12,8 @@
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
- name: CONSTANT_UPPERCASE
enabled: true
diff --git a/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml b/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml
index 354c4d0fc4..a2f0b92333 100644
--- a/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml
+++ b/examples/gradle-kotlin-dsl-multiproject/diktat-analysis.yml
@@ -5,6 +5,8 @@
testDirs: test
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
- name: CONSTANT_UPPERCASE
enabled: true
- name: ENUM_VALUE
diff --git a/examples/gradle-kotlin-dsl/diktat-analysis.yml b/examples/gradle-kotlin-dsl/diktat-analysis.yml
index c5945beb5b..464d7674d0 100644
--- a/examples/gradle-kotlin-dsl/diktat-analysis.yml
+++ b/examples/gradle-kotlin-dsl/diktat-analysis.yml
@@ -12,6 +12,8 @@
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
- name: CONSTANT_UPPERCASE
enabled: true
diff --git a/examples/maven/diktat-analysis.yml b/examples/maven/diktat-analysis.yml
index c5945beb5b..464d7674d0 100644
--- a/examples/maven/diktat-analysis.yml
+++ b/examples/maven/diktat-analysis.yml
@@ -12,6 +12,8 @@
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
+ # all code blocks with MyAnnotation will be ignored and not checked
+ ignoreAnnotated: [ MyAnnotation ]
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
- name: CONSTANT_UPPERCASE
enabled: true