Skip to content

Commit

Permalink
Merge branch 'master' into bugfix/sanitize-diktat-config(#737)
Browse files Browse the repository at this point in the history
  • Loading branch information
aktsay6 authored Feb 8, 2021
2 parents fd0760d + d8e644e commit 003c172
Show file tree
Hide file tree
Showing 29 changed files with 438 additions and 145 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ Now diKTat was already added to the lists of [static analysis tools](https://git

## See first

| | | | | |
| --- | --- | --- | --- | --- |
|[DiKTat codestyle](info/guide/diktat-coding-convention.md)|[Supported Rules](info/available-rules.md) | [Examples of Usage](https://github.com/akuleshov7/diktat-examples) | [Online Demo](https://ktlint-demo.herokuapp.com) | [White Paper](wp/wp.pdf) |
| | | | | | |
| --- | --- | --- | --- | --- | --- |
|[Codestyle](info/guide/diktat-coding-convention.md)|[Inspections](info/available-rules.md) | [Examples](https://github.com/akuleshov7/diktat-examples) | [Demo](https://ktlint-demo.herokuapp.com) | [White Paper](wp/wp.pdf) | [Groups of Inspections](info/rules-mapping.md) |

## Why should I use diktat in my CI/CD?

Expand Down Expand Up @@ -64,7 +64,7 @@ Main features of diktat are the following:

3. Finally, run KTlint (with diKTat injected) to check your `*.kt` files in `dir/your/dir`:
```bash
$ ./ktlint -R diktat.jar "dir/your/dir/**/*.kt"
$ ./ktlint -R diktat.jar --disabled_rules=standard "dir/your/dir/**/*.kt"
```

To **autofix** all code style violations use `-F` option.
Expand Down
5 changes: 4 additions & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# testDirs: test
disabledChapters: ""
testDirs: test
kotlinVersion: "1.4.21"
kotlinVersion: 1.4.30
# Checks that the Class/Enum/Interface name does not match Pascal case
- name: CLASS_NAME_INCORRECT
enabled: true
Expand Down Expand Up @@ -183,6 +183,9 @@
# Checks that properties with comments are separated by a blank line
- name: BLANK_LINE_BETWEEN_PROPERTIES
enabled: true
# Checks top level order
- name: TOP_LEVEL_ORDER
enabled: true
# Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style)
- name: BRACES_BLOCK_STRUCTURE_ERROR
enabled: true
Expand Down
2 changes: 1 addition & 1 deletion diktat-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<dependency>
<groupId>com.charleskorn.kaml</groupId>
<artifactId>kaml</artifactId>
<version>0.26.0</version>
<version>0.27.0</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,6 @@ 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
*
Expand Down Expand Up @@ -142,6 +135,13 @@ data class CommonConfiguration(private val configuration: Map<String, String>?)

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

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

/**
* Get [RulesConfig] for particular [Rule] object.
*
Expand All @@ -150,11 +150,6 @@ data class CommonConfiguration(private val configuration: Map<String, String>?)
*/
fun List<RulesConfig>.getRuleConfig(rule: Rule): RulesConfig? = this.find { it.name == rule.ruleName() }

/**
* Get [RulesConfig] representing common configuration part that can be used in any rule
*/
private fun List<RulesConfig>.getCommonConfig() = find { it.name == DIKTAT_COMMON }

/**
* checking if in yml config particular rule is enabled or disabled
* (!) the default value is "true" (in case there is no config specified)
Expand Down Expand Up @@ -183,3 +178,8 @@ fun String.kotlinVersion(): KotlinVersion {
KotlinVersion(versions[0], versions[1], versions[2])
}
}

/**
* Get [RulesConfig] representing common configuration part that can be used in any rule
*/
private fun List<RulesConfig>.getCommonConfig() = find { it.name == DIKTAT_COMMON }
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ class ConfigReaderTest {
fun `testing kotlin version`() {
val rulesConfigList: List<RulesConfig>? = RulesConfigReader(javaClass.classLoader)
.readResource("src/test/resources/test-rules-config.yml")
val currentKotlinVersion = KotlinVersion.CURRENT
val kotlinVersionForTest = KotlinVersion(1, 4, 21)
requireNotNull(rulesConfigList)
assert(rulesConfigList.getCommonConfiguration().value.kotlinVersion == currentKotlinVersion)
assert(rulesConfigList.getCommonConfiguration().value.kotlinVersion == kotlinVersionForTest)
assert(rulesConfigList.find { it.name == DIKTAT_COMMON }
?.configuration
?.get("kotlinVersion")
?.kotlinVersion() == currentKotlinVersion)
?.kotlinVersion() == kotlinVersionForTest)
}
}
2 changes: 1 addition & 1 deletion diktat-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurr

plugins {
`java-gradle-plugin`
kotlin("jvm") version "1.4.21"
kotlin("jvm") version "1.4.30"
jacoco
id("pl.droidsonroids.jacoco.testkit") version "1.0.7"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@
* Utilities for diktat gradle plugin
*/

@file:Suppress("FILE_NAME_MATCH_CLASS")
@file:Suppress("FILE_NAME_MATCH_CLASS", "MatchingDeclarationName")

package org.cqfn.diktat.plugin.gradle

import groovy.lang.Closure

// These two are copy-pasted from `kotlin-dsl` plugin's groovy interop.
// Because `kotlin-dsl` depends on kotlin 1.3.x.
@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG")
fun <T> Any.closureOf(action: T.() -> Unit): Closure<Any?> =
KotlinClosure1(action, this, this)

@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "KDOC_NO_CONSTRUCTOR_PROPERTY",
"MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG")
class KotlinClosure1<in T : Any?, V : Any>(
Expand All @@ -24,3 +18,9 @@ class KotlinClosure1<in T : Any?, V : Any>(
@Suppress("unused") // to be called dynamically by Groovy
fun doCall(it: T): V? = it.function()
}

// These two are copy-pasted from `kotlin-dsl` plugin's groovy interop.
// Because `kotlin-dsl` depends on kotlin 1.3.x.
@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG")
fun <T> Any.closureOf(action: T.() -> Unit): Closure<Any?> =
KotlinClosure1(action, this, this)
2 changes: 2 additions & 0 deletions diktat-rules/src/main/kotlin/generated/WarningNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public object WarningNames {

public const val BLANK_LINE_BETWEEN_PROPERTIES: String = "BLANK_LINE_BETWEEN_PROPERTIES"

public const val TOP_LEVEL_ORDER: String = "TOP_LEVEL_ORDER"

public const val BRACES_BLOCK_STRUCTURE_ERROR: String = "BRACES_BLOCK_STRUCTURE_ERROR"

public const val WRONG_INDENTATION: String = "WRONG_INDENTATION"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ fun Warnings.isRuleFromActiveChapter(configRules: List<RulesConfig>): Boolean {
return disabledChapters?.let { return chapterFromRule !in it } ?: true
}

private fun validate(chapter: String) =
require(chapter in Chapters.values().map { it.title }) {
val closestMatch = Chapters.values().minByOrNull { Levenshtein.distance(it.title, chapter) }
"Chapter name <$chapter> in configuration file is invalid, did you mean <$closestMatch>?"
}

/**
* Function get chapter by warning
*
* @return chapter to which warning refers
*/
@Suppress("UnsafeCallOnNullableType")
fun Warnings.getChapterByWarning() = Chapters.values().find { it.number == this.ruleId.first().toString() }!!

private fun validate(chapter: String) =
require(chapter in Chapters.values().map { it.title }) {
val closestMatch = Chapters.values().minByOrNull { Levenshtein.distance(it.title, chapter) }
"Chapter name <$chapter> in configuration file is invalid, did you mean <$closestMatch>?"
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ enum class Warnings(
NO_BRACES_IN_CONDITIONALS_AND_LOOPS(true, "3.2.1", "in if, else, when, for, do, and while statements braces should be used. Exception: single line if statement."),
WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES(true, "3.1.4", "the declaration part of a class-like code structures (class/interface/etc.) should be in the proper order"),
BLANK_LINE_BETWEEN_PROPERTIES(true, "3.1.4", "there should be no blank lines between properties without comments; comment or KDoc on property should have blank line before"),
TOP_LEVEL_ORDER(true, "3.1.5", "the declaration part of a top level elements should be in the proper order"),
BRACES_BLOCK_STRUCTURE_ERROR(true, "3.2.2", "braces should follow 1TBS style"),
WRONG_INDENTATION(true, "3.3.1", "only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed)"),
EMPTY_BLOCK_STRUCTURE_ERROR(true, "3.4.1", "incorrect format of empty block"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.cqfn.diktat.ruleset.rules.chapter3.files.FileSize
import org.cqfn.diktat.ruleset.rules.chapter3.files.FileStructureRule
import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule
import org.cqfn.diktat.ruleset.rules.chapter3.files.NewlinesRule
import org.cqfn.diktat.ruleset.rules.chapter3.files.TopLevelOrderRule
import org.cqfn.diktat.ruleset.rules.chapter3.files.WhiteSpaceRule
import org.cqfn.diktat.ruleset.rules.chapter3.identifiers.LocalVariablesRule
import org.cqfn.diktat.ruleset.rules.chapter4.ImmutableValNoVarRule
Expand Down Expand Up @@ -150,6 +151,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
::EmptyBlock,
::AvoidEmptyPrimaryConstructor,
::EnumsSeparated,
::TopLevelOrderRule,
::SingleLineStatementsRule,
::MultipleModifiersSequence,
::TrivialPropertyAccessors,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.cqfn.diktat.ruleset.rules.chapter3.files

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.Warnings.TOP_LEVEL_ORDER
import org.cqfn.diktat.ruleset.rules.DiktatRule
import org.cqfn.diktat.ruleset.utils.*

import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.FILE
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
import com.pinterest.ktlint.core.ast.ElementType.INTERNAL_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION
import com.pinterest.ktlint.core.ast.ElementType.OVERRIDE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.PRIVATE_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
import com.pinterest.ktlint.core.ast.ElementType.PROTECTED_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.isPartOfComment
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.psi.psiUtil.siblings

/**
* Rule that checks order in top level
*/
class TopLevelOrderRule(configRules: List<RulesConfig>) : DiktatRule("top-level-order", configRules, listOf(TOP_LEVEL_ORDER)) {
override fun logic(node: ASTNode) {
if (node.elementType == FILE) {
checkNode(node)
}
}

@Suppress("UnsafeCallOnNullableType")
private fun checkNode(node: ASTNode) {
val children = node.getChildren(null)
val initialElementsOrder = children.filter { it.elementType in sortedType }
if (initialElementsOrder.isEmpty()) {
return
}
val properties = Properties(children.filter { it.elementType == PROPERTY }).sortElements()
val functions = children.filter { it.elementType == FUN }
val classes = children.filter { it.elementType == CLASS || it.elementType == OBJECT_DECLARATION }
val sortOrder = Blocks(properties, functions, classes).sortElements().map { astNode ->
Pair(astNode, astNode.siblings(false).takeWhile { it.elementType == WHITE_SPACE || it.isPartOfComment() }.toList())
}
val lastNonSortedChildren = initialElementsOrder.last().siblings(true).toList()
sortOrder.filterIndexed { index, pair -> initialElementsOrder[index] != pair.first }
.forEach { listOfChildren ->
val wrongNode = listOfChildren.first
TOP_LEVEL_ORDER.warnAndFix(configRules, emitWarn, isFixMode, wrongNode.text, wrongNode.startOffset, wrongNode) {
node.removeRange(node.findChildByType(IMPORT_LIST)!!.treeNext, node.lastChildNode)
node.removeChild(node.lastChildNode)
sortOrder.map { (sortedNode, sortedNodePrevSibling) ->
sortedNodePrevSibling.reversed().map { node.addChild(it, null) }
node.addChild(sortedNode, null)
}
lastNonSortedChildren.map { node.addChild(it, null) }
}
}
}

/**
* Interface for classes to collect child and sort them
*/
interface Elements {
/**
* Method to sort children
*
* @return sorted mutable list
*/
fun sortElements(): MutableList<ASTNode>
}

/**
* Class containing different groups of properties in file
*/
private data class Properties(private val properties: List<ASTNode>) : Elements {
override fun sortElements(): MutableList<ASTNode> {
val constValProperties = properties.filter { it.isConstant() }
val valProperties = properties.filter { it.isValProperty() && !it.isConstant() }
val lateinitProperties = properties.filter { it.isLateInit() }
val varProperties = properties.filter { it.isVarProperty() && !it.isLateInit() }
return listOf(constValProperties, valProperties, lateinitProperties, varProperties).flatten().toMutableList()
}
}

/**
* Class containing different children in file
*/
private data class Blocks(
private val properties: List<ASTNode>,
private val functions: List<ASTNode>,
private val classes: List<ASTNode>) : Elements {
override fun sortElements(): MutableList<ASTNode> {
val (extensionFun, nonExtensionFun) = functions.partition { (it.psi as KtFunction).isExtensionDeclaration() }
return (properties + listOf(classes, extensionFun, nonExtensionFun).map { nodes ->
val (privatePart, notPrivatePart) = nodes.partition { it.hasModifier(PRIVATE_KEYWORD) }
val (protectedPart, notProtectedPart) = notPrivatePart.partition { it.hasModifier(PROTECTED_KEYWORD) || it.hasModifier(OVERRIDE_KEYWORD) }
val (internalPart, publicPart) = notProtectedPart.partition { it.hasModifier(INTERNAL_KEYWORD) }
listOf(publicPart, internalPart, protectedPart, privatePart).flatten()
}.flatten()).toMutableList()
}
}

companion object {
/**
* List of children that should be sort
*/
val sortedType = listOf(PROPERTY, FUN, CLASS, OBJECT_DECLARATION)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON
import com.pinterest.ktlint.core.ast.ElementType.WHILE
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE

internal const val GET_PREFIX = "get"
internal const val SET_PREFIX = "set"
internal const val EMPTY_BLOCK_TEXT = "{}"

/**
* List of standard methods which do not need mandatory documentation
*/
internal val standardMethods = listOf("main", "equals", "hashCode", "toString", "clone", "finalize")

internal const val GET_PREFIX = "get"
internal const val SET_PREFIX = "set"

/**
* List of element types present in empty code block `{ }`
*/
Expand All @@ -30,7 +31,12 @@ val commentType = listOf(BLOCK_COMMENT, EOL_COMMENT, KDOC)
val loopType = listOf(FOR, WHILE, DO_WHILE)
val copyrightWords = setOf("copyright", "版权")

internal const val EMPTY_BLOCK_TEXT = "{}"
internal val operatorMap = mapOf(
"unaryPlus" to "+", "unaryMinus" to "-", "not" to "!",
"plus" to "+", "minus" to "-", "times" to "*", "div" to "/", "rem" to "%", "mod" to "%", "rangeTo" to "..",
"inc" to "++", "dec" to "--", "contains" to "in",
"plusAssign" to "+=", "minusAssign" to "-=", "timesAssign" to "*=", "divAssign" to "/=", "modAssign" to "%="
)

/**
* Enum that represents some standard platforms that can appear in kotlin code
Expand All @@ -42,10 +48,3 @@ enum class StandardPlatforms(val packages: List<String>) {
KOTLIN(listOf("kotlin", "kotlinx")),
;
}

internal val operatorMap = mapOf(
"unaryPlus" to "+", "unaryMinus" to "-", "not" to "!",
"plus" to "+", "minus" to "-", "times" to "*", "div" to "/", "rem" to "%", "mod" to "%", "rangeTo" to "..",
"inc" to "++", "dec" to "--", "contains" to "in",
"plusAssign" to "+=", "minusAssign" to "-=", "timesAssign" to "*=", "divAssign" to "/=", "modAssign" to "%="
)
Loading

0 comments on commit 003c172

Please sign in to comment.