Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation on symbols in test classes #271

Merged
merged 6 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import java.io.BufferedReader
import java.io.File
import java.util.stream.Collectors
import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader
import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -85,6 +84,25 @@ open class RulesConfigReader(override val classLoader: ClassLoader) : JsonResour
}
}

/**
* @return common configuration from list of all rules configuration
*/
fun List<RulesConfig>.getCommonConfiguration() = lazy { CommonConfiguration(getCommonConfig()?.configuration) }

/**
* class returns the list of common configurations that we have read from a configuration map
*
* @param configuration map of common configuration
*/
class CommonConfiguration(configuration: Map<String, String>?) {
val testAnchors: List<String> by lazy {
(configuration ?: mapOf()).getOrDefault("testDirs", "test").split(',')
}
val domainName: String by lazy {
(configuration ?: mapOf()).getOrDefault("domainName", "")
}
}

// ================== utils for List<RulesConfig> from yml config

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.isLeaf
import com.pinterest.ktlint.core.ast.parent
import org.cqfn.diktat.common.config.rules.RuleConfiguration
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.getCommonConfig
import org.cqfn.diktat.common.config.rules.getCommonConfiguration
import org.cqfn.diktat.ruleset.constants.Warnings.INCORRECT_PACKAGE_SEPARATOR
import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_INCORRECT_CASE
import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_INCORRECT_PATH
Expand Down Expand Up @@ -64,7 +64,7 @@ class PackageNaming(private val configRules: List<RulesConfig>) : Rule("package-
if (domainNameConfiguration == null) {
log.error("Not able to find an external configuration for domain name in the common configuration (is it missing in yml config?)")
}
val configuration = PackageNamingConfiguration(domainNameConfiguration ?: mapOf())
val configuration = configRules.getCommonConfiguration().value
domainName = configuration.domainName

if (node.elementType == PACKAGE_DIRECTIVE) {
Expand Down Expand Up @@ -255,11 +255,4 @@ class PackageNaming(private val configRules: List<RulesConfig>) : Rule("package-
}
}
}

/**
* this class represents yml-map configuration with the only one field (domainName) - can be used for yml parsing
*/
class PackageNamingConfiguration(config: Map<String, String>) : RuleConfiguration(config) {
val domainName by config
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cqfn.diktat.ruleset.rules.kdoc

import com.pinterest.ktlint.core.KtLint.FILE_PATH_USER_DATA_KEY
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
Expand All @@ -9,14 +10,11 @@ import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.getCommonConfiguration
import org.cqfn.diktat.ruleset.constants.Warnings
import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS
import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_TOP_LEVEL
import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
import org.cqfn.diktat.ruleset.utils.getFirstChildWithType
import org.cqfn.diktat.ruleset.utils.getIdentifierName
import org.cqfn.diktat.ruleset.utils.isAccessibleOutside
import org.cqfn.diktat.ruleset.utils.isStandardMethod
import org.cqfn.diktat.ruleset.utils.*
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet

Expand All @@ -41,10 +39,13 @@ class KdocComments(private val configRules: List<RulesConfig>) : Rule("kdoc-comm
emitWarn = emit
isFixMode = autoCorrect

when (node.elementType) {
FILE -> checkTopLevelDoc(node)
CLASS -> checkClassElements(node)
}
val config = configRules.getCommonConfiguration().value
val fileName = node.getRootNode().getUserData(FILE_PATH_USER_DATA_KEY)!!
if (!(node.hasTestAnnotation() || isLocatedInTest(fileName.splitPathToDirs(), config.testAnchors)))
when (node.elementType) {
FILE -> checkTopLevelDoc(node)
CLASS -> checkClassElements(node)
}
}

private fun checkClassElements(node: ASTNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.THROW
import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_WITH_EXPRESSION
import org.cqfn.diktat.common.config.rules.RuleConfiguration
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.getRuleConfig
import org.cqfn.diktat.common.config.rules.getCommonConfiguration
import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION
import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_TRIVIAL_KDOC_ON_FUNCTION
import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_PARAM_TAG
Expand Down Expand Up @@ -62,10 +61,9 @@ class KdocMethods(private val configRules: List<RulesConfig>) : Rule("kdoc-metho
emitWarn = emit

if (node.elementType == FUN && node.getFirstChildWithType(MODIFIER_LIST).isAccessibleOutside()) {
val config = KdocMethodsConfiguration(configRules.getRuleConfig(MISSING_KDOC_ON_FUNCTION)?.configuration
?: mapOf())
val config = configRules.getCommonConfiguration().value
val fileName = node.getRootNode().getUserData(FILE_PATH_USER_DATA_KEY)!!
val isTestMethod = node.hasTestAnnotation() || node.isLocatedInTest(fileName.splitPathToDirs(), config.testAnchors)
val isTestMethod = node.hasTestAnnotation() || isLocatedInTest(fileName.splitPathToDirs(), config.testAnchors)
if (!isTestMethod && !node.isStandardMethod() && !node.isSingleLineGetterOrSetter()) {
checkSignatureDescription(node)
}
Expand Down Expand Up @@ -224,10 +222,3 @@ class KdocMethods(private val configRules: List<RulesConfig>) : Rule("kdoc-metho

private fun ASTNode.isSingleLineGetterOrSetter() = isGetterOrSetter() && (expressionBodyTypes.any { hasChildOfType(it) } || getBodyLines().size == 1)
}

private class KdocMethodsConfiguration(config: Map<String, String>) : RuleConfiguration(config) {
/**
* Names of directories which indicate that this is path to tests. Will be checked like "src/$testAnchor" for each entry.
*/
val testAnchors = config.getOrDefault("testDirs", "test").split(',')
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.pinterest.ktlint.core.ast.isLeaf
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.lineNumber
import com.pinterest.ktlint.core.ast.parent
import org.cqfn.diktat.ruleset.rules.PackageNaming
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.TokenType
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
Expand Down Expand Up @@ -427,6 +428,32 @@ fun ASTNode.extractLineOfText(): String {
return text.joinToString(separator = "").trim()
}

/**
* Checks node has `@Test` annotation
*/
fun ASTNode.hasTestAnnotation(): Boolean {
return findChildByType(MODIFIER_LIST)
?.getAllChildrenWithType(ElementType.ANNOTATION_ENTRY)
?.flatMap { it.findAllNodesWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) }
?.any { it.findLeafWithSpecificType(ElementType.IDENTIFIER)?.text == "Test" }
?: false
}

/**
* Checks node is located in file src/test/**/*Test.kt
* @param testAnchors names of test directories, e.g. "test", "jvmTest"
*/
fun isLocatedInTest(filePathParts: List<String>, testAnchors: List<String>): Boolean {
return filePathParts
.takeIf { it.contains(PackageNaming.PACKAGE_PATH_ANCHOR) }
?.run { subList(lastIndexOf(PackageNaming.PACKAGE_PATH_ANCHOR), size) }
?.run {
// e.g. src/test/ClassTest.kt, other files like src/test/Utils.kt are still checked
testAnchors.any { contains(it) } && last().substringBeforeLast('.').endsWith("Test")
}
?: false
}

fun ASTNode.firstLineOfText(suffix: String = "") = text.lines().run { singleOrNull() ?: (first() + suffix) }

fun ASTNode.lastLineNumber() = lineNumber()?.plus(text.count { it == '\n' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,6 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtParameterList

/**
* Checks whether function from this [ElementType.FUN] node has `@Test` annotation
*/
fun ASTNode.hasTestAnnotation(): Boolean {
checkNodeIsFun(this)
return findChildByType(MODIFIER_LIST)
?.getAllChildrenWithType(ANNOTATION_ENTRY)
?.flatMap { it.findAllNodesWithSpecificType(CONSTRUCTOR_CALLEE) }
?.any { it.findLeafWithSpecificType(IDENTIFIER)?.text == "Test" }
?: false
}

/**
* Checks whether function from this [ElementType.FUN] node is located in file src/test/**/*Test.kt
* @param testAnchors names of test directories, e.g. "test", "jvmTest"
*/
fun ASTNode.isLocatedInTest(filePathParts: List<String>, testAnchors: List<String>): Boolean {
checkNodeIsFun(this)
return filePathParts
.takeIf { it.contains(PACKAGE_PATH_ANCHOR) }
?.run { subList(lastIndexOf(PACKAGE_PATH_ANCHOR), size) }
?.run {
// e.g. src/test/ClassTest.kt, other files like src/test/Utils.kt are still checked
testAnchors.any { contains(it) } && last().substringBeforeLast('.').endsWith("Test")
}
?: false
}

fun ASTNode.hasParameters(): Boolean {
checkNodeIsFun(this)
val argList = this.argList()
Expand Down
1 change: 1 addition & 0 deletions diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- name: DIKTAT_COMMON
configuration:
domainName: your.package.name.here
testDirs: test
- name: CLASS_NAME_INCORRECT
enabled: true
configuration: {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.cqfn.diktat.ruleset.chapter2
import com.pinterest.ktlint.core.LintError
import org.cqfn.diktat.common.config.rules.RulesConfig
import generated.WarningNames
import org.cqfn.diktat.common.config.rules.DIKTAT_COMMON
import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_TRIVIAL_KDOC_ON_FUNCTION
import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_PARAM_TAG
import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_RETURN_TAG
Expand Down Expand Up @@ -76,7 +77,7 @@ class KdocMethodsTest : LintTestBase(::KdocMethods) {
)
// should allow to set custom test dirs
lintMethod(funCode, fileName = "src/jvmTest/kotlin/org/cqfn/diktat/ExampleTest.kt",
rulesConfigList = listOf(RulesConfig(MISSING_KDOC_ON_FUNCTION.name, true, mapOf("testDirs" to "test,jvmTest")))
rulesConfigList = listOf(RulesConfig(DIKTAT_COMMON, true, mapOf("testDirs" to "test,jvmTest")))
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,33 @@ class KdocWarnTest : LintTestBase(::KdocComments) {
)
}

@Test
@Tag(WarningNames.MISSING_KDOC_CLASS_ELEMENTS)
fun `Kdoc shouldn't present for each class element because Test annotation`() {
lintMethod(
"""
/**
* class that contains fields, functions and public subclasses
**/
@Test
class SomeGoodName {
val variable: String = ""
private val privateVariable: String = ""
fun perfectFunction() {
}

private fun privateFunction() {
}

class InternalClass {
}

private class InternalClass {
}
}
""".trimIndent())
}

@Test
@Tag(WarningNames.MISSING_KDOC_CLASS_ELEMENTS)
fun `Kdoc should present for each class element (positive)`() {
Expand Down