Skip to content

Commit

Permalink
Merge branch 'master' into bugfix/gradle-plugin-onfig-resolving#703
Browse files Browse the repository at this point in the history
  • Loading branch information
petertrr authored Jan 19, 2021
2 parents 3cbf195 + b43fb71 commit 00e6bda
Show file tree
Hide file tree
Showing 84 changed files with 2,054 additions and 661 deletions.
6 changes: 3 additions & 3 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) | [Whitepaper](wp/wp.pdf) |
| | | | | |
| --- | --- | --- | --- | --- |
|[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) |

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

Expand Down
22 changes: 19 additions & 3 deletions diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
# Checks that KDoc does not contain single line with words 'return', 'get' or 'set'
- name: KDOC_TRIVIAL_KDOC_ON_FUNCTION
enabled: true
# Checks that kdoc does not contain @author tag or date
- name: KDOC_CONTAINS_DATE_OR_AUTHOR
enabled: true
configuration:
versionRegex: \d+\.\d+\.\d+[-.\w\d]*
# Checks that there is newline after header KDoc
- name: HEADER_WRONG_FORMAT
enabled: true
Expand All @@ -130,9 +135,6 @@
# Checks that header kdoc is located before package directive
- name: HEADER_NOT_BEFORE_PACKAGE
enabled: true
# Checks that header kdoc does not contain @author tag ar date
- name: HEADER_CONTAINS_DATE_OR_AUTHOR
enabled: true
# Checks that file does not contain lines > maxSize
- name: FILE_IS_TOO_LONG
enabled: true
Expand Down Expand Up @@ -222,6 +224,9 @@
maxParametersInOneLine: 2
# 3 by default.
# maxCallsInOneLine: 3
# Inspection that checks if a long dot qualified expression is used in condition or as an argument
- name: COMPLEX_EXPRESSION
enabled: true
# Checks that there are not too many consecutive spaces in line
- name: TOO_MANY_CONSECUTIVE_SPACES
enabled: true
Expand Down Expand Up @@ -348,6 +353,14 @@
# Checks that function use default values, instead overloading
- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS
enabled: true
# Checks that using runBlocking inside async block code
- name: RUN_BLOCKING_INSIDE_ASYNC
enabled: true
# Checks that the long lambda has parameters
- name: TOO_MANY_LINES_IN_LAMBDA
enabled: true
configuration:
maxLambdaLength: 10 # max length of lambda without parameters
# Checks that property in constructor doesn't contains comment
- name: KDOC_NO_CONSTRUCTOR_PROPERTY
enabled: true
Expand Down Expand Up @@ -411,4 +424,7 @@
enabled: true
# If there is stateless class it is preferred to use object
- name: OBJECT_IS_PREFERRED
enabled: true
# If there exists negated version of function you should prefer it instead of !functionCall
- name: INVERSE_FUNCTION_PREFERRED
enabled: true
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ data class CommonConfiguration(private val configuration: Map<String, String>?)
/**
* Start of package name, which shoould be common, e.g. org.example.myproject
*/
val domainName: String by lazy {
(configuration ?: emptyMap()).getOrDefault("domainName", "")
val domainName: String? by lazy {
configuration?.get("domainName")
}

/**
Expand Down
12 changes: 10 additions & 2 deletions diktat-rules/src/main/kotlin/generated/WarningNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ public object WarningNames {
public const val KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT: String =
"KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT"

public const val KDOC_CONTAINS_DATE_OR_AUTHOR: String = "KDOC_CONTAINS_DATE_OR_AUTHOR"

public const val HEADER_WRONG_FORMAT: String = "HEADER_WRONG_FORMAT"

public const val HEADER_MISSING_OR_WRONG_COPYRIGHT: String = "HEADER_MISSING_OR_WRONG_COPYRIGHT"

public const val WRONG_COPYRIGHT_YEAR: String = "WRONG_COPYRIGHT_YEAR"

public const val HEADER_CONTAINS_DATE_OR_AUTHOR: String = "HEADER_CONTAINS_DATE_OR_AUTHOR"

public const val HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE: String =
"HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE"

Expand Down Expand Up @@ -143,6 +143,8 @@ public object WarningNames {

public const val WRONG_NEWLINES: String = "WRONG_NEWLINES"

public const val COMPLEX_EXPRESSION: String = "COMPLEX_EXPRESSION"

public const val STRING_CONCATENATION: String = "STRING_CONCATENATION"

public const val TOO_MANY_BLANK_LINES: String = "TOO_MANY_BLANK_LINES"
Expand Down Expand Up @@ -199,6 +201,8 @@ public object WarningNames {
public const val WRONG_OVERLOADING_FUNCTION_ARGUMENTS: String =
"WRONG_OVERLOADING_FUNCTION_ARGUMENTS"

public const val RUN_BLOCKING_INSIDE_ASYNC: String = "RUN_BLOCKING_INSIDE_ASYNC"

public const val SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY: String =
"SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY"

Expand Down Expand Up @@ -229,4 +233,8 @@ public object WarningNames {
public const val AVOID_USING_UTILITY_CLASS: String = "AVOID_USING_UTILITY_CLASS"

public const val OBJECT_IS_PREFERRED: String = "OBJECT_IS_PREFERRED"

public const val INVERSE_FUNCTION_PREFERRED: String = "INVERSE_FUNCTION_PREFERRED"

public const val TOO_MANY_LINES_IN_LAMBDA: String = "TOO_MANY_LINES_IN_LAMBDA"
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ enum class Warnings(
KDOC_NO_CONSTRUCTOR_PROPERTY(true, "2.1.1", "all properties from the primary constructor should be documented in a @property tag in KDoc"),
KDOC_EXTRA_PROPERTY(false, "2.1.1", "There is property in KDoc which is not present in the class"),
KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT(true, "2.1.1", "replace comment before property with @property tag in class KDoc"),
KDOC_CONTAINS_DATE_OR_AUTHOR(false, "2.1.3", "KDoc should not contain creation date and author name"),
HEADER_WRONG_FORMAT(true, "2.2.1", "file header comments should be properly formatted"),
HEADER_MISSING_OR_WRONG_COPYRIGHT(true, "2.2.1", "file header comment must include copyright information inside a block comment"),
WRONG_COPYRIGHT_YEAR(true, "2.2.1", "year defined in copyright and current year are different"),
HEADER_CONTAINS_DATE_OR_AUTHOR(false, "2.2.1", "file header comment should not contain creation date and author name"),
HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE(false, "2.2.1", "files that contain multiple or no classes should contain description of what is inside of this file"),
HEADER_NOT_BEFORE_PACKAGE(true, "2.2.1", "header KDoc should be placed before package and imports"),
COMMENTED_OUT_CODE(false, "2.4.2", "you should not comment out code, use VCS to save it in history and delete this block"),
Expand All @@ -96,6 +96,7 @@ enum class Warnings(
LONG_LINE(true, "3.5.1", "this line is longer than allowed"),
REDUNDANT_SEMICOLON(true, "3.6.2", "there should be no redundant semicolon at the end of lines"),
WRONG_NEWLINES(true, "3.6.2", "incorrect line breaking"),
COMPLEX_EXPRESSION(false, "3.6.3", "complex dot qualified expression should be replaced with variable"),

// FixMe: autofixing will be added for this rule
STRING_CONCATENATION(false, "3.15.1", "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"),
Expand Down Expand Up @@ -131,6 +132,8 @@ enum class Warnings(
TOO_MANY_PARAMETERS(false, "5.2.2", "function has too many parameters"),
NESTED_BLOCK(false, "5.1.2", "function has too many nested blocks and should be simplified"),
WRONG_OVERLOADING_FUNCTION_ARGUMENTS(false, "5.2.3", "use default argument instead of function overloading"),
RUN_BLOCKING_INSIDE_ASYNC(false, "5.2.4", "avoid using runBlocking in asynchronous code"),
TOO_MANY_LINES_IN_LAMBDA(false, "5.2.5", "long lambdas should have a parameter name instead of it"),

// ======== chapter 6 ========
SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY(true, "6.1.1", "if a class has single constructor, it should be converted to a primary constructor"),
Expand All @@ -147,6 +150,7 @@ enum class Warnings(
NO_CORRESPONDING_PROPERTY(false, "6.1.7", "backing property should have the same name, but there is no corresponding property"),
AVOID_USING_UTILITY_CLASS(false, "6.4.1", "avoid using utility classes/objects, use extensions functions"),
OBJECT_IS_PREFERRED(true, "6.4.2", "it is better to use object for stateless classes"),
INVERSE_FUNCTION_PREFERRED(true, "5.1.4", "it is better to use inverse function"),
;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,28 @@ private fun generateWarningNames() {
}

private fun validateYear() {
val file = File("diktat-rules/src/test/resources/test/paragraph2/header/CopyrightDifferentYearExpected.kt")
val tempFile = createTempFile()
tempFile.printWriter().use { writer ->
file.forEachLine { line ->
writer.println(when {
hyphenRegex.matches(line) -> hyphenRegex.replace(line) {
val years = it.value.split("-")
val validYears = "${years[0]}-$curYear"
line.replace(hyphenRegex, validYears)
val files = File("diktat-rules/src/test/resources/test/paragraph2/header")
files
.listFiles()
.filterNot { it.name.contains("CopyrightDifferentYearTest.kt") }
.forEach { file ->
val tempFile = createTempFile()
tempFile.printWriter().use { writer ->
file.forEachLine { line ->
writer.println(when {
line.contains(hyphenRegex) -> line.replace(hyphenRegex) {
val years = it.value.split("-")
"${years[0]}-$curYear"
}
line.contains(afterCopyrightRegex) -> line.replace(afterCopyrightRegex) {
val copyrightYears = it.value.split("(c)", "(C)", "©")
"${copyrightYears[0]}-$curYear"
}
else -> line
})
}
afterCopyrightRegex.matches(line) -> afterCopyrightRegex.replace(line) {
val copyrightYears = it.value.split("(c)", "(C)", "©")
val validYears = "${copyrightYears[0]}-$curYear"
line.replace(afterCopyrightRegex, validYears)
}
else -> line
})
}
file.delete()
tempFile.renameTo(file)
}
}
file.delete()
tempFile.renameTo(file)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ class AnnotationNewLineRule(private val configRules: List<RulesConfig>) : Rule("
if (node == node.treeParent.getFirstChildWithType(node.elementType)) {
// Current node is ANNOTATION_ENTRY. treeParent(ModifierList) -> treeParent(PRIMARY_CONSTRUCTOR)
// Checks if there is a white space before grandparent node
if (node.treeParent.treeParent.treePrev.isWhiteSpace()) {
val hasSpaceBeforeGrandparent = node
.treeParent
.treeParent
.treePrev
.isWhiteSpace()
if (hasSpaceBeforeGrandparent) {
(node.treeParent.treeParent.treePrev as LeafPsiElement).replaceWithText("\n")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.constants.Warnings.RUN_BLOCKING_INSIDE_ASYNC
import org.cqfn.diktat.ruleset.utils.hasChildOfType

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.parent
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier

/**
* This rule finds if using runBlocking in asynchronous code
*/
class AsyncAndSyncRule(private val configRules: List<RulesConfig>) : Rule("sync-in-async") {
private val asyncList = listOf("async", "launch")
private var isFixMode: Boolean = false
private lateinit var emitWarn: EmitType

override fun visit(node: ASTNode,
autoCorrect: Boolean,
emit: EmitType) {
emitWarn = emit
isFixMode = autoCorrect

if (node.isRunBlocking()) {
checkRunBlocking(node)
}
}

private fun checkRunBlocking(node: ASTNode) {
node.parent({it.isAsync() || it.isSuspend()})?.let {
RUN_BLOCKING_INSIDE_ASYNC.warn(configRules, emitWarn, isFixMode, node.text, node.startOffset, node)
}
}

private fun ASTNode.isAsync() = this.elementType == CALL_EXPRESSION && this.findChildByType(REFERENCE_EXPRESSION)?.text in asyncList

private fun ASTNode.isSuspend() = this.elementType == FUN && (this.psi as KtFunction).modifierList?.hasSuspendModifier() ?: false

private fun ASTNode.isRunBlocking() = this.elementType == REFERENCE_EXPRESSION && this.text == "runBlocking" && this.treeParent.hasChildOfType(LAMBDA_ARGUMENT)
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,14 @@ class BlockStructureBraces(private val configRules: List<RulesConfig>) : Rule("b
allMiddleSpace: List<ASTNode>,
node: ASTNode,
keyword: IElementType) {
allMiddleSpace.forEach {
if (checkBraceNode(it, true)) {
allMiddleSpace.forEach { space ->
if (checkBraceNode(space, true)) {
BRACES_BLOCK_STRUCTURE_ERROR.warnAndFix(configRules, emitWarn, isFixMode, "incorrect new line after closing brace",
it.startOffset, it) {
if (it.elementType != WHITE_SPACE) {
space.startOffset, space) {
if (space.elementType != WHITE_SPACE) {
node.addChild(PsiWhiteSpaceImpl(" "), node.findChildByType(keyword))
} else {
(it as LeafPsiElement).replaceWithText(" ")
(space as LeafPsiElement).replaceWithText(" ")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.cqfn.diktat.ruleset.rules

import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.constants.Warnings.INVERSE_FUNCTION_PREFERRED

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.ElementType.LPAR
import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.RPAR
import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.psiUtil.siblings

/**
* This rule checks if inverse method can be used.
* For example if there is !isEmpty() on collection call that it changes it to isNotEmpty()
*/
class CheckInverseMethodRule(private val configRules: List<RulesConfig>) : Rule("inverse-method") {
private var isFixMode: Boolean = false
private lateinit var emitWarn: EmitType

override fun visit(node: ASTNode,
autoCorrect: Boolean,
emit: EmitType) {
emitWarn = emit
isFixMode = autoCorrect

if (node.elementType == CALL_EXPRESSION && node.text in methodMap.keys) {
checkCallExpressionName(node)
}
}

private fun checkCallExpressionName(node: ASTNode) {
val operationRef = node
.treeParent
.siblings(forward = false)
.takeWhile { it.elementType in intermediateTokens }
.firstOrNull { it.elementType == OPERATION_REFERENCE}
if (operationRef?.text == "!") {
INVERSE_FUNCTION_PREFERRED.warnAndFix(configRules, emitWarn, isFixMode, "${methodMap[node.text]} instead of !${node.text}", node.startOffset, node) {
val callExpression = CompositeElement(CALL_EXPRESSION)
val referenceExp = CompositeElement(REFERENCE_EXPRESSION)
val argList = CompositeElement(VALUE_ARGUMENT_LIST)
node.treeParent.addChild(callExpression, node)
callExpression.addChild(referenceExp)
callExpression.addChild(argList)
referenceExp.addChild(LeafPsiElement(IDENTIFIER, "${methodMap[node.text]}".dropLast(2)))
argList.addChild(LeafPsiElement(LPAR, "("))
argList.addChild(LeafPsiElement(RPAR, ")"))
node.treeParent.treeParent.removeChild(node.treeParent.treePrev) // removing OPERATION_EXPRESSION - !
node.treeParent.removeChild(node)
}
}
}

companion object {
val methodMap = mapOf(
"isEmpty()" to "isNotEmpty()",
"isBlank()" to "isNotBlank()",
"isNotEmpty()" to "isEmpty()",
"isNotBlank()" to "isBlank()"
)
val intermediateTokens = listOf(WHITE_SPACE, OPERATION_REFERENCE, BLOCK_COMMENT)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
::CustomGetterSetterRule,
::CompactInitialization,
// other rules
::CheckInverseMethodRule,
::StatelessClassesRule,
::ImplicitBackingPropertyRule,
::StringTemplateFormatRule,
Expand All @@ -136,11 +137,13 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
::FunctionArgumentsSize,
::BlankLinesRule,
::FileSize,
::AsyncAndSyncRule,
::NullableTypeRule,
::NullChecksRule,
::ImmutableValNoVarRule,
::AvoidNestedFunctionsRule,
::ExtensionFunctionsSameNameRule,
::LambdaLengthRule,
// formatting: moving blocks, adding line breaks, indentations etc.
::BlockStructureBraces,
::ConsecutiveSpacesRule,
Expand Down
Loading

0 comments on commit 00e6bda

Please sign in to comment.