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