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

Allow custom source directories #778

Merged
merged 10 commits into from
Feb 20, 2021
Merged
3 changes: 2 additions & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
domainName: org.cqfn.diktat
# testDirs: test
disabledChapters: ""
testDirs: test
testDirs: "test"
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
kotlinVersion: 1.4.30
srcDirectories: "main"
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ data class CommonConfiguration(private val configuration: Map<String, String>?)
* List of directory names which will be used to detect test sources
*/
val testAnchors: List<String> by lazy {
(configuration ?: emptyMap()).getOrDefault("testDirs", "test").split(',')
val testDirs = (configuration ?: emptyMap()).getOrDefault("testDirs", "test").split(',')
if (testDirs.none { it.toLowerCase().endsWith("test") }) {
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
log.error("test directories should have `test` word")
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
}
testDirs
}

/**
Expand Down Expand Up @@ -130,6 +134,17 @@ data class CommonConfiguration(private val configuration: Map<String, String>?)
}
}

/**
* Get source directories from configuration
*/
val srcDirectories: List<String> by lazy {
val srcDirs = configuration?.get("srcDirectories")?.split(",")?.map { it.trim() } ?: listOf("main")
if (srcDirs.none { it.toLowerCase().endsWith("main") }) {
log.error("source directories should have `main` word")
}
srcDirs
}

/**
* False if configuration has been read from config file, true if defaults are used
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cqfn.diktat.ruleset.rules.chapter1

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.CommonConfiguration
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
Expand All @@ -16,13 +17,13 @@ import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
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 java.util.concurrent.atomic.AtomicInteger
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.lexer.KtTokens.PACKAGE_KEYWORD
import org.slf4j.LoggerFactory
import java.util.concurrent.atomic.AtomicInteger

/**
* Rule 1.3: package name is in lower case and separated by dots, code developed internally in your company (in example Huawei) should start
Expand All @@ -47,7 +48,7 @@ class PackageNaming(configRules: List<RulesConfig>) : DiktatRule(
if (node.elementType == PACKAGE_DIRECTIVE) {
val filePath = node.getRootNode().getFilePath()
// calculating package name based on the directory where the file is placed
val realPackageName = calculateRealPackageName(filePath)
val realPackageName = calculateRealPackageName(filePath, configuration)

// if node isLeaf - this means that there is no package name declared
if (node.isLeaf() && !filePath.isKotlinScript()) {
Expand Down Expand Up @@ -92,7 +93,7 @@ class PackageNaming(configRules: List<RulesConfig>) : DiktatRule(
*
* @return list with words that are parts of package name like [org, diktat, name]
*/
private fun calculateRealPackageName(fileName: String): List<String> {
private fun calculateRealPackageName(fileName: String, configuration: CommonConfiguration): List<String> {
val filePathParts = fileName.splitPathToDirs()

return if (!filePathParts.contains(PACKAGE_PATH_ANCHOR)) {
Expand All @@ -104,8 +105,9 @@ class PackageNaming(configRules: List<RulesConfig>) : DiktatRule(
// 1) getting a path after the base project directory (after "src" directory)
// 2) removing src/main/kotlin/java/e.t.c dirs and removing file name
// 3) adding company's domain name at the beginning
val allDirs = languageDirNames + configuration.srcDirectories + configuration.testAnchors
val fileSubDir = filePathParts.subList(filePathParts.lastIndexOf(PACKAGE_PATH_ANCHOR), filePathParts.size - 1)
.dropWhile { languageDirNames.contains(it) }
.dropWhile { allDirs.contains(it) }
// no need to add DOMAIN_NAME to the package name if it is already in path
val domainPrefix = if (!fileSubDir.joinToString(PACKAGE_SEPARATOR).startsWith(domainName)) domainName.split(PACKAGE_SEPARATOR) else emptyList()
domainPrefix + fileSubDir
Expand Down Expand Up @@ -259,6 +261,6 @@ class PackageNaming(configRules: List<RulesConfig>) : DiktatRule(
* Directories that are supposed to be first in sources file paths, relative to [PACKAGE_PATH_ANCHOR].
* For kotlin multiplatform projects directories for targets from [kmmTargets] are supported.
*/
val languageDirNames = listOf("src", "main", "test", "java", "kotlin") + kmmTargets.flatMap { listOf("${it}Main", "${it}Test") }
val languageDirNames = listOf("src", "java", "kotlin") + kmmTargets.flatMap { listOf("${it}Main", "${it}Test") }
}
}
3 changes: 2 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
domainName: com.huawei
# testDirs: test
disabledChapters: ""
testDirs: test
testDirs: "test"
kotlinVersion: 1.4.30
srcDirectories: "main"
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
Expand Down
3 changes: 2 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
configuration:
# put your package name here - it will be autofixed and checked
domainName: your.name.here
testDirs: test
testDirs: "test"
disabledChapters: ""
kotlinVersion: 1.4
srcDirectories: "main"
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class PackageNamingWarnTest : LintTestBase(::PackageNaming) {
private val rulesConfigListEmptyDomainName: List<RulesConfig> = listOf(
RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to ""))
)
private val rulesConfigSourceDirectories: List<RulesConfig> = listOf(
RulesConfig("DIKTAT_COMMON", true, mapOf(
"domainName" to "org.cqfn.diktat",
"srcDirectories" to "nativeMain, mobileMain",
"testDirs" to "nativeTest"
))
)

@Test
@Tag(WarningNames.PACKAGE_NAME_MISSING)
Expand Down Expand Up @@ -148,6 +155,84 @@ class PackageNamingWarnTest : LintTestBase(::PackageNaming) {
)
}

@Test
@Tag(WarningNames.PACKAGE_NAME_INCORRECT_SYMBOLS)
fun `test source config`() {
lintMethod(

"""
package org.cqfn.diktat.domain

import org.cqfn.diktat.a.b.c

/**
* testComment
*/
class TestPackageName { }

""".trimIndent(),
fileName = "~/diktat/diktat-rules/src/nativeMain/kotlin/org/cqfn/diktat/domain/BlaBla.kt",
rulesConfigList = rulesConfigSourceDirectories
)

lintMethod(

"""
package org.cqfn.diktat.domain

import org.cqfn.diktat.a.b.c

/**
* testComment
*/
class TestPackageName { }

""".trimIndent(),
LintError(1, 9, ruleId, "${PACKAGE_NAME_INCORRECT_PATH.warnText()} org.cqfn.diktat.main.kotlin.org.cqfn.diktat.domain", true),
fileName = "~/diktat/diktat-rules/src/main/kotlin/org/cqfn/diktat/domain/BlaBla.kt",
rulesConfigList = rulesConfigSourceDirectories
)
}

@Test
@Tag(WarningNames.PACKAGE_NAME_INCORRECT_SYMBOLS)
fun `test directories for test config`() {
lintMethod(

"""
package org.cqfn.diktat.domain

import org.cqfn.diktat.a.b.c

/**
* testComment
*/
class TestPackageName { }

""".trimIndent(),
fileName = "~/diktat/diktat-rules/src/nativeTest/kotlin/org/cqfn/diktat/domain/BlaBla.kt",
rulesConfigList = rulesConfigSourceDirectories
)

lintMethod(

"""
package org.cqfn.diktat.domain

import org.cqfn.diktat.a.b.c

/**
* testComment
*/
class TestPackageName { }

""".trimIndent(),
LintError(1, 9, ruleId, "${PACKAGE_NAME_INCORRECT_PATH.warnText()} org.cqfn.diktat.test.kotlin.org.cqfn.diktat.domain", true),
fileName = "~/diktat/diktat-rules/src/test/kotlin/org/cqfn/diktat/domain/BlaBla.kt",
rulesConfigList = rulesConfigSourceDirectories
)
}

@Test
@Tag(WarningNames.PACKAGE_NAME_INCORRECT_PATH)
fun `regression - incorrect warning on file under test directory`() {
Expand Down
3 changes: 3 additions & 0 deletions info/guide/guide-chapter-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ Package names are in lower case and separated by dots. Code developed within you
Each file should have a `package` directive.
Package names are all written in lowercase, and consecutive words are concatenated together (no underscores). Package names should contain both the product or module names and the department (or team) name to prevent conflicts with other teams. Numbers are not permitted. For example: `org.apache.commons.lang3`, `xxx.yyy.v2`.

**Note**
Source directory can be change in configuration file, same as test directory. You can specify several options separated by commas.
kentr0w marked this conversation as resolved.
Show resolved Hide resolved

**Exceptions:**

- In certain cases, such as open-source projects or commercial cooperation, package names should not start with `your.company.domain.`
Expand Down