From f314c9711fb284c1303e14d6b91e96d51d16110c Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 3 Jul 2020 14:20:11 +0300 Subject: [PATCH] Added rule to detect commented code (#1) * Added rule to detect commented code ### What's done: * Added CommentsRule with warning COMMENTED_OUT_CODE * Added tests * Fixed typo in all of custom rules *Updated README.md --- README.md | 1 + diktat-rules/rules-config.json | 5 + .../cqfn/diktat/ruleset/constants/Warnings.kt | 1 + .../ruleset/rules/DiktatRuleSetProvider.kt | 4 +- .../cqfn/diktat/ruleset/rules/FileNaming.kt | 8 +- .../diktat/ruleset/rules/IdentifierNaming.kt | 28 +-- .../cqfn/diktat/ruleset/rules/KdocComments.kt | 6 +- .../diktat/ruleset/rules/KdocFormatting.kt | 22 +-- .../cqfn/diktat/ruleset/rules/KdocMethods.kt | 12 +- .../diktat/ruleset/rules/PackageNaming.kt | 18 +- .../ruleset/rules/comments/CommentsRule.kt | 117 +++++++++++++ .../rules/comments/HeaderCommentRule.kt | 14 +- .../chapter2/comments/CommentedCodeTest.kt | 164 ++++++++++++++++++ 13 files changed, 345 insertions(+), 55 deletions(-) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt diff --git a/README.md b/README.md index ffef22cd6f..d3e0f00a6b 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,4 @@ To install git hooks using gradle run `gradle installGitHooks`. |HEADER_MISSING_OR_WRONG_COPYRIGHT|Checks: copyright exists on top of file and is properly formatted (as a block comment). Fix: adds copyright if it is missing and required| |HEADER_CONTAINS_DATE_OR_AUTHOR|Checks: header KDoc contains `@author` tag| |HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE|Check: warns if file with zero or >1 classes doesn't have header KDoc| +|COMMENTED_OUT_CODE|Check: warns if valid kotlin code is detected in commented blocks (both single-line and block comments)| diff --git a/diktat-rules/rules-config.json b/diktat-rules/rules-config.json index bed3b036fb..37be4d5d91 100644 --- a/diktat-rules/rules-config.json +++ b/diktat-rules/rules-config.json @@ -173,5 +173,10 @@ "name": "HEADER_MISSING_IN_LONG_FILE", "enabled": true, "configuration": {} + }, + { + "name": "COMMENTED_OUT_CODE", + "enabled": true, + "configuration": {} } ] 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 776bbcf3ed..e4ebc7017f 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 @@ -54,6 +54,7 @@ enum class Warnings(private val id: Int, private val canBeAutoCorrected: Boolean HEADER_MISSING_OR_WRONG_COPYRIGHT(37, true, "file header comment must include copyright information inside a block comment"), HEADER_CONTAINS_DATE_OR_AUTHOR(38, false, "file header comment should not contain creation date and author name"), HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE(39, false, "files that contain multiple or no classes should contain description of what is inside of this file"), + COMMENTED_OUT_CODE(39, false, "you should not comment out code, use VCS to save it in history and delete this block"), ; override fun ruleName(): String = this.name diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index 2dd19cfec9..00c224d359 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -6,6 +6,7 @@ import com.pinterest.ktlint.core.RuleSet import com.pinterest.ktlint.core.RuleSetProvider import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader +import org.cqfn.diktat.ruleset.rules.comments.CommentsRule /** * this constant will be used everywhere in the code to mark usage of Diktat ruleset @@ -15,13 +16,14 @@ const val DIKTAT_RULE_SET_ID = "diktat-ruleset" class RuleSetDiktat(val rulesConfig: List, vararg rules: Rule) : RuleSet(DIKTAT_RULE_SET_ID, *rules) fun KtLint.Params.getDiktatConfigRules(): List { - return (this.ruleSets.find { it.id == DIKTAT_RULE_SET_ID } as RuleSetDiktat).rulesConfig?: listOf() + return (this.ruleSets.find { it.id == DIKTAT_RULE_SET_ID } as RuleSetDiktat).rulesConfig } class DiktatRuleSetProvider(private val jsonRulesConfig: String = "rules-config.json") : RuleSetProvider { override fun get(): RuleSet { return RuleSetDiktat( RulesConfigReader().readResource(jsonRulesConfig)?: listOf(), + CommentsRule(), KdocComments(), KdocMethods(), KdocFormatting(), diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FileNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FileNaming.kt index e10aa64f1f..374feb1773 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FileNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/FileNaming.kt @@ -28,7 +28,7 @@ class FileNaming : Rule("file-naming") { val VALID_EXTENSIONS = listOf(".kt", ".kts") } - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var fileName: String? = null private var isFixMode: Boolean = false @@ -37,7 +37,7 @@ class FileNaming : Rule("file-naming") { autoCorrect: Boolean, params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() fileName = params.fileName emitWarn = emit isFixMode = autoCorrect @@ -52,7 +52,7 @@ class FileNaming : Rule("file-naming") { if (fileName != null) { val (name, extension) = getFileParts() if (!name.isPascalCase() || !VALID_EXTENSIONS.contains(extension)) { - FILE_NAME_INCORRECT.warnAndFix(confiRules, emitWarn, isFixMode, "$name$extension", 0) { + FILE_NAME_INCORRECT.warnAndFix(configRules, emitWarn, isFixMode, "$name$extension", 0) { // FixMe: we can add an autocorrect here in future, but is there any purpose to change file or class name? } } @@ -66,7 +66,7 @@ class FileNaming : Rule("file-naming") { if (classes.size == 1) { val className = classes[0].getFirstChildWithType(IDENTIFIER)!!.text if (className != fileNameWithoutSuffix) { - FILE_NAME_MATCH_CLASS.warnAndFix(confiRules, emitWarn, isFixMode, "$fileNameWithoutSuffix$fileNameSuffix vs $className", 0) { + FILE_NAME_MATCH_CLASS.warnAndFix(configRules, emitWarn, isFixMode, "$fileNameWithoutSuffix$fileNameSuffix vs $className", 0) { // FixMe: we can add an autocorrect here in future, but is there any purpose to change file name? } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/IdentifierNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/IdentifierNaming.kt index f76883d793..8055259e3d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/IdentifierNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/IdentifierNaming.kt @@ -32,7 +32,7 @@ class IdentifierNaming : Rule("identifier-naming") { val BOOLEAN_METHOD_PREFIXES = setOf("has", "is") } - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false @@ -42,7 +42,7 @@ class IdentifierNaming : Rule("identifier-naming") { params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() isFixMode = autoCorrect emitWarn = emit @@ -77,7 +77,7 @@ class IdentifierNaming : Rule("identifier-naming") { if (!ONE_CHAR_IDENTIFIERS.contains(variableName!!.text)) { // generally variables with prefixes are not allowed (like mVariable) if (variableName.text.hasPrefix()) { - VARIABLE_HAS_PREFIX.warnAndFix(confiRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { + VARIABLE_HAS_PREFIX.warnAndFix(configRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { // FixMe: this correction should be done only after we checked variable case (below) (variableName as LeafPsiElement).replaceWithText(variableName.text.removePrefix()) } @@ -86,7 +86,7 @@ class IdentifierNaming : Rule("identifier-naming") { // variable should not contain only one letter in it's name. This is a bad example: b512 // but no need to raise a warning here if length of a variable. In this case we will raise IDENTIFIER_LENGTH if (variableName.text.containsOneLetterOrZero() && variableName.text.length > 1) { - VARIABLE_NAME_INCORRECT.warnAndFix(confiRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { + VARIABLE_NAME_INCORRECT.warnAndFix(configRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { } } @@ -94,7 +94,7 @@ class IdentifierNaming : Rule("identifier-naming") { // it should be in UPPER_CASE, no need to raise this warning if it is one-letter variable if ((node.isNodeFromCompanionObject() || node.isNodeFromFileLevel()) && node.isValProperty() && node.isConst()) { if (!variableName.text.isUpperSnakeCase() && variableName.text.length > 1) { - CONSTANT_UPPERCASE.warnAndFix(confiRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { + CONSTANT_UPPERCASE.warnAndFix(configRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { (variableName as LeafPsiElement).replaceWithText(variableName.text.toUpperCase()) } } @@ -103,7 +103,7 @@ class IdentifierNaming : Rule("identifier-naming") { // variable name should be in camel case. The only exception is a list of industry standard variables like i, j, k. if (!variableName.text.isLowerCamelCase()) { - VARIABLE_NAME_INCORRECT_FORMAT.warnAndFix(confiRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { + VARIABLE_NAME_INCORRECT_FORMAT.warnAndFix(configRules, emitWarn, isFixMode, variableName.text, variableName.startOffset) { // FixMe: cover fixes with tests (variableName as LeafPsiElement).replaceWithText(variableName.text.toLowerCamelCase()) } @@ -120,14 +120,14 @@ class IdentifierNaming : Rule("identifier-naming") { private fun checkCLassNamings(node: ASTNode): List { val genericType: ASTNode? = node.getTypeParameterList() if (genericType != null && !validGenericTypeName(genericType.text)) { - GENERIC_NAME.warnAndFix(confiRules, emitWarn, isFixMode, genericType.text, genericType.startOffset) { + GENERIC_NAME.warnAndFix(configRules, emitWarn, isFixMode, genericType.text, genericType.startOffset) { // FixMe: should fix generic name here } } val className: ASTNode? = node.getIdentifierName() if (!(className!!.text.isPascalCase())) { - CLASS_NAME_INCORRECT.warnAndFix(confiRules, emitWarn, isFixMode, className.text, className.startOffset) { + CLASS_NAME_INCORRECT.warnAndFix(configRules, emitWarn, isFixMode, className.text, className.startOffset) { (className as LeafPsiElement).replaceWithText(className.text.toPascalCase()) } } @@ -153,7 +153,7 @@ class IdentifierNaming : Rule("identifier-naming") { ?.text if (superClassName != null && hasExceptionSuffix(superClassName) && !hasExceptionSuffix(classNameNode!!.text)) { - EXCEPTION_SUFFIX.warnAndFix(confiRules, emitWarn, isFixMode, classNameNode.text, classNameNode.startOffset) { + EXCEPTION_SUFFIX.warnAndFix(configRules, emitWarn, isFixMode, classNameNode.text, classNameNode.startOffset) { // FixMe: need to add tests for this (classNameNode as LeafPsiElement).replaceWithText(classNameNode.text + "Exception") } @@ -168,7 +168,7 @@ class IdentifierNaming : Rule("identifier-naming") { val objectName: ASTNode? = node.getIdentifierName() // checking object naming, the only extension is "companion" keyword if (!(objectName!!.text.isPascalCase()) && objectName.text != "companion") { - OBJECT_NAME_INCORRECT.warnAndFix(confiRules, emitWarn, isFixMode, objectName.text, objectName.startOffset) { + OBJECT_NAME_INCORRECT.warnAndFix(configRules, emitWarn, isFixMode, objectName.text, objectName.startOffset) { (objectName as LeafPsiElement).replaceWithText(objectName.text.toPascalCase()) } @@ -185,7 +185,7 @@ class IdentifierNaming : Rule("identifier-naming") { val enumValues: List = node.getChildren(null).filter { it.elementType == ElementType.IDENTIFIER } enumValues.forEach { value -> if (!value.text.isUpperSnakeCase()) { - ENUM_VALUE.warnAndFix(confiRules, emitWarn, isFixMode, value.text, value.startOffset) { + ENUM_VALUE.warnAndFix(configRules, emitWarn, isFixMode, value.text, value.startOffset) { // FixMe: add tests for this (value as LeafPsiElement).replaceWithText(value.text.toUpperSnakeCase()) } @@ -205,7 +205,7 @@ class IdentifierNaming : Rule("identifier-naming") { // basic check for camel case if (!functionName!!.text.isLowerCamelCase()) { - FUNCTION_NAME_INCORRECT_CASE.warnAndFix(confiRules, emitWarn, isFixMode, functionName.text, functionName.startOffset) { + FUNCTION_NAME_INCORRECT_CASE.warnAndFix(configRules, emitWarn, isFixMode, functionName.text, functionName.startOffset) { // FixMe: add tests for this (functionName as LeafPsiElement).replaceWithText(functionName.text.toLowerCamelCase()) } @@ -217,7 +217,7 @@ class IdentifierNaming : Rule("identifier-naming") { // if function has Boolean return type in 99% of cases it is much better to name it with isXXX or hasXXX prefix if (functionReturnType != null && functionReturnType == PrimitiveType.BOOLEAN.typeName.asString()) { if (!(BOOLEAN_METHOD_PREFIXES.any { functionReturnType.startsWith(it) })) { - FUNCTION_BOOLEAN_PREFIX.warnAndFix(confiRules, emitWarn, isFixMode, functionName.text, functionName.startOffset) { + FUNCTION_BOOLEAN_PREFIX.warnAndFix(configRules, emitWarn, isFixMode, functionName.text, functionName.startOffset) { // FixMe: add agressive autofix for this } } @@ -247,7 +247,7 @@ class IdentifierNaming : Rule("identifier-naming") { isVariable: Boolean) { nodes.forEach { if (!(it.checkLength(2..64) || (ONE_CHAR_IDENTIFIERS.contains(it.text)) && isVariable)) { - IDENTIFIER_LENGTH.warn(confiRules, emitWarn, isFixMode, it.text, it.startOffset) + IDENTIFIER_LENGTH.warn(configRules, emitWarn, isFixMode, it.text, it.startOffset) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocComments.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocComments.kt index 0eeaba5053..44199c61a6 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocComments.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocComments.kt @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode */ class KdocComments : Rule("kdoc-comments") { - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false @@ -36,7 +36,7 @@ class KdocComments : Rule("kdoc-comments") { params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() emitWarn = emit isFixMode = autoCorrect @@ -72,7 +72,7 @@ class KdocComments : Rule("kdoc-comments") { val name = node.getIdentifierName() if (modifier.isAccessibleOutside() && kDoc == null) { - warning.warn(confiRules, emitWarn, isFixMode, name!!.text, node.startOffset) + warning.warn(configRules, emitWarn, isFixMode, name!!.text, node.startOffset) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocFormatting.kt index 8ee69378e3..840c154d2b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocFormatting.kt @@ -58,7 +58,7 @@ class KdocFormatting : Rule("kdoc-formatting") { private val basicTagsList = listOf(KDocKnownTag.PARAM, KDocKnownTag.RETURN, KDocKnownTag.THROWS) private val specialTagNames = setOf("implSpec", "implNote", "apiNote") - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false private var fileName: String = "" @@ -68,7 +68,7 @@ class KdocFormatting : Rule("kdoc-formatting") { params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() isFixMode = autoCorrect emitWarn = emit fileName = params.fileName ?: "" @@ -95,7 +95,7 @@ class KdocFormatting : Rule("kdoc-formatting") { val nodeAfterKdoc = kdoc?.treeNext val name = node.getFirstChildWithType(ElementType.IDENTIFIER) if (nodeAfterKdoc?.elementType == WHITE_SPACE && nodeAfterKdoc.text.countSubStringOccurrences("\n") > 1) { - BLANK_LINE_AFTER_KDOC.warnAndFix(confiRules, emitWarn, isFixMode, name!!.text, nodeAfterKdoc.startOffset) { + BLANK_LINE_AFTER_KDOC.warnAndFix(configRules, emitWarn, isFixMode, name!!.text, nodeAfterKdoc.startOffset) { nodeAfterKdoc.leaveOnlyOneNewLine() } } @@ -107,7 +107,7 @@ class KdocFormatting : Rule("kdoc-formatting") { it.elementType != KDOC_LEADING_ASTERISK && it.elementType != WHITE_SPACE } ?: false if (!isKdocNotEmpty) { - KDOC_EMPTY_KDOC.warn(confiRules, emitWarn, isFixMode, + KDOC_EMPTY_KDOC.warn(configRules, emitWarn, isFixMode, node.treeParent.getIdentifierName()?.text ?: node.nextSibling { it.elementType in KtTokens.KEYWORDS }?.text ?: fileName, node.startOffset) @@ -119,7 +119,7 @@ class KdocFormatting : Rule("kdoc-formatting") { val kDocTags = node.kDocTags() kDocTags?.find { it.name == "deprecated" } ?.let { kDocTag -> - KDOC_NO_DEPRECATED_TAG.warnAndFix(confiRules, emitWarn, isFixMode, kDocTag.text, kDocTag.node.startOffset) { + KDOC_NO_DEPRECATED_TAG.warnAndFix(configRules, emitWarn, isFixMode, kDocTag.text, kDocTag.node.startOffset) { val kDocSection = kDocTag.node.treeParent val deprecatedTagNode = kDocSection.getChildren(TokenSet.create(KDOC_TAG)) .find { "@deprecated" in it.text }!! @@ -137,7 +137,7 @@ class KdocFormatting : Rule("kdoc-formatting") { kDocTags?.filter { it.getSubjectName() == null && it.getContent().isEmpty() }?.forEach { - KDOC_NO_EMPTY_TAGS.warn(confiRules, emitWarn, isFixMode, "@${it.name!!}", it.node.startOffset) + KDOC_NO_EMPTY_TAGS.warn(configRules, emitWarn, isFixMode, "@${it.name!!}", it.node.startOffset) } } @@ -151,7 +151,7 @@ class KdocFormatting : Rule("kdoc-formatting") { hasSubject && tag.node.findChildBefore(KDOC_TEXT, WHITE_SPACE)?.text != " " || tag.node.findChildAfter(KDOC_TAG_NAME, WHITE_SPACE)?.text != " " }?.forEach { tag -> - KDOC_WRONG_SPACES_AFTER_TAG.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_WRONG_SPACES_AFTER_TAG.warnAndFix(configRules, emitWarn, isFixMode, "@${tag.name!!}", tag.node.startOffset) { tag.node.findChildBefore(KDOC_TEXT, WHITE_SPACE) ?.let { tag.node.replaceChild(it, LeafPsiElement(WHITE_SPACE, " ")) } @@ -178,7 +178,7 @@ class KdocFormatting : Rule("kdoc-formatting") { ?.map { it.knownTag }?.equals(basicTagsOrdered) if (kDocTags != null && !isTagsInCorrectOrder!!) { - KDOC_WRONG_TAGS_ORDER.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_WRONG_TAGS_ORDER.warnAndFix(configRules, emitWarn, isFixMode, basicTags.joinToString(", ") { "@${it.name}" }, basicTags.first().node.startOffset) { val kDocSection = node.getFirstChildWithType(KDOC_SECTION)!! val basicTagChildren = kDocTags @@ -208,7 +208,7 @@ class KdocFormatting : Rule("kdoc-formatting") { ?.treeNext?.elementType == WHITE_SPACE) if (hasContentBefore xor hasEmptyLineBefore) { - KDOC_NEWLINES_BEFORE_BASIC_TAGS.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_NEWLINES_BEFORE_BASIC_TAGS.warnAndFix(configRules, emitWarn, isFixMode, "@${firstBasicTag.name!!}", firstBasicTag.node.startOffset) { if (hasContentBefore) { if (previousTag != null) { @@ -244,7 +244,7 @@ class KdocFormatting : Rule("kdoc-formatting") { } tagsWithRedundantEmptyLines.forEach { tag -> - KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS.warnAndFix(configRules, emitWarn, isFixMode, "@${tag.name}", tag.startOffset) { tag.node.nextSibling { it.elementType == WHITE_SPACE }?.leaveOnlyOneNewLine() // the first asterisk before tag is not included inside KDOC_TAG node @@ -271,7 +271,7 @@ class KdocFormatting : Rule("kdoc-formatting") { } if (presentSpecialTagNodes != null && poorlyFormattedTagNodes!!.isNotEmpty()) { - KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS.warnAndFix(configRules, emitWarn, isFixMode, poorlyFormattedTagNodes.joinToString(", ") { "@${(it.psi as KDocTag).name!!}" }, poorlyFormattedTagNodes.first().startOffset) { poorlyFormattedTagNodes.forEach { node -> diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocMethods.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocMethods.kt index e0db480fe9..22b382b620 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocMethods.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/KdocMethods.kt @@ -48,7 +48,7 @@ class KdocMethods : Rule("kdoc-methods") { ElementType.SAFE_ACCESS_EXPRESSION, ElementType.WHEN_CONDITION_WITH_EXPRESSION, ElementType.COLLECTION_LITERAL_EXPRESSION) - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false @@ -57,7 +57,7 @@ class KdocMethods : Rule("kdoc-methods") { params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() isFixMode = autoCorrect emitWarn = emit @@ -86,7 +86,7 @@ class KdocMethods : Rule("kdoc-methods") { val name = node.getIdentifierName()!!.text if (paramCheckFailed) { - KDOC_WITHOUT_PARAM_TAG.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_WITHOUT_PARAM_TAG.warnAndFix(configRules, emitWarn, isFixMode, "$name (${missingParameters.joinToString()})", node.startOffset) { val beforeTag = kDocTags?.find { it.knownTag == KDocKnownTag.RETURN } ?: kDocTags?.find { it.knownTag == KDocKnownTag.THROWS } @@ -100,7 +100,7 @@ class KdocMethods : Rule("kdoc-methods") { } } if (returnCheckFailed) { - KDOC_WITHOUT_RETURN_TAG.warnAndFix(confiRules, emitWarn, isFixMode, name, node.startOffset) { + KDOC_WITHOUT_RETURN_TAG.warnAndFix(configRules, emitWarn, isFixMode, name, node.startOffset) { val beforeTag = kDocTags?.find { it.knownTag == KDocKnownTag.THROWS } kDoc?.insertTagBefore(beforeTag?.node) { addChild(LeafPsiElement(KDOC_TAG_NAME, "@return")) @@ -108,7 +108,7 @@ class KdocMethods : Rule("kdoc-methods") { } } if (throwsCheckFailed) { - KDOC_WITHOUT_THROWS_TAG.warnAndFix(confiRules, emitWarn, isFixMode, + KDOC_WITHOUT_THROWS_TAG.warnAndFix(configRules, emitWarn, isFixMode, "$name (${missingExceptions.joinToString()})", node.startOffset) { explicitlyThrownExceptions.forEach { kDoc?.insertTagBefore(null) { @@ -123,7 +123,7 @@ class KdocMethods : Rule("kdoc-methods") { // if no tag failed, we have too little information to suggest KDoc - it would just be empty val anyTagFailed = paramCheckFailed || returnCheckFailed || throwsCheckFailed if (kDoc == null && anyTagFailed) { - MISSING_KDOC_ON_FUNCTION.warnAndFix(confiRules, emitWarn, isFixMode, + MISSING_KDOC_ON_FUNCTION.warnAndFix(configRules, emitWarn, isFixMode, node.getIdentifierName()!!.text, node.startOffset) { val indent = node.prevSibling { it.elementType == WHITE_SPACE }?.text ?.substringAfterLast("\n")?.count { it == ' ' } ?: 0 diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt index 9aa52b04c0..3a683767f9 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt @@ -38,7 +38,7 @@ class PackageNaming : Rule("package-naming") { private val log = LoggerFactory.getLogger(PackageNaming::class.java) } - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false private lateinit var domainName: String @@ -48,11 +48,11 @@ class PackageNaming : Rule("package-naming") { params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() isFixMode = autoCorrect emitWarn = emit - val domainNameConfiguration = confiRules.getRuleConfig(PACKAGE_NAME_MISSING)?.configuration + val domainNameConfiguration = configRules.getRuleConfig(PACKAGE_NAME_MISSING)?.configuration if (domainNameConfiguration == null) { log.error("Not able to find an external configuration for domain name in the configuration of" + " ${PACKAGE_NAME_MISSING.name} check (is it missing in json config?)") @@ -85,7 +85,7 @@ class PackageNaming : Rule("package-naming") { * checking and fixing the case when package directive is missing in the file */ private fun checkMissingPackageName(packageDirectiveNode: ASTNode, realPackageName: List, fileName: String) { - PACKAGE_NAME_MISSING.warnAndFix(confiRules, emitWarn, isFixMode, fileName, packageDirectiveNode.startOffset) { + PACKAGE_NAME_MISSING.warnAndFix(configRules, emitWarn, isFixMode, fileName, packageDirectiveNode.startOffset) { if (realPackageName.isNotEmpty()) { packageDirectiveNode.addChild(LeafPsiElement(PACKAGE_KEYWORD, PACKAGE_KEYWORD.toString()), null) packageDirectiveNode.addChild(PsiWhiteSpaceImpl(" "), null) @@ -130,14 +130,14 @@ class PackageNaming : Rule("package-naming") { wordsInPackageName .filter { word -> word.text.hasUppercaseLetter() } .forEach { - PACKAGE_NAME_INCORRECT_CASE.warnAndFix(confiRules, emitWarn, isFixMode, it.text, it.startOffset) { + PACKAGE_NAME_INCORRECT_CASE.warnAndFix(configRules, emitWarn, isFixMode, it.text, it.startOffset) { it.toLower() } } // package name should start from a company's domain name if (!isDomainMatches(wordsInPackageName)) { - PACKAGE_NAME_INCORRECT_PREFIX.warnAndFix(confiRules, emitWarn, isFixMode, domainName, wordsInPackageName[0].startOffset) { + PACKAGE_NAME_INCORRECT_PREFIX.warnAndFix(configRules, emitWarn, isFixMode, domainName, wordsInPackageName[0].startOffset) { val parentNodeToInsert = wordsInPackageName[0].parent(DOT_QUALIFIED_EXPRESSION)!! createAndInsertPackageName(parentNodeToInsert, wordsInPackageName[0].treeParent, domainName.split(PACKAGE_SEPARATOR)) } @@ -146,7 +146,7 @@ class PackageNaming : Rule("package-naming") { // all words should contain only ASCII letters or digits wordsInPackageName .filter { word -> !correctSymbolsAreUsed(word.text) } - .forEach { PACKAGE_NAME_INCORRECT_SYMBOLS.warn(confiRules, emitWarn, isFixMode, it.text, it.startOffset) } + .forEach { PACKAGE_NAME_INCORRECT_SYMBOLS.warn(configRules, emitWarn, isFixMode, it.text, it.startOffset) } // all words should contain only ASCII letters or digits wordsInPackageName.forEach { correctPackageWordSeparatorsUsed(it) } @@ -167,7 +167,7 @@ class PackageNaming : Rule("package-naming") { */ private fun correctPackageWordSeparatorsUsed(word: ASTNode) { if (word.text.contains("_") && !exceptionForUnderscore(word.text)) { - INCORRECT_PACKAGE_SEPARATOR.warnAndFix(confiRules, emitWarn, isFixMode, word.text, word.startOffset) { + INCORRECT_PACKAGE_SEPARATOR.warnAndFix(configRules, emitWarn, isFixMode, word.text, word.startOffset) { (word as LeafPsiElement).replaceWithText(word.text.replace("_", "")) } } @@ -234,7 +234,7 @@ class PackageNaming : Rule("package-naming") { */ private fun checkFilePathMatchesWithPackageName(packageNameParts: List, realName: List) { if (realName.isNotEmpty() && packageNameParts.map { node -> node.text } != realName) { - PACKAGE_NAME_INCORRECT_PATH.warnAndFix(confiRules, emitWarn, isFixMode, realName.joinToString(PACKAGE_SEPARATOR), packageNameParts[0].startOffset) { + PACKAGE_NAME_INCORRECT_PATH.warnAndFix(configRules, emitWarn, isFixMode, realName.joinToString(PACKAGE_SEPARATOR), packageNameParts[0].startOffset) { // need to get first top-level DOT-QUALIFIED-EXPRESSION // -- PACKAGE_DIRECTIVE // -- DOT_QUALIFIED_EXPRESSION diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt new file mode 100644 index 0000000000..06a7fec91e --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt @@ -0,0 +1,117 @@ +package org.cqfn.diktat.ruleset.rules.comments + +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT +import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT +import com.pinterest.ktlint.core.ast.ElementType.FILE +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.prevSibling +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings.COMMENTED_OUT_CODE +import org.cqfn.diktat.ruleset.rules.getDiktatConfigRules +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.TokenType +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.ImportPath + +/** + * This rule performs checks if there is any commented code. + * No commented out code is allowed, including imports. + */ +class CommentsRule : Rule("comments") { + + companion object { + private val IMPORT_KEYWORD = KtTokens.IMPORT_KEYWORD.value + private val PACKAGE_KEYWORD = KtTokens.PACKAGE_KEYWORD.value + private val IMPORT_OR_PACKAGE = """($IMPORT_KEYWORD|$PACKAGE_KEYWORD) """.toRegex() + private val EOL_COMMENT_START = """// \S""".toRegex() + } + + private lateinit var configRules: List + private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) + private var isFixMode: Boolean = false + private lateinit var ktPsiFactory: KtPsiFactory + + override fun visit(node: ASTNode, + autoCorrect: Boolean, + params: KtLint.Params, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + configRules = params.getDiktatConfigRules() + emitWarn = emit + isFixMode = autoCorrect + + ktPsiFactory = KtPsiFactory(node.psi.project, false) // regarding markGenerated see KDoc in Kotlin sources + if (node.elementType == FILE) { + checkCommentedCode(node) + } + } + + /** + * This method tries to detect commented code by uncommenting and parsing it. If parser reports errors, + * we assume this is not code (it is hard to try and parse further because comments can contain code snippets). + * @implNote + * 1. Import and package directives should be separated from the rest of uncommented lines + * 2. Import usually go on top of the file, so if comment contains not only imports, it probably contains imports only on top. (this possibly needs fixme) + * 3. Code can be surrounded by actual comments. One possible heuristic here is to assume actual comments start + * with '// ' with whitespace, while automatic commenting in, e.g., IDEA creates slashes in the beginning of the line + */ + private fun checkCommentedCode(node: ASTNode) { + val eolCommentsOffsetToText = getOffsetsToTextBlocksFromEOLComments(node) + val blockCommentsOffsetToText = node.findAllNodesWithSpecificType(BLOCK_COMMENT).map { it.startOffset to it.text.removeSurrounding("/*", "*/") } + + (eolCommentsOffsetToText + blockCommentsOffsetToText) + .flatMap { (offset, text) -> + val (singleLines, blockLines) = text.lines().partition { it.contains(IMPORT_OR_PACKAGE) } + val block = if (blockLines.isNotEmpty()) listOf(blockLines.joinToString("\n")) else listOf() + (singleLines + block).map { offset to it } + } + .map { (offset, text) -> + when { + text.contains(IMPORT_KEYWORD) -> + offset to ktPsiFactory.createImportDirective(ImportPath.fromString(text.substringAfter("$IMPORT_KEYWORD "))).node + text.contains(PACKAGE_KEYWORD) -> + offset to ktPsiFactory.createPackageDirective(FqName(text.substringAfter("$PACKAGE_KEYWORD "))).node + else -> + offset to ktPsiFactory.createBlockCodeFragment(text, null).node + } + } + .filter { (_, parsedNode) -> + parsedNode.findAllNodesWithSpecificType(TokenType.ERROR_ELEMENT).isEmpty() + }.forEach { (offset, parsedNode) -> + COMMENTED_OUT_CODE.warn(configRules, emitWarn, isFixMode, parsedNode.text.substringBefore("\n").trim(), offset) + } + } + + /** + * This method is used to extract text from EOL comments in a form which can be used for parsing. + * Multiple consecutive EOL comments can correspond to one code block, so we try to glue them together here. + * Splitting back into lines, if necessary, will be done outside of this method, for both text from EOL and block. + * fixme: in this case offset is lost for lines which will be split once more + */ + private fun getOffsetsToTextBlocksFromEOLComments(node: ASTNode): List> { + val comments = node.findAllNodesWithSpecificType(EOL_COMMENT) + .filter { !it.text.contains(EOL_COMMENT_START) } + return if (comments.isNotEmpty()) { + val result = mutableListOf(mutableListOf(comments.first())) + comments.drop(1).fold(result) { acc, astNode -> + val isImportOrPackage = astNode.text.contains(IMPORT_OR_PACKAGE) + val previousNonWhiteSpaceNode = astNode.prevSibling { it.elementType != WHITE_SPACE } + if (!isImportOrPackage && previousNonWhiteSpaceNode in acc.last()) { + acc.last().add(astNode) + } else { + acc.add(mutableListOf(astNode)) + } + acc + }.map { list -> + list.first().startOffset to list.joinToString("\n") { it.text.removePrefix("//") } + } + } else { + listOf() + } + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/HeaderCommentRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/HeaderCommentRule.kt index ff4f2e55a0..78f46c4ca8 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/HeaderCommentRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/HeaderCommentRule.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement class HeaderCommentRule : Rule("header-comment") { private val copyrightWords = setOf("copyright", "版权") - private lateinit var confiRules: List + private lateinit var configRules: List private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) private var isFixMode: Boolean = false private var fileName: String = "" @@ -39,7 +39,7 @@ class HeaderCommentRule : Rule("header-comment") { autoCorrect: Boolean, params: KtLint.Params, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { - confiRules = params.getDiktatConfigRules() + configRules = params.getDiktatConfigRules() isFixMode = autoCorrect emitWarn = emit @@ -51,7 +51,7 @@ class HeaderCommentRule : Rule("header-comment") { } private fun checkCopyright(node: ASTNode) { - val configuration = CopyrightConfiguration(confiRules.getRuleConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT)?.configuration + val configuration = CopyrightConfiguration(configRules.getRuleConfig(HEADER_MISSING_OR_WRONG_COPYRIGHT)?.configuration ?: mapOf()) val copyrightText = configuration.getCopyrightText() @@ -64,7 +64,7 @@ class HeaderCommentRule : Rule("header-comment") { } if (isWrongCopyright || isMissingCopyright || isCopyrightInsideKdoc) { - HEADER_MISSING_OR_WRONG_COPYRIGHT.warnAndFix(confiRules, emitWarn, isFixMode, fileName, node.startOffset) { + HEADER_MISSING_OR_WRONG_COPYRIGHT.warnAndFix(configRules, emitWarn, isFixMode, fileName, node.startOffset) { if (headerComment != null) { node.removeChild(headerComment) } @@ -86,20 +86,20 @@ class HeaderCommentRule : Rule("header-comment") { if (headerKdoc == null) { val nDeclaredClasses = node.getAllChildrenWithType(ElementType.CLASS).size if (nDeclaredClasses == 0 || nDeclaredClasses > 1) { - HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warn(confiRules, emitWarn, isFixMode, fileName, node.startOffset) + HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warn(configRules, emitWarn, isFixMode, fileName, node.startOffset) } } else { // fixme we should also check date of creation, but it can be in different formats headerKdoc.text.split('\n') .filter { it.contains("@author") } .forEach { - HEADER_CONTAINS_DATE_OR_AUTHOR.warn(confiRules, emitWarn, isFixMode, + HEADER_CONTAINS_DATE_OR_AUTHOR.warn(configRules, emitWarn, isFixMode, it.trim(), headerKdoc.startOffset) } if (headerKdoc.treeNext != null && headerKdoc.treeNext.elementType == WHITE_SPACE && headerKdoc.treeNext.text.count { it == '\n' } != 2) { - HEADER_WRONG_FORMAT.warnAndFix(confiRules, emitWarn, isFixMode, + HEADER_WRONG_FORMAT.warnAndFix(configRules, emitWarn, isFixMode, "header KDoc should have a new line after", headerKdoc.startOffset) { node.replaceChild(headerKdoc.treeNext, LeafPsiElement(WHITE_SPACE, "\n\n")) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt new file mode 100644 index 0000000000..f7f89205c1 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt @@ -0,0 +1,164 @@ +package org.cqfn.diktat.ruleset.chapter2.comments + + +import com.pinterest.ktlint.core.LintError +import org.cqfn.diktat.ruleset.constants.Warnings.COMMENTED_OUT_CODE +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.comments.CommentsRule +import org.cqfn.diktat.ruleset.utils.lintMethod +import org.junit.Test + +class CommentedCodeTest { + private val ruleId = "$DIKTAT_RULE_SET_ID:comments" + + @Test + fun `Should warn if commented out import or package directive is detected (single line comments)`() { + lintMethod(CommentsRule(), + """ + |//package org.cqfn.diktat.example + | + |import org.junit.Test + |// this is an actual comment + |//import org.junit.Ignore + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} package org.cqfn.diktat.example", false), + LintError(5, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import org.junit.Ignore", false) + ) + } + + @Test + fun `Should warn if commented out imports are detected (block comments)`() { + lintMethod(CommentsRule(), + """ + |/*import org.junit.Test + |import org.junit.Ignore*/ + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import org.junit.Test", false), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import org.junit.Ignore", false) + ) + } + + @Test + fun `Should warn if commented out code is detected (block comments)`() { + lintMethod(CommentsRule(), + """ + |import org.junit.Test + | + |fun foo(a: Int): Int { + | /* println(a + 42) + | println("This is a test string") + | */ + | return 0 + |} + """.trimMargin(), + LintError(4, 5, ruleId, "${COMMENTED_OUT_CODE.warnText()} println(a + 42)", false) + ) + } + + @Test + fun `Should warn if commented out code is detected (single line comments)`() { + lintMethod(CommentsRule(), + """ + |import org.junit.Test + | + |fun foo(a: Int): Int { + |// println(a + 42) + |// println("This is a test string") + | return 0 + |} + """.trimMargin(), + LintError(4, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} println(a + 42)", false) + ) + } + + @Test + fun `Should warn if commented out function is detected (single line comments)`() { + lintMethod(CommentsRule(), + """ + |import org.junit.Test + | + |//fun foo(a: Int): Int { + |// println(a + 42) + |// println("This is a test string") + |// return 0 + |//} + """.trimMargin(), + LintError(3, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) + ) + } + + @Test + fun `Should warn if commented out function is detected (single line comments with surrounding text)`() { + lintMethod(CommentsRule(), + """ + |import org.junit.Test + | + |// this function is disabled for now + |//fun foo(a: Int): Int { + |// println(a + 42) + |// println("This is a test string") + |// return 0 + |//} + """.trimMargin(), + LintError(4, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) + ) + } + + @Test + fun `Should warn if commented out function is detected (block comment)`() { + lintMethod(CommentsRule(), + """ + |import org.junit.Test + | + |/*fun foo(a: Int): Int { + | println(a + 42) + | println("This is a test string") + | return 0 + |}*/ + """.trimMargin(), + LintError(3, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) + ) + } + + @Test + fun `Should warn if detects commented out code (example with indents)`() { + lintMethod(CommentsRule(), + """ + |//import org.junit.Ignore + |import org.junit.Test + | + |class Example { + | // this function is disabled for now + | //fun foo(a: Int): Int { + | // println(a + 42) + | // println("This is a test string") + | // return 0 + | //} + |} + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import org.junit.Ignore", false), + LintError(6, 5, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) + ) + } + + @Test + fun `Should warn if detects commented out code (example with IDEA style indents)`() { + lintMethod(CommentsRule(), + """ + |//import org.junit.Ignore + |import org.junit.Test + | + |class Example { + | // this function is disabled for now + |// fun foo(a: Int): Int { + |// println(a + 42) + |// println("This is a test string") + |// return 0 + |// } + |} + """.trimMargin(), + LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import org.junit.Ignore", false), + LintError(6, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false) + ) + } +}